diff --git a/src/parser.rs b/src/parser.rs index d9ee3996..2bf5d3b9 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -43,22 +43,17 @@ const NEVER_ENDS: &str = "`Token`"; /// Unroll `switch` ranges no larger than this. const SMALL_SWITCH_RANGE: usize = 16; -#[derive(Debug, Default, Clone)] -pub struct InternedStrings<'e> { - pub main: StringsInterner<'e>, - #[cfg(not(feature = "no_object"))] - pub getters: StringsInterner<'e>, - #[cfg(not(feature = "no_object"))] - pub setters: StringsInterner<'e>, -} +const NUM_INTERNERS: usize = if cfg!(feature = "no_object") { 1 } else { 3 }; /// _(internals)_ A type that encapsulates the current state of the parser. /// Exported under the `internals` feature only. pub struct ParseState<'e> { /// 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: InternedStrings<'e>, + interned_strings: [StringsInterner<'e>; NUM_INTERNERS], /// External [scope][Scope] with constants. pub scope: &'e Scope<'e>, /// Global runtime state. @@ -67,8 +62,6 @@ pub struct ParseState<'e> { pub stack: Scope<'e>, /// Size of the local variables stack upon entry of the current block scope. pub block_stack_len: usize, - /// Controls whether parsing of an expression should stop given the next token. - pub expr_filter: fn(&Token) -> bool, /// Tracks a list of external variables (variables that are not explicitly declared in the scope). #[cfg(not(feature = "no_closure"))] pub external_vars: Vec, @@ -118,7 +111,7 @@ impl<'e> ParseState<'e> { pub fn new( engine: &Engine, scope: &'e Scope, - interners: InternedStrings<'e>, + interned_strings: [StringsInterner<'e>; NUM_INTERNERS], tokenizer_control: TokenizerControl, ) -> Self { Self { @@ -128,7 +121,7 @@ impl<'e> ParseState<'e> { external_vars: Vec::new(), #[cfg(not(feature = "no_closure"))] allow_capture: true, - interned_strings: interners, + interned_strings, scope, global: GlobalRuntimeState::new(engine), stack: Scope::new(), @@ -250,40 +243,35 @@ impl<'e> ParseState<'e> { /// Get an interned string, creating one if it is not yet interned. #[inline(always)] - #[allow(dead_code)] #[must_use] pub fn get_interned_string( &mut self, text: impl AsRef + Into, ) -> ImmutableString { - self.interned_strings.main.get(text) + self.interned_strings[0].get(text) } /// Get an interned property getter, creating one if it is not yet interned. #[cfg(not(feature = "no_object"))] #[inline(always)] - #[allow(dead_code)] #[must_use] pub fn get_interned_getter( &mut self, text: impl AsRef + Into, ) -> ImmutableString { - self.interned_strings - .getters + self.interned_strings[1] .get_with_mapper(|s| crate::engine::make_getter(s.as_ref()).into(), text) } /// Get an interned property setter, creating one if it is not yet interned. #[cfg(not(feature = "no_object"))] #[inline(always)] - #[allow(dead_code)] #[must_use] pub fn get_interned_setter( &mut self, text: impl AsRef + Into, ) -> ImmutableString { - self.interned_strings - .setters + self.interned_strings[2] .get_with_mapper(|s| crate::engine::make_setter(s.as_ref()).into(), text) } } @@ -1384,12 +1372,13 @@ impl Engine { // | ... #[cfg(not(feature = "no_function"))] Token::Pipe | Token::Or if settings.options.contains(LangOptions::ANON_FN) => { - let interners = std::mem::take(&mut state.interned_strings); + // Build new parse state + let interned_strings = std::mem::take(&mut state.interned_strings); let mut new_state = ParseState::new( self, state.scope, - interners, + interned_strings, state.tokenizer_control.clone(), ); @@ -1435,6 +1424,7 @@ impl Engine { let result = self.parse_anon_fn(input, &mut new_state, lib, new_settings); + // Restore parse state state.interned_strings = new_state.interned_strings; let (expr, func) = result?; @@ -3259,12 +3249,13 @@ impl Engine { match input.next().expect(NEVER_ENDS) { (Token::Fn, pos) => { - let interners = std::mem::take(&mut state.interned_strings); + // Build new parse state + let interned_strings = std::mem::take(&mut state.interned_strings); let mut new_state = ParseState::new( self, state.scope, - interners, + interned_strings, state.tokenizer_control.clone(), ); @@ -3314,6 +3305,7 @@ impl Engine { comments, ); + // Restore parse state state.interned_strings = new_state.interned_strings; let func = func?; diff --git a/src/types/interner.rs b/src/types/interner.rs index 548f469a..da93de3c 100644 --- a/src/types/interner.rs +++ b/src/types/interner.rs @@ -5,6 +5,7 @@ use crate::ImmutableString; use std::prelude::v1::*; use std::{ collections::BTreeMap, + fmt, hash::{Hash, Hasher}, marker::PhantomData, ops::AddAssign, @@ -20,10 +21,12 @@ pub const MAX_STRING_LEN: usize = 24; /// Exported under the `internals` feature only. /// /// Normal identifiers, property getters and setters are interned separately. -#[derive(Debug, Clone, Hash)] +#[derive(Clone, Hash)] pub struct StringsInterner<'a> { - /// Maximum capacity. - max: usize, + /// Maximum number of strings interned. + pub capacity: usize, + /// Maximum string length. + pub max_string_len: usize, /// Normal strings. strings: BTreeMap, /// Take care of the lifetime parameter. @@ -37,24 +40,26 @@ impl Default for StringsInterner<'_> { } } +impl fmt::Debug for StringsInterner<'_> { + #[inline] + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_list().entries(self.strings.values()).finish() + } +} + impl StringsInterner<'_> { /// Create a new [`StringsInterner`]. #[inline(always)] #[must_use] pub fn new() -> Self { - Self::new_with_capacity(MAX_INTERNED_STRINGS) - } - - /// Create a new [`StringsInterner`] with maximum capacity. - #[inline] - #[must_use] - pub fn new_with_capacity(capacity: usize) -> Self { Self { - max: capacity, + capacity: MAX_INTERNED_STRINGS, + max_string_len: MAX_STRING_LEN, strings: BTreeMap::new(), dummy: PhantomData, } } + /// Get an identifier from a text string, adding it to the interner if necessary. #[inline(always)] #[must_use] @@ -72,12 +77,9 @@ impl StringsInterner<'_> { ) -> ImmutableString { let key = text.as_ref(); - // Do not intern numbers - if key.bytes().all(|c| c == b'.' || (c >= b'0' && c <= b'9')) { - return text.into(); - } - - if key.len() > MAX_STRING_LEN { + // Do not intern long strings or numbers + if key.len() > MAX_STRING_LEN || key.bytes().all(|c| c == b'.' || (c >= b'0' && c <= b'9')) + { return mapper(text); } @@ -98,11 +100,15 @@ impl StringsInterner<'_> { self.strings.insert(key, value.clone()); // If the interner is over capacity, remove the longest entry - if self.strings.len() > self.max { + if self.strings.len() > self.capacity { // Leave some buffer to grow when shrinking the cache. // We leave at least two entries, one for the empty string, and one for the string // that has just been inserted. - let max = if self.max < 5 { 2 } else { self.max - 3 }; + let max = if self.capacity < 5 { + 2 + } else { + self.capacity - 3 + }; while self.strings.len() > max { let (_, n) = self.strings.iter().fold((0, 0), |(x, n), (&k, v)| { @@ -127,7 +133,7 @@ impl StringsInterner<'_> { self.strings.len() } - /// Number of strings interned. + /// Are there no interned strings? #[inline(always)] #[must_use] pub fn is_empty(&self) -> bool { @@ -135,7 +141,7 @@ impl StringsInterner<'_> { } /// Clear all interned strings. - #[inline] + #[inline(always)] pub fn clear(&mut self) { self.strings.clear(); }