diff --git a/CHANGELOG.md b/CHANGELOG.md index a83391e6..e5df697b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -34,6 +34,7 @@ Breaking changes * The _reflections_ API such as `Engine::gen_fn_signatures`, `Module::update_fn_metadata` etc. are put under the `metadata` feature gate. * The shebang `#!` is now a reserved symbol. * Shebangs at the very beginning of script files are skipped when loading them. +* [`smartstring`](https://crates.io/crates/smartstring) is used for identifiers. Enhancements ------------ diff --git a/Cargo.toml b/Cargo.toml index b16375d0..f2f17533 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,6 +17,7 @@ categories = ["no-std", "embedded", "wasm", "parser-implementations"] [dependencies] smallvec = { version = "1.6", default-features = false, features = ["union"] } +smartstring = { version = "0.2.6" } ahash = { version = "0.7", default-features = false } num-traits = { version = "0.2", default_features = false } rhai_codegen = { version = "0.3.4", path = "codegen", features = ["metadata"] } diff --git a/README.md b/README.md index 57cc560e..8042448b 100644 --- a/README.md +++ b/README.md @@ -35,7 +35,7 @@ Standard features * Built-in support for most common [data types](https://rhai.rs/book/language/values-and-types.html) including booleans, [integers](https://rhai.rs/book/language/numbers.html), [floating-point numbers](https://rhai.rs/book/language/numbers.html) (including [`Decimal`](https://crates.io/crates/rust_decimal)), [strings](https://rhai.rs/book/language/strings-chars.html), [Unicode characters](https://rhai.rs/book/language/strings-chars.html), [arrays](https://rhai.rs/book/language/arrays.html) and [object maps](https://rhai.rs/book/language/object-maps.html). * Easily [call a script-defined function](https://rhai.rs/book/engine/call-fn.html) from Rust. * Relatively little `unsafe` code (yes there are some for performance reasons). -* Few dependencies (currently only [`smallvec`](https://crates.io/crates/smallvec), [`num-traits`](https://crates.io/crates/num-traits) and [`ahash`](https://crates.io/crates/ahash)). +* Few dependencies (currently only [`smallvec`](https://crates.io/crates/smallvec), [`num-traits`](https://crates.io/crates/num-traits), [`ahash`](https://crates.io/crates/ahash)) and [`smartstring`](https://crates.io/crates/smartstring)). * Re-entrant scripting engine can be made `Send + Sync` (via the `sync` feature). * Compile once to [AST](https://rhai.rs/book/engine/compile.html) form for repeated evaluations. * Scripts are [optimized](https://rhai.rs/book/engine/optimize.html) (useful for template-based machine-generated scripts). diff --git a/src/ast.rs b/src/ast.rs index 07c0870f..02f30e1f 100644 --- a/src/ast.rs +++ b/src/ast.rs @@ -16,7 +16,8 @@ use crate::stdlib::{ }; use crate::token::Token; use crate::{ - Dynamic, FnNamespace, FnPtr, ImmutableString, Module, Position, Shared, StaticVec, INT, + Dynamic, FnNamespace, FnPtr, Identifier, ImmutableString, Module, Position, Shared, StaticVec, + INT, }; #[cfg(not(feature = "no_float"))] @@ -50,14 +51,14 @@ pub struct ScriptFnDef { #[cfg(not(feature = "no_module"))] pub mods: crate::engine::Imports, /// Function name. - pub name: ImmutableString, + pub name: Identifier, /// Function access mode. pub access: FnAccess, /// Names of function parameters. - pub params: StaticVec, + pub params: StaticVec, /// Access to external variables. #[cfg(not(feature = "no_closure"))] - pub externals: BTreeSet, + pub externals: BTreeSet, /// Function doc-comments (if any). pub comments: StaticVec, } @@ -144,7 +145,7 @@ impl<'a> Into> for &'a ScriptFnDef { #[derive(Debug, Clone)] pub struct AST { /// Source of the [`AST`]. - source: Option, + source: Option, /// Global statements. body: StmtBlock, /// Script-defined functions. @@ -190,7 +191,7 @@ impl AST { pub fn new_with_source( statements: impl IntoIterator, functions: impl Into>, - source: impl Into, + source: impl Into, ) -> Self { Self { source: Some(source.into()), @@ -210,12 +211,12 @@ impl AST { } /// Clone the source, if any. #[inline(always)] - pub(crate) fn clone_source(&self) -> Option { + pub(crate) fn clone_source(&self) -> Option { self.source.clone() } /// Set the source. #[inline(always)] - pub fn set_source(&mut self, source: impl Into) -> &mut Self { + pub fn set_source(&mut self, source: impl Into) -> &mut Self { self.source = Some(source.into()); if let Some(module) = Shared::get_mut(&mut self.functions) { @@ -756,7 +757,7 @@ impl AsRef for AST { } } -/// _(INTERNALS)_ An identifier containing an [immutable string][ImmutableString] name and a [position][Position]. +/// _(INTERNALS)_ An identifier containing a name and a [position][Position]. /// Exported under the `internals` feature only. /// /// # Volatile Data Structure @@ -765,7 +766,7 @@ impl AsRef for AST { #[derive(Clone, Eq, PartialEq, Hash)] pub struct Ident { /// Identifier name. - pub name: ImmutableString, + pub name: Identifier, /// Declaration position. pub pos: Position, } @@ -869,11 +870,11 @@ pub enum Stmt { /// `do` `{` stmt `}` `while`|`until` expr Do(Box, Expr, bool, Position), /// `for` id `in` expr `{` stmt `}` - For(Expr, Box<(String, StmtBlock)>, Position), + For(Expr, Box<(Identifier, StmtBlock)>, Position), /// \[`export`\] `let` id `=` expr - Let(Expr, Ident, bool, Position), + Let(Expr, Box, bool, Position), /// \[`export`\] `const` id `=` expr - Const(Expr, Ident, bool, Position), + Const(Expr, Box, bool, Position), /// expr op`=` expr Assignment(Box<(Expr, Expr, Option)>, Position), /// `{` stmt`;` ... `}` @@ -894,7 +895,7 @@ pub enum Stmt { Return(ReturnType, Option, Position), /// `import` expr `as` var #[cfg(not(feature = "no_module"))] - Import(Expr, Option, Position), + Import(Expr, Option>, Position), /// `export` var `as` var `,` ... #[cfg(not(feature = "no_module"))] Export(Vec<(Ident, Option)>, Position), @@ -1256,7 +1257,7 @@ pub struct CustomExpr { /// List of keywords. pub keywords: StaticVec, /// List of tokens actually parsed. - pub tokens: Vec, + pub tokens: Vec, /// Delta number of variables in the scope. pub scope_delta: isize, } @@ -1400,11 +1401,11 @@ pub struct FnCallExpr { /// List of function call argument expressions. pub args: StaticVec, /// List of function call arguments that are constants. - pub constant_args: StaticVec<(Dynamic, Position)>, + pub constant_args: smallvec::SmallVec<[(Dynamic, Position); 2]>, /// Namespace of the function, if any. Boxed because it occurs rarely. pub namespace: Option, /// Function name. - pub name: ImmutableString, + pub name: Identifier, } impl FnCallExpr { @@ -1554,7 +1555,7 @@ pub enum Expr { /// Variable access - (optional index, optional (hash, modules), variable name) Variable(Box<(Option, Option<(u64, NamespaceRef)>, Ident)>), /// Property access - ((getter, hash), (setter, hash), prop) - Property(Box<((ImmutableString, u64), (ImmutableString, u64), Ident)>), + Property(Box<((Identifier, u64), (Identifier, u64), Ident)>), /// { [statement][Stmt] ... } Stmt(Box), /// func `(` expr `,` ... `)` @@ -1609,7 +1610,7 @@ impl Expr { Self::Map(x, _) if self.is_constant() => { let mut map = x.1.clone(); x.0.iter().for_each(|(k, v)| { - *map.get_mut(&k.name).unwrap() = v.get_constant_value().unwrap() + *map.get_mut(k.name.as_str()).unwrap() = v.get_constant_value().unwrap() }); Dynamic(Union::Map(Box::new(map), AccessMode::ReadOnly)) } diff --git a/src/dynamic.rs b/src/dynamic.rs index 7bde7ba2..f32b68b7 100644 --- a/src/dynamic.rs +++ b/src/dynamic.rs @@ -1667,6 +1667,18 @@ impl> From for Dynamic { Self(Union::Str(value.into(), AccessMode::ReadWrite)) } } +impl From<&ImmutableString> for Dynamic { + #[inline(always)] + fn from(value: &ImmutableString) -> Self { + value.clone().into() + } +} +impl From<&smartstring::SmartString> for Dynamic { + #[inline(always)] + fn from(value: &smartstring::SmartString) -> Self { + value.to_string().into() + } +} #[cfg(not(feature = "no_index"))] impl From> for Dynamic { #[inline(always)] diff --git a/src/engine.rs b/src/engine.rs index 442e806f..e8255c63 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -25,8 +25,8 @@ use crate::stdlib::{ use crate::syntax::CustomSyntax; use crate::utils::get_hasher; use crate::{ - Dynamic, EvalAltResult, FnPtr, ImmutableString, Module, Position, RhaiResult, Scope, Shared, - StaticVec, + Dynamic, EvalAltResult, FnPtr, Identifier, ImmutableString, Module, Position, RhaiResult, + Scope, Shared, StaticVec, }; #[cfg(not(feature = "no_index"))] @@ -50,7 +50,7 @@ pub type Precedence = NonZeroU8; // the module name will live beyond the AST of the eval script text. // The best we can do is a shared reference. #[derive(Debug, Clone, Default)] -pub struct Imports(StaticVec, StaticVec>); +pub struct Imports(StaticVec, StaticVec>); impl Imports { /// Get the length of this stack of imported [modules][Module]. @@ -79,7 +79,7 @@ impl Imports { } /// Push an imported [modules][Module] onto the stack. #[inline(always)] - pub fn push(&mut self, name: impl Into, module: impl Into>) { + pub fn push(&mut self, name: impl Into, module: impl Into>) { self.0.push(name.into()); self.1.push(module.into()); } @@ -102,18 +102,18 @@ impl Imports { /// Get an iterator to this stack of imported [modules][Module] in reverse order. #[allow(dead_code)] #[inline(always)] - pub(crate) fn iter_raw(&self) -> impl Iterator)> { + pub(crate) fn iter_raw(&self) -> impl Iterator)> { self.0.iter().rev().zip(self.1.iter().rev()) } /// Get an iterator to this stack of imported [modules][Module] in forward order. #[allow(dead_code)] #[inline(always)] - pub(crate) fn scan_raw(&self) -> impl Iterator)> { + pub(crate) fn scan_raw(&self) -> impl Iterator)> { self.0.iter().zip(self.1.iter()) } /// Get a consuming iterator to this stack of imported [modules][Module] in reverse order. #[inline(always)] - pub fn into_iter(self) -> impl Iterator)> { + pub fn into_iter(self) -> impl Iterator)> { self.0.into_iter().rev().zip(self.1.into_iter().rev()) } /// Does the specified function hash key exist in this stack of imported [modules][Module]? @@ -124,7 +124,7 @@ impl Imports { } /// Get specified function via its hash key. #[inline(always)] - pub fn get_fn(&self, hash: u64) -> Option<(&CallableFunction, Option<&ImmutableString>)> { + pub fn get_fn(&self, hash: u64) -> Option<(&CallableFunction, Option<&Identifier>)> { self.1 .iter() .rev() @@ -491,7 +491,7 @@ pub struct FnResolutionCacheEntry { /// Function. pub func: CallableFunction, /// Optional source. - pub source: Option, + pub source: Option, } /// A function resolution cache. @@ -506,7 +506,7 @@ pub type FnResolutionCache = BTreeMap>; #[derive(Debug, Clone, Default)] pub struct State { /// Source of the current context. - pub source: Option, + pub source: Option, /// Normally, access to variables are parsed with a relative offset into the scope to avoid a lookup. /// In some situation, e.g. after running an `eval` statement, subsequent offsets become mis-aligned. /// When that happens, this flag is turned on to force a scope lookup by name. @@ -703,7 +703,7 @@ pub struct Engine { /// A collection of all modules loaded into the global namespace of the Engine. pub(crate) global_modules: StaticVec>, /// A collection of all sub-modules directly loaded into the Engine. - pub(crate) global_sub_modules: BTreeMap>, + pub(crate) global_sub_modules: BTreeMap>, /// A module resolution service. #[cfg(not(feature = "no_module"))] @@ -717,7 +717,7 @@ pub struct Engine { /// A map containing custom keywords and precedence to recognize. pub(crate) custom_keywords: BTreeMap>, /// Custom syntax. - pub(crate) custom_syntax: BTreeMap, + pub(crate) custom_syntax: BTreeMap, /// Callback closure for resolving variable access. pub(crate) resolve_var: Option, @@ -1183,7 +1183,7 @@ impl Engine { // {xxx:map}.id op= ??? Expr::Property(x) if target_val.is::() && new_val.is_some() => { let Ident { name, pos, .. } = &x.2; - let index = name.clone().into(); + let index = name.into(); let val = self.get_indexed_mut( mods, state, lib, target_val, index, *pos, true, is_ref, false, level, )?; @@ -1196,7 +1196,7 @@ impl Engine { // {xxx:map}.id Expr::Property(x) if target_val.is::() => { let Ident { name, pos, .. } = &x.2; - let index = name.clone().into(); + let index = name.into(); let val = self.get_indexed_mut( mods, state, lib, target_val, index, *pos, false, is_ref, false, level, )?; @@ -1231,7 +1231,7 @@ impl Engine { let mut val = match &x.lhs { Expr::Property(p) => { let Ident { name, pos, .. } = &p.2; - let index = name.clone().into(); + let index = name.into(); self.get_indexed_mut( mods, state, lib, target_val, index, *pos, false, is_ref, true, level, @@ -1696,7 +1696,7 @@ impl Engine { Expr::Map(x, _) => { let mut map = x.1.clone(); for (Ident { name: key, .. }, expr) in &x.0 { - *map.get_mut(key).unwrap() = self + *map.get_mut(key.as_str()).unwrap() = self .eval_expr(scope, mods, state, lib, this_ptr, expr, level)? .flatten(); } @@ -2209,7 +2209,7 @@ impl Engine { if let Some(func) = func { // Add the loop variable let var_name: Cow<'_, str> = if state.is_global() { - name.clone().into() + name.to_string().into() } else { unsafe_cast_var_name_to_lifetime(name).into() }; @@ -2300,7 +2300,7 @@ impl Engine { err_map.insert("message".into(), err.to_string().into()); if let Some(ref source) = state.source { - err_map.insert("source".into(), source.clone().into()); + err_map.insert("source".into(), source.into()); } if err_pos.is_none() { @@ -2382,8 +2382,8 @@ impl Engine { } // Let/const statement - Stmt::Let(expr, Ident { name, .. }, export, _) - | Stmt::Const(expr, Ident { name, .. }, export, _) => { + Stmt::Let(expr, x, export, _) | Stmt::Const(expr, x, export, _) => { + let name = &x.name; let entry_type = match stmt { Stmt::Let(_, _, _, _) => AccessMode::ReadWrite, Stmt::Const(_, _, _, _) => AccessMode::ReadOnly, diff --git a/src/engine_api.rs b/src/engine_api.rs index 8703d3d0..cd37ac6c 100644 --- a/src/engine_api.rs +++ b/src/engine_api.rs @@ -12,7 +12,7 @@ use crate::stdlib::{ string::String, }; use crate::{ - scope::Scope, Dynamic, Engine, EvalAltResult, FnAccess, FnNamespace, ImmutableString, Module, + scope::Scope, Dynamic, Engine, EvalAltResult, FnAccess, FnNamespace, Identifier, Module, NativeCallContext, ParseError, Position, RhaiResult, Shared, AST, }; @@ -53,7 +53,7 @@ impl Engine { #[inline] pub fn register_fn(&mut self, name: N, func: F) -> &mut Self where - N: AsRef + Into, + N: AsRef + Into, F: RegisterNativeFunction, { let param_types = F::param_types(); @@ -113,7 +113,7 @@ impl Engine { #[inline] pub fn register_result_fn(&mut self, name: N, func: F) -> &mut Self where - N: AsRef + Into, + N: AsRef + Into, F: RegisterNativeFunction>>, { let param_types = F::param_types(); @@ -170,7 +170,7 @@ impl Engine { + 'static, ) -> &mut Self where - N: AsRef + Into, + N: AsRef + Into, T: Variant + Clone, { self.global_namespace.set_raw_fn( @@ -901,12 +901,12 @@ impl Engine { #[cfg(not(feature = "no_module"))] pub fn register_static_module( &mut self, - name: impl AsRef + Into, + name: impl AsRef + Into, module: Shared, ) -> &mut Self { fn register_static_module_raw( - root: &mut crate::stdlib::collections::BTreeMap>, - name: impl AsRef + Into, + root: &mut crate::stdlib::collections::BTreeMap>, + name: impl AsRef + Into, module: Shared, ) { let separator = crate::token::Token::DoubleColon.syntax(); @@ -952,7 +952,7 @@ impl Engine { #[deprecated(since = "0.19.9", note = "use `register_static_module` instead")] pub fn register_module( &mut self, - name: impl AsRef + Into, + name: impl AsRef + Into, module: impl Into>, ) -> &mut Self { self.register_static_module(name, module.into()) @@ -1045,14 +1045,14 @@ impl Engine { fn collect_imports( ast: &AST, resolver: &StaticModuleResolver, - imports: &mut BTreeSet, + imports: &mut BTreeSet, ) { ast.walk(&mut |path| match path.last().unwrap() { // Collect all `import` statements with a string constant path ASTNode::Stmt(Stmt::Import(Expr::StringConstant(s, _), _, _)) - if !resolver.contains_path(s) && !imports.contains(s) => + if !resolver.contains_path(s) && !imports.contains(s.as_str()) => { - imports.insert(s.clone()); + imports.insert(s.clone().into()); true } _ => true, diff --git a/src/fn_native.rs b/src/fn_native.rs index e893550b..4535cf8f 100644 --- a/src/fn_native.rs +++ b/src/fn_native.rs @@ -13,8 +13,8 @@ use crate::stdlib::{ }; use crate::token::is_valid_identifier; use crate::{ - calc_fn_hash, Dynamic, Engine, EvalAltResult, EvalContext, ImmutableString, Module, Position, - RhaiResult, StaticVec, + calc_fn_hash, Dynamic, Engine, EvalAltResult, EvalContext, Identifier, ImmutableString, Module, + Position, RhaiResult, StaticVec, }; /// Trait that maps to `Send + Sync` only under the `sync` feature. @@ -144,9 +144,7 @@ impl<'a> NativeCallContext<'a> { #[cfg(not(feature = "no_module"))] #[allow(dead_code)] #[inline(always)] - pub(crate) fn iter_imports_raw( - &self, - ) -> impl Iterator)> { + pub(crate) fn iter_imports_raw(&self) -> impl Iterator)> { self.mods.iter().flat_map(|&m| m.iter_raw()) } /// _(INTERNALS)_ The current set of modules imported via `import` statements. diff --git a/src/lib.rs b/src/lib.rs index 87996279..04173c37 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -292,6 +292,8 @@ type StaticVec = smallvec::SmallVec<[T; 4]>; #[cfg(feature = "internals")] pub type StaticVec = smallvec::SmallVec<[T; 4]>; +pub type Identifier = smartstring::SmartString; + // Compiler guards against mutually-exclusive feature flags #[cfg(feature = "no_float")] diff --git a/src/module/mod.rs b/src/module/mod.rs index 472e4319..2e7924be 100644 --- a/src/module/mod.rs +++ b/src/module/mod.rs @@ -18,8 +18,8 @@ use crate::stdlib::{ use crate::token::Token; use crate::utils::StringInterner; use crate::{ - calc_fn_hash, calc_fn_params_hash, combine_hashes, Dynamic, EvalAltResult, ImmutableString, - NativeCallContext, Position, Shared, StaticVec, + calc_fn_hash, calc_fn_params_hash, combine_hashes, Dynamic, EvalAltResult, Identifier, + ImmutableString, NativeCallContext, Position, Shared, StaticVec, }; #[cfg(not(feature = "no_index"))] @@ -55,14 +55,14 @@ pub struct FuncInfo { /// Function access mode. pub access: FnAccess, /// Function name. - pub name: ImmutableString, + pub name: Identifier, /// Number of parameters. pub params: usize, /// Parameter types (if applicable). pub param_types: StaticVec, /// Parameter names (if available). #[cfg(feature = "metadata")] - pub param_names: StaticVec, + pub param_names: StaticVec, } impl FuncInfo { @@ -128,11 +128,11 @@ fn calc_native_fn_hash<'a>( #[derive(Clone)] pub struct Module { /// ID identifying the module. - id: Option, + id: Option, /// Sub-modules. - modules: BTreeMap>, + modules: BTreeMap>, /// [`Module`] variables. - variables: BTreeMap, + variables: BTreeMap, /// Flattened collection of all [`Module`] variables, including those in sub-modules. all_variables: BTreeMap, /// External Rust functions. @@ -288,7 +288,7 @@ impl Module { self.id_raw().map(|s| s.as_str()) } - /// Get the ID of the [`Module`] as an [`ImmutableString`], if any. + /// Get the ID of the [`Module`] as an [`Identifier`], if any. /// /// # Example /// @@ -300,7 +300,7 @@ impl Module { /// assert_eq!(module.id_raw().map(|s| s.as_str()), Some("hello")); /// ``` #[inline(always)] - pub fn id_raw(&self) -> Option<&ImmutableString> { + pub fn id_raw(&self) -> Option<&Identifier> { self.id.as_ref() } @@ -316,7 +316,7 @@ impl Module { /// assert_eq!(module.id(), Some("hello")); /// ``` #[inline(always)] - pub fn set_id>(&mut self, id: Option) { + pub fn set_id>(&mut self, id: Option) { self.id = id.map(|s| s.into()); } @@ -440,7 +440,7 @@ impl Module { #[inline(always)] pub fn set_var( &mut self, - name: impl Into, + name: impl Into, value: impl Variant + Clone, ) -> &mut Self { self.variables.insert(name.into(), Dynamic::from(value)); @@ -514,7 +514,7 @@ impl Module { /// Thus the [`Module`] is automatically set to be non-indexed. #[cfg(not(feature = "no_module"))] #[inline(always)] - pub(crate) fn sub_modules_mut(&mut self) -> &mut BTreeMap> { + pub(crate) fn sub_modules_mut(&mut self) -> &mut BTreeMap> { // We must assume that the user has changed the sub-modules // (otherwise why take a mutable reference?) self.all_functions.clear(); @@ -577,7 +577,7 @@ impl Module { #[inline(always)] pub fn set_sub_module( &mut self, - name: impl Into, + name: impl Into, sub_module: impl Into>, ) -> &mut Self { self.modules.insert(name.into(), sub_module.into()); @@ -672,7 +672,7 @@ impl Module { #[inline] pub fn set_fn( &mut self, - name: impl AsRef + Into, + name: impl AsRef + Into, namespace: FnNamespace, access: FnAccess, _arg_names: Option<&[&str]>, @@ -793,7 +793,7 @@ impl Module { func: F, ) -> u64 where - N: AsRef + Into, + N: AsRef + Into, T: Variant + Clone, F: Fn(NativeCallContext, &mut FnCallArgs) -> Result> + SendSync @@ -838,7 +838,7 @@ impl Module { #[inline(always)] pub fn set_native_fn(&mut self, name: N, func: F) -> u64 where - N: AsRef + Into, + N: AsRef + Into, T: Variant + Clone, F: RegisterNativeFunction>>, { diff --git a/src/optimize.rs b/src/optimize.rs index 61edaac9..e4b62c26 100644 --- a/src/optimize.rs +++ b/src/optimize.rs @@ -1,6 +1,6 @@ //! Module implementing the [`AST`] optimizer. -use crate::ast::{Expr, Ident, Stmt, StmtBlock}; +use crate::ast::{Expr, Stmt, StmtBlock}; use crate::dynamic::AccessMode; use crate::engine::{KEYWORD_DEBUG, KEYWORD_EVAL, KEYWORD_PRINT, KEYWORD_TYPE_OF}; use crate::fn_builtin::get_builtin_binary_op_fn; @@ -214,17 +214,17 @@ fn optimize_stmt_block( statements.iter_mut().for_each(|stmt| { match stmt { // Add constant literals into the state - Stmt::Const(value_expr, Ident { name, .. }, _, _) => { + Stmt::Const(value_expr, x, _, _) => { optimize_expr(value_expr, state); if value_expr.is_constant() { - state.push_var(name, AccessMode::ReadOnly, value_expr.clone()); + state.push_var(&x.name, AccessMode::ReadOnly, value_expr.clone()); } } // Add variables into the state - Stmt::Let(value_expr, Ident { name, pos, .. }, _, _) => { + Stmt::Let(value_expr, x, _, _) => { optimize_expr(value_expr, state); - state.push_var(name, AccessMode::ReadWrite, Expr::Unit(*pos)); + state.push_var(&x.name, AccessMode::ReadWrite, Expr::Unit(x.pos)); } // Optimize the statement _ => optimize_stmt(stmt, state, preserve_result), @@ -649,7 +649,7 @@ fn optimize_expr(expr: &mut Expr, state: &mut State) { // 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.name == *s) + *expr = mem::take(&mut m.0).into_iter().find(|(x, _)| x.name.as_str() == s.as_str()) .map(|(_, mut expr)| { expr.set_position(*pos); expr }) .unwrap_or_else(|| Expr::Unit(*pos)); } diff --git a/src/packages/fn_basic.rs b/src/packages/fn_basic.rs index 65ae4469..634f5253 100644 --- a/src/packages/fn_basic.rs +++ b/src/packages/fn_basic.rs @@ -1,5 +1,5 @@ use crate::plugin::*; -use crate::{def_package, FnPtr, ImmutableString, NativeCallContext}; +use crate::{def_package, FnPtr, Identifier, ImmutableString, NativeCallContext}; def_package!(crate:BasicFnPackage:"Basic Fn functions.", lib, { combine_with_exported_module!(lib, "FnPtr", fn_ptr_functions); @@ -38,18 +38,21 @@ fn collect_fn_metadata(ctx: NativeCallContext) -> crate::Array { // Create a metadata record for a function. fn make_metadata( - dict: &BTreeSet, - namespace: Option, + dict: &BTreeSet, + namespace: Option, f: &ScriptFnDef, ) -> Map { let mut map = Map::new(); if let Some(ns) = namespace { - map.insert(dict.get("namespace").unwrap().clone(), ns.into()); + map.insert(dict.get("namespace").unwrap().clone().into(), ns.into()); } - map.insert(dict.get("name").unwrap().clone(), f.name.clone().into()); map.insert( - dict.get("access").unwrap().clone(), + dict.get("name").unwrap().clone().into(), + f.name.clone().into(), + ); + map.insert( + dict.get("access").unwrap().clone().into(), match f.access { FnAccess::Public => dict.get("public").unwrap().clone(), FnAccess::Private => dict.get("private").unwrap().clone(), @@ -57,11 +60,11 @@ fn collect_fn_metadata(ctx: NativeCallContext) -> crate::Array { .into(), ); map.insert( - dict.get("is_anonymous").unwrap().clone(), + dict.get("is_anonymous").unwrap().clone().into(), f.name.starts_with(crate::engine::FN_ANONYMOUS).into(), ); map.insert( - dict.get("params").unwrap().clone(), + dict.get("params").unwrap().clone().into(), f.params .iter() .cloned() @@ -74,7 +77,7 @@ fn collect_fn_metadata(ctx: NativeCallContext) -> crate::Array { } // Intern strings - let dict: BTreeSet = [ + let dict: BTreeSet = [ "namespace", "name", "access", @@ -98,8 +101,8 @@ fn collect_fn_metadata(ctx: NativeCallContext) -> crate::Array { // Recursively scan modules for script-defined functions. fn scan_module( list: &mut Array, - dict: &BTreeSet, - namespace: ImmutableString, + dict: &BTreeSet, + namespace: Identifier, module: &Module, ) { module.iter_script_fn().for_each(|(_, _, _, _, f)| { diff --git a/src/parser.rs b/src/parser.rs index a662aaee..daa02cfb 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -24,8 +24,8 @@ use crate::syntax::{CustomSyntax, MARKER_BLOCK, MARKER_EXPR, MARKER_IDENT}; use crate::token::{is_keyword_function, is_valid_identifier, Token, TokenStream}; use crate::utils::{get_hasher, StringInterner}; use crate::{ - calc_fn_hash, Dynamic, Engine, ImmutableString, LexError, ParseError, ParseErrorType, Position, - Scope, Shared, StaticVec, AST, + calc_fn_hash, Dynamic, Engine, Identifier, ImmutableString, LexError, ParseError, + ParseErrorType, Position, Scope, Shared, StaticVec, AST, }; #[cfg(not(feature = "no_float"))] @@ -46,12 +46,12 @@ struct ParseState<'e> { /// Interned strings. interned_strings: StringInterner, /// Encapsulates a local stack with variable names to simulate an actual runtime scope. - stack: Vec<(ImmutableString, AccessMode)>, + stack: Vec<(Identifier, AccessMode)>, /// Size of the local variables stack upon entry of the current block scope. entry_stack_len: usize, /// Tracks a list of external variables (variables that are not explicitly declared in the scope). #[cfg(not(feature = "no_closure"))] - external_vars: BTreeMap, + external_vars: BTreeMap, /// An indicator that disables variable capturing into externals one single time /// up until the nearest consumed Identifier token. /// If set to false the next call to `access_var` will not capture the variable. @@ -60,7 +60,7 @@ struct ParseState<'e> { allow_capture: bool, /// Encapsulates a local stack with imported [module][crate::Module] names. #[cfg(not(feature = "no_module"))] - modules: StaticVec, + modules: StaticVec, /// Maximum levels of expression nesting. #[cfg(not(feature = "unchecked"))] max_expr_depth: Option, @@ -166,10 +166,7 @@ impl<'e> ParseState<'e> { /// Get an interned string, creating one if it is not yet interned. #[inline(always)] - pub fn get_interned_string( - &mut self, - text: impl AsRef + Into, - ) -> ImmutableString { + pub fn get_interned_string(&mut self, text: impl AsRef) -> Identifier { self.interned_strings.get(text) } } @@ -310,7 +307,7 @@ fn parse_fn_call( input: &mut TokenStream, state: &mut ParseState, lib: &mut FunctionsLib, - id: ImmutableString, + id: Identifier, capture: bool, mut namespace: Option, settings: ParseSettings, @@ -668,6 +665,8 @@ fn parse_array_literal( }; } + arr.shrink_to_fit(); + Ok(Expr::Array(Box::new(arr), settings.pos)) } @@ -707,7 +706,7 @@ fn parse_map_literal( let (name, pos) = match input.next().unwrap() { (Token::Identifier(s), pos) | (Token::StringConstant(s), pos) => { - if map.iter().any(|(p, _)| p.name == &s) { + if map.iter().any(|(p, _)| p.name == s) { return Err(PERR::DuplicatedProperty(s).into_err(pos)); } (s, pos) @@ -757,7 +756,7 @@ fn parse_map_literal( let expr = parse_expr(input, state, lib, settings.level_up())?; let name = state.get_interned_string(name); - template.insert(name.clone(), Default::default()); + template.insert(name.clone().into(), Default::default()); map.push((Ident { name, pos }, expr)); match input.peek().unwrap() { @@ -782,6 +781,8 @@ fn parse_map_literal( } } + map.shrink_to_fit(); + Ok(Expr::Map(Box::new((map, template)), settings.pos)) } @@ -935,7 +936,7 @@ fn parse_primary( Token::IntegerConstant(x) => Expr::IntegerConstant(x, settings.pos), Token::CharConstant(c) => Expr::CharConstant(c, settings.pos), Token::StringConstant(s) => { - Expr::StringConstant(state.get_interned_string(s), settings.pos) + Expr::StringConstant(state.get_interned_string(s).into(), settings.pos) } Token::True => Expr::BoolConstant(true, settings.pos), Token::False => Expr::BoolConstant(false, settings.pos), @@ -1830,7 +1831,7 @@ fn parse_custom_syntax( MARKER_IDENT => match input.next().unwrap() { (Token::Identifier(s), pos) => { let name = state.get_interned_string(s); - segments.push(name.clone()); + segments.push(name.clone().into()); tokens.push(state.get_interned_string(MARKER_IDENT)); let var_name_def = Ident { name, pos }; keywords.push(Expr::Variable(Box::new((None, None, var_name_def)))); @@ -1843,14 +1844,14 @@ fn parse_custom_syntax( MARKER_EXPR => { keywords.push(parse_expr(input, state, lib, settings)?); let keyword = state.get_interned_string(MARKER_EXPR); - segments.push(keyword.clone()); + segments.push(keyword.clone().into()); tokens.push(keyword); } MARKER_BLOCK => match parse_block(input, state, lib, settings)? { block @ Stmt::Block(_, _) => { keywords.push(Expr::Stmt(Box::new(block.into()))); let keyword = state.get_interned_string(MARKER_BLOCK); - segments.push(keyword.clone()); + segments.push(keyword.clone().into()); tokens.push(keyword); } stmt => unreachable!("expecting Stmt::Block, but gets {:?}", stmt), @@ -1859,7 +1860,7 @@ fn parse_custom_syntax( (Token::LexError(err), pos) => return Err(err.into_err(pos)), (t, _) if t.syntax().as_ref() == s => { segments.push(required_token.clone()); - tokens.push(required_token.clone()); + tokens.push(required_token.clone().into()); } (_, pos) => { return Err(PERR::MissingToken( @@ -1901,7 +1902,7 @@ fn parse_expr( match token { Token::Custom(key) | Token::Reserved(key) | Token::Identifier(key) => { - match state.engine.custom_syntax.get_key_value(key) { + match state.engine.custom_syntax.get_key_value(key.as_str()) { Some((key, syntax)) => { input.next().unwrap(); return parse_custom_syntax( @@ -2119,16 +2120,20 @@ fn parse_for( ensure_not_statement_expr(input, "a boolean")?; let expr = parse_expr(input, state, lib, settings.level_up())?; - let loop_var = state.get_interned_string(name.clone()); + let loop_var = state.get_interned_string(name); let prev_stack_len = state.stack.len(); - state.stack.push((loop_var, AccessMode::ReadWrite)); + state.stack.push((loop_var.clone(), AccessMode::ReadWrite)); settings.is_breakable = true; let body = parse_block(input, state, lib, settings.level_up())?; state.stack.truncate(prev_stack_len); - Ok(Stmt::For(expr, Box::new((name, body.into())), settings.pos)) + Ok(Stmt::For( + expr, + Box::new((loop_var, body.into())), + settings.pos, + )) } /// Parse a variable definition statement. @@ -2174,9 +2179,9 @@ fn parse_let( match var_type { // let name = expr - AccessMode::ReadWrite => Ok(Stmt::Let(expr, var_def, export, settings.pos)), + AccessMode::ReadWrite => Ok(Stmt::Let(expr, var_def.into(), export, settings.pos)), // const name = { expr:constant } - AccessMode::ReadOnly => Ok(Stmt::Const(expr, var_def, export, settings.pos)), + AccessMode::ReadOnly => Ok(Stmt::Const(expr, var_def.into(), export, settings.pos)), } } @@ -2217,10 +2222,13 @@ fn parse_import( Ok(Stmt::Import( expr, - Some(Ident { - name, - pos: name_pos, - }), + Some( + Ident { + name, + pos: name_pos, + } + .into(), + ), settings.pos, )) } @@ -2511,7 +2519,7 @@ fn parse_stmt( if lib.contains_key(&hash) { return Err(PERR::FnDuplicatedDefinition( - func.name.into_owned(), + func.name.to_string(), func.params.len(), ) .into_err(pos)); @@ -2904,7 +2912,7 @@ fn parse_anon_fn( comments: Default::default(), }; - let expr = Expr::FnPointer(fn_name, settings.pos); + let expr = Expr::FnPointer(fn_name.into(), settings.pos); #[cfg(not(feature = "no_closure"))] let expr = make_curry_from_externals(state, expr, externals, settings.pos); diff --git a/src/scope.rs b/src/scope.rs index ac19bf2c..7d4b114b 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -2,7 +2,7 @@ use crate::dynamic::{AccessMode, Variant}; use crate::stdlib::{borrow::Cow, boxed::Box, iter, vec::Vec}; -use crate::{Dynamic, ImmutableString, StaticVec}; +use crate::{Dynamic, Identifier, StaticVec}; /// Keep a number of entries inline (since [`Dynamic`] is usually small enough). const SCOPE_SIZE: usize = 16; @@ -54,7 +54,7 @@ pub struct Scope<'a> { /// Current value of the entry. values: smallvec::SmallVec<[Dynamic; SCOPE_SIZE]>, /// (Name, aliases) of the entry. - names: Vec<(Cow<'a, str>, Option>>)>, + names: Vec<(Cow<'a, str>, Option>>)>, } impl Default for Scope<'_> { @@ -414,7 +414,7 @@ impl<'a> Scope<'a> { pub(crate) fn add_entry_alias( &mut self, index: usize, - alias: impl Into + PartialEq, + alias: impl Into + PartialEq, ) -> &mut Self { let entry = self.names.get_mut(index).expect("invalid index in Scope"); if entry.1.is_none() { @@ -449,7 +449,7 @@ impl<'a> Scope<'a> { #[allow(dead_code)] pub(crate) fn into_iter( self, - ) -> impl Iterator, Dynamic, Vec)> { + ) -> impl Iterator, Dynamic, Vec)> { self.names .into_iter() .zip(self.values.into_iter()) diff --git a/src/syntax.rs b/src/syntax.rs index 2e39554b..3eea6244 100644 --- a/src/syntax.rs +++ b/src/syntax.rs @@ -6,7 +6,8 @@ use crate::fn_native::SendSync; use crate::stdlib::{boxed::Box, format, string::ToString}; use crate::token::{is_valid_identifier, Token}; use crate::{ - Engine, ImmutableString, LexError, ParseError, Position, RhaiResult, Shared, StaticVec, + Engine, Identifier, ImmutableString, LexError, ParseError, Position, RhaiResult, Shared, + StaticVec, }; pub const MARKER_EXPR: &str = "$expr$"; @@ -103,7 +104,7 @@ impl Engine { /// /// If `new_vars` is negative, then it is expected that only the top `new_vars` variables in the /// current [`Scope`][crate::Scope] will be _popped_. Do not randomly remove variables. - pub fn register_custom_syntax + Into>( + pub fn register_custom_syntax + Into>( &mut self, keywords: &[S], new_vars: isize, @@ -221,7 +222,7 @@ impl Engine { /// Otherwise, custom keywords won't be recognized. pub fn register_custom_syntax_raw( &mut self, - key: impl Into, + key: impl Into, parse: impl Fn(&[ImmutableString], &str) -> Result, ParseError> + SendSync + 'static, diff --git a/src/utils.rs b/src/utils.rs index 60fa7e0f..380a1f8b 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -15,7 +15,7 @@ use crate::stdlib::{ str::FromStr, string::{String, ToString}, }; -use crate::Shared; +use crate::{Identifier, Shared}; /// A hasher that only takes one single [`u64`] and returns it as a hash key. /// @@ -590,6 +590,20 @@ impl PartialOrd for String { } } +impl From for Identifier { + #[inline(always)] + fn from(value: ImmutableString) -> Self { + value.into_owned().into() + } +} + +impl From for ImmutableString { + #[inline(always)] + fn from(value: Identifier) -> Self { + value.to_string().into() + } +} + impl ImmutableString { /// Consume the [`ImmutableString`] and convert it into a [`String`]. /// If there are other references to the same string, a cloned copy is returned. @@ -608,16 +622,12 @@ impl ImmutableString { /// A collection of interned strings. #[derive(Debug, Clone, Default, Hash)] -pub struct StringInterner(BTreeSet); +pub struct StringInterner(BTreeSet); impl StringInterner { /// Get an interned string, creating one if it is not yet interned. #[inline(always)] - pub fn get(&mut self, text: impl AsRef + Into) -> ImmutableString { - self.0.get(text.as_ref()).cloned().unwrap_or_else(|| { - let s = text.into(); - self.0.insert(s.clone()); - s - }) + pub fn get(&mut self, text: impl AsRef) -> Identifier { + text.as_ref().into() } }