diff --git a/src/api/compile.rs b/src/api/compile.rs index e031820b..0eab7376 100644 --- a/src/api/compile.rs +++ b/src/api/compile.rs @@ -1,7 +1,6 @@ //! Module that defines the public compilation API of [`Engine`]. use crate::parser::{ParseResult, ParseState}; -use crate::types::StringsInterner; use crate::{Engine, OptimizationLevel, Scope, AST}; #[cfg(feature = "no_std")] use std::prelude::v1::*; @@ -222,7 +221,8 @@ impl Engine { scripts.as_ref(), self.token_mapper.as_ref().map(<_>::as_ref), ); - let mut state = ParseState::new(self, scope, StringsInterner::default(), tokenizer_control); + let interned_strings = &mut *self.interned_strings.borrow_mut(); + let mut state = ParseState::new(self, scope, interned_strings, tokenizer_control); let mut _ast = self.parse(&mut stream.peekable(), &mut state, optimization_level)?; #[cfg(feature = "metadata")] _ast.set_doc(state.tokenizer_control.borrow().global_comments.join("\n")); @@ -295,7 +295,8 @@ impl Engine { self.lex_raw(&scripts, self.token_mapper.as_ref().map(<_>::as_ref)); let mut peekable = stream.peekable(); - let mut state = ParseState::new(self, scope, StringsInterner::default(), tokenizer_control); + let interned_strings = &mut *self.interned_strings.borrow_mut(); + let mut state = ParseState::new(self, scope, interned_strings, tokenizer_control); self.parse_global_expr(&mut peekable, &mut state, |_| {}, self.optimization_level) } } diff --git a/src/api/eval.rs b/src/api/eval.rs index fd248f58..10bb3138 100644 --- a/src/api/eval.rs +++ b/src/api/eval.rs @@ -2,7 +2,7 @@ use crate::eval::{Caches, GlobalRuntimeState}; use crate::parser::ParseState; -use crate::types::{dynamic::Variant, StringsInterner}; +use crate::types::dynamic::Variant; use crate::{ reify, Dynamic, Engine, OptimizationLevel, Position, RhaiResult, RhaiResultOf, Scope, AST, ERR, }; @@ -117,20 +117,25 @@ impl Engine { script: &str, ) -> RhaiResultOf { let scripts = [script]; - let (stream, tokenizer_control) = - self.lex_raw(&scripts, self.token_mapper.as_ref().map(<_>::as_ref)); - let mut state = ParseState::new(self, scope, StringsInterner::default(), tokenizer_control); + let ast = { + let interned_strings = &mut *self.interned_strings.borrow_mut(); - // No need to optimize a lone expression - let ast = self.parse_global_expr( - &mut stream.peekable(), - &mut state, - |_| {}, - #[cfg(not(feature = "no_optimize"))] - OptimizationLevel::None, - #[cfg(feature = "no_optimize")] - OptimizationLevel::default(), - )?; + let (stream, tokenizer_control) = + self.lex_raw(&scripts, self.token_mapper.as_ref().map(<_>::as_ref)); + + let mut state = ParseState::new(self, scope, interned_strings, tokenizer_control); + + // No need to optimize a lone expression + self.parse_global_expr( + &mut stream.peekable(), + &mut state, + |_| {}, + #[cfg(not(feature = "no_optimize"))] + OptimizationLevel::None, + #[cfg(feature = "no_optimize")] + OptimizationLevel::default(), + )? + }; self.eval_ast_with_scope(scope, &ast) } diff --git a/src/api/json.rs b/src/api/json.rs index 036db0b3..cc0792ff 100644 --- a/src/api/json.rs +++ b/src/api/json.rs @@ -3,7 +3,6 @@ use crate::parser::{ParseSettingFlags, ParseState}; use crate::tokenizer::Token; -use crate::types::StringsInterner; use crate::{Engine, LexError, Map, OptimizationLevel, RhaiResultOf, Scope}; #[cfg(feature = "no_std")] use std::prelude::v1::*; @@ -117,19 +116,21 @@ impl Engine { }, ); - let scope = Scope::new(); - let mut state = - ParseState::new(self, &scope, StringsInterner::default(), tokenizer_control); + let ast = { + let scope = Scope::new(); + let interned_strings = &mut *self.interned_strings.borrow_mut(); + let mut state = ParseState::new(self, &scope, interned_strings, tokenizer_control); - let ast = self.parse_global_expr( - &mut stream.peekable(), - &mut state, - |s| s.flags |= ParseSettingFlags::DISALLOW_UNQUOTED_MAP_PROPERTIES, - #[cfg(not(feature = "no_optimize"))] - OptimizationLevel::None, - #[cfg(feature = "no_optimize")] - OptimizationLevel::default(), - )?; + self.parse_global_expr( + &mut stream.peekable(), + &mut state, + |s| s.flags |= ParseSettingFlags::DISALLOW_UNQUOTED_MAP_PROPERTIES, + #[cfg(not(feature = "no_optimize"))] + OptimizationLevel::None, + #[cfg(feature = "no_optimize")] + OptimizationLevel::default(), + )? + }; self.eval_ast(&ast) } diff --git a/src/api/run.rs b/src/api/run.rs index 0bc418df..ca744900 100644 --- a/src/api/run.rs +++ b/src/api/run.rs @@ -2,7 +2,6 @@ use crate::eval::{Caches, GlobalRuntimeState}; use crate::parser::ParseState; -use crate::types::StringsInterner; use crate::{Engine, RhaiResultOf, Scope, AST}; #[cfg(feature = "no_std")] use std::prelude::v1::*; @@ -57,10 +56,16 @@ impl Engine { #[inline] pub fn run_with_scope(&self, scope: &mut Scope, script: &str) -> RhaiResultOf<()> { let scripts = [script]; - let (stream, tokenizer_control) = - self.lex_raw(&scripts, self.token_mapper.as_ref().map(<_>::as_ref)); - let mut state = ParseState::new(self, scope, StringsInterner::default(), tokenizer_control); - let ast = self.parse(&mut stream.peekable(), &mut state, self.optimization_level)?; + let ast = { + let interned_strings = &mut *self.interned_strings.borrow_mut(); + + let (stream, tokenizer_control) = + self.lex_raw(&scripts, self.token_mapper.as_ref().map(<_>::as_ref)); + + let mut state = ParseState::new(self, scope, interned_strings, tokenizer_control); + + self.parse(&mut stream.peekable(), &mut state, self.optimization_level)? + }; self.run_ast_with_scope(scope, &ast) } /// Evaluate an [`AST`]. diff --git a/src/engine.rs b/src/engine.rs index f888807b..03761b90 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -103,7 +103,7 @@ pub struct Engine { pub(crate) module_resolver: Box, /// An empty [`ImmutableString`] for cloning purposes. - pub(crate) interned_strings: Locked>, + pub(crate) interned_strings: Locked, /// A set of symbols to disable. pub(crate) disabled_symbols: BTreeSet, diff --git a/src/packages/lang_core.rs b/src/packages/lang_core.rs index 6f53a98d..40d1f1b2 100644 --- a/src/packages/lang_core.rs +++ b/src/packages/lang_core.rs @@ -6,6 +6,9 @@ use crate::{Dynamic, RhaiResultOf, ERR, INT}; #[cfg(feature = "no_std")] use std::prelude::v1::*; +#[cfg(not(feature = "no_float"))] +use crate::FLOAT; + def_package! { /// Package of core language features. pub LanguageCorePackage(lib) { @@ -76,10 +79,17 @@ mod core_functions { } /// Block the current thread for a particular number of `seconds`. + /// + /// # Example + /// + /// ```rhai + /// // Do nothing for 10 seconds! + /// sleep(10.0); + /// ``` #[cfg(not(feature = "no_float"))] #[cfg(not(feature = "no_std"))] #[rhai_fn(name = "sleep")] - pub fn sleep_float(seconds: crate::FLOAT) { + pub fn sleep_float(seconds: FLOAT) { if seconds <= 0.0 { return; } @@ -90,6 +100,13 @@ mod core_functions { std::thread::sleep(std::time::Duration::from_secs_f32(seconds)); } /// Block the current thread for a particular number of `seconds`. + /// + /// # Example + /// + /// ```rhai + /// // Do nothing for 10 seconds! + /// sleep(10); + /// ``` #[cfg(not(feature = "no_std"))] pub fn sleep(seconds: INT) { if seconds <= 0 { @@ -121,17 +138,24 @@ mod core_functions { #[cfg(not(feature = "no_object"))] #[export_module] mod reflection_functions { - pub fn get_fn_metadata_list(ctx: NativeCallContext) -> crate::Array { + use crate::Array; + + /// Return an array of object maps containing metadata of all script-defined functions. + pub fn get_fn_metadata_list(ctx: NativeCallContext) -> Array { collect_fn_metadata(ctx, |_, _, _, _, _| true) } + /// Return an array of object maps containing metadata of all script-defined functions + /// matching the specified name. #[rhai_fn(name = "get_fn_metadata_list")] - pub fn get_fn_metadata(ctx: NativeCallContext, name: &str) -> crate::Array { + pub fn get_fn_metadata(ctx: NativeCallContext, name: &str) -> Array { collect_fn_metadata(ctx, |_, _, n, _, _| n == name) } + /// Return an array of object maps containing metadata of all script-defined functions + /// matching the specified name and arity (number of parameters). #[rhai_fn(name = "get_fn_metadata_list")] - pub fn get_fn_metadata2(ctx: NativeCallContext, name: &str, params: INT) -> crate::Array { + pub fn get_fn_metadata2(ctx: NativeCallContext, name: &str, params: INT) -> Array { if params < 0 || params > crate::MAX_USIZE_INT { - crate::Array::new() + Array::new() } else { collect_fn_metadata(ctx, |_, _, n, p, _| p == (params as usize) && n == name) } @@ -146,38 +170,47 @@ fn collect_fn_metadata( filter: impl Fn(FnNamespace, FnAccess, &str, usize, &crate::Shared) -> bool + Copy, ) -> crate::Array { - use crate::{ast::ScriptFnDef, Array, Map}; + #[cfg(not(feature = "no_module"))] + use crate::Identifier; + use crate::{ast::ScriptFnDef, engine::FN_ANONYMOUS, Array, Map}; // Create a metadata record for a function. fn make_metadata( - dict: &mut crate::types::StringsInterner, - #[cfg(not(feature = "no_module"))] namespace: crate::Identifier, + engine: &Engine, + #[cfg(not(feature = "no_module"))] namespace: Identifier, func: &ScriptFnDef, ) -> Map { let mut map = Map::new(); #[cfg(not(feature = "no_module"))] if !namespace.is_empty() { - map.insert("namespace".into(), dict.get(namespace).into()); + map.insert( + "namespace".into(), + engine.get_interned_string(namespace).into(), + ); } - map.insert("name".into(), dict.get(func.name.as_str()).into()); + map.insert( + "name".into(), + engine.get_interned_string(func.name.clone()).into(), + ); map.insert( "access".into(), - dict.get(match func.access { - FnAccess::Public => "public", - FnAccess::Private => "private", - }) - .into(), + engine + .get_interned_string(match func.access { + FnAccess::Public => "public", + FnAccess::Private => "private", + }) + .into(), ); map.insert( "is_anonymous".into(), - func.name.starts_with(crate::engine::FN_ANONYMOUS).into(), + func.name.starts_with(FN_ANONYMOUS).into(), ); map.insert( "params".into(), func.params .iter() - .map(|p| dict.get(p.as_str()).into()) + .map(|p| engine.get_interned_string(p.clone()).into()) .collect::() .into(), ); @@ -187,7 +220,7 @@ fn collect_fn_metadata( "comments".into(), func.comments .iter() - .map(|s| dict.get(s.as_ref()).into()) + .map(|s| engine.get_interned_string(s.as_ref()).into()) .collect::() .into(), ); @@ -196,7 +229,7 @@ fn collect_fn_metadata( map } - let dict = &mut crate::types::StringsInterner::new(); + let engine = ctx.engine(); let mut list = Array::new(); ctx.iter_namespaces() @@ -205,9 +238,9 @@ fn collect_fn_metadata( .for_each(|(.., f)| { list.push( make_metadata( - dict, + engine, #[cfg(not(feature = "no_module"))] - crate::Identifier::new_const(), + Identifier::new_const(), f, ) .into(), @@ -222,9 +255,9 @@ fn collect_fn_metadata( .for_each(|(.., f)| { list.push( make_metadata( - dict, + engine, #[cfg(not(feature = "no_module"))] - crate::Identifier::new_const(), + Identifier::new_const(), f, ) .into(), @@ -240,9 +273,9 @@ fn collect_fn_metadata( .for_each(|(.., f)| { list.push( make_metadata( - dict, + engine, #[cfg(not(feature = "no_module"))] - crate::Identifier::new_const(), + Identifier::new_const(), f, ) .into(), @@ -251,41 +284,31 @@ fn collect_fn_metadata( #[cfg(not(feature = "no_module"))] { + use crate::{tokenizer::Token::DoubleColon, Shared, SmartString}; + // Recursively scan modules for script-defined functions. fn scan_module( - dict: &mut crate::types::StringsInterner, + engine: &Engine, list: &mut Array, namespace: &str, module: &Module, - filter: impl Fn( - FnNamespace, - FnAccess, - &str, - usize, - &crate::Shared, - ) -> bool - + Copy, + filter: impl Fn(FnNamespace, FnAccess, &str, usize, &Shared) -> bool + Copy, ) { module .iter_script_fn() .filter(|(s, a, n, p, f)| filter(*s, *a, n, *p, f)) - .for_each(|(.., f)| list.push(make_metadata(dict, namespace.into(), f).into())); + .for_each(|(.., f)| list.push(make_metadata(engine, namespace.into(), f).into())); for (name, m) in module.iter_sub_modules() { use std::fmt::Write; - let mut ns = crate::SmartString::new_const(); - write!( - &mut ns, - "{namespace}{}{name}", - crate::tokenizer::Token::DoubleColon.literal_syntax() - ) - .unwrap(); - scan_module(dict, list, &ns, &**m, filter); + let mut ns = SmartString::new_const(); + write!(&mut ns, "{namespace}{}{name}", DoubleColon.literal_syntax()).unwrap(); + scan_module(engine, list, &ns, &**m, filter); } } for (ns, m) in ctx.iter_imports_raw() { - scan_module(dict, &mut list, ns, &**m, filter); + scan_module(engine, &mut list, ns, &**m, filter); } } diff --git a/src/parser.rs b/src/parser.rs index 2ed24cd9..9eeba45e 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -46,13 +46,13 @@ const SMALL_SWITCH_RANGE: INT = 16; /// _(internals)_ A type that encapsulates the current state of the parser. /// Exported under the `internals` feature only. -pub struct ParseState<'e> { +pub struct ParseState<'e, 's> { /// Input stream buffer containing the next character to read. pub tokenizer_control: TokenizerControl, /// Controls whether parsing of an expression should stop given the next token. pub expr_filter: fn(&Token) -> bool, /// String interners. - interned_strings: StringsInterner<'e>, + interned_strings: &'s mut StringsInterner, /// External [scope][Scope] with constants. pub scope: &'e Scope<'e>, /// Global runtime state. @@ -81,7 +81,7 @@ pub struct ParseState<'e> { pub max_expr_depth: usize, } -impl fmt::Debug for ParseState<'_> { +impl fmt::Debug for ParseState<'_, '_> { #[cold] #[inline(never)] fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { @@ -105,14 +105,14 @@ impl fmt::Debug for ParseState<'_> { } } -impl<'e> ParseState<'e> { +impl<'e, 's> ParseState<'e, 's> { /// Create a new [`ParseState`]. #[inline] #[must_use] pub fn new( engine: &Engine, scope: &'e Scope, - interned_strings: StringsInterner<'e>, + interned_strings: &'s mut StringsInterner, tokenizer_control: TokenizerControl, ) -> Self { Self { @@ -1422,15 +1422,17 @@ impl Engine { #[cfg(not(feature = "no_function"))] Token::Pipe | Token::Or if settings.has_option(LangOptions::ANON_FN) => { // Build new parse state - let interned_strings = std::mem::take(&mut state.interned_strings); - + let new_interner = &mut StringsInterner::new(); let mut new_state = ParseState::new( self, state.scope, - interned_strings, + new_interner, state.tokenizer_control.clone(), ); + // We move the strings interner to the new parse state object by swapping it... + std::mem::swap(state.interned_strings, new_state.interned_strings); + #[cfg(not(feature = "no_module"))] { // Do not allow storing an index to a globally-imported module @@ -1471,8 +1473,8 @@ impl Engine { let result = self.parse_anon_fn(input, &mut new_state, state, lib, new_settings); - // Restore parse state - state.interned_strings = new_state.interned_strings; + // Restore the strings interner by swapping it back + std::mem::swap(state.interned_strings, new_state.interned_strings); let (expr, func) = result?; @@ -3301,12 +3303,10 @@ impl Engine { match input.next().expect(NEVER_ENDS) { (Token::Fn, pos) => { // Build new parse state - let interned_strings = std::mem::take(&mut state.interned_strings); - let mut new_state = ParseState::new( self, state.scope, - interned_strings, + state.interned_strings, state.tokenizer_control.clone(), ); @@ -3353,8 +3353,6 @@ impl Engine { ); // Restore parse state - state.interned_strings = new_state.interned_strings; - let func = func?; let hash = calc_fn_hash(None, &func.name, func.params.len()); diff --git a/src/types/interner.rs b/src/types/interner.rs index cd090fe9..1127012f 100644 --- a/src/types/interner.rs +++ b/src/types/interner.rs @@ -12,7 +12,6 @@ use std::prelude::v1::*; use std::{ fmt, hash::{Hash, Hasher}, - marker::PhantomData, ops::AddAssign, }; @@ -24,7 +23,9 @@ pub const MAX_STRING_LEN: usize = 24; /// _(internals)_ A cache for interned strings. /// Exported under the `internals` feature only. -pub struct StringsInterner<'a> { +#[derive(Clone)] +#[must_use] +pub struct StringsInterner { /// Maximum number of strings interned. pub capacity: usize, /// Maximum string length. @@ -33,19 +34,16 @@ pub struct StringsInterner<'a> { cache: StraightHashMap, /// Bloom filter to avoid caching "one-hit wonders". filter: BloomFilterU64, - /// Take care of the lifetime parameter. - dummy: PhantomData<&'a ()>, } -impl Default for StringsInterner<'_> { +impl Default for StringsInterner { #[inline(always)] - #[must_use] fn default() -> Self { Self::new() } } -impl fmt::Debug for StringsInterner<'_> { +impl fmt::Debug for StringsInterner { #[cold] #[inline(never)] fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { @@ -53,17 +51,15 @@ impl fmt::Debug for StringsInterner<'_> { } } -impl StringsInterner<'_> { +impl StringsInterner { /// Create a new [`StringsInterner`]. #[inline(always)] - #[must_use] pub fn new() -> Self { Self { capacity: MAX_INTERNED_STRINGS, max_string_len: MAX_STRING_LEN, cache: StraightHashMap::default(), filter: BloomFilterU64::new(), - dummy: PhantomData, } } @@ -169,14 +165,14 @@ impl StringsInterner<'_> { } } -impl AddAssign for StringsInterner<'_> { +impl AddAssign for StringsInterner { #[inline(always)] fn add_assign(&mut self, rhs: Self) { self.cache.extend(rhs.cache.into_iter()); } } -impl AddAssign<&Self> for StringsInterner<'_> { +impl AddAssign<&Self> for StringsInterner { #[inline(always)] fn add_assign(&mut self, rhs: &Self) { self.cache