diff --git a/Cargo.toml b/Cargo.toml index 6239e658..23f4aa90 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rhai" -version = "0.14.2" +version = "0.15.0" edition = "2018" authors = ["Jonathan Turner", "Lukáš Hozda", "Stephen Chung"] description = "Embedded scripting for Rust" diff --git a/README.md b/README.md index 7801ebbd..c8f5fb73 100644 --- a/README.md +++ b/README.md @@ -37,7 +37,7 @@ Features to do checked arithmetic operations); for [`no-std`](#optional-features) builds, a number of additional dependencies are pulled in to provide for functionalities that used to be in `std`. -**Note:** Currently, the version is 0.14.2, so the language and API's may change before they stabilize. +**Note:** Currently, the version is 0.15.0, so the language and API's may change before they stabilize. What Rhai doesn't do -------------------- @@ -59,7 +59,7 @@ Install the Rhai crate by adding this line to `dependencies`: ```toml [dependencies] -rhai = "0.14.2" +rhai = "0.15.0" ``` Use the latest released crate version on [`crates.io`](https::/crates.io/crates/rhai/): @@ -395,14 +395,14 @@ are supported. ### Built-in operators -| Operator | Supported for type (see [standard types]) | -| ---------------------------- | -------------------------------------------------------------------- | -| `+`, `-`, `*`, `/`, `%`, `~` | `INT`, `FLOAT` (if not [`no_float`]) | -| `<<`, `>>`, `^` | `INT` | -| `&`, `\|` | `INT`, `bool` | -| `&&`, `\|\|` | `bool` | -| `==`, `!=` | `INT`, `FLOAT` (if not [`no_float`]), `bool`, `char`, `()`, `String` | -| `>`, `>=`, `<`, `<=` | `INT`, `FLOAT` (if not [`no_float`]), `char`, `()`, `String` | +| Operator | Supported for type (see [standard types]) | +| ---------------------------- | ----------------------------------------------------------------------------- | +| `+`, `-`, `*`, `/`, `%`, `~` | `INT`, `FLOAT` (if not [`no_float`]) | +| `<<`, `>>`, `^` | `INT` | +| `&`, `\|` | `INT`, `bool` | +| `&&`, `\|\|` | `bool` | +| `==`, `!=` | `INT`, `FLOAT` (if not [`no_float`]), `bool`, `char`, `()`, `ImmutableString` | +| `>`, `>=`, `<`, `<=` | `INT`, `FLOAT` (if not [`no_float`]), `char`, `()`, `ImmutableString` | ### Packages @@ -494,7 +494,7 @@ The following primitive types are supported natively: | **Floating-point number** (disabled with [`no_float`]) | `f32`, `f64` _(default)_ | `"f32"` or `"f64"` | `"123.4567"` etc. | | **Boolean value** | `bool` | `"bool"` | `"true"` or `"false"` | | **Unicode character** | `char` | `"char"` | `"A"`, `"x"` etc. | -| **Unicode string** | `String` (_not_ `&str`) | `"string"` | `"hello"` etc. | +| **Immutable Unicode string** | `rhai::ImmutableString` (implemented as `Rc` or `Arc`, _not_ `&str`) | `"string"` | `"hello"` etc. | | **Array** (disabled with [`no_index`]) | `rhai::Array` | `"array"` | `"[ ?, ?, ? ]"` | | **Object map** (disabled with [`no_object`]) | `rhai::Map` | `"map"` | `#{ "a": 1, "b": 2 }` | | **Timestamp** (implemented in the [`BasicTimePackage`](#packages)) | `std::time::Instant` | `"timestamp"` | _not supported_ | @@ -514,6 +514,10 @@ 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. + The `to_string` function converts a standard type into a [string] for display purposes. The `type_of` function detects the actual type of a value. This is useful because all variables are [`Dynamic`] in nature. @@ -638,12 +642,12 @@ Traits A number of traits, under the `rhai::` module namespace, provide additional functionalities. -| Trait | Description | Methods | -| ------------------ | -------------------------------------------------------------------------------------- | --------------------------------------- | -| `RegisterFn` | Trait for registering functions | `register_fn` | -| `RegisterResultFn` | Trait for registering fallible functions returning `Result<`_T_`, Box>` | `register_result_fn` | -| `Func` | Trait for creating anonymous functions from script | `create_from_ast`, `create_from_script` | -| `ModuleResolver` | Trait implemented by module resolution services | `resolve` | +| Trait | Description | Methods | +| ------------------ | ---------------------------------------------------------------------------------------- | --------------------------------------- | +| `RegisterFn` | Trait for registering functions | `register_fn` | +| `RegisterResultFn` | Trait for registering fallible functions returning `Result>` | `register_result_fn` | +| `Func` | Trait for creating anonymous functions from script | `create_from_ast`, `create_from_script` | +| `ModuleResolver` | Trait implemented by module resolution services | `resolve` | Working with functions ---------------------- @@ -967,20 +971,23 @@ Similarly, custom types can expose members by registering a `get` and/or `set` f ```rust #[derive(Clone)] struct TestStruct { - field: i64 + field: String } +// Remember Rhai uses 'ImmutableString' instead of 'String' impl TestStruct { - fn get_field(&mut self) -> i64 { - self.field + fn get_field(&mut self) -> ImmutableString { + // Make an 'ImmutableString' from a 'String' + self.field.into(0) } - fn set_field(&mut self, new_val: i64) { - self.field = new_val; + fn set_field(&mut self, new_val: ImmutableString) { + // Get a 'String' from an 'ImmutableString' + self.field = (*new_val).clone(); } fn new() -> Self { - TestStruct { field: 1 } + TestStruct { field: "hello" } } } @@ -991,7 +998,8 @@ engine.register_type::(); engine.register_get_set("xyz", TestStruct::get_field, TestStruct::set_field); engine.register_fn("new_ts", TestStruct::new); -let result = engine.eval::("let a = new_ts(); a.xyz = 42; a.xyz")?; +// Return result can be 'String' - Rhai will automatically convert it from 'ImmutableString' +let result = engine.eval::(r#"let a = new_ts(); a.xyz = "42"; a.xyz"#)?; println!("Answer: {}", result); // prints 42 ``` @@ -1033,7 +1041,7 @@ println!("Answer: {}", result); // prints 42 Needless to say, `register_type`, `register_type_with_name`, `register_get`, `register_set`, `register_get_set` and `register_indexer` are not available when the [`no_object`] feature is turned on. -`register_indexer` is also not available when the [`no_index`] feature is turned on. +`register_indexer` is also not available when the [`no_index`] feature is turned on. `Scope` - Initializing and maintaining state ------------------------------------------- @@ -1324,6 +1332,9 @@ Unicode characters. Individual characters within a Rhai string can also be replaced just as if the string is an array of Unicode characters. In Rhai, there is also no separate concepts of `String` and `&str` as in Rust. +Rhai strings are _immutable_ and can be shared. +Modifying a Rhai string actually causes it first to be cloned, and then the modification made to the copy. + Strings can be built up from other strings and types via the `+` operator (provided by the [`MoreStringPackage`](#packages) but excluded if using a [raw `Engine`]). This is particularly useful when printing output. diff --git a/src/any.rs b/src/any.rs index c313de6a..768c66bb 100644 --- a/src/any.rs +++ b/src/any.rs @@ -1,6 +1,6 @@ //! Helper module which defines the `Any` trait to to allow dynamic value handling. -use crate::parser::INT; +use crate::parser::{ImmutableString, INT}; use crate::r#unsafe::{unsafe_cast_box, unsafe_try_cast}; #[cfg(not(feature = "no_module"))] @@ -151,7 +151,7 @@ pub struct Dynamic(pub(crate) Union); pub enum Union { Unit(()), Bool(bool), - Str(Box), + Str(ImmutableString), Char(char), Int(INT), #[cfg(not(feature = "no_float"))] @@ -178,6 +178,10 @@ impl Dynamic { /// Is the value held by this `Dynamic` a particular type? pub fn is(&self) -> bool { self.type_id() == TypeId::of::() + || match self.0 { + Union::Str(_) => TypeId::of::() == TypeId::of::(), + _ => false, + } } /// Get the TypeId of the value held by this `Dynamic`. @@ -185,7 +189,7 @@ impl Dynamic { match &self.0 { Union::Unit(_) => TypeId::of::<()>(), Union::Bool(_) => TypeId::of::(), - Union::Str(_) => TypeId::of::(), + Union::Str(_) => TypeId::of::(), Union::Char(_) => TypeId::of::(), Union::Int(_) => TypeId::of::(), #[cfg(not(feature = "no_float"))] @@ -342,6 +346,12 @@ impl Dynamic { return Self(result); } else if let Some(result) = dyn_value.downcast_ref::().cloned().map(Union::Char) { return Self(result); + } else if let Some(result) = dyn_value + .downcast_ref::() + .cloned() + .map(Union::Str) + { + return Self(result); } #[cfg(not(feature = "no_float"))] @@ -358,7 +368,7 @@ impl Dynamic { Err(var) => var, }; var = match unsafe_cast_box::<_, String>(var) { - Ok(s) => return Self(Union::Str(s)), + Ok(s) => return Self(Union::Str(s.into())), Err(var) => var, }; #[cfg(not(feature = "no_index"))] @@ -395,14 +405,22 @@ impl Dynamic { /// assert_eq!(x.try_cast::().unwrap(), 42); /// ``` pub fn try_cast(self) -> Option { - if TypeId::of::() == TypeId::of::() { + let type_id = TypeId::of::(); + + if type_id == TypeId::of::() { return unsafe_cast_box::<_, T>(Box::new(self)).ok().map(|v| *v); } match self.0 { Union::Unit(value) => unsafe_try_cast(value), Union::Bool(value) => unsafe_try_cast(value), - Union::Str(value) => unsafe_cast_box::<_, T>(value).ok().map(|v| *v), + Union::Str(value) => { + if type_id == TypeId::of::() { + unsafe_try_cast(value) + } else { + unsafe_try_cast((*value).clone()) + } + } Union::Char(value) => unsafe_try_cast(value), Union::Int(value) => unsafe_try_cast(value), #[cfg(not(feature = "no_float"))] @@ -434,16 +452,21 @@ impl Dynamic { /// assert_eq!(x.cast::(), 42); /// ``` pub fn cast(self) -> T { - //self.try_cast::().unwrap() + let type_id = TypeId::of::(); - if TypeId::of::() == TypeId::of::() { + if type_id == TypeId::of::() { return *unsafe_cast_box::<_, T>(Box::new(self)).unwrap(); } match self.0 { Union::Unit(value) => unsafe_try_cast(value).unwrap(), Union::Bool(value) => unsafe_try_cast(value).unwrap(), - Union::Str(value) => *unsafe_cast_box::<_, T>(value).unwrap(), + Union::Str(value) => if type_id == TypeId::of::() { + unsafe_try_cast(value) + } else { + unsafe_try_cast((*value).clone()) + } + .unwrap(), Union::Char(value) => unsafe_try_cast(value).unwrap(), Union::Int(value) => unsafe_try_cast(value).unwrap(), #[cfg(not(feature = "no_float"))] @@ -469,7 +492,9 @@ impl Dynamic { match &self.0 { Union::Unit(value) => (value as &dyn Any).downcast_ref::(), Union::Bool(value) => (value as &dyn Any).downcast_ref::(), - Union::Str(value) => (value.as_ref() as &dyn Any).downcast_ref::(), + Union::Str(value) => (value as &dyn Any) + .downcast_ref::() + .or_else(|| (value.as_ref() as &dyn Any).downcast_ref::()), Union::Char(value) => (value as &dyn Any).downcast_ref::(), Union::Int(value) => (value as &dyn Any).downcast_ref::(), #[cfg(not(feature = "no_float"))] @@ -495,7 +520,7 @@ impl Dynamic { match &mut self.0 { Union::Unit(value) => (value as &mut dyn Any).downcast_mut::(), Union::Bool(value) => (value as &mut dyn Any).downcast_mut::(), - Union::Str(value) => (value.as_mut() as &mut dyn Any).downcast_mut::(), + Union::Str(value) => (value as &mut dyn Any).downcast_mut::(), Union::Char(value) => (value as &mut dyn Any).downcast_mut::(), Union::Int(value) => (value as &mut dyn Any).downcast_mut::(), #[cfg(not(feature = "no_float"))] @@ -560,7 +585,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(*s), + Union::Str(s) => Ok((*s).clone()), _ => Err(self.type_name()), } } @@ -594,7 +619,12 @@ impl From for Dynamic { } impl From for Dynamic { fn from(value: String) -> Self { - Self(Union::Str(Box::new(value))) + Self(Union::Str(value.into())) + } +} +impl From for Dynamic { + fn from(value: ImmutableString) -> Self { + Self(Union::Str(value)) } } #[cfg(not(feature = "no_index"))] diff --git a/src/engine.rs b/src/engine.rs index 54fd0a75..64f7f0d8 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -7,7 +7,7 @@ use crate::fn_native::{FnCallArgs, Shared}; use crate::module::Module; use crate::optimize::OptimizationLevel; use crate::packages::{CorePackage, Package, PackageLibrary, PackagesCollection, StandardPackage}; -use crate::parser::{Expr, FnAccess, FnDef, ReturnType, Stmt, AST, INT}; +use crate::parser::{Expr, FnAccess, FnDef, ImmutableString, ReturnType, Stmt, AST, INT}; use crate::r#unsafe::{unsafe_cast_var_name_to_lifetime, unsafe_mut_cast_to_lifetime}; use crate::result::EvalAltResult; use crate::scope::{EntryType as ScopeEntryType, Scope}; @@ -129,7 +129,7 @@ impl Target<'_> { Target::Value(_) => { return Err(Box::new(EvalAltResult::ErrorAssignmentToUnknownLHS(pos))) } - Target::StringChar(Dynamic(Union::Str(s)), index, _) => { + Target::StringChar(Dynamic(Union::Str(ref mut s)), index, _) => { // Replace the character at the specified index position let new_ch = new_val .as_char() @@ -141,8 +141,7 @@ impl Target<'_> { // See if changed - if so, update the String if ch != new_ch { chars[*index] = new_ch; - s.clear(); - chars.iter().for_each(|&ch| s.push(ch)); + *s = chars.iter().collect::().into(); } } _ => unreachable!(), @@ -1277,14 +1276,18 @@ impl Engine { #[cfg(not(feature = "no_object"))] Dynamic(Union::Map(map)) => { // val_map[idx] - let index = idx - .take_string() - .map_err(|_| EvalAltResult::ErrorStringIndexExpr(idx_pos))?; - Ok(if create { + let index = idx + .take_string() + .map_err(|_| EvalAltResult::ErrorStringIndexExpr(idx_pos))?; + map.entry(index).or_insert(Default::default()).into() } else { - map.get_mut(&index) + let index = idx + .downcast_ref::() + .ok_or_else(|| EvalAltResult::ErrorStringIndexExpr(idx_pos))?; + + map.get_mut(index) .map(Target::from) .unwrap_or_else(|| Target::from(())) }) @@ -1864,7 +1867,7 @@ impl Engine { if let Some(path) = self .eval_expr(scope, state, lib, &expr, level)? - .try_cast::() + .try_cast::() { if let Some(resolver) = &self.module_resolver { // Use an empty scope to create a module @@ -1879,7 +1882,7 @@ impl Engine { Ok(Default::default()) } else { Err(Box::new(EvalAltResult::ErrorModuleNotFound( - path, + path.to_string(), expr.position(), ))) } @@ -2015,9 +2018,9 @@ fn run_builtin_binary_op( "!=" => return Ok(Some((x != y).into())), _ => (), } - } else if args_type == TypeId::of::() { - let x = x.downcast_ref::().unwrap(); - let y = y.downcast_ref::().unwrap(); + } else if args_type == TypeId::of::() { + let x = x.downcast_ref::().unwrap(); + let y = y.downcast_ref::().unwrap(); match op { "==" => return Ok(Some((x == y).into())), diff --git a/src/lib.rs b/src/lib.rs index df039436..454e3d13 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -93,7 +93,7 @@ pub use engine::Engine; pub use error::{ParseError, ParseErrorType}; pub use fn_register::{RegisterFn, RegisterResultFn}; pub use module::Module; -pub use parser::{AST, INT}; +pub use parser::{ImmutableString, AST, INT}; pub use result::EvalAltResult; pub use scope::Scope; pub use token::Position; diff --git a/src/optimize.rs b/src/optimize.rs index 5cb0006d..1d182c19 100644 --- a/src/optimize.rs +++ b/src/optimize.rs @@ -438,7 +438,7 @@ fn optimize_expr<'a>(expr: Expr, state: &mut State<'a>) -> Expr { // All other items can be thrown away. state.set_dirty(); let pos = m.1; - m.0.into_iter().find(|((name, _), _)| name == &s.0) + m.0.into_iter().find(|((name, _), _)| name == s.0.as_ref()) .map(|(_, expr)| expr.set_position(pos)) .unwrap_or_else(|| Expr::Unit(pos)) } @@ -466,7 +466,7 @@ fn optimize_expr<'a>(expr: Expr, state: &mut State<'a>) -> Expr { // "xxx" in "xxxxx" (Expr::StringConstant(a), Expr::StringConstant(b)) => { state.set_dirty(); - if b.0.contains(&a.0) { Expr::True(a.1) } else { Expr::False(a.1) } + if b.0.contains(a.0.as_ref()) { Expr::True(a.1) } else { Expr::False(a.1) } } // 'x' in "xxxxx" (Expr::CharConstant(a), Expr::StringConstant(b)) => { @@ -476,7 +476,7 @@ fn optimize_expr<'a>(expr: Expr, state: &mut State<'a>) -> Expr { // "xxx" in #{...} (Expr::StringConstant(a), Expr::Map(b)) => { state.set_dirty(); - if b.0.iter().find(|((name, _), _)| name == &a.0).is_some() { + if b.0.iter().find(|((name, _), _)| name == a.0.as_ref()).is_some() { Expr::True(a.1) } else { Expr::False(a.1) diff --git a/src/packages/array_basic.rs b/src/packages/array_basic.rs index eb56bbf4..1abdd3be 100644 --- a/src/packages/array_basic.rs +++ b/src/packages/array_basic.rs @@ -4,9 +4,9 @@ use crate::any::{Dynamic, Variant}; use crate::def_package; use crate::engine::Array; use crate::module::FuncReturn; -use crate::parser::INT; +use crate::parser::{ImmutableString, INT}; -use crate::stdlib::{any::TypeId, boxed::Box, string::String}; +use crate::stdlib::{any::TypeId, boxed::Box}; // Register array utility functions fn push(list: &mut Array, item: T) -> FuncReturn<()> { @@ -45,9 +45,9 @@ macro_rules! reg_tri { #[cfg(not(feature = "no_index"))] def_package!(crate:BasicArrayPackage:"Basic array utilities.", lib, { - reg_op!(lib, "push", push, INT, bool, char, String, Array, ()); - reg_tri!(lib, "pad", pad, INT, bool, char, String, Array, ()); - reg_tri!(lib, "insert", ins, INT, bool, char, String, Array, ()); + reg_op!(lib, "push", push, INT, bool, char, ImmutableString, Array, ()); + reg_tri!(lib, "pad", pad, INT, bool, char, ImmutableString, Array, ()); + reg_tri!(lib, "insert", ins, INT, bool, char, ImmutableString, Array, ()); lib.set_fn_2_mut("append", |x: &mut Array, y: Array| { x.extend(y); diff --git a/src/packages/eval.rs b/src/packages/eval.rs index a6756d96..6f97901d 100644 --- a/src/packages/eval.rs +++ b/src/packages/eval.rs @@ -1,11 +1,11 @@ use crate::def_package; use crate::module::FuncReturn; -use crate::stdlib::string::String; +use crate::parser::ImmutableString; def_package!(crate:EvalPackage:"Disable 'eval'.", lib, { - lib.set_fn_1_mut( + lib.set_fn_1( "eval", - |_: &mut String| -> FuncReturn<()> { + |_: ImmutableString| -> FuncReturn<()> { Err("eval is evil!".into()) }, ); diff --git a/src/packages/logic.rs b/src/packages/logic.rs index 03a12e9d..50d74466 100644 --- a/src/packages/logic.rs +++ b/src/packages/logic.rs @@ -1,8 +1,6 @@ use crate::def_package; use crate::module::FuncReturn; -use crate::parser::INT; - -use crate::stdlib::string::String; +use crate::parser::{ImmutableString, INT}; // Comparison operators pub fn lt(x: T, y: T) -> FuncReturn { @@ -50,12 +48,12 @@ def_package!(crate:LogicPackage:"Logical operators.", lib, { // reg_op!(lib, "!=", ne, INT, char, bool, ()); // Special versions for strings - at least avoid copying the first string - // lib.set_fn_2_mut("<", |x: &mut String, y: String| Ok(*x < y)); - // lib.set_fn_2_mut("<=", |x: &mut String, y: String| Ok(*x <= y)); - // lib.set_fn_2_mut(">", |x: &mut String, y: String| Ok(*x > y)); - // lib.set_fn_2_mut(">=", |x: &mut String, y: String| Ok(*x >= y)); - // lib.set_fn_2_mut("==", |x: &mut String, y: String| Ok(*x == y)); - // lib.set_fn_2_mut("!=", |x: &mut String, y: String| 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)); + // lib.set_fn_2("!=", |x: ImmutableString, y: ImmutableString| Ok(*x != y)); #[cfg(not(feature = "only_i32"))] #[cfg(not(feature = "only_i64"))] diff --git a/src/packages/map_basic.rs b/src/packages/map_basic.rs index c9f4e894..cc2e4c1c 100644 --- a/src/packages/map_basic.rs +++ b/src/packages/map_basic.rs @@ -4,7 +4,7 @@ use crate::any::Dynamic; use crate::def_package; use crate::engine::Map; use crate::module::FuncReturn; -use crate::parser::INT; +use crate::parser::{ImmutableString, INT}; use crate::stdlib::{ string::{String, ToString}, @@ -22,7 +22,7 @@ fn map_get_values(map: &mut Map) -> FuncReturn> { def_package!(crate:BasicMapPackage:"Basic object map utilities.", lib, { lib.set_fn_2_mut( "has", - |map: &mut Map, prop: String| Ok(map.contains_key(&prop)), + |map: &mut Map, prop: ImmutableString| Ok(map.contains_key(prop.as_str())), ); lib.set_fn_1_mut("len", |map: &mut Map| Ok(map.len() as INT)); lib.set_fn_1_mut("clear", |map: &mut Map| { @@ -31,7 +31,7 @@ def_package!(crate:BasicMapPackage:"Basic object map utilities.", lib, { }); lib.set_fn_2_mut( "remove", - |x: &mut Map, name: String| Ok(x.remove(&name).unwrap_or_else(|| ().into())), + |x: &mut Map, name: ImmutableString| Ok(x.remove(name.as_str()).unwrap_or_else(|| ().into())), ); lib.set_fn_2_mut( "mixin", diff --git a/src/packages/string_basic.rs b/src/packages/string_basic.rs index 3b1689cf..18839aed 100644 --- a/src/packages/string_basic.rs +++ b/src/packages/string_basic.rs @@ -1,7 +1,7 @@ use crate::def_package; use crate::engine::{FUNC_TO_STRING, KEYWORD_DEBUG, KEYWORD_PRINT}; use crate::module::FuncReturn; -use crate::parser::INT; +use crate::parser::{ImmutableString, INT}; #[cfg(not(feature = "no_index"))] use crate::engine::Array; @@ -16,15 +16,15 @@ use crate::stdlib::{ }; // Register print and debug -fn to_debug(x: &mut T) -> FuncReturn { - Ok(format!("{:?}", x)) +fn to_debug(x: &mut T) -> FuncReturn { + Ok(format!("{:?}", x).into()) } -fn to_string(x: &mut T) -> FuncReturn { - Ok(format!("{}", x)) +fn to_string(x: &mut T) -> FuncReturn { + Ok(format!("{}", x).into()) } #[cfg(not(feature = "no_object"))] -fn format_map(x: &mut Map) -> FuncReturn { - Ok(format!("#{:?}", x)) +fn format_map(x: &mut Map) -> FuncReturn { + Ok(format!("#{:?}", x).into()) } macro_rules! reg_op { @@ -41,10 +41,10 @@ def_package!(crate:BasicStringPackage:"Basic string utilities, including printin lib.set_fn_1(KEYWORD_PRINT, |_: ()| Ok("".to_string())); lib.set_fn_1(FUNC_TO_STRING, |_: ()| Ok("".to_string())); - lib.set_fn_1_mut(KEYWORD_PRINT, |s: &mut String| Ok(s.clone())); - lib.set_fn_1_mut(FUNC_TO_STRING, |s: &mut String| Ok(s.clone())); + lib.set_fn_1(KEYWORD_PRINT, |s: ImmutableString| Ok(s.clone())); + lib.set_fn_1(FUNC_TO_STRING, |s: ImmutableString| Ok(s.clone())); - reg_op!(lib, KEYWORD_DEBUG, to_debug, INT, bool, (), char, String); + reg_op!(lib, KEYWORD_DEBUG, to_debug, INT, bool, (), char, ImmutableString); #[cfg(not(feature = "only_i32"))] #[cfg(not(feature = "only_i64"))] @@ -80,26 +80,32 @@ def_package!(crate:BasicStringPackage:"Basic string utilities, including printin lib.set_fn_2( "+", - |mut s: String, ch: char| { + |s: ImmutableString, ch: char| { + let mut s = (*s).clone(); s.push(ch); Ok(s) }, ); lib.set_fn_2( "+", - |mut s: String, s2: String| { - s.push_str(&s2); + |s:ImmutableString, s2:ImmutableString| { + let mut s = (*s).clone(); + s.push_str(s2.as_str()); Ok(s) }, ); - lib.set_fn_2_mut("append", |s: &mut String, ch: char| { - s.push(ch); + lib.set_fn_2_mut("append", |s: &mut ImmutableString, ch: char| { + let mut copy = (**s).clone(); + copy.push(ch); + *s = copy.into(); Ok(()) }); lib.set_fn_2_mut( "append", - |s: &mut String, s2: String| { - s.push_str(&s2); + |s: &mut ImmutableString, s2: ImmutableString| { + let mut copy = (**s).clone(); + copy.push_str(s2.as_str()); + *s = copy.into(); Ok(()) } ); diff --git a/src/packages/string_more.rs b/src/packages/string_more.rs index 2c90bea5..0217fe63 100644 --- a/src/packages/string_more.rs +++ b/src/packages/string_more.rs @@ -1,6 +1,6 @@ use crate::def_package; use crate::module::FuncReturn; -use crate::parser::INT; +use crate::parser::{ImmutableString, INT}; use crate::utils::StaticVec; #[cfg(not(feature = "no_index"))] @@ -10,22 +10,21 @@ use crate::stdlib::{ fmt::Display, format, string::{String, ToString}, - vec::Vec, }; -fn prepend(x: T, y: String) -> FuncReturn { - Ok(format!("{}{}", x, y)) +fn prepend(x: T, y: ImmutableString) -> FuncReturn { + Ok(format!("{}{}", x, y).into()) } -fn append(x: String, y: T) -> FuncReturn { - Ok(format!("{}{}", x, y)) +fn append(x: ImmutableString, y: T) -> FuncReturn { + Ok(format!("{}{}", x, y).into()) } -fn sub_string(s: &mut String, start: INT, len: INT) -> FuncReturn { +fn sub_string(s: ImmutableString, start: INT, len: INT) -> FuncReturn { let offset = if s.is_empty() || len <= 0 { - return Ok("".to_string()); + return Ok("".to_string().into()); } else if start < 0 { 0 } else if (start as usize) >= s.chars().count() { - return Ok("".to_string()); + return Ok("".to_string().into()); } else { start as usize }; @@ -38,37 +37,45 @@ fn sub_string(s: &mut String, start: INT, len: INT) -> FuncReturn { len as usize }; - Ok(chars.iter().skip(offset).take(len).cloned().collect()) -} -fn crop_string(s: &mut String, start: INT, len: INT) -> FuncReturn<()> { - let offset = if s.is_empty() || len <= 0 { - s.clear(); - return Ok(()); - } else if start < 0 { - 0 - } else if (start as usize) >= s.chars().count() { - s.clear(); - return Ok(()); - } else { - start as usize - }; - - let chars: StaticVec<_> = s.chars().collect(); - - let len = if offset + (len as usize) > chars.len() { - chars.len() - offset - } else { - len as usize - }; - - s.clear(); - - chars + Ok(chars .iter() .skip(offset) .take(len) - .for_each(|&ch| s.push(ch)); + .cloned() + .collect::() + .into()) +} +fn crop_string(s: &mut ImmutableString, start: INT, len: INT) -> FuncReturn<()> { + let mut copy = (**s).clone(); + let offset = if copy.is_empty() || len <= 0 { + copy.clear(); + *s = copy.into(); + return Ok(()); + } else if start < 0 { + 0 + } else if (start as usize) >= copy.chars().count() { + copy.clear(); + *s = copy.into(); + return Ok(()); + } else { + start as usize + }; + + let chars: StaticVec<_> = copy.chars().collect(); + + let len = if offset + (len as usize) > chars.len() { + chars.len() - offset + } else { + len as usize + }; + + *s = chars + .iter() + .skip(offset) + .take(len) + .collect::() + .into(); Ok(()) } @@ -80,10 +87,10 @@ macro_rules! reg_op { def_package!(crate:MoreStringPackage:"Additional string utilities, including string building.", lib, { reg_op!(lib, "+", append, INT, bool, char); - lib.set_fn_2_mut( "+", |x: &mut String, _: ()| Ok(x.clone())); + lib.set_fn_2( "+", |x: ImmutableString, _: ()| Ok(x)); reg_op!(lib, "+", prepend, INT, bool, char); - lib.set_fn_2("+", |_: (), y: String| Ok(y)); + lib.set_fn_2("+", |_: (), y: ImmutableString| Ok(y)); #[cfg(not(feature = "only_i32"))] #[cfg(not(feature = "only_i64"))] @@ -100,22 +107,22 @@ def_package!(crate:MoreStringPackage:"Additional string utilities, including str #[cfg(not(feature = "no_index"))] { - lib.set_fn_2("+", |x: String, y: Array| Ok(format!("{}{:?}", x, y))); - lib.set_fn_2("+", |x: Array, y: String| Ok(format!("{:?}{}", x, y))); + lib.set_fn_2("+", |x: ImmutableString, y: Array| Ok(format!("{}{:?}", x, y))); + lib.set_fn_2_mut("+", |x: &mut Array, y: ImmutableString| Ok(format!("{:?}{}", x, y))); } - lib.set_fn_1_mut("len", |s: &mut String| Ok(s.chars().count() as INT)); - lib.set_fn_2_mut( + lib.set_fn_1("len", |s: ImmutableString| Ok(s.chars().count() as INT)); + lib.set_fn_2( "contains", - |s: &mut String, ch: char| Ok(s.contains(ch)), + |s: ImmutableString, ch: char| Ok(s.contains(ch)), ); - lib.set_fn_2_mut( + lib.set_fn_2( "contains", - |s: &mut String, find: String| Ok(s.contains(&find)), + |s: ImmutableString, find: ImmutableString| Ok(s.contains(find.as_str())), ); - lib.set_fn_3_mut( + lib.set_fn_3( "index_of", - |s: &mut String, ch: char, start: INT| { + |s: ImmutableString, ch: char, start: INT| { let start = if start < 0 { 0 } else if (start as usize) >= s.chars().count() { @@ -130,17 +137,17 @@ def_package!(crate:MoreStringPackage:"Additional string utilities, including str .unwrap_or(-1 as INT)) }, ); - lib.set_fn_2_mut( + lib.set_fn_2( "index_of", - |s: &mut String, ch: char| { + |s: ImmutableString, ch: char| { Ok(s.find(ch) .map(|index| s[0..index].chars().count() as INT) .unwrap_or(-1 as INT)) }, ); - lib.set_fn_3_mut( + lib.set_fn_3( "index_of", - |s: &mut String, find: String, start: INT| { + |s: ImmutableString, find: ImmutableString, start: INT| { let start = if start < 0 { 0 } else if (start as usize) >= s.chars().count() { @@ -150,109 +157,108 @@ def_package!(crate:MoreStringPackage:"Additional string utilities, including str }; Ok(s[start..] - .find(&find) + .find(find.as_str()) .map(|index| s[0..start + index].chars().count() as INT) .unwrap_or(-1 as INT)) }, ); - lib.set_fn_2_mut( + lib.set_fn_2( "index_of", - |s: &mut String, find: String| { - Ok(s.find(&find) + |s: ImmutableString, find: ImmutableString| { + Ok(s.find(find.as_str()) .map(|index| s[0..index].chars().count() as INT) .unwrap_or(-1 as INT)) }, ); - lib.set_fn_1_mut("clear", |s: &mut String| { - s.clear(); + lib.set_fn_1_mut("clear", |s: &mut ImmutableString| { + *s = "".to_string().into(); Ok(()) }); - lib.set_fn_2_mut( "append", |s: &mut String, ch: char| { - s.push(ch); + lib.set_fn_2_mut("append", |s: &mut ImmutableString, ch: char| { + let mut copy = (**s).clone(); + copy.push(ch); + *s = copy.into(); Ok(()) }); lib.set_fn_2_mut( "append", - |s: &mut String, add: String| { - s.push_str(&add); + |s: &mut ImmutableString, add: ImmutableString| { + let mut copy = (**s).clone(); + copy.push_str(add.as_str()); + *s = copy.into(); Ok(()) } ); - lib.set_fn_3_mut( "sub_string", sub_string); - lib.set_fn_2_mut( + lib.set_fn_3("sub_string", sub_string); + lib.set_fn_2( "sub_string", - |s: &mut String, start: INT| sub_string(s, start, s.len() as INT), + |s: ImmutableString, start: INT| { + let len = s.len() as INT; + sub_string(s, start, len) + }, ); - lib.set_fn_3_mut( "crop", crop_string); + lib.set_fn_3_mut("crop", crop_string); lib.set_fn_2_mut( "crop", - |s: &mut String, start: INT| crop_string(s, start, s.len() as INT), + |s: &mut ImmutableString, start: INT| crop_string(s, start, s.len() as INT), ); lib.set_fn_2_mut( "truncate", - |s: &mut String, len: INT| { + |s: &mut ImmutableString, len: INT| { if len >= 0 { - let chars: StaticVec<_> = s.chars().take(len as usize).collect(); - s.clear(); - chars.iter().for_each(|&ch| s.push(ch)); + *s = (**s).clone().chars().take(len as usize).collect::().into(); } else { - s.clear(); + *s = "".to_string().into(); } Ok(()) }, ); lib.set_fn_3_mut( "pad", - |s: &mut String, len: INT, ch: char| { - for _ in 0..s.chars().count() - len as usize { - s.push(ch); + |s: &mut ImmutableString, len: INT, ch: char| { + let mut copy = (**s).clone(); + for _ in 0..copy.chars().count() - len as usize { + copy.push(ch); } + *s = copy.into(); Ok(()) }, ); lib.set_fn_3_mut( "replace", - |s: &mut String, find: String, sub: String| { - let new_str = s.replace(&find, &sub); - s.clear(); - s.push_str(&new_str); + |s: &mut ImmutableString, find: ImmutableString, sub: ImmutableString| { + *s = s.replace(find.as_str(), sub.as_str()).into(); Ok(()) }, ); lib.set_fn_3_mut( "replace", - |s: &mut String, find: String, sub: char| { - let new_str = s.replace(&find, &sub.to_string()); - s.clear(); - s.push_str(&new_str); + |s: &mut ImmutableString, find: ImmutableString, sub: char| { + *s = s.replace(find.as_str(), &sub.to_string()).into(); Ok(()) }, ); lib.set_fn_3_mut( "replace", - |s: &mut String, find: char, sub: String| { - let new_str = s.replace(&find.to_string(), &sub); - s.clear(); - s.push_str(&new_str); + |s: &mut ImmutableString, find: char, sub: ImmutableString| { + *s = s.replace(&find.to_string(), sub.as_str()).into(); Ok(()) }, ); lib.set_fn_3_mut( "replace", - |s: &mut String, find: char, sub: char| { - let new_str = s.replace(&find.to_string(), &sub.to_string()); - s.clear(); - s.push_str(&new_str); + |s: &mut ImmutableString, find: char, sub: char| { + *s = s.replace(&find.to_string(), &sub.to_string()).into(); Ok(()) }, ); lib.set_fn_1_mut( "trim", - |s: &mut String| { + |s: &mut ImmutableString| { let trimmed = s.trim(); if trimmed.len() < s.len() { - *s = trimmed.to_string(); + *s = trimmed.to_string().into(); } Ok(()) }, diff --git a/src/parser.rs b/src/parser.rs index 68ea09f7..4180ffbf 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -4,6 +4,7 @@ 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}; @@ -48,6 +49,9 @@ 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; /// Compiled AST (abstract syntax tree) of a Rhai script. @@ -375,7 +379,7 @@ pub enum Expr { /// Character constant. CharConstant(Box<(char, Position)>), /// String constant. - StringConstant(Box<(String, Position)>), + StringConstant(Box<(ImmutableString, Position)>), /// Variable access - ((variable name, position), optional modules, hash, optional index) Variable( Box<( @@ -1208,7 +1212,7 @@ fn parse_primary<'a>( #[cfg(not(feature = "no_float"))] Token::FloatConstant(x) => Expr::FloatConstant(Box::new((x, pos))), Token::CharConstant(c) => Expr::CharConstant(Box::new((c, pos))), - Token::StringConst(s) => Expr::StringConstant(Box::new((s, pos))), + Token::StringConst(s) => Expr::StringConstant(Box::new((s.into(), pos))), Token::Identifier(s) => { let index = state.find(&s); Expr::Variable(Box::new(((s, pos), None, 0, index))) @@ -2603,7 +2607,7 @@ pub fn map_dynamic_to_expr(value: Dynamic, pos: Position) -> Option { Union::Unit(_) => Some(Expr::Unit(pos)), Union::Int(value) => Some(Expr::IntegerConstant(Box::new((value, pos)))), Union::Char(value) => Some(Expr::CharConstant(Box::new((value, pos)))), - Union::Str(value) => Some(Expr::StringConstant(Box::new(((*value).clone(), pos)))), + Union::Str(value) => Some(Expr::StringConstant(Box::new((value.clone(), pos)))), Union::Bool(true) => Some(Expr::True(pos)), Union::Bool(false) => Some(Expr::False(pos)), #[cfg(not(feature = "no_index"))] diff --git a/tests/get_set.rs b/tests/get_set.rs index 94c3064a..2df59671 100644 --- a/tests/get_set.rs +++ b/tests/get_set.rs @@ -1,6 +1,6 @@ #![cfg(not(feature = "no_object"))] -use rhai::{Engine, EvalAltResult, RegisterFn, INT}; +use rhai::{Engine, EvalAltResult, ImmutableString, RegisterFn, INT}; #[test] fn test_get_set() -> Result<(), Box> { @@ -43,7 +43,9 @@ fn test_get_set() -> Result<(), Box> { engine.register_fn("new_ts", TestStruct::new); #[cfg(not(feature = "no_index"))] - engine.register_indexer(|value: &mut TestStruct, index: String| value.array[index.len()]); + engine.register_indexer(|value: &mut TestStruct, index: ImmutableString| { + value.array[index.len()] + }); assert_eq!(engine.eval::("let a = new_ts(); a.x = 500; a.x")?, 500); assert_eq!(engine.eval::("let a = new_ts(); a.x.add(); a.x")?, 42);