Complete ImmutableString.

This commit is contained in:
Stephen Chung 2020-05-26 14:14:03 +08:00
parent 95e67c48bd
commit b34d5fe3a1
15 changed files with 379 additions and 274 deletions

View File

@ -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. 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 [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 is an alias to `Rc<String>` or `Arc<String>` (depending on the [`sync`] feature).
and the modifications made to the copy. 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 `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<i64>` (`i32` if [`only_i32`]) * `From<i64>` (`i32` if [`only_i32`])
* `From<f64>` (if not [`no_float`]) * `From<f64>` (if not [`no_float`])
* `From<bool>` * `From<bool>`
* `From<rhai::ImmutableString>`
* `From<String>` * `From<String>`
* `From<char>` * `From<char>`
* `From<Vec<T>>` (into an [array]) * `From<Vec<T>>` (into an [array])

View File

@ -4,8 +4,8 @@ Rhai Release Notes
Version 0.14.2 Version 0.14.2
============== ==============
Regression Regression fix
---------- --------------
* Do not optimize script with `eval_expression` - it is assumed to be one-off and short. * 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. * 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 * Some operator functions are now built in (see _Speed enhancements_ below), so they are available even
under `Engine::new_raw`. 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<String>` or `Arc<String>` depending on
whether the `sync` feature is used).
New features New features
------------ ------------
@ -35,6 +39,8 @@ Speed enhancements
significant speed-up. significant speed-up.
* Implementations of common operators for standard types are removed from the `ArithmeticPackage` and `LogicPackage` * 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`. (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 Version 0.14.1

View File

@ -1,6 +1,5 @@
//! Helper module which defines the `Any` trait to to allow dynamic value handling. //! 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::parser::{ImmutableString, INT};
use crate::r#unsafe::{unsafe_cast_box, unsafe_try_cast}; use crate::r#unsafe::{unsafe_cast_box, unsafe_try_cast};
@ -21,9 +20,7 @@ use crate::stdlib::{
boxed::Box, boxed::Box,
collections::HashMap, collections::HashMap,
fmt, fmt,
rc::Rc,
string::String, string::String,
sync::Arc,
vec::Vec, vec::Vec,
}; };
@ -417,13 +414,10 @@ impl Dynamic {
match self.0 { match self.0 {
Union::Unit(value) => unsafe_try_cast(value), Union::Unit(value) => unsafe_try_cast(value),
Union::Bool(value) => unsafe_try_cast(value), Union::Bool(value) => unsafe_try_cast(value),
Union::Str(value) => { Union::Str(value) if type_id == TypeId::of::<ImmutableString>() => {
if type_id == TypeId::of::<ImmutableString>() {
unsafe_try_cast(value) unsafe_try_cast(value)
} else {
unsafe_try_cast((*value).clone())
}
} }
Union::Str(value) => unsafe_try_cast(value.into_owned()),
Union::Char(value) => unsafe_try_cast(value), Union::Char(value) => unsafe_try_cast(value),
Union::Int(value) => unsafe_try_cast(value), Union::Int(value) => unsafe_try_cast(value),
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
@ -464,12 +458,10 @@ impl Dynamic {
match self.0 { match self.0 {
Union::Unit(value) => unsafe_try_cast(value).unwrap(), Union::Unit(value) => unsafe_try_cast(value).unwrap(),
Union::Bool(value) => unsafe_try_cast(value).unwrap(), Union::Bool(value) => unsafe_try_cast(value).unwrap(),
Union::Str(value) => if type_id == TypeId::of::<ImmutableString>() { Union::Str(value) if type_id == TypeId::of::<ImmutableString>() => {
unsafe_try_cast(value) unsafe_try_cast(value).unwrap()
} else {
unsafe_try_cast((*value).clone())
} }
.unwrap(), Union::Str(value) => unsafe_try_cast(value.into_owned()).unwrap(),
Union::Char(value) => unsafe_try_cast(value).unwrap(), Union::Char(value) => unsafe_try_cast(value).unwrap(),
Union::Int(value) => unsafe_try_cast(value).unwrap(), Union::Int(value) => unsafe_try_cast(value).unwrap(),
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
@ -588,7 +580,7 @@ impl Dynamic {
/// Returns the name of the actual type if the cast fails. /// Returns the name of the actual type if the cast fails.
pub fn take_string(self) -> Result<String, &'static str> { pub fn take_string(self) -> Result<String, &'static str> {
match self.0 { 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()), _ => Err(self.type_name()),
} }
} }

View File

@ -2055,6 +2055,7 @@ fn run_builtin_binary_op(
let y = y.downcast_ref::<ImmutableString>().unwrap(); let y = y.downcast_ref::<ImmutableString>().unwrap();
match op { 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())), "!=" => return Ok(Some((x != y).into())),
">" => return Ok(Some((x > y).into())), ">" => return Ok(Some((x > y).into())),

View File

@ -19,6 +19,8 @@ pub type Shared<T> = Rc<T>;
#[cfg(feature = "sync")] #[cfg(feature = "sync")]
pub type Shared<T> = Arc<T>; pub type Shared<T> = Arc<T>;
/// 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<T: Clone>(value: &mut Shared<T>) -> &mut T { pub fn shared_make_mut<T: Clone>(value: &mut Shared<T>) -> &mut T {
#[cfg(not(feature = "sync"))] #[cfg(not(feature = "sync"))]
{ {
@ -30,14 +32,19 @@ pub fn shared_make_mut<T: Clone>(value: &mut Shared<T>) -> &mut T {
} }
} }
pub fn shared_unwrap<T: Clone>(value: Shared<T>) -> Result<T, Shared<T>> { /// 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<T: Clone>(value: Shared<T>) -> T {
#[cfg(not(feature = "sync"))] #[cfg(not(feature = "sync"))]
{ {
Rc::try_unwrap(value) Rc::try_unwrap(value).map_err(|_| ()).unwrap()
} }
#[cfg(feature = "sync")] #[cfg(feature = "sync")]
{ {
Arc::try_unwrap(value) Arc::try_unwrap(value).map_err(|_| ()).unwrap()
} }
} }

View File

@ -8,7 +8,7 @@ use crate::fn_native::{CallableFunction, FnAny, FnCallArgs};
use crate::parser::FnAccess; use crate::parser::FnAccess;
use crate::result::EvalAltResult; 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`. /// Trait to register custom functions with the `Engine`.
pub trait RegisterFn<FN, ARGS, RET> { pub trait RegisterFn<FN, ARGS, RET> {

View File

@ -50,7 +50,7 @@
//! ## Optional features //! ## Optional features
//! //!
//! | Feature | Description | //! | Feature | Description |
//! | ------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- | //! | ------------- | ----------------------------------------------------------------------------------------------------------------------------------|
//! | `unchecked` | Exclude arithmetic checking (such as overflows and division by zero). Beware that a bad script may panic the entire system! | //! | `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_function` | Disable script-defined functions if not needed. |
//! | `no_index` | Disable arrays and indexing features if not needed. | //! | `no_index` | Disable arrays and indexing features if not needed. |

View File

@ -269,141 +269,69 @@ macro_rules! reg_op {
} }
def_package!(crate:ArithmeticPackage:"Basic arithmetic", lib, { def_package!(crate:ArithmeticPackage:"Basic arithmetic", lib, {
// Checked basic arithmetic
#[cfg(not(feature = "unchecked"))]
{
// 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_i32"))]
#[cfg(not(feature = "only_i64"))] #[cfg(not(feature = "only_i64"))]
{ {
// reg_op!(lib, "+", add, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128); #[cfg(not(feature = "unchecked"))]
// 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); // Checked basic arithmetic
// reg_op!(lib, "/", div, i8, u8, i16, u16, i32, i64, u32, u64, i128, u128);
reg_op!(lib, "+", add, i8, u8, i16, u16, i32, u32, u64, i128, u128); 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, "-", 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, "*", mul, i8, u8, i16, u16, i32, u32, u64, i128, u128);
reg_op!(lib, "/", div, 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 // 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);
reg_op!(lib, "<<", shl, i8, u8, i16, u16, i32, u32, u64, i128, u128); 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, ">>", shr, i8, u8, i16, u16, i32, u32, u64, i128, u128);
reg_op!(lib, "%", modulo, 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")] #[cfg(feature = "unchecked")]
{ {
// reg_op!(lib, "<<", shl_u, INT, INT); // Unchecked basic arithmetic
// reg_op!(lib, ">>", shr_u, INT, INT); reg_op!(lib, "+", add_u, i8, u8, i16, u16, i32, u32, u64, i128, u128);
// reg_op!(lib, "%", modulo_u, INT); 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);
#[cfg(not(feature = "only_i32"))] reg_op!(lib, "/", div_u, i8, u8, i16, u16, i32, u32, u64, i128, u128);
#[cfg(not(feature = "only_i64"))] // Unchecked bit shifts
{
// 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);
reg_op!(lib, "<<", shl_u, i64, i8, u8, i16, u16, i32, u32, u64, i128, u128); 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, ">>", 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); reg_op!(lib, "%", modulo_u, i8, u8, i16, u16, i32, u32, u64, i128, u128);
} }
} }
// Checked power // Basic arithmetic for floating-point - no need to check
#[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "no_float"))]
{ {
// lib.set_fn_2("~", pow_i_i); 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"))] #[cfg(not(feature = "no_float"))]
{
// Checked power
#[cfg(not(feature = "unchecked"))]
lib.set_fn_2("~", pow_f_i); lib.set_fn_2("~", pow_f_i);
}
// Unchecked power // Unchecked power
#[cfg(feature = "unchecked")] #[cfg(feature = "unchecked")]
{
// lib.set_fn_2("~", pow_i_i_u);
#[cfg(not(feature = "no_float"))]
lib.set_fn_2("~", pow_f_i_u); lib.set_fn_2("~", pow_f_i_u);
}
// Floating-point modulo and power // Floating-point modulo and power
#[cfg(not(feature = "no_float"))]
{
// reg_op!(lib, "%", modulo_u, f32, f64);
reg_op!(lib, "%", modulo_u, f32); 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 // Checked unary
@ -433,11 +361,4 @@ def_package!(crate:ArithmeticPackage:"Basic arithmetic", lib, {
reg_unary!(lib, "abs", abs_u, i8, i16, i32, i64, i128); 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);
}
}); });

View File

@ -1,6 +1,5 @@
use crate::def_package; use crate::def_package;
use crate::module::FuncReturn; use crate::module::FuncReturn;
use crate::parser::{ImmutableString, INT};
// Comparison operators // Comparison operators
pub fn lt<T: PartialOrd>(x: T, y: T) -> FuncReturn<bool> { pub fn lt<T: PartialOrd>(x: T, y: T) -> FuncReturn<bool> {
@ -23,12 +22,6 @@ pub fn ne<T: PartialEq>(x: T, y: T) -> FuncReturn<bool> {
} }
// Logic operators // Logic operators
fn and(x: bool, y: bool) -> FuncReturn<bool> {
Ok(x && y)
}
fn or(x: bool, y: bool) -> FuncReturn<bool> {
Ok(x || y)
}
fn not(x: bool) -> FuncReturn<bool> { fn not(x: bool) -> FuncReturn<bool> {
Ok(!x) Ok(!x)
} }
@ -40,30 +33,9 @@ macro_rules! reg_op {
} }
def_package!(crate:LogicPackage:"Logical operators.", lib, { 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_i32"))]
#[cfg(not(feature = "only_i64"))] #[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, "<", 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, "<=", lte, i8, u8, i16, u16, i32, u32, u64, i128, u128);
reg_op!(lib, ">", gt, 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"))] #[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, "<", lt, f32);
reg_op!(lib, "<=", lte, f32); reg_op!(lib, "<=", lte, f32);
reg_op!(lib, ">", gt, f32); reg_op!(lib, ">", gt, f32);
@ -88,12 +54,5 @@ def_package!(crate:LogicPackage:"Logical operators.", lib, {
reg_op!(lib, "!=", ne, f32); 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); lib.set_fn_1("!", not);
}); });

View File

@ -6,10 +6,7 @@ use crate::engine::Map;
use crate::module::FuncReturn; use crate::module::FuncReturn;
use crate::parser::{ImmutableString, INT}; use crate::parser::{ImmutableString, INT};
use crate::stdlib::{ use crate::stdlib::{string::ToString, vec::Vec};
string::{String, ToString},
vec::Vec,
};
fn map_get_keys(map: &mut Map) -> FuncReturn<Vec<Dynamic>> { fn map_get_keys(map: &mut Map) -> FuncReturn<Vec<Dynamic>> {
Ok(map.iter().map(|(k, _)| k.to_string().into()).collect()) Ok(map.iter().map(|(k, _)| k.to_string().into()).collect())

View File

@ -4,7 +4,7 @@ use crate::fn_native::{CallableFunction, IteratorFn, Shared};
use crate::module::Module; use crate::module::Module;
use crate::utils::StaticVec; 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; pub(crate) mod arithmetic;
mod array_basic; mod array_basic;

View File

@ -1,6 +1,5 @@
use crate::def_package; use crate::def_package;
use crate::engine::{FUNC_TO_STRING, KEYWORD_DEBUG, KEYWORD_PRINT}; use crate::engine::{FUNC_TO_STRING, KEYWORD_DEBUG, KEYWORD_PRINT};
use crate::fn_native::shared_make_mut;
use crate::module::FuncReturn; use crate::module::FuncReturn;
use crate::parser::{ImmutableString, INT}; use crate::parser::{ImmutableString, INT};
@ -13,7 +12,7 @@ use crate::engine::Map;
use crate::stdlib::{ use crate::stdlib::{
fmt::{Debug, Display}, fmt::{Debug, Display},
format, format,
string::{String, ToString}, string::ToString,
}; };
// Register print and debug // 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_1_mut(KEYWORD_DEBUG, format_map);
} }
lib.set_fn_2( lib.set_fn_2("+", |s: ImmutableString, ch: char| Ok(s + ch));
"+", lib.set_fn_2_mut("+=", |s: &mut ImmutableString, ch: char| { *s += ch; Ok(()) });
|s: ImmutableString, ch: char| { lib.set_fn_2_mut("append", |s: &mut ImmutableString, ch: char| { *s += ch; Ok(()) });
if s.is_empty() { lib.set_fn_2_mut("+=", |s: &mut ImmutableString, s2: ImmutableString| { *s += &s2; Ok(()) });
return Ok(ch.to_string().into()); lib.set_fn_2_mut("append", |s: &mut ImmutableString, s2: ImmutableString| { *s += &s2; Ok(()) });
}
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(())
}
);
}); });

View File

@ -1,5 +1,4 @@
use crate::def_package; use crate::def_package;
use crate::fn_native::shared_make_mut;
use crate::module::FuncReturn; use crate::module::FuncReturn;
use crate::parser::{ImmutableString, INT}; use crate::parser::{ImmutableString, INT};
use crate::utils::StaticVec; use crate::utils::StaticVec;
@ -48,12 +47,12 @@ fn sub_string(s: ImmutableString, start: INT, len: INT) -> FuncReturn<ImmutableS
} }
fn crop_string(s: &mut ImmutableString, start: INT, len: INT) -> FuncReturn<()> { fn crop_string(s: &mut ImmutableString, start: INT, len: INT) -> FuncReturn<()> {
let offset = if s.is_empty() || len <= 0 { let offset = if s.is_empty() || len <= 0 {
shared_make_mut(s).clear(); s.make_mut().clear();
return Ok(()); return Ok(());
} else if start < 0 { } else if start < 0 {
0 0
} else if (start as usize) >= s.chars().count() { } else if (start as usize) >= s.chars().count() {
shared_make_mut(s).clear(); s.make_mut().clear();
return Ok(()); return Ok(());
} else { } else {
start as usize start as usize
@ -67,7 +66,7 @@ fn crop_string(s: &mut ImmutableString, start: INT, len: INT) -> FuncReturn<()>
len as usize len as usize
}; };
let copy = shared_make_mut(s); let copy = s.make_mut();
copy.clear(); copy.clear();
copy.extend(chars.iter().skip(offset).take(len)); 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| { lib.set_fn_1_mut("clear", |s: &mut ImmutableString| {
shared_make_mut(s).clear(); s.make_mut().clear();
Ok(()) Ok(())
}); });
lib.set_fn_2_mut("append", |s: &mut ImmutableString, ch: char| { lib.set_fn_2_mut("append", |s: &mut ImmutableString, ch: char| {
shared_make_mut(s).push(ch); s.make_mut().push(ch);
Ok(()) Ok(())
}); });
lib.set_fn_2_mut( lib.set_fn_2_mut(
"append", "append",
|s: &mut ImmutableString, add: ImmutableString| { |s: &mut ImmutableString, add: ImmutableString| {
shared_make_mut(s).push_str(add.as_str()); s.make_mut().push_str(add.as_str());
Ok(()) Ok(())
} }
); );
@ -198,11 +197,11 @@ def_package!(crate:MoreStringPackage:"Additional string utilities, including str
|s: &mut ImmutableString, len: INT| { |s: &mut ImmutableString, len: INT| {
if len > 0 { if len > 0 {
let chars: StaticVec<_> = s.chars().collect(); let chars: StaticVec<_> = s.chars().collect();
let copy = shared_make_mut(s); let copy = s.make_mut();
copy.clear(); copy.clear();
copy.extend(chars.into_iter().take(len as usize)); copy.extend(chars.into_iter().take(len as usize));
} else { } else {
shared_make_mut(s).clear(); s.make_mut().clear();
} }
Ok(()) Ok(())
}, },
@ -210,7 +209,7 @@ def_package!(crate:MoreStringPackage:"Additional string utilities, including str
lib.set_fn_3_mut( lib.set_fn_3_mut(
"pad", "pad",
|s: &mut ImmutableString, len: INT, ch: char| { |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 { for _ in 0..copy.chars().count() - len as usize {
copy.push(ch); copy.push(ch);
} }

View File

@ -4,7 +4,6 @@ use crate::any::{Dynamic, Union};
use crate::calc_fn_hash; use crate::calc_fn_hash;
use crate::engine::{make_getter, make_setter, Engine, FunctionsLib}; use crate::engine::{make_getter, make_setter, Engine, FunctionsLib};
use crate::error::{LexError, ParseError, ParseErrorType}; use crate::error::{LexError, ParseError, ParseErrorType};
use crate::fn_native::Shared;
use crate::optimize::{optimize_into_ast, OptimizationLevel}; use crate::optimize::{optimize_into_ast, OptimizationLevel};
use crate::scope::{EntryType as ScopeEntryType, Scope}; use crate::scope::{EntryType as ScopeEntryType, Scope};
use crate::token::{Position, Token, TokenIterator}; use crate::token::{Position, Token, TokenIterator};
@ -49,11 +48,10 @@ pub type INT = i32;
#[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_float"))]
pub type FLOAT = f64; pub type FLOAT = f64;
/// The system immutable string type.
pub type ImmutableString = Shared<String>;
type PERR = ParseErrorType; type PERR = ParseErrorType;
pub use crate::utils::ImmutableString;
/// Compiled AST (abstract syntax tree) of a Rhai script. /// 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`. /// Currently, `AST` is neither `Send` nor `Sync`. Turn on the `sync` feature to make it `Send + Sync`.

View File

@ -4,14 +4,18 @@
//! //!
//! The `StaticVec` type has some `unsafe` blocks to handle conversions between `MaybeUninit` and regular types. //! 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::{ use crate::stdlib::{
any::TypeId, any::TypeId,
borrow::Borrow,
fmt, fmt,
hash::{Hash, Hasher}, hash::{Hash, Hasher},
iter::FromIterator, iter::FromIterator,
mem, mem,
mem::MaybeUninit, mem::MaybeUninit,
ops::{Drop, Index, IndexMut}, ops::{Add, AddAssign, Deref, Drop, Index, IndexMut},
str::FromStr,
vec::Vec, vec::Vec,
}; };
@ -560,3 +564,273 @@ impl<T> From<Vec<T>> for StaticVec<T> {
arr arr
} }
} }
/// The system immutable string type.
///
/// An `ImmutableString` wraps an `Rc<String>` (or `Arc<String>` 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<String>);
impl Deref for ImmutableString {
type Target = String;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl AsRef<String> for ImmutableString {
fn as_ref(&self) -> &String {
&self.0
}
}
impl Borrow<str> 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<String> for ImmutableString {
fn from(value: String) -> Self {
Self(value.into())
}
}
impl From<Box<String>> for ImmutableString {
fn from(value: Box<String>) -> Self {
Self(value.into())
}
}
impl From<ImmutableString> for String {
fn from(value: ImmutableString) -> Self {
value.into_owned()
}
}
impl FromStr for ImmutableString {
type Err = ();
fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(Self(s.to_string().into()))
}
}
impl FromIterator<char> for ImmutableString {
fn from_iter<T: IntoIterator<Item = char>>(iter: T) -> Self {
Self(iter.into_iter().collect::<String>().into())
}
}
impl<'a> FromIterator<&'a char> for ImmutableString {
fn from_iter<T: IntoIterator<Item = &'a char>>(iter: T) -> Self {
Self(iter.into_iter().cloned().collect::<String>().into())
}
}
impl<'a> FromIterator<&'a str> for ImmutableString {
fn from_iter<T: IntoIterator<Item = &'a str>>(iter: T) -> Self {
Self(iter.into_iter().collect::<String>().into())
}
}
impl<'a> FromIterator<String> for ImmutableString {
fn from_iter<T: IntoIterator<Item = String>>(iter: T) -> Self {
Self(iter.into_iter().collect::<String>().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<String> 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<String> 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<char> for ImmutableString {
type Output = Self;
fn add(mut self, rhs: char) -> Self::Output {
self.make_mut().push(rhs);
self
}
}
impl Add<char> 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<char> 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)
}
}