diff --git a/CHANGELOG.md b/CHANGELOG.md index 5f19a7e41..4611360c1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,28 @@ Rhai Release Notes ================== +Version 1.17.0 +============== + +Potentially breaking changes +---------------------------- + +* `ImmutableString` now derefs to `&str` instead of `&SmartString`. Normally this should not be a breaking change. +* Traits implemented by `ImmutableString` are cleaned up. However, I cannot guarantee that there are absolutely no breaking changes, although I try to be careful. +* `EvalContext::new`, `FloatWrapper` and `ConditionalExpr` are now exported only under `internals`. + +Deprecated API's +---------------- + +* `rhai::config::hashing::set_ahash_seed`, `rhai::config::hashing::get_ahash_seed` and the `RHAI_AHASH_SEED` environment variable are deprecated in favor of `rhai::config::hashing::set_hashing_seed`, `rhai::config::hashing::get_hashing_seed` and `RHAI_HASHING_SEED`. + +Enhancements +------------ + +* Added `to_int` method for characters. +* `Token::FloatConstant` and `Token::DecimalConstant` now carry the original text representation for use in, say, a _token mapper_. + + Version 1.16.2 ============== diff --git a/Cargo.toml b/Cargo.toml index 81b0e6eb7..6e8a839de 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,7 +3,7 @@ members = [".", "codegen"] [package] name = "rhai" -version = "1.16.2" +version = "1.17.0" rust-version = "1.66.0" edition = "2018" resolver = "2" diff --git a/build.rs b/build.rs index 39c112389..847a2d8ce 100644 --- a/build.rs +++ b/build.rs @@ -8,6 +8,7 @@ fn main() { // Tell Cargo that if the given environment variable changes, to rerun this build script. println!("cargo:rerun-if-changed=build.template"); println!("cargo:rerun-if-env-changed=RHAI_AHASH_SEED"); + println!("cargo:rerun-if-env-changed=RHAI_HASHING_SEED"); let mut contents = String::new(); File::open("build.template") @@ -15,9 +16,11 @@ fn main() { .read_to_string(&mut contents) .expect("cannot read from `build.template`"); - let seed = env::var("RHAI_AHASH_SEED").map_or_else(|_| "None".into(), |s| format!("Some({s})")); + let seed = env::var("RHAI_HASHING_SEED") + .or_else(|_| env::var("RHAI_AHASH_SEED")) + .map_or_else(|_| "None".into(), |s| format!("Some({s})")); - contents = contents.replace("{{AHASH_SEED}}", &seed); + contents = contents.replace("{{HASHING_SEED}}", &seed); File::create("src/config/hashing_env.rs") .expect("cannot create `config.rs`") diff --git a/build.template b/build.template index 783764e19..e443a5c9d 100644 --- a/build.template +++ b/build.template @@ -1,3 +1,3 @@ //! This file is automatically recreated during build time by `build.rs` from `build.template`. -pub const AHASH_SEED: Option<[u64; 4]> = {{AHASH_SEED}}; +pub const HASHING_SEED: Option<[u64; 4]> = {{HASHING_SEED}}; diff --git a/no_std/no_std_test/src/main.rs b/no_std/no_std_test/src/main.rs index 621029ee2..355eb57da 100644 --- a/no_std/no_std_test/src/main.rs +++ b/no_std/no_std_test/src/main.rs @@ -35,7 +35,7 @@ fn foo(_: core::alloc::Layout) -> ! { #[panic_handler] #[lang = "panic_impl"] -extern "C" fn rust_begin_panic(_: &core::panic::PanicInfo) -> ! { +fn rust_begin_panic(_: &core::panic::PanicInfo) -> ! { core::intrinsics::abort(); } diff --git a/src/api/compile.rs b/src/api/compile.rs index 6d506b412..2057dd7c4 100644 --- a/src/api/compile.rs +++ b/src/api/compile.rs @@ -123,7 +123,7 @@ impl Engine { collect_imports(&ast, &resolver, &mut imports); if !imports.is_empty() { - while let Some(path) = imports.iter().next() { + while let Some(path) = imports.pop_first() { let path = path.clone(); match self @@ -141,7 +141,6 @@ impl Engine { let module = shared_take_or_clone(module); - imports.remove(&path); resolver.insert(path, module); } ast.set_resolver(resolver); @@ -203,11 +202,7 @@ impl Engine { scope: &Scope, scripts: impl AsRef<[S]>, ) -> ParseResult { - self.compile_with_scope_and_optimization_level( - Some(scope), - scripts, - self.optimization_level, - ) + self.compile_scripts_with_scope_raw(Some(scope), scripts, self.optimization_level) } /// Join a list of strings and compile into an [`AST`] using own scope at a specific optimization level. /// @@ -217,7 +212,7 @@ impl Engine { /// throughout the script _including_ functions. This allows functions to be optimized based on /// dynamic global constants. #[inline] - pub(crate) fn compile_with_scope_and_optimization_level>( + pub(crate) fn compile_scripts_with_scope_raw>( &self, scope: Option<&Scope>, scripts: impl AsRef<[S]>, @@ -283,8 +278,6 @@ impl Engine { /// scope.push_constant("x", 10_i64); // 'x' is a constant /// /// // Compile a script to an AST and store it for later evaluation. - /// // Notice that `Full` optimization is on, so constants are folded - /// // into function calls and operators. /// let ast = engine.compile_expression_with_scope(&mut scope, /// "2 + (x + x) * 2" // all 'x' are replaced with 10 /// )?; diff --git a/src/api/custom_syntax.rs b/src/api/custom_syntax.rs index 292ac5a59..995db5f01 100644 --- a/src/api/custom_syntax.rs +++ b/src/api/custom_syntax.rs @@ -107,10 +107,10 @@ impl Expression<'_> { match self.0 { #[cfg(not(feature = "no_module"))] Expr::Variable(x, ..) if !x.1.is_empty() => None, - Expr::Variable(x, ..) => Some(x.3.as_str()), + Expr::Variable(x, ..) => Some(&x.3), #[cfg(not(feature = "no_function"))] Expr::ThisPtr(..) => Some(crate::engine::KEYWORD_THIS), - Expr::StringConstant(x, ..) => Some(x.as_str()), + Expr::StringConstant(x, ..) => Some(&x), _ => None, } } diff --git a/src/api/definitions/mod.rs b/src/api/definitions/mod.rs index 79dd649c0..cd4f29c28 100644 --- a/src/api/definitions/mod.rs +++ b/src/api/definitions/mod.rs @@ -457,7 +457,7 @@ impl Module { !f.metadata.name.contains('$') && !is_valid_function_name(&f.metadata.name); #[cfg(not(feature = "no_custom_syntax"))] - let operator = operator || def.engine.is_custom_keyword(f.metadata.name.as_str()); + let operator = operator || def.engine.is_custom_keyword(&f.metadata.name); f.write_definition(writer, def, operator)?; } diff --git a/src/api/deprecated.rs b/src/api/deprecated.rs index 5241d0074..9c7d7ed79 100644 --- a/src/api/deprecated.rs +++ b/src/api/deprecated.rs @@ -379,6 +379,27 @@ impl Dynamic { } } +impl AST { + /// _(internals)_ Get the internal [`Module`][crate::Module] containing all script-defined functions. + /// Exported under the `internals` feature only. + /// + /// Not available under `no_function`. + /// + /// # Deprecated + /// + /// This method is deprecated. Use [`shared_lib`][AST::shared_lib] instead. + /// + /// This method will be removed in the next major version. + #[deprecated(since = "1.3.0", note = "use `shared_lib` instead")] + #[cfg(feature = "internals")] + #[cfg(not(feature = "no_function"))] + #[inline(always)] + #[must_use] + pub fn lib(&self) -> &crate::Module { + self.shared_lib() + } +} + impl NativeCallContext<'_> { /// Create a new [`NativeCallContext`]. /// @@ -1256,3 +1277,36 @@ pub mod deprecated_array_functions { retain(ctx, array, FnPtr::new(filter)?) } } + +pub mod config { + pub mod hashing { + /// Set the hashing seed. This is used to hash functions etc. + /// + /// # Deprecated + /// + /// This method is deprecated. + /// Use [`set_hashing_seed`][crate::config::hashing::set_hashing_seed] instead. + /// + /// This method will be removed in the next major version. + #[deprecated(since = "1.17.0", note = "use `set_hashing_seed` instead")] + #[inline(always)] + pub fn set_ahash_seed(new_seed: Option<[u64; 4]>) -> Result<(), Option<[u64; 4]>> { + crate::config::hashing::set_hashing_seed(new_seed) + } + + /// Get the current hashing Seed. + /// + /// # Deprecated + /// + /// This method is deprecated. + /// Use [`get_hashing_seed`][crate::config::hashing::get_hashing_seed] instead. + /// + /// This method will be removed in the next major version. + #[deprecated(since = "1.17.0", note = "use `get_hashing_seed` instead")] + #[inline] + #[must_use] + pub fn get_ahash_seed() -> &'static Option<[u64; 4]> { + crate::config::hashing::get_hashing_seed() + } + } +} diff --git a/src/api/eval.rs b/src/api/eval.rs index ae771ff21..5434578a5 100644 --- a/src/api/eval.rs +++ b/src/api/eval.rs @@ -69,11 +69,8 @@ impl Engine { scope: &mut Scope, script: &str, ) -> RhaiResultOf { - let ast = self.compile_with_scope_and_optimization_level( - Some(scope), - [script], - self.optimization_level, - )?; + let ast = + self.compile_scripts_with_scope_raw(Some(scope), [script], self.optimization_level)?; self.eval_ast_with_scope(scope, &ast) } /// Evaluate a string containing an expression, returning the result value or an error. diff --git a/src/api/register.rs b/src/api/register.rs index dae0f9802..8deed1a32 100644 --- a/src/api/register.rs +++ b/src/api/register.rs @@ -323,7 +323,7 @@ impl Engine { name: impl AsRef, get_fn: impl RegisterNativeFunction<(Mut,), 1, C, V, L> + SendSync + 'static, ) -> &mut Self { - self.register_fn(crate::engine::make_getter(name.as_ref()).as_str(), get_fn) + self.register_fn(crate::engine::make_getter(name.as_ref()), get_fn) } /// Register a setter function for a member of a registered type with the [`Engine`]. /// @@ -373,7 +373,7 @@ impl Engine { name: impl AsRef, set_fn: impl RegisterNativeFunction<(Mut, V), 2, C, (), L> + SendSync + 'static, ) -> &mut Self { - self.register_fn(crate::engine::make_setter(name.as_ref()).as_str(), set_fn) + self.register_fn(crate::engine::make_setter(name.as_ref()), set_fn) } /// Short-hand for registering both getter and setter functions /// of a registered type with the [`Engine`]. diff --git a/src/ast/ast.rs b/src/ast/ast.rs index e7b25c747..37584f78e 100644 --- a/src/ast/ast.rs +++ b/src/ast/ast.rs @@ -167,7 +167,7 @@ impl AST { #[inline(always)] #[must_use] pub fn source(&self) -> Option<&str> { - self.source.as_ref().map(|s| s.as_str()) + self.source.as_deref() } /// Get a reference to the source. #[inline(always)] @@ -882,7 +882,7 @@ impl AST { #[cfg(not(feature = "internals"))] #[cfg(not(feature = "no_module"))] #[inline(always)] - pub(crate) fn walk(&self, on_node: &mut impl FnMut(&[ASTNode]) -> bool) -> bool { + pub(crate) fn walk(&self, on_node: &mut (impl FnMut(&[ASTNode]) -> bool + ?Sized)) -> bool { self._walk(on_node) } /// _(internals)_ Recursively walk the [`AST`], including function bodies (if any). @@ -890,12 +890,12 @@ impl AST { /// Exported under the `internals` feature only. #[cfg(feature = "internals")] #[inline(always)] - pub fn walk(&self, on_node: &mut impl FnMut(&[ASTNode]) -> bool) -> bool { + pub fn walk(&self, on_node: &mut (impl FnMut(&[ASTNode]) -> bool + ?Sized)) -> bool { self._walk(on_node) } /// Recursively walk the [`AST`], including function bodies (if any). /// Return `false` from the callback to terminate the walk. - fn _walk(&self, on_node: &mut impl FnMut(&[ASTNode]) -> bool) -> bool { + fn _walk(&self, on_node: &mut (impl FnMut(&[ASTNode]) -> bool + ?Sized)) -> bool { let path = &mut Vec::new(); for stmt in self.statements() { @@ -1043,24 +1043,3 @@ impl ASTNode<'_> { } } } - -impl AST { - /// _(internals)_ Get the internal [`Module`][crate::Module] containing all script-defined functions. - /// Exported under the `internals` feature only. - /// - /// Not available under `no_function`. - /// - /// # Deprecated - /// - /// This method is deprecated. Use [`shared_lib`][AST::shared_lib] instead. - /// - /// This method will be removed in the next major version. - #[deprecated(since = "1.3.0", note = "use `shared_lib` instead")] - #[cfg(feature = "internals")] - #[cfg(not(feature = "no_function"))] - #[inline(always)] - #[must_use] - pub fn lib(&self) -> &crate::Module { - &self.lib - } -} diff --git a/src/ast/expr.rs b/src/ast/expr.rs index af8837c78..82b73fa41 100644 --- a/src/ast/expr.rs +++ b/src/ast/expr.rs @@ -1,7 +1,7 @@ //! Module defining script expressions. use super::{ASTFlags, ASTNode, Ident, Namespace, Stmt, StmtBlock}; -use crate::engine::{KEYWORD_FN_PTR, OP_EXCLUSIVE_RANGE, OP_INCLUSIVE_RANGE}; +use crate::engine::KEYWORD_FN_PTR; use crate::tokenizer::Token; use crate::types::dynamic::Union; use crate::{ @@ -30,16 +30,6 @@ pub struct BinaryExpr { pub rhs: Expr, } -impl From<(Expr, Expr)> for BinaryExpr { - #[inline(always)] - fn from(value: (Expr, Expr)) -> Self { - Self { - lhs: value.0, - rhs: value.1, - } - } -} - /// _(internals)_ A custom syntax expression. /// Exported under the `internals` feature only. /// @@ -490,7 +480,7 @@ impl Expr { let mut map = x.1.clone(); for (k, v) in &x.0 { - *map.get_mut(k.name.as_str()).unwrap() = v.get_literal_value().unwrap(); + *map.get_mut(k.as_str()).unwrap() = v.get_literal_value().unwrap(); } Dynamic::from_map(map) @@ -518,6 +508,9 @@ impl Expr { // Binary operators Self::FnCall(x, ..) if !x.is_qualified() && x.args.len() == 2 => { + pub const OP_EXCLUSIVE_RANGE: &str = Token::ExclusiveRange.literal_syntax(); + pub const OP_INCLUSIVE_RANGE: &str = Token::InclusiveRange.literal_syntax(); + match x.name.as_str() { // x..y OP_EXCLUSIVE_RANGE => match (&x.args[0], &x.args[1]) { @@ -590,7 +583,7 @@ impl Expr { match self { #[cfg(not(feature = "no_module"))] Self::Variable(x, ..) if _non_qualified && !x.1.is_empty() => None, - Self::Variable(x, ..) => Some(x.3.as_str()), + Self::Variable(x, ..) => Some(&x.3), _ => None, } } @@ -830,7 +823,7 @@ impl Expr { pub fn walk<'a>( &'a self, path: &mut Vec>, - on_node: &mut impl FnMut(&[ASTNode]) -> bool, + on_node: &mut (impl FnMut(&[ASTNode]) -> bool + ?Sized), ) -> bool { // Push the current node onto the path path.push(self.into()); diff --git a/src/ast/ident.rs b/src/ast/ident.rs index e80691286..3641bd577 100644 --- a/src/ast/ident.rs +++ b/src/ast/ident.rs @@ -45,7 +45,7 @@ impl Ident { #[inline(always)] #[must_use] pub fn as_str(&self) -> &str { - self.name.as_str() + &self.name } /// Is the identifier empty? #[inline(always)] diff --git a/src/ast/script_fn.rs b/src/ast/script_fn.rs index a566871a2..74f892476 100644 --- a/src/ast/script_fn.rs +++ b/src/ast/script_fn.rs @@ -62,7 +62,7 @@ impl fmt::Display for ScriptFnDef { self.name, self.params .iter() - .map(|s| s.as_str()) + .map(ImmutableString::as_str) .collect::>() .join(", ") ) @@ -115,10 +115,7 @@ pub struct ScriptFnMetadata<'a> { /// /// Each line in non-block doc-comments starts with `///`. #[cfg(feature = "metadata")] - #[cfg_attr( - feature = "serde", - serde(default, skip_serializing_if = "Vec::is_empty") - )] + #[serde(default, skip_serializing_if = "Vec::is_empty")] pub comments: Vec<&'a str>, } @@ -156,10 +153,10 @@ impl<'a> From<&'a ScriptFnDef> for ScriptFnMetadata<'a> { fn from(value: &'a ScriptFnDef) -> Self { Self { name: &value.name, - params: value.params.iter().map(|s| s.as_str()).collect(), + params: value.params.iter().map(ImmutableString::as_str).collect(), access: value.access, #[cfg(not(feature = "no_object"))] - this_type: value.this_type.as_ref().map(|s| s.as_str()), + this_type: value.this_type.as_deref(), #[cfg(feature = "metadata")] comments: value.comments.iter().map(<_>::as_ref).collect(), } diff --git a/src/ast/stmt.rs b/src/ast/stmt.rs index cab19ed00..fa5afb35b 100644 --- a/src/ast/stmt.rs +++ b/src/ast/stmt.rs @@ -173,7 +173,8 @@ impl fmt::Debug for OpAssignment { } } -/// An expression with a condition. +/// _(internals)_ An expression with a condition. +/// Exported under the `internals` feature only. /// /// The condition may simply be [`Expr::BoolConstant`] with `true` if there is actually no condition. #[derive(Debug, Clone, Default, Hash)] @@ -723,27 +724,6 @@ impl Default for Stmt { } } -impl From for Stmt { - #[inline(always)] - fn from(block: StmtBlock) -> Self { - Self::Block(block.into()) - } -} - -impl> From<(T, Position, Position)> for Stmt { - #[inline(always)] - fn from(value: (T, Position, Position)) -> Self { - StmtBlock::new(value.0, value.1, value.2).into() - } -} - -impl> From<(T, Span)> for Stmt { - #[inline(always)] - fn from(value: (T, Span)) -> Self { - StmtBlock::new_with_span(value.0, value.1).into() - } -} - impl Stmt { /// Is this statement [`Noop`][Stmt::Noop]? #[inline(always)] @@ -1038,7 +1018,7 @@ impl Stmt { pub fn walk<'a>( &'a self, path: &mut Vec>, - on_node: &mut impl FnMut(&[ASTNode]) -> bool, + on_node: &mut (impl FnMut(&[ASTNode]) -> bool + ?Sized), ) -> bool { // Push the current node onto the path path.push(self.into()); diff --git a/src/bin/rhai-dbg.rs b/src/bin/rhai-dbg.rs index 6fedabb50..cecb2ad5c 100644 --- a/src/bin/rhai-dbg.rs +++ b/src/bin/rhai-dbg.rs @@ -84,7 +84,7 @@ fn print_current_source( /// Pretty-print error. fn print_error(input: &str, mut err: EvalAltResult) { - let lines: Vec<_> = input.trim().split('\n').collect(); + let lines: Vec<_> = input.trim().lines().collect(); let pos = err.take_position(); let line_no = if lines.len() > 1 { @@ -105,7 +105,7 @@ fn print_error(input: &str, mut err: EvalAltResult) { // Specific position - print line text println!("{line_no}{}", lines[pos.line().unwrap() - 1]); - for (i, err_line) in err.to_string().split('\n').enumerate() { + for (i, err_line) in err.to_string().lines().enumerate() { // Display position marker println!( "\x1b[31m{0:>1$}{err_line}\x1b[39m", @@ -604,7 +604,7 @@ fn main() { let (ast, script) = load_script(&engine); // Hook up debugger - let lines: Vec<_> = script.trim().split('\n').map(|s| s.to_string()).collect(); + let lines: Vec<_> = script.trim().lines().map(|s| s.to_string()).collect(); #[allow(deprecated)] engine.register_debugger( diff --git a/src/bin/rhai-repl.rs b/src/bin/rhai-repl.rs index 8139348a1..685a38a4c 100644 --- a/src/bin/rhai-repl.rs +++ b/src/bin/rhai-repl.rs @@ -11,7 +11,7 @@ const HISTORY_FILE: &str = ".rhai-repl-history"; /// Pretty-print error. fn print_error(input: &str, mut err: EvalAltResult) { - let lines: Vec<_> = input.split('\n').collect(); + let lines: Vec<_> = input.lines().collect(); let pos = err.take_position(); let line_no = if lines.len() > 1 { @@ -32,7 +32,7 @@ fn print_error(input: &str, mut err: EvalAltResult) { // Specific position - print line text println!("{line_no}{}", lines[pos.line().unwrap() - 1]); - for (i, err_line) in err.to_string().split('\n').enumerate() { + for (i, err_line) in err.to_string().lines().enumerate() { // Display position marker println!( "{0:>1$}{err_line}", @@ -411,7 +411,7 @@ fn main() { "exit" | "quit" => break, // quit "history" => { for (i, h) in rl.history().iter().enumerate() { - match &h.split('\n').collect::>()[..] { + match &h.lines().collect::>()[..] { [line] => println!("[{}] {line}", history_offset + i), lines => { for (x, line) in lines.iter().enumerate() { diff --git a/src/bin/rhai-run.rs b/src/bin/rhai-run.rs index 4d59fc65f..b8503db0f 100644 --- a/src/bin/rhai-run.rs +++ b/src/bin/rhai-run.rs @@ -9,7 +9,7 @@ fn eprint_error(input: &str, mut err: EvalAltResult) { eprintln!("{line_no}{}", lines[line - 1]); - for (i, err_line) in err_msg.to_string().split('\n').enumerate() { + for (i, err_line) in err_msg.to_string().lines().enumerate() { // Display position marker println!( "{0:>1$}{err_line}", @@ -20,7 +20,7 @@ fn eprint_error(input: &str, mut err: EvalAltResult) { eprintln!(); } - let lines: Vec<_> = input.split('\n').collect(); + let lines: Vec<_> = input.lines().collect(); // Print error let pos = err.take_position(); diff --git a/src/config/hashing.rs b/src/config/hashing.rs index 570a75524..1d45ad322 100644 --- a/src/config/hashing.rs +++ b/src/config/hashing.rs @@ -2,20 +2,20 @@ //! //! Set to [`None`] to disable stable hashing. //! -//! See [`rhai::config::hashing::set_ahash_seed`][set_ahash_seed]. +//! See [`rhai::config::hashing::set_hashing_seed`][set_hashing_seed]. //! //! # Example //! //! ```rust //! // Set the hashing seed to [1, 2, 3, 4] -//! rhai::config::hashing::set_ahash_seed(Some([1, 2, 3, 4])).unwrap(); +//! rhai::config::hashing::set_hashing_seed(Some([1, 2, 3, 4])).unwrap(); //! ``` -//! Alternatively, set this at compile time via the `RHAI_AHASH_SEED` environment variable. +//! Alternatively, set this at compile time via the `RHAI_HASHING_SEED` environment variable. //! //! # Example //! //! ```sh -//! env RHAI_AHASH_SEED ="[236,800,954,213]" +//! env RHAI_HASHING_SEED ="[236,800,954,213]" //! ``` use super::hashing_env; @@ -26,13 +26,18 @@ pub use once_cell::sync::OnceCell; #[cfg(not(feature = "std"))] pub use once_cell::race::OnceBox as OnceCell; -static AHASH_SEED: OnceCell> = OnceCell::new(); +static HASHING_SEED: OnceCell> = OnceCell::new(); + +#[allow(deprecated)] +pub use crate::api::deprecated::config::hashing::{get_ahash_seed, set_ahash_seed}; /// Set the hashing seed. This is used to hash functions etc. /// /// This is a static global value and affects every Rhai instance. /// This should not be used _unless_ you know you need it. /// +/// Set the hashing seed to all zeros effectively disables stable hashing. +/// /// # Warning /// /// * You can only call this function **ONCE** for the entire duration of program execution. @@ -47,29 +52,29 @@ static AHASH_SEED: OnceCell> = OnceCell::new(); /// ```rust /// # use rhai::Engine; /// // Set the hashing seed to [1, 2, 3, 4] -/// rhai::config::hashing::set_ahash_seed(Some([1, 2, 3, 4])).unwrap(); +/// rhai::config::hashing::set_hashing_seed(Some([1, 2, 3, 4])).unwrap(); /// /// // Use Rhai AFTER setting the hashing seed /// let engine = Engine::new(); /// ``` #[inline(always)] -pub fn set_ahash_seed(new_seed: Option<[u64; 4]>) -> Result<(), Option<[u64; 4]>> { +pub fn set_hashing_seed(new_seed: Option<[u64; 4]>) -> Result<(), Option<[u64; 4]>> { #[cfg(feature = "std")] - return AHASH_SEED.set(new_seed); + return HASHING_SEED.set(new_seed); #[cfg(not(feature = "std"))] - return AHASH_SEED.set(new_seed.into()).map_err(|err| *err); + return HASHING_SEED.set(new_seed.into()).map_err(|err| *err); } /// Get the current hashing Seed. /// -/// If the seed is not yet defined, the `RHAI_AHASH_SEED` environment variable (if any) is used. +/// If the seed is not yet defined, the `RHAI_HASHING_SEED` environment variable (if any) is used. /// /// Otherwise, the hashing seed is randomized to protect against DOS attacks. /// -/// See [`rhai::config::hashing::set_ahash_seed`][set_ahash_seed] for more. +/// See [`rhai::config::hashing::set_hashing_seed`][set_hashing_seed] for more. #[inline] #[must_use] -pub fn get_ahash_seed() -> &'static Option<[u64; 4]> { - AHASH_SEED.get().unwrap_or(&hashing_env::AHASH_SEED) +pub fn get_hashing_seed() -> &'static Option<[u64; 4]> { + HASHING_SEED.get().unwrap_or(&hashing_env::HASHING_SEED) } diff --git a/src/config/hashing_env.rs b/src/config/hashing_env.rs index 9890487cf..8a20efea9 100644 --- a/src/config/hashing_env.rs +++ b/src/config/hashing_env.rs @@ -1,3 +1,3 @@ //! This file is automatically recreated during build time by `build.rs` from `build.template`. -pub const AHASH_SEED: Option<[u64; 4]> = None; +pub const HASHING_SEED: Option<[u64; 4]> = None; diff --git a/src/engine.rs b/src/engine.rs index 14a77c284..4f43316ae 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -57,12 +57,6 @@ pub const OP_CONTAINS: &str = "contains"; /// Standard not operator. pub const OP_NOT: &str = Token::Bang.literal_syntax(); -/// Standard exclusive range operator. -pub const OP_EXCLUSIVE_RANGE: &str = Token::ExclusiveRange.literal_syntax(); - -/// Standard inclusive range operator. -pub const OP_INCLUSIVE_RANGE: &str = Token::InclusiveRange.literal_syntax(); - /// Separator for namespaces. #[cfg(not(feature = "no_module"))] pub const NAMESPACE_SEPARATOR: &str = Token::DoubleColon.literal_syntax(); diff --git a/src/eval/cache.rs b/src/eval/cache.rs index 3a2df0371..5b8775099 100644 --- a/src/eval/cache.rs +++ b/src/eval/cache.rs @@ -19,7 +19,7 @@ pub struct FnResolutionCacheEntry { /// _(internals)_ A function resolution cache with a bloom filter. /// Exported under the `internals` feature only. /// -/// The bloom filter is used to rapidly check whether a function hash has never been encountered. +/// The [bloom filter][`BloomFilterU64`] is used to rapidly check whether a function hash has never been encountered. /// It enables caching a hash only during the second encounter to avoid "one-hit wonders". #[derive(Debug, Clone, Default)] pub struct FnResolutionCache { diff --git a/src/eval/debugger.rs b/src/eval/debugger.rs index 39607cafd..29f2335f3 100644 --- a/src/eval/debugger.rs +++ b/src/eval/debugger.rs @@ -330,11 +330,11 @@ impl Debugger { #[cfg(not(feature = "no_position"))] BreakPoint::AtPosition { source, pos, .. } if pos.is_beginning_of_line() => { node.position().line().unwrap_or(0) == pos.line().unwrap() - && _src == source.as_ref().map(|s| s.as_str()) + && _src == source.as_deref() } #[cfg(not(feature = "no_position"))] BreakPoint::AtPosition { source, pos, .. } => { - node.position() == *pos && _src == source.as_ref().map(|s| s.as_str()) + node.position() == *pos && _src == source.as_deref() } BreakPoint::AtFunctionName { name, .. } => match node { ASTNode::Expr(Expr::FnCall(x, ..)) | ASTNode::Stmt(Stmt::FnCall(x, ..)) => { @@ -503,11 +503,10 @@ impl Engine { let orig_scope_len = scope.len(); let src = global.source_raw().cloned(); - let src = src.as_ref().map(|s| s.as_str()); let context = EvalContext::new(self, global, caches, scope, this_ptr); let (.., ref on_debugger) = *x; - let command = on_debugger(context, event, node, src, node.position()); + let command = on_debugger(context, event, node, src.as_deref(), node.position()); if orig_scope_len != scope.len() { // The scope is changed, always search from now on diff --git a/src/eval/eval_context.rs b/src/eval/eval_context.rs index fc596629a..08fd970cd 100644 --- a/src/eval/eval_context.rs +++ b/src/eval/eval_context.rs @@ -21,7 +21,28 @@ pub struct EvalContext<'a, 's, 'ps, 'g, 'c, 't> { } impl<'a, 's, 'ps, 'g, 'c, 't> EvalContext<'a, 's, 'ps, 'g, 'c, 't> { + /// _(internals)_ Create a new [`EvalContext`]. + /// Exported under the `internals` feature only. + #[cfg(not(feature = "internals"))] + #[inline(always)] + #[must_use] + pub(crate) fn new( + engine: &'a Engine, + global: &'g mut GlobalRuntimeState, + caches: &'c mut Caches, + scope: &'s mut Scope<'ps>, + this_ptr: Option<&'t mut Dynamic>, + ) -> Self { + Self { + engine, + global, + caches, + scope, + this_ptr, + } + } /// Create a new [`EvalContext`]. + #[cfg(feature = "internals")] #[inline(always)] #[must_use] pub fn new( diff --git a/src/eval/expr.rs b/src/eval/expr.rs index a79b6e7cd..63ba88e70 100644 --- a/src/eval/expr.rs +++ b/src/eval/expr.rs @@ -142,9 +142,12 @@ impl Engine { Expr::Variable(v, None, ..) => { // Scripted function with the same name #[cfg(not(feature = "no_function"))] - if let Some(fn_def) = global.lib.iter().flat_map(|m| m.iter_script_fn()).find_map( - |(_, _, f, _, func)| if f == v.3.as_str() { Some(func) } else { None }, - ) { + if let Some(fn_def) = global + .lib + .iter() + .flat_map(|m| m.iter_script_fn()) + .find_map(|(_, _, f, _, func)| if f == &v.3 { Some(func) } else { None }) + { let mut fn_ptr = crate::FnPtr::new_unchecked(v.3.clone(), Vec::new()); fn_ptr.set_fn_def(Some(fn_def.clone())); let val: Dynamic = fn_ptr.into(); diff --git a/src/eval/global_state.rs b/src/eval/global_state.rs index 470c18f7d..656511959 100644 --- a/src/eval/global_state.rs +++ b/src/eval/global_state.rs @@ -134,7 +134,7 @@ impl GlobalRuntimeState { #[inline] #[must_use] pub fn find_import(&self, name: &str) -> Option { - self.imports.iter().rposition(|key| key.as_str() == name) + self.imports.iter().rposition(|key| key == name) } /// Push an imported [module][crate::Module] onto the stack. /// @@ -266,7 +266,7 @@ impl GlobalRuntimeState { #[inline(always)] #[must_use] pub fn source(&self) -> Option<&str> { - self.source.as_ref().map(|s| s.as_str()) + self.source.as_deref() } /// Get the current source. #[inline(always)] diff --git a/src/eval/stmt.rs b/src/eval/stmt.rs index 3832d1a48..1e757b7b4 100644 --- a/src/eval/stmt.rs +++ b/src/eval/stmt.rs @@ -330,7 +330,6 @@ impl Engine { let mut target = self.search_namespace(global, caches, scope, this_ptr, lhs)?; let is_temp_result = !target.is_ref(); - let var_name = x.3.as_str(); #[cfg(not(feature = "no_closure"))] // Also handle case where target is a `Dynamic` shared value @@ -340,7 +339,7 @@ impl Engine { // Cannot assign to temp result from expression if is_temp_result { return Err(ERR::ErrorAssignmentToConstant( - var_name.to_string(), + x.3.to_string(), lhs.position(), ) .into()); diff --git a/src/func/call.rs b/src/func/call.rs index 81103bdfb..0be66b2d3 100644 --- a/src/func/call.rs +++ b/src/func/call.rs @@ -399,11 +399,9 @@ impl Engine { // Run external function let is_method = func.is_method(); - let src = source.as_ref().map(|s| s.as_str()); - let context = func .has_context() - .then(|| (self, name, src, &*global, pos).into()); + .then(|| (self, name, source.as_deref(), &*global, pos).into()); let mut _result = if !func.is_pure() && !args.is_empty() && args[0].is_read_only() { // If function is not pure, there must be at least one argument @@ -832,7 +830,6 @@ impl Engine { .map(|v| (v, false)) } _ => { - let name = fn_name.as_str(); let is_ref_mut = target.is_ref(); // Add the first argument with the object pointer @@ -841,25 +838,28 @@ impl Engine { // Recalculate hash let num_args = args.len(); - let new_hash = if !is_anon && !is_valid_function_name(name) { - FnCallHashes::from_native_only(calc_fn_hash(None, name, num_args)) + let new_hash = if !is_anon && !is_valid_function_name(&fn_name) { + FnCallHashes::from_native_only(calc_fn_hash(None, &fn_name, num_args)) } else { #[cfg(not(feature = "no_function"))] { FnCallHashes::from_script_and_native( - calc_fn_hash(None, name, num_args - 1), - calc_fn_hash(None, name, num_args), + calc_fn_hash(None, &fn_name, num_args - 1), + calc_fn_hash(None, &fn_name, num_args), ) } #[cfg(feature = "no_function")] { - FnCallHashes::from_native_only(calc_fn_hash(None, name, num_args)) + FnCallHashes::from_native_only(calc_fn_hash( + None, &fn_name, num_args, + )) } }; // Map it to name(args) in function-call style self.exec_fn_call( - global, caches, None, name, None, new_hash, args, is_ref_mut, true, pos, + global, caches, None, &fn_name, None, new_hash, args, is_ref_mut, true, + pos, ) } } @@ -1594,7 +1594,7 @@ impl Engine { // Compile the script text // No optimizations because we only run it once - let ast = self.compile_with_scope_and_optimization_level( + let ast = self.compile_scripts_with_scope_raw( None, [script], #[cfg(not(feature = "no_optimize"))] diff --git a/src/func/hashing.rs b/src/func/hashing.rs index 51d9b7a0e..fba4a5b71 100644 --- a/src/func/hashing.rs +++ b/src/func/hashing.rs @@ -56,7 +56,7 @@ impl BuildHasher for StraightHasherBuilder { #[inline(always)] #[must_use] pub fn get_hasher() -> ahash::AHasher { - match config::hashing::get_ahash_seed() { + match config::hashing::get_hashing_seed() { Some([seed1, seed2, seed3, seed4]) if (seed1 | seed2 | seed3 | seed4) != 0 => { ahash::RandomState::with_seeds(*seed1, *seed2, *seed3, *seed4).build_hasher() } diff --git a/src/func/native.rs b/src/func/native.rs index 9a5ecc401..fd770b9f7 100644 --- a/src/func/native.rs +++ b/src/func/native.rs @@ -77,7 +77,7 @@ pub struct NativeCallContext<'a> { pos: Position, } -/// _(internals)_ Context of a native Rust function call. +/// _(internals)_ Context of a native Rust function call, intended for persistence. /// Exported under the `internals` feature only. /// /// # WARNING - Volatile Type diff --git a/src/lib.rs b/src/lib.rs index 88187e57e..d3e326a03 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -238,6 +238,7 @@ use func::calc_typed_method_hash; use func::{calc_fn_hash, calc_fn_hash_full, calc_var_hash}; pub use func::{plugin, FuncArgs, NativeCallContext, RegisterNativeFunction}; pub use module::{FnNamespace, Module}; +pub use packages::string_basic::{FUNC_TO_DEBUG, FUNC_TO_STRING}; pub use rhai_codegen::*; #[cfg(not(feature = "no_time"))] pub use types::Instant; @@ -331,7 +332,7 @@ pub use types::dynamic::{AccessMode, DynamicReadLock, DynamicWriteLock, Variant} pub use types::FloatWrapper; #[cfg(feature = "internals")] -pub use types::{CustomTypeInfo, Span, StringsInterner}; +pub use types::{BloomFilterU64, CustomTypeInfo, Span, StringsInterner}; #[cfg(feature = "internals")] pub use tokenizer::{ diff --git a/src/module/mod.rs b/src/module/mod.rs index 4656ff6d8..4c954a889 100644 --- a/src/module/mod.rs +++ b/src/module/mod.rs @@ -352,7 +352,7 @@ impl Module { #[inline] #[must_use] pub fn id(&self) -> Option<&str> { - self.id.as_ref().map(|s| s.as_str()) + self.id.as_deref() } /// Get the ID of the [`Module`] as an [`Identifier`], if any. @@ -1935,7 +1935,7 @@ impl Module { f.metadata.namespace, f.metadata.access, f.func.is_script(), - f.metadata.name.as_str(), + &f.metadata.name, f.metadata.num_params, ) }) @@ -1978,7 +1978,7 @@ impl Module { filter( f.metadata.namespace, f.metadata.access, - f.metadata.name.as_str(), + &f.metadata.name, f.metadata.num_params, ) } else { @@ -2421,7 +2421,7 @@ impl Module { } else { let hash_fn = calc_native_fn_hash( path.iter().copied(), - f.metadata.name.as_str(), + &f.metadata.name, &f.metadata.param_types, ); diff --git a/src/optimizer.rs b/src/optimizer.rs index e74dca840..9584d06e8 100644 --- a/src/optimizer.rs +++ b/src/optimizer.rs @@ -28,6 +28,8 @@ use std::{ }; /// Level of optimization performed. +/// +/// Not available under `no_optimize`. #[derive(Debug, Eq, PartialEq, Clone, Copy, Default, Hash)] #[non_exhaustive] pub enum OptimizationLevel { @@ -119,7 +121,7 @@ impl<'a> OptimizerState<'a> { self.variables .iter() .rev() - .find(|(n, _)| n.as_str() == name) + .find(|(n, _)| n == name) .and_then(|(_, value)| value.as_ref()) } /// Call a registered function @@ -427,12 +429,15 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b *stmt = if preserve_result { // -> { expr, Noop } - ( - [Stmt::Expr(expr.into()), Stmt::Noop(pos)], - pos, - Position::NONE, + Stmt::Block( + StmtBlock::new( + [Stmt::Expr(expr.into()), Stmt::Noop(pos)], + pos, + Position::NONE, + ) + .into(), ) - .into() + .into() } else { // -> expr Stmt::Expr(expr.into()) @@ -456,7 +461,9 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b let body = x.branch.take_statements(); *stmt = match optimize_stmt_block(body, state, preserve_result, true, false) { statements if statements.is_empty() => Stmt::Noop(x.branch.position()), - statements => (statements, x.branch.span()).into(), + statements => { + Stmt::Block(StmtBlock::new_with_span(statements, x.branch.span()).into()) + } } } // if true { if_block } else { else_block } -> if_block @@ -465,7 +472,9 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b let body = x.body.take_statements(); *stmt = match optimize_stmt_block(body, state, preserve_result, true, false) { statements if statements.is_empty() => Stmt::Noop(x.body.position()), - statements => (statements, x.body.span()).into(), + statements => { + Stmt::Block(StmtBlock::new_with_span(statements, x.body.span()).into()) + } } } // if expr { if_block } else { else_block } @@ -626,7 +635,7 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b optimize_stmt(&mut def_stmt, state, true); *stmt = def_stmt; } - _ => *stmt = StmtBlock::empty(*pos).into(), + _ => *stmt = Stmt::Block(StmtBlock::empty(*pos).into()), } } // switch @@ -751,32 +760,28 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b Stmt::Import(x, ..) => optimize_expr(&mut x.0, state, false), // { block } Stmt::Block(block) => { - let span = block.span(); - let statements = block.take_statements().into_vec().into(); - let mut block = optimize_stmt_block(statements, state, preserve_result, true, false); + let mut stmts = + optimize_stmt_block(block.take_statements(), state, preserve_result, true, false); - match block.as_mut_slice() { + match stmts.as_mut_slice() { [] => { state.set_dirty(); - *stmt = Stmt::Noop(span.start()); + *stmt = Stmt::Noop(block.span().start()); } // Only one statement which is not block-dependent - promote [s] if !s.is_block_dependent() => { state.set_dirty(); *stmt = s.take(); } - _ => *stmt = (block, span).into(), + _ => *block.statements_mut() = stmts, } } // try { pure try_block } catch ( var ) { catch_block } -> try_block Stmt::TryCatch(x, ..) if x.body.iter().all(Stmt::is_pure) => { // If try block is pure, there will never be any exceptions state.set_dirty(); - *stmt = ( - optimize_stmt_block(x.body.take_statements(), state, false, true, false), - x.body.span(), - ) - .into(); + *x.body.statements_mut() = + optimize_stmt_block(x.body.take_statements(), state, false, true, false); } // try { try_block } catch ( var ) { catch_block } Stmt::TryCatch(x, ..) => { @@ -791,10 +796,10 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b state.set_dirty(); match expr.as_mut() { Expr::Stmt(block) if !block.is_empty() => { - let mut stmt_block = *mem::take(block); - *stmt_block.statements_mut() = - optimize_stmt_block(stmt_block.take_statements(), state, true, true, false); - *stmt = stmt_block.into(); + let mut stmts_blk = mem::take(block.as_mut()); + *stmts_blk.statements_mut() = + optimize_stmt_block(stmts_blk.take_statements(), state, true, true, false); + *stmt = Stmt::Block(stmts_blk.into()); } Expr::Stmt(..) => *stmt = Stmt::Noop(expr.position()), _ => unreachable!("`Expr::Stmt`"), @@ -896,11 +901,10 @@ fn optimize_expr(expr: &mut Expr, state: &mut OptimizerState, _chaining: bool) { Expr::Dot(x, ..) if !_chaining => match (&mut x.lhs, &mut x.rhs) { // map.string (Expr::Map(m, pos), Expr::Property(p, ..)) if m.0.iter().all(|(.., x)| x.is_pure()) => { - let prop = p.2.as_str(); // Map literal where everything is pure - promote the indexed item. // All other items can be thrown away. state.set_dirty(); - *expr = mem::take(&mut m.0).into_iter().find(|(x, ..)| x.as_str() == prop) + *expr = mem::take(&mut m.0).into_iter().find(|(x, ..)| x.name == p.2) .map_or_else(|| Expr::Unit(*pos), |(.., mut expr)| { expr.set_position(*pos); expr }); } // var.rhs or this.rhs @@ -963,7 +967,7 @@ fn optimize_expr(expr: &mut Expr, state: &mut OptimizerState, _chaining: bool) { // Map literal where everything is pure - promote the indexed item. // All other items can be thrown away. state.set_dirty(); - *expr = mem::take(&mut m.0).into_iter().find(|(x, ..)| x.as_str() == s.as_str()) + *expr = mem::take(&mut m.0).into_iter().find(|(x, ..)| x.name == s) .map_or_else(|| Expr::Unit(*pos), |(.., mut expr)| { expr.set_position(*pos); expr }); } // int[int] diff --git a/src/packages/array_basic.rs b/src/packages/array_basic.rs index b2cebc8a7..099103150 100644 --- a/src/packages/array_basic.rs +++ b/src/packages/array_basic.rs @@ -1621,9 +1621,9 @@ pub mod array_functions { } if type_id == TypeId::of::() { array.sort_by(|a, b| { - let a = a.read_lock::().expect("`ImmutableString`"); - let b = b.read_lock::().expect("`ImmutableString`"); - a.as_str().cmp(b.as_str()) + let a = &*a.read_lock::().expect("`ImmutableString`"); + let b = &*b.read_lock::().expect("`ImmutableString`"); + a.cmp(b) }); return Ok(()); } diff --git a/src/packages/debugging.rs b/src/packages/debugging.rs index c73b01a07..e739f1920 100644 --- a/src/packages/debugging.rs +++ b/src/packages/debugging.rs @@ -45,7 +45,7 @@ mod debugging_functions { .iter() .rev() .filter(|CallStackFrame { fn_name, args, .. }| { - fn_name.as_str() != "back_trace" || !args.is_empty() + fn_name != "back_trace" || !args.is_empty() }) .map( |frame @ CallStackFrame { diff --git a/src/packages/iter_basic.rs b/src/packages/iter_basic.rs index 367931212..7e43705f1 100644 --- a/src/packages/iter_basic.rs +++ b/src/packages/iter_basic.rs @@ -12,6 +12,7 @@ use std::{ fmt::Debug, iter::{ExactSizeIterator, FusedIterator}, ops::{Range, RangeInclusive}, + vec::IntoIter, }; #[cfg(not(feature = "no_float"))] @@ -173,19 +174,26 @@ impl ExactSizeIterator for BitRange { } // String iterator over characters -#[derive(Debug, Clone, Hash, Eq, PartialEq)] -pub struct CharsStream(Vec, usize); +#[derive(Debug, Clone)] +pub struct CharsStream(IntoIter); impl CharsStream { #[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)] pub fn new(string: &str, from: INT, len: INT) -> Self { if len <= 0 || from > MAX_USIZE_INT { - return Self(Vec::new(), 0); + return Self(Vec::new().into_iter()); } let len = len.min(MAX_USIZE_INT) as usize; if from >= 0 { - return Self(string.chars().skip(from as usize).take(len).collect(), 0); + return Self( + string + .chars() + .skip(from as usize) + .take(len) + .collect::>() + .into_iter(), + ); } let abs_from = from.unsigned_abs() as usize; @@ -195,36 +203,37 @@ impl CharsStream { } else { num_chars - abs_from }; - Self(string.chars().skip(offset).take(len).collect(), 0) + Self( + string + .chars() + .skip(offset) + .take(len) + .collect::>() + .into_iter(), + ) } } impl Iterator for CharsStream { type Item = char; + #[inline(always)] fn next(&mut self) -> Option { - if self.1 >= self.0.len() { - return None; - } - - let ch = self.0[self.1]; - self.1 += 1; - Some(ch) + self.0.next() } - #[inline] + #[inline(always)] fn size_hint(&self) -> (usize, Option) { - let remaining = self.0.len() - self.1; - (remaining, Some(remaining)) + self.0.size_hint() } } impl FusedIterator for CharsStream {} impl ExactSizeIterator for CharsStream { - #[inline] + #[inline(always)] fn len(&self) -> usize { - self.0.len() - self.1 + self.0.len() } } diff --git a/src/packages/lang_core.rs b/src/packages/lang_core.rs index 940bba03a..3a9aec42c 100644 --- a/src/packages/lang_core.rs +++ b/src/packages/lang_core.rs @@ -273,7 +273,7 @@ fn collect_fn_metadata( "comments".into(), func.comments .iter() - .map(|s| engine.get_interned_string(s.as_str()).into()) + .map(|s| engine.get_interned_string(s).into()) .collect::() .into(), ); diff --git a/src/packages/string_basic.rs b/src/packages/string_basic.rs index f6d6b5163..1be420c84 100644 --- a/src/packages/string_basic.rs +++ b/src/packages/string_basic.rs @@ -12,7 +12,14 @@ use crate::Array; #[cfg(not(feature = "no_object"))] use crate::Map; +/// Standard pretty-print function. +/// +/// This function is called to convert any type into text format for display. pub const FUNC_TO_STRING: &str = "to_string"; + +/// Standard debug-print function. +/// +/// This function is called to convert any type into text format suitable for debugging. pub const FUNC_TO_DEBUG: &str = "to_debug"; def_package! { @@ -22,6 +29,7 @@ def_package! { combine_with_exported_module!(lib, "print_debug", print_debug_functions); combine_with_exported_module!(lib, "number_formatting", number_formatting); + combine_with_exported_module!(lib, "char", char_functions); // Register characters iterator #[cfg(not(feature = "no_index"))] @@ -433,3 +441,11 @@ mod number_formatting { } } } + +#[export_module] +mod char_functions { + /// Convert the Unicode character into a 32-bit integer value. + pub fn to_int(ch: char) -> INT { + (ch as u32) as INT + } +} diff --git a/src/packages/string_more.rs b/src/packages/string_more.rs index ec1ffc4a0..2a37f5eca 100644 --- a/src/packages/string_more.rs +++ b/src/packages/string_more.rs @@ -33,7 +33,7 @@ mod string_functions { return string.clone(); } - let mut buf = SmartString::from(string.as_str()); + let mut buf = string.as_raw().clone(); buf.push_str(&s); buf.into() } @@ -45,7 +45,7 @@ mod string_functions { return; } - let mut buf = SmartString::from(string.as_str()); + let mut buf = string.as_raw().clone(); buf.push_str(&s); *string = buf.into(); } @@ -127,7 +127,7 @@ mod string_functions { } .into() } else { - let mut x = SmartString::from(string.as_str()); + let mut x = string.as_raw().clone(); x.push_str(s.as_ref()); x.into() } @@ -138,7 +138,7 @@ mod string_functions { return; } - let mut s = SmartString::from(string.as_str()); + let mut s = string.as_raw().clone(); s.push_str(&String::from_utf8_lossy(&utf8)); *string = s.into(); } @@ -1484,7 +1484,7 @@ mod string_functions { if abs_index as u64 > MAX_USIZE_INT as u64 { return vec![ ctx.engine().const_empty_string().into(), - string.as_str().into(), + string.clone().into(), ]; } let abs_index = abs_index as usize; @@ -1493,7 +1493,7 @@ mod string_functions { if abs_index > num_chars { vec![ ctx.engine().const_empty_string().into(), - string.as_str().into(), + string.clone().into(), ] } else { let prefix: String = string.chars().take(num_chars - abs_index).collect(); @@ -1502,7 +1502,7 @@ mod string_functions { } } else if index > MAX_USIZE_INT { vec![ - string.as_str().into(), + string.clone().into(), ctx.engine().const_empty_string().into(), ] } else { diff --git a/src/parser.rs b/src/parser.rs index 3e40145c5..ee7d93dff 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -13,8 +13,10 @@ use crate::tokenizer::{ is_reserved_keyword_or_symbol, is_valid_function_name, is_valid_identifier, Token, TokenStream, TokenizerControl, }; -use crate::types::dynamic::{AccessMode, Union}; -use crate::types::StringsInterner; +use crate::types::{ + dynamic::{AccessMode, Union}, + StringsInterner, +}; use crate::{ calc_fn_hash, Dynamic, Engine, EvalAltResult, EvalContext, ExclusiveRange, FnArgsVec, Identifier, ImmutableString, InclusiveRange, LexError, OptimizationLevel, ParseError, Position, @@ -56,7 +58,7 @@ pub struct ParseState<'e, 's> { /// Encapsulates a local stack with variable names to simulate an actual runtime scope. pub stack: Scope<'e>, /// Size of the local variables stack upon entry of the current block scope. - pub block_stack_len: usize, + pub frame_pointer: usize, /// Tracks a list of external variables (variables that are not explicitly declared in the scope). #[cfg(not(feature = "no_closure"))] pub external_vars: Vec, @@ -87,7 +89,7 @@ impl fmt::Debug for ParseState<'_, '_> { .field("external_constants_scope", &self.external_constants) .field("global", &self.global) .field("stack", &self.stack) - .field("block_stack_len", &self.block_stack_len); + .field("frame_pointer", &self.frame_pointer); #[cfg(not(feature = "no_closure"))] f.field("external_vars", &self.external_vars) @@ -120,7 +122,7 @@ impl<'e, 's> ParseState<'e, 's> { external_constants, global: None, stack: Scope::new(), - block_stack_len: 0, + frame_pointer: 0, #[cfg(not(feature = "no_module"))] imports: Vec::new(), #[cfg(not(feature = "no_module"))] @@ -222,7 +224,7 @@ impl<'e, 's> ParseState<'e, 's> { self.imports .iter() .rev() - .rposition(|n| n.as_str() == name) + .rposition(|n| n == name) .and_then(|i| NonZeroUsize::new(i + 1)) } @@ -381,9 +383,9 @@ impl Expr { Self::Variable(x, ..) if !x.1.is_empty() => unreachable!("qualified property"), Self::Variable(x, .., pos) => { let ident = x.3.clone(); - let getter = state.get_interned_getter(ident.as_str()); + let getter = state.get_interned_getter(&ident); let hash_get = calc_fn_hash(None, &getter, 1); - let setter = state.get_interned_setter(ident.as_str()); + let setter = state.get_interned_setter(&ident); let hash_set = calc_fn_hash(None, &setter, 2); Self::Property( @@ -497,7 +499,7 @@ fn unindent_block_comment(comment: String, pos: usize) -> String { } let offset = comment - .split('\n') + .lines() .skip(1) .map(|s| s.len() - s.trim_start().len()) .min() @@ -509,7 +511,7 @@ fn unindent_block_comment(comment: String, pos: usize) -> String { } comment - .split('\n') + .lines() .enumerate() .map(|(i, s)| if i > 0 { &s[offset..] } else { s }) .collect::>() @@ -522,7 +524,7 @@ fn parse_var_name(input: &mut TokenStream) -> ParseResult<(SmartString, Position // Variable name (Token::Identifier(s), pos) => Ok((*s, pos)), // Reserved keyword - (Token::Reserved(s), pos) if is_valid_identifier(s.as_str()) => { + (Token::Reserved(s), pos) if is_valid_identifier(&s) => { Err(PERR::Reserved(s.to_string()).into_err(pos)) } // Bad identifier @@ -663,7 +665,7 @@ impl Engine { if settings.has_option(LangOptions::STRICT_VAR) && index.is_none() && !is_global - && !state.global_imports.iter().any(|m| m.as_str() == root) + && !state.global_imports.iter().any(|m| m == root) && !self.global_sub_modules.contains_key(root) { return Err( @@ -730,7 +732,7 @@ impl Engine { if settings.has_option(LangOptions::STRICT_VAR) && index.is_none() && !is_global - && !state.global_imports.iter().any(|m| m.as_str() == root) + && !state.global_imports.iter().any(|m| m == root) && !self.global_sub_modules.contains_key(root) { return Err( @@ -1054,7 +1056,7 @@ impl Engine { (Token::InterpolatedString(..), pos) => { return Err(PERR::PropertyExpected.into_err(pos)) } - (Token::Reserved(s), pos) if is_valid_identifier(s.as_str()) => { + (Token::Reserved(s), pos) if is_valid_identifier(&s) => { return Err(PERR::Reserved(s.to_string()).into_err(pos)); } (Token::LexError(err), pos) => return Err(err.into_err(pos)), @@ -1385,15 +1387,15 @@ impl Engine { }, #[cfg(not(feature = "no_float"))] Token::FloatConstant(x) => { - let x = *x; + let x = x.0; input.next(); Expr::FloatConstant(x, settings.pos) } #[cfg(feature = "decimal")] Token::DecimalConstant(x) => { - let x = (**x).into(); + let x = x.0; input.next(); - Expr::DynamicConstant(Box::new(x), settings.pos) + Expr::DynamicConstant(Box::new(x.into()), settings.pos) } // { - block statement as expression @@ -1918,7 +1920,7 @@ impl Engine { if settings.has_option(LangOptions::STRICT_VAR) && index.is_none() && !is_global - && !state.global_imports.iter().any(|m| m.as_str() == root) + && !state.global_imports.iter().any(|m| m == root) && !self.global_sub_modules.contains_key(root) { return Err( @@ -2073,10 +2075,10 @@ impl Engine { match lhs { // this = rhs - Expr::ThisPtr(_) => Ok(Stmt::Assignment((op_info, (lhs, rhs).into()).into())), + Expr::ThisPtr(_) => Ok(Stmt::Assignment((op_info, BinaryExpr { lhs, rhs }).into())), // var (non-indexed) = rhs Expr::Variable(ref x, None, _) if x.0.is_none() => { - Ok(Stmt::Assignment((op_info, (lhs, rhs).into()).into())) + Ok(Stmt::Assignment((op_info, BinaryExpr { lhs, rhs }).into())) } // var (indexed) = rhs Expr::Variable(ref x, i, var_pos) => { @@ -2092,7 +2094,7 @@ impl Engine { .access_mode() { AccessMode::ReadWrite => { - Ok(Stmt::Assignment((op_info, (lhs, rhs).into()).into())) + Ok(Stmt::Assignment((op_info, BinaryExpr { lhs, rhs }).into())) } // Constant values cannot be assigned to AccessMode::ReadOnly => { @@ -2114,7 +2116,7 @@ impl Engine { match x.lhs { // var[???] = rhs, this[???] = rhs, var.??? = rhs, this.??? = rhs Expr::Variable(..) | Expr::ThisPtr(..) => { - Ok(Stmt::Assignment((op_info, (lhs, rhs).into()).into())) + Ok(Stmt::Assignment((op_info, BinaryExpr { lhs, rhs }).into())) } // expr[???] = rhs, expr.??? = rhs ref expr => { @@ -2180,8 +2182,10 @@ impl Engine { // lhs.Fn() or lhs.eval() (.., Expr::FnCall(f, func_pos)) if f.args.is_empty() - && [crate::engine::KEYWORD_FN_PTR, crate::engine::KEYWORD_EVAL] - .contains(&f.name.as_str()) => + && matches!( + &*f.name, + crate::engine::KEYWORD_FN_PTR | crate::engine::KEYWORD_EVAL + ) => { let err_msg = format!( "'{}' should not be called in method style. Try {}(...);", @@ -2436,7 +2440,7 @@ impl Engine { } #[cfg(not(feature = "no_custom_syntax"))] - Token::Custom(s) if self.is_custom_keyword(s.as_str()) => { + Token::Custom(s) if self.is_custom_keyword(&s) => { op_base.hashes = if native_only { FnCallHashes::from_native_only(calc_fn_hash(None, &s, 2)) } else { @@ -2584,8 +2588,8 @@ impl Engine { #[cfg(not(feature = "no_float"))] CUSTOM_SYNTAX_MARKER_FLOAT => match input.next().expect(NEVER_ENDS) { (Token::FloatConstant(f), pos) => { - inputs.push(Expr::FloatConstant(f, pos)); - segments.push(f.to_string().into()); + inputs.push(Expr::FloatConstant(f.0, pos)); + segments.push(f.1.into()); tokens.push(state.get_interned_string(CUSTOM_SYNTAX_MARKER_FLOAT)); } (.., pos) => { @@ -2633,7 +2637,7 @@ impl Engine { tokens.shrink_to_fit(); let self_terminated = matches!( - required_token.as_str(), + &*required_token, // It is self-terminating if the last symbol is a block CUSTOM_SYNTAX_MARKER_BLOCK | // If the last symbol is `;` or `}`, it is self-terminating @@ -2957,7 +2961,7 @@ impl Engine { #[cfg(not(feature = "no_module"))] offset if !state.stack.get_entry_by_index(offset).2.is_empty() => None, // Defined in parent block - offset if offset < state.block_stack_len => None, + offset if offset < state.frame_pointer => None, offset => Some(offset), } } else { @@ -3107,15 +3111,17 @@ impl Engine { }; let mut settings = settings.level_up_with_position(brace_start_pos)?; - let mut statements = StaticVec::new_const(); + let mut block = StmtBlock::empty(settings.pos); if settings.has_flag(ParseSettingFlags::DISALLOW_STATEMENTS_IN_BLOCKS) { let stmt = self.parse_expr_stmt(input, state, lib, settings)?; - statements.push(stmt); + block.statements_mut().push(stmt); // Must end with } return match input.next().expect(NEVER_ENDS) { - (Token::RightBrace, pos) => Ok((statements, settings.pos, pos).into()), + (Token::RightBrace, pos) => { + Ok(Stmt::Block(StmtBlock::new(block, settings.pos, pos).into())) + } (Token::LexError(err), pos) => Err(err.into_err(pos)), (.., pos) => Err(PERR::MissingToken( Token::LeftBrace.into(), @@ -3125,8 +3131,8 @@ impl Engine { }; } - let prev_entry_stack_len = state.block_stack_len; - state.block_stack_len = state.stack.len(); + let prev_frame_pointer = state.frame_pointer; + state.frame_pointer = state.stack.len(); #[cfg(not(feature = "no_module"))] let orig_imports_len = state.imports.len(); @@ -3157,7 +3163,7 @@ impl Engine { // See if it needs a terminating semicolon let need_semicolon = !stmt.is_self_terminated(); - statements.push(stmt); + block.statements_mut().push(stmt); match input.peek().expect(NEVER_ENDS) { // { ... stmt } @@ -3186,13 +3192,15 @@ impl Engine { } }; - state.stack.rewind(state.block_stack_len); - state.block_stack_len = prev_entry_stack_len; + state.stack.rewind(state.frame_pointer); + state.frame_pointer = prev_frame_pointer; #[cfg(not(feature = "no_module"))] state.imports.truncate(orig_imports_len); - Ok((statements, settings.pos, end_pos).into()) + Ok(Stmt::Block( + StmtBlock::new(block, settings.pos, end_pos).into(), + )) } /// Parse an expression as a statement. @@ -3648,7 +3656,7 @@ impl Engine { match input.next().expect(NEVER_ENDS) { (Token::RightParen, ..) => break, (Token::Identifier(s), pos) => { - if params.iter().any(|(p, _)| p.as_str() == *s) { + if params.iter().any(|(p, _)| p == &*s) { return Err( PERR::FnDuplicatedParam(name.into(), s.to_string()).into_err(pos) ); @@ -3709,27 +3717,33 @@ impl Engine { parent: &mut ParseState, lib: &FnLib, fn_expr: Expr, - externals: FnArgsVec, + externals: impl AsRef<[Ident]> + IntoIterator, pos: Position, ) -> Expr { // If there are no captured variables, no need to curry - if externals.is_empty() { + if externals.as_ref().is_empty() { return fn_expr; } - let num_externals = externals.len(); - let mut args = Vec::with_capacity(externals.len() + 1); + let num_externals = externals.as_ref().len(); + let mut args = Vec::with_capacity(externals.as_ref().len() + 1); args.push(fn_expr); - args.extend(externals.iter().cloned().map(|Ident { name, pos }| { - let (index, is_func) = parent.access_var(&name, lib, pos); - let idx = match index { - Some(n) if !is_func => u8::try_from(n.get()).ok().and_then(NonZeroU8::new), - _ => None, - }; - Expr::Variable((index, <_>::default(), 0, name).into(), idx, pos) - })); + args.extend( + externals + .as_ref() + .iter() + .cloned() + .map(|Ident { name, pos }| { + let (index, is_func) = parent.access_var(&name, lib, pos); + let idx = match index { + Some(n) if !is_func => u8::try_from(n.get()).ok().and_then(NonZeroU8::new), + _ => None, + }; + Expr::Variable((index, <_>::default(), 0, name).into(), idx, pos) + }), + ); let expr = FnCallExpr { namespace: Namespace::NONE, @@ -3780,7 +3794,7 @@ impl Engine { match input.next().expect(NEVER_ENDS) { (Token::Pipe, ..) => break, (Token::Identifier(s), pos) => { - if params_list.iter().any(|p| p.as_str() == *s) { + if params_list.iter().any(|p| p == &*s) { return Err( PERR::FnDuplicatedParam(String::new(), s.to_string()).into_err(pos) ); @@ -3827,11 +3841,7 @@ impl Engine { FnArgsVec::new_const(), ) } else { - let externals = state - .external_vars - .iter() - .cloned() - .collect::>(); + let externals: FnArgsVec<_> = state.external_vars.clone().into(); let mut params = FnArgsVec::with_capacity(params_list.len() + externals.len()); params.extend(externals.iter().map(|Ident { name, .. }| name.clone())); diff --git a/src/serde/de.rs b/src/serde/de.rs index af3b00b6d..699e8d466 100644 --- a/src/serde/de.rs +++ b/src/serde/de.rs @@ -343,10 +343,9 @@ impl<'de> Deserializer<'de> for DynamicDeserializer<'de> { } fn deserialize_str>(self, visitor: V) -> RhaiResultOf { - self.0.downcast_ref::().map_or_else( - || self.type_error(), - |x| visitor.visit_borrowed_str(x.as_str()), - ) + self.0 + .downcast_ref::() + .map_or_else(|| self.type_error(), |x| visitor.visit_borrowed_str(x)) } fn deserialize_string>(self, visitor: V) -> RhaiResultOf { @@ -454,7 +453,7 @@ impl<'de> Deserializer<'de> for DynamicDeserializer<'de> { visitor: V, ) -> RhaiResultOf { match self.0.read_lock::() { - Some(s) => visitor.visit_enum(s.as_str().into_deserializer()), + Some(s) => visitor.visit_enum(s.into_deserializer()), None => { #[cfg(not(feature = "no_object"))] return self.0.downcast_ref::().map_or_else( diff --git a/src/serde/metadata.rs b/src/serde/metadata.rs index 8d8bfb0a4..15ec63620 100644 --- a/src/serde/metadata.rs +++ b/src/serde/metadata.rs @@ -124,7 +124,7 @@ impl<'a> From<&'a FuncInfo> for FnMetadata<'a> { is_anonymous: crate::parser::is_anonymous_fn(&info.metadata.name), typ, #[cfg(not(feature = "no_object"))] - this_type: info.metadata.this_type.as_ref().map(|s| s.as_str()), + this_type: info.metadata.this_type.as_deref(), num_params: info.metadata.num_params, params: info .metadata diff --git a/src/serde/serialize.rs b/src/serde/serialize.rs index 580bb3441..3d8307452 100644 --- a/src/serde/serialize.rs +++ b/src/serde/serialize.rs @@ -17,7 +17,7 @@ impl Serialize for Dynamic { match self.0 { Union::Unit(..) => ser.serialize_unit(), Union::Bool(x, ..) => ser.serialize_bool(x), - Union::Str(ref s, ..) => ser.serialize_str(s.as_str()), + Union::Str(ref s, ..) => ser.serialize_str(s), Union::Char(c, ..) => ser.serialize_char(c), #[cfg(not(feature = "only_i32"))] @@ -60,8 +60,7 @@ impl Serialize for Dynamic { #[cfg(not(feature = "no_object"))] Union::Map(ref m, ..) => { let mut map = ser.serialize_map(Some(m.len()))?; - m.iter() - .try_for_each(|(k, v)| map.serialize_entry(k.as_str(), v))?; + m.iter().try_for_each(|(k, v)| map.serialize_entry(k, v))?; map.end() } Union::FnPtr(ref f, ..) => ser.serialize_str(f.fn_name()), @@ -83,7 +82,7 @@ impl Serialize for Dynamic { impl Serialize for ImmutableString { #[inline(always)] fn serialize(&self, ser: S) -> Result { - ser.serialize_str(self.as_str()) + ser.serialize_str(&self) } } diff --git a/src/tokenizer.rs b/src/tokenizer.rs index 1b827d9f0..21882995e 100644 --- a/src/tokenizer.rs +++ b/src/tokenizer.rs @@ -62,16 +62,16 @@ pub type TokenStream<'a> = Peekable>; pub enum Token { /// An `INT` constant. IntegerConstant(INT), - /// A `FLOAT` constant. + /// A `FLOAT` constant, including its text representation. /// /// Reserved under the `no_float` feature. #[cfg(not(feature = "no_float"))] - FloatConstant(crate::types::FloatWrapper), + FloatConstant(Box<(crate::types::FloatWrapper, Identifier)>), /// A [`Decimal`][rust_decimal::Decimal] constant. /// - /// Requires the `decimal` feature. + /// Requires the `decimal` feature, including its text representation. #[cfg(feature = "decimal")] - DecimalConstant(Box), + DecimalConstant(Box<(rust_decimal::Decimal, Identifier)>), /// An identifier. Identifier(Box), /// A character constant. @@ -284,9 +284,9 @@ impl fmt::Display for Token { match self { IntegerConstant(i) => write!(f, "{i}"), #[cfg(not(feature = "no_float"))] - FloatConstant(v) => write!(f, "{v}"), + FloatConstant(v) => write!(f, "{}", v.0), #[cfg(feature = "decimal")] - DecimalConstant(d) => write!(f, "{d}"), + DecimalConstant(d) => write!(f, "{}", d.0), StringConstant(s) => write!(f, r#""{s}""#), InterpolatedString(..) => f.write_str("string"), CharConstant(c) => write!(f, "{c}"), @@ -1147,6 +1147,9 @@ impl From for String { #[derive(Debug, Clone, Eq, PartialEq, Default)] pub struct TokenizeState { /// Maximum length of a string. + /// + /// Not available under `unchecked`. + #[cfg(not(feature = "unchecked"))] pub max_string_len: Option, /// Can the next token be a unary operator? pub next_token_cannot_be_unary: bool, @@ -1176,17 +1179,12 @@ pub trait InputStream { /// Peek the next character in the `InputStream`. #[must_use] fn peek_next(&mut self) -> Option; -} -/// Return error if the string is longer than the maximum length. -#[inline] -const fn ensure_string_len_within_limit( - max: Option, - value: &str, -) -> Result<(), LexError> { - match max { - Some(max) if value.len() > max.get() => Err(LexError::StringTooLong(max.get())), - _ => Ok(()), + /// Consume the next character. + #[inline(always)] + fn eat_next_and_advance(&mut self, pos: &mut Position) -> Option { + pos.advance(); + self.get_next() } } @@ -1221,7 +1219,7 @@ const fn ensure_string_len_within_limit( /// Any time a [`StringConstant`][`Token::StringConstant`] is returned with /// `state.is_within_text_terminated_by` set to `Some(_)` is one of the above conditions. pub fn parse_string_literal( - stream: &mut impl InputStream, + stream: &mut (impl InputStream + ?Sized), state: &mut TokenizeState, pos: &mut Position, termination_char: char, @@ -1287,14 +1285,19 @@ pub fn parse_string_literal( break; } - ensure_string_len_within_limit(state.max_string_len, &result) - .map_err(|err| (err, start))?; + // Check string length + #[cfg(not(feature = "unchecked"))] + if let Some(max) = state.max_string_len { + if result.len() > max.get() { + return Err((LexError::StringTooLong(max.get()), start)); + } + } // Close wrapper if termination_char == next_char && escape.is_empty() { // Double wrapper if stream.peek_next().map_or(false, |c| c == termination_char) { - eat_next_and_advance(stream, pos); + stream.eat_next_and_advance(pos); if let Some(ref mut last) = state.last_token { last.push(termination_char); } @@ -1429,21 +1432,20 @@ pub fn parse_string_literal( } } - ensure_string_len_within_limit(state.max_string_len, &result).map_err(|err| (err, start))?; + // Check string length + #[cfg(not(feature = "unchecked"))] + if let Some(max) = state.max_string_len { + if result.len() > max.get() { + return Err((LexError::StringTooLong(max.get()), start)); + } + } Ok((result, interpolated, first_char)) } -/// Consume the next character. -#[inline(always)] -fn eat_next_and_advance(stream: &mut impl InputStream, pos: &mut Position) -> Option { - pos.advance(); - stream.get_next() -} - /// Scan for a block comment until the end. fn scan_block_comment( - stream: &mut impl InputStream, + stream: &mut (impl InputStream + ?Sized), level: usize, pos: &mut Position, comment: Option<&mut String>, @@ -1461,7 +1463,7 @@ fn scan_block_comment( match c { '/' => { if let Some(c2) = stream.peek_next().filter(|&ch| ch == '*') { - eat_next_and_advance(stream, pos); + stream.eat_next_and_advance(pos); if let Some(comment) = comment.as_mut() { comment.push(c2); } @@ -1470,7 +1472,7 @@ fn scan_block_comment( } '*' => { if let Some(c2) = stream.peek_next().filter(|&ch| ch == '/') { - eat_next_and_advance(stream, pos); + stream.eat_next_and_advance(pos); if let Some(comment) = comment.as_mut() { comment.push(c2); } @@ -1489,37 +1491,30 @@ fn scan_block_comment( level } -/// _(internals)_ Get the next token from the input stream. -/// Exported under the `internals` feature only. -#[inline] -#[must_use] -pub fn get_next_token( - stream: &mut impl InputStream, - state: &mut TokenizeState, - pos: &mut Position, -) -> Option<(Token, Position)> { - let result = get_next_token_inner(stream, state, pos); - - // Save the last token's state - if let Some((ref token, ..)) = result { - state.next_token_cannot_be_unary = !token.is_next_unary(); - } - - result -} - /// Test if the given character is a hex character. #[inline(always)] const fn is_hex_digit(c: char) -> bool { matches!(c, 'a'..='f' | 'A'..='F' | '0'..='9') } -/// Test if the given character is a numeric digit. +/// Test if the given character is a numeric digit (i.e. 0-9). #[inline(always)] const fn is_numeric_digit(c: char) -> bool { c.is_ascii_digit() } +/// Test if the given character is an octal digit (i.e. 0-7). +#[inline(always)] +const fn is_octal_digit(c: char) -> bool { + matches!(c, '0'..='7') +} + +/// Test if the given character is a binary digit (i.e. 0 or 1). +#[inline(always)] +const fn is_binary_digit(c: char) -> bool { + c == '0' || c == '1' +} + /// Test if the comment block is a doc-comment. #[cfg(not(feature = "no_function"))] #[cfg(feature = "metadata")] @@ -1530,10 +1525,29 @@ pub fn is_doc_comment(comment: &str) -> bool { || (comment.starts_with("/**") && !comment.starts_with("/***")) } +/// _(internals)_ Get the next token from the input stream. +/// Exported under the `internals` feature only. +#[inline(always)] +#[must_use] +pub fn get_next_token( + stream: &mut (impl InputStream + ?Sized), + state: &mut TokenizeState, + pos: &mut Position, +) -> Option<(Token, Position)> { + let result = get_next_token_inner(stream, state, pos); + + // Save the last token's state + if let Some((ref token, ..)) = result { + state.next_token_cannot_be_unary = !token.is_next_unary(); + } + + result +} + /// Get the next token. #[must_use] fn get_next_token_inner( - stream: &mut impl InputStream, + stream: &mut (impl InputStream + ?Sized), state: &mut TokenizeState, pos: &mut Position, ) -> Option<(Token, Position)> { @@ -1599,11 +1613,11 @@ fn get_next_token_inner( while let Some(next_char) = stream.peek_next() { match next_char { NUMBER_SEPARATOR => { - eat_next_and_advance(stream, pos); + stream.eat_next_and_advance(pos); } ch if valid(ch) => { result.push(next_char); - eat_next_and_advance(stream, pos); + stream.eat_next_and_advance(pos); } #[cfg(any(not(feature = "no_float"), feature = "decimal"))] '.' => { @@ -1669,12 +1683,12 @@ fn get_next_token_inner( if c == '0' && result.len() <= 1 => { result.push(next_char); - eat_next_and_advance(stream, pos); + stream.eat_next_and_advance(pos); valid = match ch { 'x' | 'X' => is_hex_digit, - 'o' | 'O' => is_numeric_digit, - 'b' | 'B' => is_numeric_digit, + 'o' | 'O' => is_octal_digit, + 'b' | 'B' => is_binary_digit, c => unreachable!("x/X or o/O or b/B expected but gets '{}'", c), }; @@ -1700,51 +1714,48 @@ fn get_next_token_inner( } // Parse number - let token = radix_base.map_or_else( - || { + let token = if let Some(radix) = radix_base { + let result = &result[2..]; + + UNSIGNED_INT::from_str_radix(result, radix) + .map(|v| v as INT) + .map_or_else( + |_| Token::LexError(LERR::MalformedNumber(result.to_string()).into()), + Token::IntegerConstant, + ) + } else { + (|| { let num = INT::from_str(&result).map(Token::IntegerConstant); // If integer parsing is unnecessary, try float instead #[cfg(not(feature = "no_float"))] - let num = num.or_else(|_| { - crate::types::FloatWrapper::from_str(&result).map(Token::FloatConstant) - }); + if num.is_err() { + if let Ok(v) = crate::types::FloatWrapper::from_str(&result) { + return Token::FloatConstant((v, result).into()); + } + } // Then try decimal #[cfg(feature = "decimal")] - let num = num.or_else(|_| { - rust_decimal::Decimal::from_str(&result) - .map(Box::new) - .map(Token::DecimalConstant) - }); + if num.is_err() { + if let Ok(v) = rust_decimal::Decimal::from_str(&result) { + return Token::DecimalConstant((v, result).into()); + } + } // Then try decimal in scientific notation #[cfg(feature = "decimal")] - let num = num.or_else(|_| { - rust_decimal::Decimal::from_scientific(&result) - .map(Box::new) - .map(Token::DecimalConstant) - }); + if num.is_err() { + if let Ok(v) = rust_decimal::Decimal::from_scientific(&result) { + return Token::DecimalConstant((v, result).into()); + } + } num.unwrap_or_else(|_| { Token::LexError(LERR::MalformedNumber(result.to_string()).into()) }) - }, - |radix| { - let result = &result[2..]; - - UNSIGNED_INT::from_str_radix(result, radix) - .map(|v| v as INT) - .map_or_else( - |_| { - Token::LexError( - LERR::MalformedNumber(result.to_string()).into(), - ) - }, - Token::IntegerConstant, - ) - }, - ); + })() + }; return Some((token, num_pos)); } @@ -1763,16 +1774,16 @@ fn get_next_token_inner( match stream.peek_next() { // `\r - start from next line Some('\r') => { - eat_next_and_advance(stream, pos); + stream.eat_next_and_advance(pos); // `\r\n if stream.peek_next() == Some('\n') { - eat_next_and_advance(stream, pos); + stream.eat_next_and_advance(pos); } pos.new_line(); } // `\n - start from next line Some('\n') => { - eat_next_and_advance(stream, pos); + stream.eat_next_and_advance(pos); pos.new_line(); } _ => (), @@ -1824,13 +1835,13 @@ fn get_next_token_inner( // Unit ('(', ')') => { - eat_next_and_advance(stream, pos); + stream.eat_next_and_advance(pos); return Some((Token::Unit, start_pos)); } // Parentheses ('(', '*') => { - eat_next_and_advance(stream, pos); + stream.eat_next_and_advance(pos); return Some((Token::Reserved(Box::new("(*".into())), start_pos)); } ('(', ..) => return Some((Token::LeftParen, start_pos)), @@ -1843,16 +1854,16 @@ fn get_next_token_inner( // Map literal #[cfg(not(feature = "no_object"))] ('#', '{') => { - eat_next_and_advance(stream, pos); + stream.eat_next_and_advance(pos); return Some((Token::MapStart, start_pos)); } // Shebang ('#', '!') => return Some((Token::Reserved(Box::new("#!".into())), start_pos)), ('#', ' ') => { - eat_next_and_advance(stream, pos); + stream.eat_next_and_advance(pos); let token = if stream.peek_next() == Some('{') { - eat_next_and_advance(stream, pos); + stream.eat_next_and_advance(pos); "# {" } else { "#" @@ -1864,11 +1875,11 @@ fn get_next_token_inner( // Operators ('+', '=') => { - eat_next_and_advance(stream, pos); + stream.eat_next_and_advance(pos); return Some((Token::PlusAssign, start_pos)); } ('+', '+') => { - eat_next_and_advance(stream, pos); + stream.eat_next_and_advance(pos); return Some((Token::Reserved(Box::new("++".into())), start_pos)); } ('+', ..) if !state.next_token_cannot_be_unary => { @@ -1879,15 +1890,15 @@ fn get_next_token_inner( ('-', '0'..='9') if !state.next_token_cannot_be_unary => negated = Some(start_pos), ('-', '0'..='9') => return Some((Token::Minus, start_pos)), ('-', '=') => { - eat_next_and_advance(stream, pos); + stream.eat_next_and_advance(pos); return Some((Token::MinusAssign, start_pos)); } ('-', '>') => { - eat_next_and_advance(stream, pos); + stream.eat_next_and_advance(pos); return Some((Token::Reserved(Box::new("->".into())), start_pos)); } ('-', '-') => { - eat_next_and_advance(stream, pos); + stream.eat_next_and_advance(pos); return Some((Token::Reserved(Box::new("--".into())), start_pos)); } ('-', ..) if !state.next_token_cannot_be_unary => { @@ -1896,19 +1907,19 @@ fn get_next_token_inner( ('-', ..) => return Some((Token::Minus, start_pos)), ('*', ')') => { - eat_next_and_advance(stream, pos); + stream.eat_next_and_advance(pos); return Some((Token::Reserved(Box::new("*)".into())), start_pos)); } ('*', '=') => { - eat_next_and_advance(stream, pos); + stream.eat_next_and_advance(pos); return Some((Token::MultiplyAssign, start_pos)); } ('*', '*') => { - eat_next_and_advance(stream, pos); + stream.eat_next_and_advance(pos); return Some(( if stream.peek_next() == Some('=') { - eat_next_and_advance(stream, pos); + stream.eat_next_and_advance(pos); Token::PowerOfAssign } else { Token::PowerOf @@ -1920,13 +1931,13 @@ fn get_next_token_inner( // Comments ('/', '/') => { - eat_next_and_advance(stream, pos); + stream.eat_next_and_advance(pos); let mut comment: Option = match stream.peek_next() { #[cfg(not(feature = "no_function"))] #[cfg(feature = "metadata")] Some('/') => { - eat_next_and_advance(stream, pos); + stream.eat_next_and_advance(pos); // Long streams of `///...` are not doc-comments match stream.peek_next() { @@ -1936,7 +1947,7 @@ fn get_next_token_inner( } #[cfg(feature = "metadata")] Some('!') => { - eat_next_and_advance(stream, pos); + stream.eat_next_and_advance(pos); Some("//!".into()) } _ if state.include_comments => Some("//".into()), @@ -1947,7 +1958,7 @@ fn get_next_token_inner( if c == '\r' { // \r\n if stream.peek_next() == Some('\n') { - eat_next_and_advance(stream, pos); + stream.eat_next_and_advance(pos); } pos.new_line(); break; @@ -1977,13 +1988,13 @@ fn get_next_token_inner( } ('/', '*') => { state.comment_level = 1; - eat_next_and_advance(stream, pos); + stream.eat_next_and_advance(pos); let mut comment: Option = match stream.peek_next() { #[cfg(not(feature = "no_function"))] #[cfg(feature = "metadata")] Some('*') => { - eat_next_and_advance(stream, pos); + stream.eat_next_and_advance(pos); // Long streams of `/****...` are not doc-comments match stream.peek_next() { @@ -2004,7 +2015,7 @@ fn get_next_token_inner( } ('/', '=') => { - eat_next_and_advance(stream, pos); + stream.eat_next_and_advance(pos); return Some((Token::DivideAssign, start_pos)); } ('/', ..) => return Some((Token::Divide, start_pos)), @@ -2013,15 +2024,15 @@ fn get_next_token_inner( (',', ..) => return Some((Token::Comma, start_pos)), ('.', '.') => { - eat_next_and_advance(stream, pos); + stream.eat_next_and_advance(pos); return Some(( match stream.peek_next() { Some('.') => { - eat_next_and_advance(stream, pos); + stream.eat_next_and_advance(pos); Token::Reserved(Box::new("...".into())) } Some('=') => { - eat_next_and_advance(stream, pos); + stream.eat_next_and_advance(pos); Token::InclusiveRange } _ => Token::ExclusiveRange, @@ -2032,56 +2043,56 @@ fn get_next_token_inner( ('.', ..) => return Some((Token::Period, start_pos)), ('=', '=') => { - eat_next_and_advance(stream, pos); + stream.eat_next_and_advance(pos); if stream.peek_next() == Some('=') { - eat_next_and_advance(stream, pos); + stream.eat_next_and_advance(pos); return Some((Token::Reserved(Box::new("===".into())), start_pos)); } return Some((Token::EqualsTo, start_pos)); } ('=', '>') => { - eat_next_and_advance(stream, pos); + stream.eat_next_and_advance(pos); return Some((Token::DoubleArrow, start_pos)); } ('=', ..) => return Some((Token::Equals, start_pos)), #[cfg(not(feature = "no_module"))] (':', ':') => { - eat_next_and_advance(stream, pos); + stream.eat_next_and_advance(pos); if stream.peek_next() == Some('<') { - eat_next_and_advance(stream, pos); + stream.eat_next_and_advance(pos); return Some((Token::Reserved(Box::new("::<".into())), start_pos)); } return Some((Token::DoubleColon, start_pos)); } (':', '=') => { - eat_next_and_advance(stream, pos); + stream.eat_next_and_advance(pos); return Some((Token::Reserved(Box::new(":=".into())), start_pos)); } (':', ';') => { - eat_next_and_advance(stream, pos); + stream.eat_next_and_advance(pos); return Some((Token::Reserved(Box::new(":;".into())), start_pos)); } (':', ..) => return Some((Token::Colon, start_pos)), ('<', '=') => { - eat_next_and_advance(stream, pos); + stream.eat_next_and_advance(pos); return Some((Token::LessThanEqualsTo, start_pos)); } ('<', '-') => { - eat_next_and_advance(stream, pos); + stream.eat_next_and_advance(pos); return Some((Token::Reserved(Box::new("<-".into())), start_pos)); } ('<', '<') => { - eat_next_and_advance(stream, pos); + stream.eat_next_and_advance(pos); return Some(( if stream.peek_next() == Some('=') { - eat_next_and_advance(stream, pos); + stream.eat_next_and_advance(pos); Token::LeftShiftAssign } else { Token::LeftShift @@ -2090,21 +2101,21 @@ fn get_next_token_inner( )); } ('<', '|') => { - eat_next_and_advance(stream, pos); + stream.eat_next_and_advance(pos); return Some((Token::Reserved(Box::new("<|".into())), start_pos)); } ('<', ..) => return Some((Token::LessThan, start_pos)), ('>', '=') => { - eat_next_and_advance(stream, pos); + stream.eat_next_and_advance(pos); return Some((Token::GreaterThanEqualsTo, start_pos)); } ('>', '>') => { - eat_next_and_advance(stream, pos); + stream.eat_next_and_advance(pos); return Some(( if stream.peek_next() == Some('=') { - eat_next_and_advance(stream, pos); + stream.eat_next_and_advance(pos); Token::RightShiftAssign } else { Token::RightShift @@ -2136,47 +2147,47 @@ fn get_next_token_inner( return Some((Token::Bang, start_pos)); } ('!', '=') => { - eat_next_and_advance(stream, pos); + stream.eat_next_and_advance(pos); if stream.peek_next() == Some('=') { - eat_next_and_advance(stream, pos); + stream.eat_next_and_advance(pos); return Some((Token::Reserved(Box::new("!==".into())), start_pos)); } return Some((Token::NotEqualsTo, start_pos)); } ('!', '.') => { - eat_next_and_advance(stream, pos); + stream.eat_next_and_advance(pos); return Some((Token::Reserved(Box::new("!.".into())), start_pos)); } ('!', ..) => return Some((Token::Bang, start_pos)), ('|', '|') => { - eat_next_and_advance(stream, pos); + stream.eat_next_and_advance(pos); return Some((Token::Or, start_pos)); } ('|', '=') => { - eat_next_and_advance(stream, pos); + stream.eat_next_and_advance(pos); return Some((Token::OrAssign, start_pos)); } ('|', '>') => { - eat_next_and_advance(stream, pos); + stream.eat_next_and_advance(pos); return Some((Token::Reserved(Box::new("|>".into())), start_pos)); } ('|', ..) => return Some((Token::Pipe, start_pos)), ('&', '&') => { - eat_next_and_advance(stream, pos); + stream.eat_next_and_advance(pos); return Some((Token::And, start_pos)); } ('&', '=') => { - eat_next_and_advance(stream, pos); + stream.eat_next_and_advance(pos); return Some((Token::AndAssign, start_pos)); } ('&', ..) => return Some((Token::Ampersand, start_pos)), ('^', '=') => { - eat_next_and_advance(stream, pos); + stream.eat_next_and_advance(pos); return Some((Token::XOrAssign, start_pos)); } ('^', ..) => return Some((Token::XOr, start_pos)), @@ -2184,7 +2195,7 @@ fn get_next_token_inner( ('~', ..) => return Some((Token::Reserved(Box::new("~".into())), start_pos)), ('%', '=') => { - eat_next_and_advance(stream, pos); + stream.eat_next_and_advance(pos); return Some((Token::ModuloAssign, start_pos)); } ('%', ..) => return Some((Token::Modulo, start_pos)), @@ -2194,7 +2205,7 @@ fn get_next_token_inner( ('$', ..) => return Some((Token::Reserved(Box::new("$".into())), start_pos)), ('?', '.') => { - eat_next_and_advance(stream, pos); + stream.eat_next_and_advance(pos); return Some(( #[cfg(not(feature = "no_object"))] Token::Elvis, @@ -2204,11 +2215,11 @@ fn get_next_token_inner( )); } ('?', '?') => { - eat_next_and_advance(stream, pos); + stream.eat_next_and_advance(pos); return Some((Token::DoubleQuestion, start_pos)); } ('?', '[') => { - eat_next_and_advance(stream, pos); + stream.eat_next_and_advance(pos); return Some(( #[cfg(not(feature = "no_index"))] Token::QuestionBracket, @@ -2242,7 +2253,7 @@ fn get_next_token_inner( /// Get the next token, parsing it as an identifier. fn parse_identifier_token( - stream: &mut impl InputStream, + stream: &mut (impl InputStream + ?Sized), state: &mut TokenizeState, pos: &mut Position, start_pos: Position, @@ -2258,7 +2269,7 @@ fn parse_identifier_token( while let Some(next_char) = stream.peek_next() { match next_char { x if is_id_continue(x) => { - eat_next_and_advance(stream, pos); + stream.eat_next_and_advance(pos); identifier.push(x); if let Some(ref mut last) = state.last_token { last.push(x); @@ -2694,8 +2705,6 @@ impl Engine { state: TokenizeState { #[cfg(not(feature = "unchecked"))] max_string_len: NonZeroUsize::new(self.max_string_size()), - #[cfg(feature = "unchecked")] - max_string_len: None, next_token_cannot_be_unary: false, tokenizer_control: buffer, comment_level: 0, diff --git a/src/types/bloom_filter.rs b/src/types/bloom_filter.rs index 51be6f3f8..d2711e487 100644 --- a/src/types/bloom_filter.rs +++ b/src/types/bloom_filter.rs @@ -13,8 +13,9 @@ const USIZE_BITS: usize = mem::size_of::() * 8; /// Number of `usize` values required for 256 bits. const SIZE: usize = 256 / USIZE_BITS; -/// A simple bloom filter implementation for `u64` hash values only - i.e. all 64 bits are assumed +/// _(internals)_ A simple bloom filter implementation for `u64` hash values only - i.e. all 64 bits are assumed /// to be relatively random. +/// Exported under the `internals` feature only. /// /// For this reason, the implementation is simplistic - it just looks at the least significant byte /// of the `u64` hash value and sets the corresponding bit in a 256-long bit vector. diff --git a/src/types/dynamic.rs b/src/types/dynamic.rs index 832db2c3e..fafdc242d 100644 --- a/src/types/dynamic.rs +++ b/src/types/dynamic.rs @@ -1603,7 +1603,7 @@ impl Dynamic { /// Returns [`None`] if the cast fails, or if the value is shared. #[inline] #[must_use] - pub(crate) fn downcast_mut(&mut self) -> Option<&mut T> { + pub(crate) fn downcast_mut(&mut self) -> Option<&mut T> { // Coded this way in order to maximally leverage potentials for dead-code removal. if TypeId::of::() == TypeId::of::() { @@ -2097,7 +2097,7 @@ impl Dynamic { #[inline] #[allow(clippy::only_used_in_recursion)] pub fn deep_scan(&mut self, mut filter: impl FnMut(&mut Self)) { - fn scan_inner(value: &mut Dynamic, filter: &mut impl FnMut(&mut Dynamic)) { + fn scan_inner(value: &mut Dynamic, filter: &mut (impl FnMut(&mut Dynamic) + ?Sized)) { filter(value); match &mut value.0 { @@ -2165,12 +2165,6 @@ impl> From for Dynamic { Self(Union::Str(value.into(), DEFAULT_TAG_VALUE, ReadWrite)) } } -impl From<&ImmutableString> for Dynamic { - #[inline(always)] - fn from(value: &ImmutableString) -> Self { - value.clone().into() - } -} impl FromStr for Dynamic { type Err = (); diff --git a/src/types/error.rs b/src/types/error.rs index fcfd11526..99ae18721 100644 --- a/src/types/error.rs +++ b/src/types/error.rs @@ -445,7 +445,7 @@ impl EvalAltResult { #[cfg(feature = "no_index")] tokens .iter() - .map(|s| s.as_str()) + .map(String::as_str) .collect::>() .join(" ") .into(), diff --git a/src/types/float.rs b/src/types/float.rs index 91af3a8d6..faafad260 100644 --- a/src/types/float.rs +++ b/src/types/float.rs @@ -11,7 +11,8 @@ use std::{ use num_traits::float::FloatCore as Float; -/// A type that wraps a floating-point number and implements [`Hash`]. +/// _(internals)_ A type that wraps a floating-point number and implements [`Hash`]. +/// Exported under the `internals` feature only. /// /// Not available under `no_float`. #[derive(Clone, Copy, Eq, PartialEq, PartialOrd)] diff --git a/src/types/fn_ptr.rs b/src/types/fn_ptr.rs index 11529bb67..003f4bed1 100644 --- a/src/types/fn_ptr.rs +++ b/src/types/fn_ptr.rs @@ -88,7 +88,7 @@ impl FnPtr { #[inline(always)] #[must_use] pub fn fn_name(&self) -> &str { - self.fn_name_raw().as_str() + self.fn_name_raw() } /// Get the name of the function. #[inline(always)] diff --git a/src/types/immutable_string.rs b/src/types/immutable_string.rs index 0d251081d..7d8e29bc6 100644 --- a/src/types/immutable_string.rs +++ b/src/types/immutable_string.rs @@ -50,7 +50,7 @@ use std::{ pub struct ImmutableString(Shared); impl Deref for ImmutableString { - type Target = SmartString; + type Target = str; #[inline(always)] fn deref(&self) -> &Self::Target { @@ -58,6 +58,14 @@ impl Deref for ImmutableString { } } +impl AsRef for ImmutableString { + #[inline(always)] + #[must_use] + fn as_ref(&self) -> &str { + &self.0 + } +} + impl AsRef for ImmutableString { #[inline(always)] #[must_use] @@ -66,10 +74,10 @@ impl AsRef for ImmutableString { } } -impl AsRef for ImmutableString { +impl Borrow for ImmutableString { #[inline(always)] #[must_use] - fn as_ref(&self) -> &str { + fn borrow(&self) -> &str { &self.0 } } @@ -82,11 +90,10 @@ impl Borrow for ImmutableString { } } -impl Borrow for ImmutableString { +impl From<&Self> for ImmutableString { #[inline(always)] - #[must_use] - fn borrow(&self) -> &str { - self.as_str() + fn from(value: &Self) -> Self { + Self(value.0.clone()) } } @@ -97,6 +104,7 @@ impl From<&str> for ImmutableString { Self(value.into()) } } + impl From> for ImmutableString { #[inline(always)] fn from(value: Box) -> Self { @@ -133,13 +141,38 @@ impl From for ImmutableString { impl From<&ImmutableString> for SmartString { #[inline(always)] fn from(value: &ImmutableString) -> Self { - value.as_str().into() + value.0.as_ref().clone() } } impl From for SmartString { #[inline(always)] fn from(mut value: ImmutableString) -> Self { - std::mem::take(shared_make_mut(&mut value.0)) + let _ = value.make_mut(); // Make sure it is unique reference + shared_take(value.0) // Should succeed + } +} +impl From<&ImmutableString> for String { + #[inline(always)] + fn from(value: &ImmutableString) -> Self { + value.0.as_ref().to_string() + } +} +impl From for String { + #[inline(always)] + fn from(value: ImmutableString) -> Self { + value.into_owned() + } +} +impl From<&ImmutableString> for Box { + #[inline(always)] + fn from(value: &ImmutableString) -> Self { + value.0.as_str().into() + } +} +impl From for Box { + #[inline(always)] + fn from(value: ImmutableString) -> Self { + value.0.as_str().into() } } @@ -274,25 +307,25 @@ impl Add for &ImmutableString { } } -impl AddAssign<&Self> for ImmutableString { +impl AddAssign for ImmutableString { #[inline] - fn add_assign(&mut self, rhs: &Self) { + fn add_assign(&mut self, rhs: Self) { if !rhs.is_empty() { if self.is_empty() { - self.0 = rhs.0.clone(); + self.0 = rhs.0; } else { - self.make_mut().push_str(rhs.as_str()); + self.make_mut().push_str(&rhs); } } } } -impl AddAssign for ImmutableString { +impl AddAssign<&Self> for ImmutableString { #[inline] - fn add_assign(&mut self, rhs: Self) { + fn add_assign(&mut self, rhs: &Self) { if !rhs.is_empty() { if self.is_empty() { - self.0 = rhs.0; + self.0 = rhs.0.clone(); } else { self.make_mut().push_str(rhs.as_str()); } @@ -441,12 +474,12 @@ impl Sub for &ImmutableString { } } -impl SubAssign<&Self> for ImmutableString { +impl SubAssign for ImmutableString { #[inline] - fn sub_assign(&mut self, rhs: &Self) { + fn sub_assign(&mut self, rhs: Self) { if !rhs.is_empty() { if self.is_empty() { - self.0 = rhs.0.clone(); + self.0 = rhs.0; } else { let rhs: SmartString = self.replace(rhs.as_str(), "").into(); self.0 = rhs.into(); @@ -455,12 +488,12 @@ impl SubAssign<&Self> for ImmutableString { } } -impl SubAssign for ImmutableString { +impl SubAssign<&Self> for ImmutableString { #[inline] - fn sub_assign(&mut self, rhs: Self) { + fn sub_assign(&mut self, rhs: &Self) { if !rhs.is_empty() { if self.is_empty() { - self.0 = rhs.0; + self.0 = rhs.0.clone(); } else { let rhs: SmartString = self.replace(rhs.as_str(), "").into(); self.0 = rhs.into(); @@ -575,13 +608,27 @@ impl SubAssign for ImmutableString { } } -impl> PartialEq for ImmutableString { +impl + ?Sized> PartialEq for ImmutableString { #[inline(always)] fn eq(&self, other: &S) -> bool { self.as_str().eq(other.as_ref()) } } +impl PartialEq for &ImmutableString { + #[inline(always)] + fn eq(&self, other: &str) -> bool { + self.as_str().eq(other) + } +} + +impl PartialEq for &ImmutableString { + #[inline(always)] + fn eq(&self, other: &String) -> bool { + self.as_str().eq(other.as_str()) + } +} + impl PartialEq for str { #[inline(always)] fn eq(&self, other: &ImmutableString) -> bool { @@ -589,19 +636,52 @@ impl PartialEq for str { } } +impl PartialEq for &str { + #[inline(always)] + fn eq(&self, other: &ImmutableString) -> bool { + (*self).eq(other.as_str()) + } +} + impl PartialEq for String { #[inline(always)] fn eq(&self, other: &ImmutableString) -> bool { - self.eq(other.as_str()) + self.as_str().eq(other.as_str()) } } -impl> PartialOrd for ImmutableString { +impl PartialEq<&ImmutableString> for String { + #[inline(always)] + fn eq(&self, other: &&ImmutableString) -> bool { + self.as_str().eq(other.as_str()) + } +} + +impl PartialEq for &String { + #[inline(always)] + fn eq(&self, other: &ImmutableString) -> bool { + self.as_str().eq(other.as_str()) + } +} + +impl + ?Sized> PartialOrd for ImmutableString { fn partial_cmp(&self, other: &S) -> Option { self.as_str().partial_cmp(other.as_ref()) } } +impl PartialOrd for &ImmutableString { + fn partial_cmp(&self, other: &str) -> Option { + self.as_str().partial_cmp(other) + } +} + +impl PartialOrd for &ImmutableString { + fn partial_cmp(&self, other: &String) -> Option { + self.as_str().partial_cmp(other.as_str()) + } +} + impl PartialOrd for str { #[inline(always)] fn partial_cmp(&self, other: &ImmutableString) -> Option { @@ -609,6 +689,13 @@ impl PartialOrd for str { } } +impl PartialOrd for &str { + #[inline(always)] + fn partial_cmp(&self, other: &ImmutableString) -> Option { + (*self).partial_cmp(other.as_str()) + } +} + impl PartialOrd for String { #[inline(always)] fn partial_cmp(&self, other: &ImmutableString) -> Option { @@ -616,6 +703,20 @@ impl PartialOrd for String { } } +impl PartialOrd<&ImmutableString> for String { + #[inline(always)] + fn partial_cmp(&self, other: &&ImmutableString) -> Option { + self.as_str().partial_cmp(other.as_str()) + } +} + +impl PartialOrd for &String { + #[inline(always)] + fn partial_cmp(&self, other: &ImmutableString) -> Option { + self.as_str().partial_cmp(other.as_str()) + } +} + impl ImmutableString { /// Create a new [`ImmutableString`]. #[inline(always)] @@ -623,6 +724,18 @@ impl ImmutableString { pub fn new() -> Self { Self(SmartString::new_const().into()) } + /// Get the string slice. + #[inline(always)] + #[must_use] + pub fn as_str(&self) -> &str { + self.0.as_str() + } + /// Get the string slice. + #[inline(always)] + #[must_use] + pub(crate) fn as_raw(&self) -> &SmartString { + &self.0 + } /// Strong count of references to the underlying string. pub(crate) fn strong_count(&self) -> usize { Shared::strong_count(&self.0) diff --git a/src/types/interner.rs b/src/types/interner.rs index 73c51619b..f39984e21 100644 --- a/src/types/interner.rs +++ b/src/types/interner.rs @@ -61,7 +61,7 @@ impl StringsInterner { /// Get an identifier from a text string, adding it to the interner if necessary. #[inline(always)] #[must_use] - pub fn get + Into>(&mut self, text: S) -> ImmutableString { + pub fn get(&mut self, text: impl AsRef + Into) -> ImmutableString { self.get_with_mapper(0, Into::into, text) } diff --git a/src/types/parse_error.rs b/src/types/parse_error.rs index 0833e321c..adccb8c2c 100644 --- a/src/types/parse_error.rs +++ b/src/types/parse_error.rs @@ -241,7 +241,7 @@ impl fmt::Display for ParseErrorType { Self::AssignmentToInvalidLHS(s) => f.write_str(s), Self::LiteralTooLarge(typ, max) => write!(f, "{typ} exceeds the maximum limit ({max})"), - Self::Reserved(s) if is_valid_identifier(s.as_str()) => write!(f, "'{s}' is a reserved keyword"), + Self::Reserved(s) if is_valid_identifier(s) => write!(f, "'{s}' is a reserved keyword"), Self::Reserved(s) => write!(f, "'{s}' is a reserved symbol"), Self::UnexpectedEOF => f.write_str("Script is incomplete"), Self::WrongSwitchIntegerCase => f.write_str("Numeric switch case cannot follow a range case"), diff --git a/src/types/scope.rs b/src/types/scope.rs index 40f783c46..9f09b6a36 100644 --- a/src/types/scope.rs +++ b/src/types/scope.rs @@ -21,14 +21,6 @@ pub const MIN_SCOPE_ENTRIES: usize = 8; /// Currently the lifetime parameter is not used, but it is not guaranteed to remain unused for /// future versions. Until then, `'static` can be used. /// -/// # Constant Generic Parameter -/// -/// There is a constant generic parameter that indicates how many entries to keep inline. -/// As long as the number of entries does not exceed this limit, no allocations occur. -/// The default is 8. -/// -/// A larger value makes [`Scope`] larger, but reduces the chance of allocations. -/// /// # Thread Safety /// /// Currently, [`Scope`] is neither [`Send`] nor [`Sync`]. Turn on the `sync` feature to make it @@ -675,7 +667,7 @@ impl Scope<'_> { pub(crate) fn get_entry_by_index( &mut self, index: usize, - ) -> (&Identifier, &Dynamic, &[ImmutableString]) { + ) -> (&str, &Dynamic, &[ImmutableString]) { if self.aliases.len() <= index { self.aliases.resize(index + 1, <_>::default()); } diff --git a/src/types/var_def.rs b/src/types/var_def.rs index 2c83bc715..e1214a89b 100644 --- a/src/types/var_def.rs +++ b/src/types/var_def.rs @@ -1,12 +1,12 @@ -//! Variable definition information. +//! Variable declaration information. #[cfg(feature = "no_std")] use std::prelude::v1::*; -/// Information on a variable definition. +/// Information on a variable declaration. #[derive(Debug, Clone, Hash)] pub struct VarDefInfo<'a> { - /// Name of the variable to be defined. + /// Name of the variable to be declared. name: &'a str, /// `true` if the statement is `const`, otherwise it is `let`. is_const: bool, @@ -33,7 +33,7 @@ impl<'a> VarDefInfo<'a> { will_shadow, } } - /// Name of the variable to be defined. + /// Name of the variable to be declared. #[inline(always)] #[must_use] pub const fn name(&self) -> &str { @@ -51,6 +51,12 @@ impl<'a> VarDefInfo<'a> { pub const fn nesting_level(&self) -> usize { self.nesting_level } + /// `true` if the variable is declared at global level (i.e. nesting level zero). + #[inline(always)] + #[must_use] + pub const fn is_global_level(&self) -> bool { + self.nesting_level == 0 + } /// Will the variable _shadow_ an existing variable? #[inline(always)] #[must_use] diff --git a/tests/string.rs b/tests/string.rs index 76e987e92..1af0083d2 100644 --- a/tests/string.rs +++ b/tests/string.rs @@ -266,3 +266,44 @@ Undeniable logic: "Undeniable logic:\n1) Hello, 42 worlds!\n2) If 123 > 42 then it is true!\n", ); } + +#[test] +fn test_immutable_string() { + let x: ImmutableString = "hello".into(); + assert_eq!(x, "hello"); + assert!(x == "hello"); + assert!(&x == "hello"); + assert!("hello" == x); + assert!("hello" == &x); + + let s2 = String::from("hello"); + let s3: ImmutableString = s2.clone().into(); + assert_eq!(s2, s3); + let s3: ImmutableString = (&s2).into(); + assert_eq!(s2, s3); + + assert!(x == s2); + assert!(&x == s2); + assert!(x == &s2); + assert!(&x == &s2); + + assert!(s2 == x); + assert!(&s2 == x); + assert!(s2 == &x); + assert!(&s2 == &x); + + assert!(x >= s2); + assert!(&x >= s2); + assert!(x >= &s2); + assert!(&x >= &s2); + + assert!(s2 >= x); + assert!(&s2 >= x); + assert!(s2 >= &x); + assert!(&s2 >= &x); + + let _sx: String = x.clone().into(); + let _sx: String = (&x).into(); + let _ssx: Box = x.clone().into(); + let _ssx: Box = (&x).into(); +}