diff --git a/CHANGELOG.md b/CHANGELOG.md index 1dec812e8..f13b6e198 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ Bug fixes --------- * The position of an undefined operation call now points to the operator instead of the first operand. +* The `optimize` command in `rhai-repl` now works properly and cycles through `None`->`Simple`->`Full`. Deprecated API's ---------------- diff --git a/src/api/compile.rs b/src/api/compile.rs index f52629793..acc6a5411 100644 --- a/src/api/compile.rs +++ b/src/api/compile.rs @@ -1,8 +1,7 @@ //! Module that defines the public compilation API of [`Engine`]. -use crate::func::native::locked_write; use crate::parser::{ParseResult, ParseState}; -use crate::{Engine, OptimizationLevel, Scope, AST}; +use crate::{Engine, Scope, AST}; #[cfg(feature = "no_std")] use std::prelude::v1::*; @@ -201,13 +200,18 @@ impl Engine { scope: &Scope, scripts: impl AsRef<[S]>, ) -> ParseResult { - self.compile_scripts_with_scope_raw(Some(scope), scripts, self.optimization_level) + self.compile_scripts_with_scope_raw( + Some(scope), + scripts, + #[cfg(not(feature = "no_optimize"))] + self.optimization_level, + ) } /// Join a list of strings and compile into an [`AST`] using own scope at a specific optimization level. /// /// ## Constants Propagation /// - /// If not [`OptimizationLevel::None`], constants defined within the scope are propagated + /// If not [`OptimizationLevel::None`][`crate::OptimizationLevel::None`], constants defined within the scope are propagated /// throughout the script _including_ functions. This allows functions to be optimized based on /// dynamic global constants. #[inline] @@ -215,23 +219,21 @@ impl Engine { &self, scope: Option<&Scope>, scripts: impl AsRef<[S]>, - optimization_level: OptimizationLevel, + #[cfg(not(feature = "no_optimize"))] optimization_level: crate::OptimizationLevel, ) -> ParseResult { let (stream, tc) = self.lex(scripts.as_ref()); - let guard = &mut self - .interned_strings - .as_ref() - .and_then(|interner| locked_write(interner)); - let interned_strings = guard.as_deref_mut(); - let input = &mut stream.peekable(); let lib = &mut <_>::default(); - let state = &mut ParseState::new(scope, interned_strings, input, tc, lib); - let mut _ast = self.parse(state, optimization_level)?; + let state = ParseState::new(scope, input, tc.clone(), lib); + let mut _ast = self.parse( + state, + #[cfg(not(feature = "no_optimize"))] + optimization_level, + )?; #[cfg(feature = "metadata")] { - let global_comments = &state.tokenizer_control.borrow().global_comments; + let global_comments = &tc.borrow().global_comments; _ast.doc = global_comments.into(); } Ok(_ast) @@ -299,16 +301,15 @@ impl Engine { let scripts = [script]; let (stream, t) = self.lex(&scripts); - let guard = &mut self - .interned_strings - .as_ref() - .and_then(|interner| locked_write(interner)); - let interned_strings = guard.as_deref_mut(); - let input = &mut stream.peekable(); let lib = &mut <_>::default(); - let state = &mut ParseState::new(Some(scope), interned_strings, input, t, lib); + let state = ParseState::new(Some(scope), input, t, lib); - self.parse_global_expr(state, |_| {}, self.optimization_level) + self.parse_global_expr( + state, + |_| {}, + #[cfg(not(feature = "no_optimize"))] + self.optimization_level, + ) } } diff --git a/src/api/eval.rs b/src/api/eval.rs index 7d42eea7f..d5d20e968 100644 --- a/src/api/eval.rs +++ b/src/api/eval.rs @@ -1,7 +1,6 @@ //! Module that defines the public evaluation API of [`Engine`]. use crate::eval::{Caches, GlobalRuntimeState}; -use crate::func::native::locked_write; use crate::parser::ParseState; use crate::types::dynamic::Variant; use crate::{Dynamic, Engine, Position, RhaiResult, RhaiResultOf, Scope, AST, ERR}; @@ -66,8 +65,12 @@ impl Engine { scope: &mut Scope, script: &str, ) -> RhaiResultOf { - let ast = - self.compile_scripts_with_scope_raw(Some(scope), [script], self.optimization_level)?; + let ast = self.compile_scripts_with_scope_raw( + Some(scope), + [script], + #[cfg(not(feature = "no_optimize"))] + self.optimization_level, + )?; self.eval_ast_with_scope(scope, &ast) } /// Evaluate a string containing an expression, returning the result value or an error. @@ -114,17 +117,11 @@ impl Engine { ) -> RhaiResultOf { let scripts = [script]; let ast = { - let guard = &mut self - .interned_strings - .as_ref() - .and_then(|interner| locked_write(interner)); - let interned_strings = guard.as_deref_mut(); - let (stream, tc) = self.lex(&scripts); let input = &mut stream.peekable(); let lib = &mut <_>::default(); - let state = &mut ParseState::new(Some(scope), interned_strings, input, tc, lib); + let state = ParseState::new(Some(scope), input, tc, lib); // No need to optimize a lone expression self.parse_global_expr( @@ -132,8 +129,6 @@ impl Engine { |_| {}, #[cfg(not(feature = "no_optimize"))] crate::OptimizationLevel::None, - #[cfg(feature = "no_optimize")] - <_>::default(), )? }; diff --git a/src/api/formatting.rs b/src/api/formatting.rs index 69f464655..d61bf1202 100644 --- a/src/api/formatting.rs +++ b/src/api/formatting.rs @@ -1,5 +1,4 @@ //! Module that provide formatting services to the [`Engine`]. -use crate::func::locked_write; use crate::packages::iter_basic::{BitRange, CharsStream, StepRange}; use crate::parser::{ParseResult, ParseState}; use crate::{ @@ -265,26 +264,17 @@ impl Engine { tc.borrow_mut().compressed = Some(String::new()); stream.state.last_token = Some(SmartString::new_const()); - let guard = &mut self - .interned_strings - .as_ref() - .and_then(|interner| locked_write(interner)); - let interned_strings = guard.as_deref_mut(); - let input = &mut stream.peekable(); let lib = &mut <_>::default(); - let mut state = ParseState::new(None, interned_strings, input, tc, lib); + let state = ParseState::new(None, input, tc.clone(), lib); let mut _ast = self.parse( - &mut state, + state, #[cfg(not(feature = "no_optimize"))] crate::OptimizationLevel::None, - #[cfg(feature = "no_optimize")] - (), )?; - let tc = state.tokenizer_control.borrow(); - - Ok(tc.compressed.as_ref().unwrap().into()) + let guard = tc.borrow(); + Ok(guard.compressed.as_ref().unwrap().into()) } } diff --git a/src/api/json.rs b/src/api/json.rs index f4f0dbcb5..2aa8740fe 100644 --- a/src/api/json.rs +++ b/src/api/json.rs @@ -1,7 +1,6 @@ //! Module that defines JSON manipulation functions for [`Engine`]. #![cfg(not(feature = "no_object"))] -use crate::func::native::locked_write; use crate::parser::{ParseSettingFlags, ParseState}; use crate::tokenizer::Token; use crate::types::dynamic::Union; @@ -117,23 +116,15 @@ impl Engine { ); let ast = { - let guard = &mut self - .interned_strings - .as_ref() - .and_then(|interner| locked_write(interner)); - let interned_strings = guard.as_deref_mut(); - let input = &mut stream.peekable(); let lib = &mut <_>::default(); - let state = &mut ParseState::new(None, interned_strings, input, tokenizer_control, lib); + let state = ParseState::new(None, input, tokenizer_control, lib); self.parse_global_expr( state, |s| s.flags |= ParseSettingFlags::DISALLOW_UNQUOTED_MAP_PROPERTIES, #[cfg(not(feature = "no_optimize"))] crate::OptimizationLevel::None, - #[cfg(feature = "no_optimize")] - <_>::default(), )? }; diff --git a/src/api/run.rs b/src/api/run.rs index 3831a8855..d4db671ce 100644 --- a/src/api/run.rs +++ b/src/api/run.rs @@ -1,7 +1,6 @@ //! Module that defines the public evaluation API of [`Engine`]. use crate::eval::Caches; -use crate::func::native::locked_write; use crate::parser::ParseState; use crate::{Engine, RhaiResultOf, Scope, AST}; #[cfg(feature = "no_std")] @@ -60,17 +59,15 @@ impl Engine { let ast = { let (stream, tc) = self.lex(&scripts); - let guard = &mut self - .interned_strings - .as_ref() - .and_then(|interner| locked_write(interner)); - let interned_strings = guard.as_deref_mut(); - let input = &mut stream.peekable(); let lib = &mut <_>::default(); - let state = &mut ParseState::new(Some(scope), interned_strings, input, tc, lib); + let state = ParseState::new(Some(scope), input, tc, lib); - self.parse(state, self.optimization_level)? + self.parse( + state, + #[cfg(not(feature = "no_optimize"))] + self.optimization_level, + )? }; self.run_ast_with_scope(scope, &ast) } diff --git a/src/ast/mod.rs b/src/ast/mod.rs index dd1a582e9..ff3489488 100644 --- a/src/ast/mod.rs +++ b/src/ast/mod.rs @@ -24,7 +24,8 @@ pub use stmt::{ SwitchCasesCollection, }; -/// _(internals)_ Placeholder for a script-defined function. +/// _(internals)_ Empty placeholder for a script-defined function. /// Exported under the `internals` feature only. #[cfg(feature = "no_function")] -pub type ScriptFuncDef = (); +#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash, Default)] +pub struct ScriptFuncDef; diff --git a/src/bin/rhai-repl.rs b/src/bin/rhai-repl.rs index 025f05d45..65b826b7b 100644 --- a/src/bin/rhai-repl.rs +++ b/src/bin/rhai-repl.rs @@ -438,15 +438,21 @@ fn main() { continue; } #[cfg(not(feature = "no_optimize"))] - "optimize" if optimize_level == rhai::OptimizationLevel::Simple => { - optimize_level = rhai::OptimizationLevel::None; - println!("Script optimization turned OFF."); - continue; - } - #[cfg(not(feature = "no_optimize"))] "optimize" => { - optimize_level = rhai::OptimizationLevel::Simple; - println!("Script optimization turned ON."); + match optimize_level { + rhai::OptimizationLevel::Full => { + optimize_level = rhai::OptimizationLevel::None; + println!("Script optimization turned OFF."); + } + rhai::OptimizationLevel::Simple => { + optimize_level = rhai::OptimizationLevel::Full; + println!("Script optimization turned to FULL."); + } + _ => { + optimize_level = rhai::OptimizationLevel::Simple; + println!("Script optimization turned to SIMPLE."); + } + } continue; } "scope" => { diff --git a/src/engine.rs b/src/engine.rs index 176b73735..23c9986b5 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -9,7 +9,7 @@ use crate::func::native::{ use crate::packages::{Package, StandardPackage}; use crate::tokenizer::Token; use crate::types::StringsInterner; -use crate::{Dynamic, Identifier, ImmutableString, Locked, OptimizationLevel, SharedModule}; +use crate::{Dynamic, Identifier, ImmutableString, Locked, SharedModule}; #[cfg(feature = "no_std")] use std::prelude::v1::*; use std::{collections::BTreeSet, fmt, num::NonZeroU8}; @@ -131,7 +131,8 @@ pub struct Engine { pub(crate) def_tag: Dynamic, /// Script optimization level. - pub(crate) optimization_level: OptimizationLevel, + #[cfg(not(feature = "no_optimize"))] + pub(crate) optimization_level: crate::OptimizationLevel, /// Max limits. #[cfg(not(feature = "unchecked"))] @@ -254,9 +255,7 @@ impl Engine { def_tag: Dynamic::UNIT, #[cfg(not(feature = "no_optimize"))] - optimization_level: OptimizationLevel::Simple, - #[cfg(feature = "no_optimize")] - optimization_level: (), + optimization_level: crate::OptimizationLevel::Simple, #[cfg(not(feature = "unchecked"))] limits: crate::api::limits::Limits::new(), @@ -333,6 +332,41 @@ impl Engine { None => string.into(), } } + /// Get an interned property getter, creating one if it is not yet interned. + #[cfg(not(feature = "no_object"))] + #[inline] + #[must_use] + pub(crate) fn get_interned_getter( + &self, + text: impl AsRef + Into, + ) -> ImmutableString { + match self.interned_strings { + Some(ref interner) => locked_write(interner).unwrap().get_with_mapper( + b'g', + |s| make_getter(s.as_ref()).into(), + text, + ), + None => make_getter(text.as_ref()).into(), + } + } + + /// Get an interned property setter, creating one if it is not yet interned. + #[cfg(not(feature = "no_object"))] + #[inline] + #[must_use] + pub(crate) fn get_interned_setter( + &self, + text: impl AsRef + Into, + ) -> ImmutableString { + match self.interned_strings { + Some(ref interner) => locked_write(interner).unwrap().get_with_mapper( + b's', + |s| make_setter(s.as_ref()).into(), + text, + ), + None => make_setter(text.as_ref()).into(), + } + } /// Get an empty [`ImmutableString`] which refers to a shared instance. #[inline(always)] diff --git a/src/func/call.rs b/src/func/call.rs index 87797d592..2f6ac5470 100644 --- a/src/func/call.rs +++ b/src/func/call.rs @@ -1621,8 +1621,6 @@ impl Engine { [script], #[cfg(not(feature = "no_optimize"))] crate::OptimizationLevel::None, - #[cfg(feature = "no_optimize")] - <_>::default(), )?; // If new functions are defined within the eval string, it is an error diff --git a/src/lib.rs b/src/lib.rs index 87ab7cd6f..a526deeb9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -323,10 +323,6 @@ pub use module::resolvers as module_resolvers; #[cfg(not(feature = "no_optimize"))] pub use optimizer::OptimizationLevel; -/// Placeholder for the optimization level. -#[cfg(feature = "no_optimize")] -type OptimizationLevel = (); - // Expose internal data structures. #[cfg(feature = "internals")] diff --git a/src/parser.rs b/src/parser.rs index c1d3e6bec..9adc5360e 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -13,14 +13,11 @@ use crate::tokenizer::{ is_reserved_keyword_or_symbol, is_valid_function_name, is_valid_identifier, Token, TokenStream, TokenizerControl, }; -use crate::types::{ - dynamic::{AccessMode, Union}, - StringsInterner, -}; +use crate::types::dynamic::{AccessMode, Union}; use crate::{ calc_fn_hash, Dynamic, Engine, EvalAltResult, EvalContext, ExclusiveRange, FnArgsVec, - ImmutableString, InclusiveRange, LexError, OptimizationLevel, ParseError, Position, Scope, - Shared, SmartString, StaticVec, ThinVec, VarDefInfo, AST, PERR, + ImmutableString, InclusiveRange, LexError, ParseError, Position, Scope, Shared, SmartString, + StaticVec, ThinVec, VarDefInfo, AST, PERR, }; use bitflags::bitflags; #[cfg(feature = "no_std")] @@ -51,7 +48,7 @@ impl PERR { /// _(internals)_ A type that encapsulates the current state of the parser. /// Exported under the `internals` feature only. -pub struct ParseState<'a, 't, 's, 'f> { +pub struct ParseState<'a, 't, 'f> { /// Stream of input tokens. pub input: &'t mut TokenStream<'a>, /// Tokenizer control interface. @@ -61,8 +58,6 @@ pub struct ParseState<'a, 't, 's, 'f> { pub lib: &'f mut FnLib, /// Controls whether parsing of an expression should stop given the next token. pub expr_filter: fn(&Token) -> bool, - /// Strings interner. - pub interned_strings: Option<&'s mut StringsInterner>, /// External [scope][Scope] with constants. pub external_constants: Option<&'a Scope<'a>>, /// Global runtime state. @@ -93,14 +88,13 @@ pub struct ParseState<'a, 't, 's, 'f> { pub dummy: &'f (), } -impl fmt::Debug for ParseState<'_, '_, '_, '_> { +impl fmt::Debug for ParseState<'_, '_, '_> { #[cold] #[inline(never)] fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let mut f = f.debug_struct("ParseState"); f.field("tokenizer_control", &self.tokenizer_control) - .field("interned_strings", &self.interned_strings) .field("external_constants_scope", &self.external_constants) .field("global", &self.global) .field("stack", &self.stack) @@ -118,13 +112,12 @@ impl fmt::Debug for ParseState<'_, '_, '_, '_> { } } -impl<'a, 't, 's, 'f> ParseState<'a, 't, 's, 'f> { +impl<'a, 't, 'f> ParseState<'a, 't, 'f> { /// Create a new [`ParseState`]. #[inline] #[must_use] pub fn new( external_constants: Option<&'a Scope>, - interned_strings: Option<&'s mut StringsInterner>, input: &'t mut TokenStream<'a>, tokenizer_control: TokenizerControl, #[cfg(not(feature = "no_function"))] lib: &'f mut FnLib, @@ -141,7 +134,6 @@ impl<'a, 't, 's, 'f> ParseState<'a, 't, 's, 'f> { #[cfg(not(feature = "no_closure"))] external_vars: ThinVec::new(), allow_capture: true, - interned_strings, external_constants, global: None, stack: Scope::new(), @@ -182,44 +174,6 @@ impl<'a, 't, 's, 'f> ParseState<'a, 't, 's, 'f> { (index, hit_barrier) } - /// Find explicitly declared variable by name in the [`ParseState`], searching in reverse order. - /// - /// If the variable is not present in the scope adds it to the list of external variables. - /// - /// The return value is the offset to be deducted from `ParseState::stack::len()`, - /// i.e. the top element of [`ParseState`]'s variables stack is offset 1. - /// - /// # Return value: `(index, is_func_name)` - /// - /// * `index`: [`None`] when the variable name is not found in the `stack`, - /// otherwise the index value. - /// - /// * `is_func_name`: `true` if the variable is actually the name of a function - /// (in which case it will be converted into a function pointer). - #[must_use] - pub fn access_var(&mut self, name: &str, _pos: Position) -> (Option, bool) { - let (index, hit_barrier) = self.find_var(name); - - #[cfg(not(feature = "no_function"))] - let is_func_name = self.lib.values().any(|f| f.name == name); - #[cfg(feature = "no_function")] - let is_func_name = false; - - #[cfg(not(feature = "no_closure"))] - if self.allow_capture { - if !is_func_name && index == 0 && !self.external_vars.iter().any(|v| v.name == name) { - let name = self.get_interned_string(name); - self.external_vars.push(Ident { name, pos: _pos }); - } - } else { - self.allow_capture = true; - } - - let index = (!hit_barrier).then(|| NonZeroUsize::new(index)).flatten(); - - (index, is_func_name) - } - /// Find a module by name in the [`ParseState`], searching in reverse. /// /// Returns the offset to be deducted from `Stack::len`, @@ -239,55 +193,6 @@ impl<'a, 't, 's, 'f> ParseState<'a, 't, 's, 'f> { .rposition(|n| n == name) .and_then(|i| NonZeroUsize::new(i + 1)) } - - /// Get an interned string, creating one if it is not yet interned. - #[inline(always)] - #[must_use] - pub fn get_interned_string( - &mut self, - text: impl AsRef + Into, - ) -> ImmutableString { - match self.interned_strings { - Some(ref mut interner) => interner.get(text), - None => text.into(), - } - } - - /// Get an interned property getter, creating one if it is not yet interned. - #[cfg(not(feature = "no_object"))] - #[inline] - #[must_use] - pub fn get_interned_getter( - &mut self, - text: impl AsRef + Into, - ) -> ImmutableString { - match self.interned_strings { - Some(ref mut interner) => interner.get_with_mapper( - b'g', - |s| crate::engine::make_getter(s.as_ref()).into(), - text, - ), - None => crate::engine::make_getter(text.as_ref()).into(), - } - } - - /// Get an interned property setter, creating one if it is not yet interned. - #[cfg(not(feature = "no_object"))] - #[inline] - #[must_use] - pub fn get_interned_setter( - &mut self, - text: impl AsRef + Into, - ) -> ImmutableString { - match self.interned_strings { - Some(ref mut interner) => interner.get_with_mapper( - b's', - |s| crate::engine::make_setter(s.as_ref()).into(), - text, - ), - None => crate::engine::make_setter(text.as_ref()).into(), - } - } } bitflags! { @@ -393,30 +298,6 @@ pub fn is_anonymous_fn(fn_name: &str) -> bool { } impl Expr { - /// Convert a [`Variable`][Expr::Variable] into a [`Property`][Expr::Property]. - /// All other variants are untouched. - #[cfg(not(feature = "no_object"))] - #[inline] - #[must_use] - fn into_property(self, state: &mut ParseState) -> Self { - match self { - #[cfg(not(feature = "no_module"))] - Self::Variable(x, ..) if !x.2.is_empty() => unreachable!("qualified property"), - Self::Variable(x, .., pos) => { - let ident = x.1.clone(); - let getter = state.get_interned_getter(&ident); - let hash_get = calc_fn_hash(None, &getter, 1); - let setter = state.get_interned_setter(&ident); - let hash_set = calc_fn_hash(None, &setter, 2); - - Self::Property( - Box::new(((getter, hash_get), (setter, hash_set), ident)), - pos, - ) - } - _ => self, - } - } /// Raise an error if the expression can never yield a boolean value. fn ensure_bool_expr(self) -> ParseResult { let type_name = match self { @@ -639,6 +520,74 @@ fn optimize_combo_chain(expr: &mut Expr) { } impl Engine { + /// Find explicitly declared variable by name in the [`ParseState`], searching in reverse order. + /// + /// If the variable is not present in the scope adds it to the list of external variables. + /// + /// The return value is the offset to be deducted from `ParseState::stack::len()`, + /// i.e. the top element of [`ParseState`]'s variables stack is offset 1. + /// + /// # Return value: `(index, is_func_name)` + /// + /// * `index`: [`None`] when the variable name is not found in the `stack`, + /// otherwise the index value. + /// + /// * `is_func_name`: `true` if the variable is actually the name of a function + /// (in which case it will be converted into a function pointer). + #[must_use] + fn access_var( + &self, + state: &mut ParseState, + name: &str, + _pos: Position, + ) -> (Option, bool) { + let (index, hit_barrier) = state.find_var(name); + + #[cfg(not(feature = "no_function"))] + let is_func_name = state.lib.values().any(|f| f.name == name); + #[cfg(feature = "no_function")] + let is_func_name = false; + + #[cfg(not(feature = "no_closure"))] + if state.allow_capture { + if !is_func_name && index == 0 && !state.external_vars.iter().any(|v| v.name == name) { + let name = self.get_interned_string(name); + state.external_vars.push(Ident { name, pos: _pos }); + } + } else { + state.allow_capture = true; + } + + let index = (!hit_barrier).then(|| NonZeroUsize::new(index)).flatten(); + + (index, is_func_name) + } + + /// Convert a [`Variable`][Expr::Variable] into a [`Property`][Expr::Property]. + /// All other variants are untouched. + #[cfg(not(feature = "no_object"))] + #[inline] + #[must_use] + fn convert_expr_into_property(&self, expr: Expr) -> Expr { + match expr { + #[cfg(not(feature = "no_module"))] + Expr::Variable(x, ..) if !x.2.is_empty() => unreachable!("qualified property"), + Expr::Variable(x, .., pos) => { + let ident = x.1.clone(); + let getter = self.get_interned_getter(&ident); + let hash_get = calc_fn_hash(None, &getter, 1); + let setter = self.get_interned_setter(&ident); + let hash_set = calc_fn_hash(None, &setter, 2); + + Expr::Property( + Box::new(((getter, hash_get), (setter, hash_set), ident)), + pos, + ) + } + _ => expr, + } + } + /// Parse a function call. fn parse_fn_call( &self, @@ -713,7 +662,7 @@ impl Engine { args.shrink_to_fit(); return Ok(FnCallExpr { - name: state.get_interned_string(id), + name: self.get_interned_string(id), capture_parent_scope, op_token: None, #[cfg(not(feature = "no_module"))] @@ -781,7 +730,7 @@ impl Engine { args.shrink_to_fit(); return Ok(FnCallExpr { - name: state.get_interned_string(id), + name: self.get_interned_string(id), capture_parent_scope, op_token: None, #[cfg(not(feature = "no_module"))] @@ -1120,7 +1069,7 @@ impl Engine { let expr = self.parse_expr(state, settings.level_up()?)?; template.insert(name.clone(), crate::Dynamic::UNIT); - let name = state.get_interned_string(name); + let name = self.get_interned_string(name); map.push((Ident { name, pos }, expr)); match state.input.peek().unwrap() { @@ -1389,7 +1338,7 @@ impl Engine { 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(self.get_interned_string(*s), settings.pos) } Token::True => Expr::BoolConstant(true, settings.pos), Token::False => Expr::BoolConstant(false, settings.pos), @@ -1477,7 +1426,6 @@ impl Engine { // Build new parse state let new_state = &mut ParseState::new( state.external_constants, - state.interned_strings.as_deref_mut(), state.input, state.tokenizer_control.clone(), state.lib, @@ -1519,7 +1467,7 @@ impl Engine { #[cfg(not(feature = "no_closure"))] for Ident { name, pos } in &_externals { - let (index, is_func) = state.access_var(name, *pos); + let (index, is_func) = self.access_var(state, name, *pos); if !is_func && index.is_none() @@ -1540,7 +1488,7 @@ impl Engine { state.lib.insert(hash_script, fn_def); #[cfg(not(feature = "no_closure"))] - let expr = Self::make_curry_from_externals(state, expr, _externals, settings.pos); + let expr = self.make_curry_from_externals(state, expr, _externals, settings.pos); expr } @@ -1553,7 +1501,7 @@ impl Engine { match state.input.next().unwrap() { (Token::InterpolatedString(s), ..) if s.is_empty() => (), (Token::InterpolatedString(s), pos) => { - segments.push(Expr::StringConstant(state.get_interned_string(*s), pos)) + segments.push(Expr::StringConstant(self.get_interned_string(*s), pos)) } token => { unreachable!("Token::InterpolatedString expected but gets {:?}", token) @@ -1577,7 +1525,7 @@ impl Engine { (Token::StringConstant(s), pos) => { if !s.is_empty() { segments - .push(Expr::StringConstant(state.get_interned_string(*s), pos)); + .push(Expr::StringConstant(self.get_interned_string(*s), pos)); } // End the interpolated string if it is terminated by a back-tick. break; @@ -1585,7 +1533,7 @@ impl Engine { (Token::InterpolatedString(s), pos) => { if !s.is_empty() { segments - .push(Expr::StringConstant(state.get_interned_string(*s), pos)); + .push(Expr::StringConstant(self.get_interned_string(*s), pos)); } } (Token::LexError(err), pos) => match *err { @@ -1602,7 +1550,7 @@ impl Engine { } if segments.is_empty() { - Expr::StringConstant(state.get_interned_string(""), settings.pos) + Expr::StringConstant(self.get_interned_string(""), settings.pos) } else { segments.shrink_to_fit(); Expr::InterpolatedString(segments, settings.pos) @@ -1646,9 +1594,9 @@ impl Engine { Expr::Variable( #[cfg(not(feature = "no_module"))] - (None, state.get_interned_string(*s), ns, 0).into(), + (None, self.get_interned_string(*s), ns, 0).into(), #[cfg(feature = "no_module")] - (None, state.get_interned_string(*s)).into(), + (None, self.get_interned_string(*s)).into(), None, settings.pos, ) @@ -1667,12 +1615,12 @@ impl Engine { // Once the identifier consumed we must enable next variables capturing state.allow_capture = true; - let name = state.get_interned_string(*s); + let name = self.get_interned_string(*s); Expr::Variable((None, name, ns, 0).into(), None, settings.pos) } // Normal variable access _ => { - let (index, is_func) = state.access_var(&s, settings.pos); + let (index, is_func) = self.access_var(state, &s, settings.pos); if !options.intersects(ChainingFlags::PROPERTY) && !is_func @@ -1690,7 +1638,7 @@ impl Engine { let short_index = index .and_then(|x| u8::try_from(x.get()).ok()) .and_then(NonZeroU8::new); - let name = state.get_interned_string(*s); + let name = self.get_interned_string(*s); Expr::Variable( #[cfg(not(feature = "no_module"))] @@ -1721,9 +1669,9 @@ impl Engine { { Expr::Variable( #[cfg(not(feature = "no_module"))] - (None, state.get_interned_string(*s), ns, 0).into(), + (None, self.get_interned_string(*s), ns, 0).into(), #[cfg(feature = "no_module")] - (None, state.get_interned_string(*s)).into(), + (None, self.get_interned_string(*s)).into(), None, settings.pos, ) @@ -1870,7 +1818,7 @@ impl Engine { namespace.path.push(var_name_def); - let var_name = state.get_interned_string(id2); + let var_name = self.get_interned_string(id2); Expr::Variable((None, var_name, namespace, 0).into(), None, pos2) } @@ -1909,7 +1857,7 @@ impl Engine { let options = ChainingFlags::PROPERTY | ChainingFlags::DISALLOW_NAMESPACES; let rhs = self.parse_primary(state, settings.level_up()?, options)?; - Self::make_dot_expr(state, expr, rhs, _parent_options, op_flags, tail_pos)? + self.make_dot_expr(state, expr, rhs, _parent_options, op_flags, tail_pos)? } // Unknown postfix operator (expr, token) => { @@ -2015,7 +1963,7 @@ impl Engine { expr => Ok(FnCallExpr { #[cfg(not(feature = "no_module"))] namespace: crate::ast::Namespace::NONE, - name: state.get_interned_string("-"), + name: self.get_interned_string("-"), hashes: FnCallHashes::from_native_only(calc_fn_hash(None, "-", 1)), args: IntoIterator::into_iter([expr]).collect(), op_token: Some(token), @@ -2038,7 +1986,7 @@ impl Engine { expr => Ok(FnCallExpr { #[cfg(not(feature = "no_module"))] namespace: crate::ast::Namespace::NONE, - name: state.get_interned_string("+"), + name: self.get_interned_string("+"), hashes: FnCallHashes::from_native_only(calc_fn_hash(None, "+", 1)), args: IntoIterator::into_iter([expr]).collect(), op_token: Some(token), @@ -2055,7 +2003,7 @@ impl Engine { Ok(FnCallExpr { #[cfg(not(feature = "no_module"))] namespace: crate::ast::Namespace::NONE, - name: state.get_interned_string("!"), + name: self.get_interned_string("!"), hashes: FnCallHashes::from_native_only(calc_fn_hash(None, "!", 1)), args: { let expr = self.parse_unary(state, settings.level_up()?)?; @@ -2186,6 +2134,7 @@ impl Engine { /// Make a dot expression. #[cfg(not(feature = "no_object"))] fn make_dot_expr( + &self, state: &mut ParseState, lhs: Expr, rhs: Expr, @@ -2199,7 +2148,7 @@ impl Engine { if !parent_options.intersects(ASTFlags::BREAK) => { let options = options | parent_options; - x.rhs = Self::make_dot_expr(state, x.rhs, rhs, options, op_flags, op_pos)?; + x.rhs = self.make_dot_expr(state, x.rhs, rhs, options, op_flags, op_pos)?; Ok(Expr::Index(x, ASTFlags::empty(), pos)) } // lhs.module::id - syntax error @@ -2207,7 +2156,7 @@ impl Engine { (.., Expr::Variable(x, ..)) if !x.2.is_empty() => unreachable!("lhs.ns::id"), // lhs.id (lhs, var_expr @ Expr::Variable(..)) => { - let rhs = var_expr.into_property(state); + let rhs = self.convert_expr_into_property(var_expr); Ok(Expr::Dot(BinaryExpr { lhs, rhs }.into(), op_flags, op_pos)) } // lhs.prop @@ -2283,7 +2232,7 @@ impl Engine { // lhs.id.dot_rhs or lhs.id[idx_rhs] Expr::Variable(..) | Expr::Property(..) => { let new_binary = BinaryExpr { - lhs: x.lhs.into_property(state), + lhs: self.convert_expr_into_property(x.lhs), rhs: x.rhs, } .into(); @@ -2415,7 +2364,7 @@ impl Engine { let mut op_base = FnCallExpr { #[cfg(not(feature = "no_module"))] namespace: crate::ast::Namespace::NONE, - name: state.get_interned_string(&op), + name: self.get_interned_string(&op), hashes: FnCallHashes::from_native_only(hash), args: IntoIterator::into_iter([root, rhs]).collect(), op_token: native_only.then(|| op_token.clone()), @@ -2445,7 +2394,7 @@ impl Engine { // Convert into a call to `contains` op_base.hashes = FnCallHashes::from_hash(calc_fn_hash(None, OP_CONTAINS, 2)); - op_base.name = state.get_interned_string(OP_CONTAINS); + op_base.name = self.get_interned_string(OP_CONTAINS); let fn_call = op_base.into_fn_call_expr(pos); if op_token == Token::In { @@ -2455,7 +2404,7 @@ impl Engine { let not_base = FnCallExpr { #[cfg(not(feature = "no_module"))] namespace: crate::ast::Namespace::NONE, - name: state.get_interned_string(OP_NOT), + name: self.get_interned_string(OP_NOT), hashes: FnCallHashes::from_native_only(calc_fn_hash(None, OP_NOT, 1)), args: IntoIterator::into_iter([fn_call]).collect(), op_token: Some(Token::Bang), @@ -2505,7 +2454,7 @@ impl Engine { if syntax.scope_may_be_changed { // Add a barrier variable to the stack so earlier variables will not be matched. // Variable searches stop at the first barrier. - let marker = state.get_interned_string(SCOPE_SEARCH_BARRIER_MARKER); + let marker = self.get_interned_string(SCOPE_SEARCH_BARRIER_MARKER); state.stack.push(marker, ()); } @@ -2526,7 +2475,7 @@ impl Engine { if seg.starts_with(CUSTOM_SYNTAX_MARKER_SYNTAX_VARIANT) && seg.len() > CUSTOM_SYNTAX_MARKER_SYNTAX_VARIANT.len() => { - inputs.push(Expr::StringConstant(state.get_interned_string(seg), pos)); + inputs.push(Expr::StringConstant(self.get_interned_string(seg), pos)); break; } Ok(Some(seg)) => seg, @@ -2537,10 +2486,10 @@ impl Engine { match required_token.as_str() { CUSTOM_SYNTAX_MARKER_IDENT => { let (name, pos) = parse_var_name(state.input)?; - let name = state.get_interned_string(name); + let name = self.get_interned_string(name); segments.push(name.clone()); - tokens.push(state.get_interned_string(CUSTOM_SYNTAX_MARKER_IDENT)); + tokens.push(self.get_interned_string(CUSTOM_SYNTAX_MARKER_IDENT)); inputs.push(Expr::Variable( #[cfg(not(feature = "no_module"))] @@ -2566,21 +2515,21 @@ impl Engine { // Not a symbol (.., pos) => Err(PERR::MissingSymbol(String::new()).into_err(pos)), }?; - let symbol = state.get_interned_string(symbol); + let symbol = self.get_interned_string(symbol); segments.push(symbol.clone()); - tokens.push(state.get_interned_string(CUSTOM_SYNTAX_MARKER_SYMBOL)); + tokens.push(self.get_interned_string(CUSTOM_SYNTAX_MARKER_SYMBOL)); inputs.push(Expr::StringConstant(symbol, pos)); } CUSTOM_SYNTAX_MARKER_EXPR => { inputs.push(self.parse_expr(state, settings)?); - let keyword = state.get_interned_string(CUSTOM_SYNTAX_MARKER_EXPR); + let keyword = self.get_interned_string(CUSTOM_SYNTAX_MARKER_EXPR); segments.push(keyword.clone()); tokens.push(keyword); } CUSTOM_SYNTAX_MARKER_BLOCK => match self.parse_block(state, settings)? { block @ Stmt::Block(..) => { inputs.push(Expr::Stmt(Box::new(block.into()))); - let keyword = state.get_interned_string(CUSTOM_SYNTAX_MARKER_BLOCK); + let keyword = self.get_interned_string(CUSTOM_SYNTAX_MARKER_BLOCK); segments.push(keyword.clone()); tokens.push(keyword); } @@ -2589,8 +2538,8 @@ impl Engine { CUSTOM_SYNTAX_MARKER_BOOL => match state.input.next().unwrap() { (b @ (Token::True | Token::False), pos) => { inputs.push(Expr::BoolConstant(b == Token::True, pos)); - segments.push(state.get_interned_string(b.literal_syntax())); - tokens.push(state.get_interned_string(CUSTOM_SYNTAX_MARKER_BOOL)); + segments.push(self.get_interned_string(b.literal_syntax())); + tokens.push(self.get_interned_string(CUSTOM_SYNTAX_MARKER_BOOL)); } (.., pos) => { return Err( @@ -2602,7 +2551,7 @@ impl Engine { (Token::IntegerConstant(i), pos) => { inputs.push(Expr::IntegerConstant(i, pos)); segments.push(i.to_string().into()); - tokens.push(state.get_interned_string(CUSTOM_SYNTAX_MARKER_INT)); + tokens.push(self.get_interned_string(CUSTOM_SYNTAX_MARKER_INT)); } (.., pos) => { return Err( @@ -2615,7 +2564,7 @@ impl Engine { (Token::FloatConstant(f), pos) => { inputs.push(Expr::FloatConstant(f.0, pos)); segments.push(f.1.into()); - tokens.push(state.get_interned_string(CUSTOM_SYNTAX_MARKER_FLOAT)); + tokens.push(self.get_interned_string(CUSTOM_SYNTAX_MARKER_FLOAT)); } (.., pos) => { return Err( @@ -2626,10 +2575,10 @@ impl Engine { }, CUSTOM_SYNTAX_MARKER_STRING => match state.input.next().unwrap() { (Token::StringConstant(s), pos) => { - let s = state.get_interned_string(*s); + let s = self.get_interned_string(*s); inputs.push(Expr::StringConstant(s.clone(), pos)); segments.push(s); - tokens.push(state.get_interned_string(CUSTOM_SYNTAX_MARKER_STRING)); + tokens.push(self.get_interned_string(CUSTOM_SYNTAX_MARKER_STRING)); } (.., pos) => { return Err(PERR::MissingSymbol("Expecting a string".into()).into_err(pos)) @@ -2850,12 +2799,12 @@ impl Engine { let expr = self.parse_expr(state, settings)?.ensure_iterable()?; let counter_var = counter_name.map(|counter_name| Ident { - name: state.get_interned_string(counter_name), + name: self.get_interned_string(counter_name), pos: counter_pos, }); let loop_var = Ident { - name: state.get_interned_string(name), + name: self.get_interned_string(name), pos: name_pos, }; @@ -2926,7 +2875,7 @@ impl Engine { } } - let name = state.get_interned_string(name); + let name = self.get_interned_string(name); // let name = ... let expr = if match_token(state.input, &Token::Equals).0 { @@ -2995,13 +2944,13 @@ impl Engine { // import expr as name ... let (name, pos) = parse_var_name(state.input)?; Ident { - name: state.get_interned_string(name), + name: self.get_interned_string(name), pos, } } else { // import expr; Ident { - name: state.get_interned_string(""), + name: self.get_interned_string(""), pos: Position::NONE, } }; @@ -3041,9 +2990,9 @@ impl Engine { let (id, id_pos) = parse_var_name(state.input)?; let (alias, alias_pos) = if match_token(state.input, &Token::As).0 { - parse_var_name(state.input).map(|(name, pos)| (state.get_interned_string(name), pos))? + parse_var_name(state.input).map(|(name, pos)| (self.get_interned_string(name), pos))? } else { - (state.get_interned_string(""), Position::NONE) + (self.get_interned_string(""), Position::NONE) }; let (existing, hit_barrier) = state.find_var(&id); @@ -3056,7 +3005,7 @@ impl Engine { let export = ( Ident { - name: state.get_interned_string(id), + name: self.get_interned_string(id), pos: id_pos, }, Ident { @@ -3307,7 +3256,6 @@ impl Engine { // Build new parse state let new_state = &mut ParseState::new( state.external_constants, - state.interned_strings.as_deref_mut(), state.input, state.tokenizer_control.clone(), state.lib, @@ -3505,12 +3453,12 @@ impl Engine { .into_err(err_pos)); } - let name = state.get_interned_string(name); + let name = self.get_interned_string(name); state.stack.push(name.clone(), ()); Ident { name, pos } } else { Ident { - name: state.get_interned_string(""), + name: self.get_interned_string(""), pos: Position::NONE, } }; @@ -3562,10 +3510,10 @@ impl Engine { Token::StringConstant(s) if next_token == &Token::Period => { eat_token(state.input, &Token::Period); let s = match s.as_str() { - "int" => state.get_interned_string(std::any::type_name::()), + "int" => self.get_interned_string(std::any::type_name::()), #[cfg(not(feature = "no_float"))] - "float" => state.get_interned_string(std::any::type_name::()), - _ => state.get_interned_string(*s), + "float" => self.get_interned_string(std::any::type_name::()), + _ => self.get_interned_string(*s), }; (state.input.next().unwrap(), Some(s)) } @@ -3579,10 +3527,10 @@ impl Engine { Token::Identifier(s) if next_token == &Token::Period => { eat_token(state.input, &Token::Period); let s = match s.as_str() { - "int" => state.get_interned_string(std::any::type_name::()), + "int" => self.get_interned_string(std::any::type_name::()), #[cfg(not(feature = "no_float"))] - "float" => state.get_interned_string(std::any::type_name::()), - _ => state.get_interned_string(*s), + "float" => self.get_interned_string(std::any::type_name::()), + _ => self.get_interned_string(*s), }; (state.input.next().unwrap(), Some(s)) } @@ -3625,7 +3573,7 @@ impl Engine { ); } - let s = state.get_interned_string(*s); + let s = self.get_interned_string(*s); state.stack.push(s.clone(), ()); params.push((s, pos)); } @@ -3661,7 +3609,7 @@ impl Engine { params.shrink_to_fit(); Ok(ScriptFuncDef { - name: state.get_interned_string(name), + name: self.get_interned_string(name), access, #[cfg(not(feature = "no_object"))] this_type, @@ -3676,6 +3624,7 @@ impl Engine { #[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_closure"))] fn make_curry_from_externals( + &self, state: &mut ParseState, fn_expr: Expr, externals: impl AsRef<[Ident]> + IntoIterator, @@ -3697,7 +3646,7 @@ impl Engine { .iter() .cloned() .map(|Ident { name, pos }| { - let (index, is_func) = state.access_var(&name, pos); + let (index, is_func) = self.access_var(state, &name, pos); let idx = match index { Some(n) if !is_func => u8::try_from(n.get()).ok().and_then(NonZeroU8::new), _ => None, @@ -3712,7 +3661,7 @@ impl Engine { let expr = FnCallExpr { #[cfg(not(feature = "no_module"))] namespace: crate::ast::Namespace::NONE, - name: state.get_interned_string(crate::engine::KEYWORD_FN_PTR_CURRY), + name: self.get_interned_string(crate::engine::KEYWORD_FN_PTR_CURRY), hashes: FnCallHashes::from_native_only(calc_fn_hash( None, crate::engine::KEYWORD_FN_PTR_CURRY, @@ -3731,7 +3680,7 @@ impl Engine { externals .into_iter() .map(|var| { - let (index, _) = state.access_var(&var.name, var.pos); + let (index, _) = self.access_var(state, &var.name, var.pos); (var, index) }) .collect::>() @@ -3762,7 +3711,7 @@ impl Engine { ); } - let s = state.get_interned_string(*s); + let s = self.get_interned_string(*s); state.stack.push(s.clone(), ()); params_list.push(s); } @@ -3815,7 +3764,7 @@ impl Engine { params.iter().for_each(|p| p.hash(hasher)); body.hash(hasher); let hash = hasher.finish(); - let fn_name = state.get_interned_string(make_anonymous_fn(hash)); + let fn_name = self.get_interned_string(make_anonymous_fn(hash)); // Define the function let script = Shared::new(ScriptFuncDef { @@ -3845,9 +3794,9 @@ impl Engine { /// Parse a global level expression. pub(crate) fn parse_global_expr( &self, - state: &mut ParseState, + mut state: ParseState, process_settings: impl FnOnce(&mut ParseSettings), - _optimization_level: OptimizationLevel, + #[cfg(not(feature = "no_optimize"))] optimization_level: crate::OptimizationLevel, ) -> ParseResult { let options = self.options & !LangOptions::STMT_EXPR & !LangOptions::LOOP_EXPR; @@ -3862,7 +3811,7 @@ impl Engine { }; process_settings(&mut settings); - let expr = self.parse_expr(state, settings)?; + let expr = self.parse_expr(&mut state, settings)?; match state.input.peek().unwrap() { (Token::EOF, ..) => (), @@ -3878,15 +3827,15 @@ impl Engine { state.external_constants, statements, #[cfg(not(feature = "no_function"))] - std::mem::take(state.lib).into_values().collect::>(), - _optimization_level, + state.lib.values().cloned().collect::>(), + optimization_level, )); #[cfg(feature = "no_optimize")] return Ok(AST::new( statements, #[cfg(not(feature = "no_function"))] - crate::Module::from(std::mem::take(state.lib).into_values()), + crate::Module::from(state.lib.values().cloned()), )); } @@ -3956,10 +3905,10 @@ impl Engine { #[inline] pub(crate) fn parse( &self, - state: &mut ParseState, - _optimization_level: OptimizationLevel, + mut state: ParseState, + #[cfg(not(feature = "no_optimize"))] optimization_level: crate::OptimizationLevel, ) -> ParseResult { - let (statements, _lib) = self.parse_global_level(state, |_| {})?; + let (statements, _lib) = self.parse_global_level(&mut state, |_| {})?; #[cfg(not(feature = "no_optimize"))] return Ok(self.optimize_into_ast( @@ -3967,27 +3916,18 @@ impl Engine { statements, #[cfg(not(feature = "no_function"))] _lib, - _optimization_level, + optimization_level, )); #[cfg(feature = "no_optimize")] - #[cfg(not(feature = "no_function"))] - { - let mut m = crate::Module::new(); - - _lib.into_iter().for_each(|fn_def| { - m.set_script_fn(fn_def); - }); - - return Ok(AST::new(statements, m)); - } - - #[cfg(feature = "no_optimize")] - #[cfg(feature = "no_function")] return Ok(AST::new( statements, #[cfg(not(feature = "no_function"))] - crate::Module::new(), + { + let mut new_lib = crate::Module::new(); + new_lib.extend(_lib); + new_lib + }, )); } } diff --git a/tests/optimizer.rs b/tests/optimizer.rs index e51ced358..0d45c3015 100644 --- a/tests/optimizer.rs +++ b/tests/optimizer.rs @@ -79,6 +79,10 @@ fn test_optimizer_parse() { engine.set_optimization_level(OptimizationLevel::Full); + let ast = engine.compile(r#"sub_string("", 7)"#).unwrap(); + + assert_eq!(format!("{ast:?}"), r#"AST { source: None, doc: "", resolver: None, body: [Expr("" @ 1:1)] }"#); + let ast = engine.compile("abs(-42)").unwrap(); assert_eq!(format!("{ast:?}"), r#"AST { source: None, doc: "", resolver: None, body: [Expr(42 @ 1:1)] }"#);