From fc64e93b93ac5e74b33b37fa00e8c7ac1539a495 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Thu, 5 May 2022 22:30:55 +0800 Subject: [PATCH 01/19] Deprecate FnPtr::num_curried. --- CHANGELOG.md | 5 +++++ src/api/deprecated.rs | 13 +++++++++++++ src/types/fn_ptr.rs | 10 ++-------- 3 files changed, 20 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8f5a5706..ad7c33d5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,11 @@ Bug fixes * Self-contained `AST` now works properly with `Engine::call_fn`. +Deprecated API's +---------------- + +* `FnPtr::num_curried` is deprecated in favor of `FnPtr::curry().len()`. + Version 1.7.0 ============= diff --git a/src/api/deprecated.rs b/src/api/deprecated.rs index 6d9d5463..01f9331f 100644 --- a/src/api/deprecated.rs +++ b/src/api/deprecated.rs @@ -259,6 +259,19 @@ impl From for RhaiResultOf { } impl FnPtr { + /// Get the number of curried arguments. + /// + /// # Deprecated + /// + /// This method is deprecated. Use [`curry().len()`][`FnPtr::curry`] instead. + /// + /// This method will be removed in the next major version. + #[deprecated(since = "1.8.0", note = "use `curry().len()` instead")] + #[inline(always)] + #[must_use] + pub fn num_curried(&self) -> usize { + self.curry().len() + } /// Call the function pointer with curried arguments (if any). /// The function may be script-defined (not available under `no_function`) or native Rust. /// diff --git a/src/types/fn_ptr.rs b/src/types/fn_ptr.rs index 9d2d60cf..1e36141b 100644 --- a/src/types/fn_ptr.rs +++ b/src/types/fn_ptr.rs @@ -88,12 +88,6 @@ impl FnPtr { pub fn is_curried(&self) -> bool { !self.1.is_empty() } - /// Get the number of curried arguments. - #[inline(always)] - #[must_use] - pub fn num_curried(&self) -> usize { - self.1.len() - } /// Does the function pointer refer to an anonymous function? /// /// Not available under `no_function`. @@ -219,8 +213,8 @@ impl FnPtr { let mut arg_values = arg_values.as_mut(); let mut args_data; - if self.num_curried() > 0 { - args_data = StaticVec::with_capacity(self.num_curried() + arg_values.len()); + if self.is_curried() { + args_data = StaticVec::with_capacity(self.curry().len() + arg_values.len()); args_data.extend(self.curry().iter().cloned()); args_data.extend(arg_values.iter_mut().map(mem::take)); arg_values = args_data.as_mut(); From 4194e2c048f41f0f17a643028a6bd8d61a76ba75 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Sat, 7 May 2022 15:54:44 +0800 Subject: [PATCH 02/19] Refine data structures. --- src/api/custom_syntax.rs | 17 +++++++++++++---- src/api/json.rs | 4 ++-- src/engine.rs | 11 +++++++++-- src/eval/cache.rs | 5 ++--- src/eval/eval_context.rs | 8 ++++---- src/func/call.rs | 41 ++++++++++++++++++++++------------------ src/lib.rs | 2 +- src/module/mod.rs | 12 +++++------- 8 files changed, 59 insertions(+), 41 deletions(-) diff --git a/src/api/custom_syntax.rs b/src/api/custom_syntax.rs index b7c1f48b..76b96443 100644 --- a/src/api/custom_syntax.rs +++ b/src/api/custom_syntax.rs @@ -8,7 +8,7 @@ use crate::tokenizer::{is_valid_identifier, Token}; use crate::types::dynamic::Variant; use crate::{ reify, Engine, EvalContext, Identifier, ImmutableString, LexError, Position, RhaiResult, - Shared, StaticVec, + StaticVec, }; use std::ops::Deref; #[cfg(feature = "no_std")] @@ -65,6 +65,15 @@ impl<'a> From<&'a Expr> for Expression<'a> { } impl Expression<'_> { + /// Evaluate this [expression tree][Expression] within an [evaluation context][`EvalContext`]. + /// + /// # WARNING - Low Level API + /// + /// This function is very low level. It evaluates an expression from an [`AST`][crate::AST]. + #[inline(always)] + pub fn eval_with_context(&self, context: &mut EvalContext) -> RhaiResult { + context.eval_expression_tree(self) + } /// Get the value of this expression if it is a variable name or a string constant. /// /// Returns [`None`] also if the constant is not of the specified type. @@ -132,7 +141,7 @@ impl Deref for Expression<'_> { } impl EvalContext<'_, '_, '_, '_, '_, '_, '_, '_> { - /// Evaluate an [expression tree][Expression]. + /// Evaluate an [expression tree][Expression] within this [evaluation context][`EvalContext`]. /// /// # WARNING - Low Level API /// @@ -165,7 +174,7 @@ pub struct CustomSyntax { /// symbols parsed so far. pub parse: Box, /// Custom syntax implementation function. - pub func: Shared, + pub func: Box, /// Any variables added/removed in the scope? pub scope_may_be_changed: bool, } @@ -356,7 +365,7 @@ impl Engine { key.into(), CustomSyntax { parse: Box::new(parse), - func: (Box::new(func) as Box).into(), + func: Box::new(func), scope_may_be_changed, } .into(), diff --git a/src/api/json.rs b/src/api/json.rs index 856e25f4..f8a8c2ce 100644 --- a/src/api/json.rs +++ b/src/api/json.rs @@ -119,8 +119,8 @@ impl Engine { }, ); - let scope = &Scope::new(); - let mut state = ParseState::new(self, scope, tokenizer_control); + let scope = Scope::new(); + let mut state = ParseState::new(self, &scope, tokenizer_control); let ast = self.parse_global_expr( &mut stream.peekable(), diff --git a/src/engine.rs b/src/engine.rs index 8b0975d4..da83207e 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -112,7 +112,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 filtering variable definition. pub(crate) def_var_filter: Option>, /// Callback closure for resolving variable access. @@ -155,7 +155,14 @@ impl fmt::Debug for Engine { f.field("disabled_symbols", &self.disabled_symbols) .field("custom_keywords", &self.custom_keywords) - .field("custom_syntax", &(!self.custom_syntax.is_empty())) + .field( + "custom_syntax", + &self + .custom_syntax + .keys() + .map(|s| s.as_str()) + .collect::(), + ) .field("def_var_filter", &self.def_var_filter.is_some()) .field("resolve_var", &self.resolve_var.is_some()) .field("token_mapper", &self.token_mapper.is_some()); diff --git a/src/eval/cache.rs b/src/eval/cache.rs index 64c9e0fb..8b552280 100644 --- a/src/eval/cache.rs +++ b/src/eval/cache.rs @@ -13,8 +13,7 @@ pub struct FnResolutionCacheEntry { /// Function. pub func: CallableFunction, /// Optional source. - /// No source if the string is empty. - pub source: Identifier, + pub source: Option>, } /// _(internals)_ A function resolution cache. @@ -22,7 +21,7 @@ pub struct FnResolutionCacheEntry { /// /// [`FnResolutionCacheEntry`] is [`Box`]ed in order to pack as many entries inside a single B-Tree /// level as possible. -pub type FnResolutionCache = BTreeMap>>; +pub type FnResolutionCache = BTreeMap>; /// _(internals)_ A type containing system-wide caches. /// Exported under the `internals` feature only. diff --git a/src/eval/eval_context.rs b/src/eval/eval_context.rs index 4e25abc9..ed93f8ff 100644 --- a/src/eval/eval_context.rs +++ b/src/eval/eval_context.rs @@ -7,13 +7,13 @@ use std::prelude::v1::*; /// Context of a script evaluation process. #[derive(Debug)] -pub struct EvalContext<'a, 's, 'ps, 'm, 'pm, 'c, 't, 'pt> { +pub struct EvalContext<'a, 's, 'ps, 'g, 'pg, 'c, 't, 'pt> { /// The current [`Engine`]. pub(crate) engine: &'a Engine, /// The current [`Scope`]. pub(crate) scope: &'s mut Scope<'ps>, /// The current [`GlobalRuntimeState`]. - pub(crate) global: &'m mut GlobalRuntimeState<'pm>, + pub(crate) global: &'g mut GlobalRuntimeState<'pg>, /// The current [caches][Caches], if available. pub(crate) caches: Option<&'c mut Caches>, /// The current stack of imported [modules][Module]. @@ -24,7 +24,7 @@ pub struct EvalContext<'a, 's, 'ps, 'm, 'pm, 'c, 't, 'pt> { pub(crate) level: usize, } -impl<'s, 'ps, 'm, 'pm, 'pt> EvalContext<'_, 's, 'ps, 'm, 'pm, '_, '_, 'pt> { +impl<'s, 'ps, 'g, 'pg, 'pt> EvalContext<'_, 's, 'ps, 'g, 'pg, '_, '_, 'pt> { /// The current [`Engine`]. #[inline(always)] #[must_use] @@ -85,7 +85,7 @@ impl<'s, 'ps, 'm, 'pm, 'pt> EvalContext<'_, 's, 'ps, 'm, 'pm, '_, '_, 'pt> { #[cfg(feature = "internals")] #[inline(always)] #[must_use] - pub fn global_runtime_state_mut(&mut self) -> &mut &'m mut GlobalRuntimeState<'pm> { + pub fn global_runtime_state_mut(&mut self) -> &mut &'g mut GlobalRuntimeState<'pg> { &mut self.global } /// Get an iterator over the namespaces containing definition of all script-defined functions. diff --git a/src/func/call.rs b/src/func/call.rs index caec732a..cfeebacb 100644 --- a/src/func/call.rs +++ b/src/func/call.rs @@ -214,14 +214,14 @@ impl Engine { .find_map(|&m| { m.get_fn(hash).cloned().map(|func| FnResolutionCacheEntry { func, - source: m.id_raw().clone(), + source: m.id().map(|s| Box::new(s.into())), }) }) .or_else(|| { self.global_modules.iter().find_map(|m| { m.get_fn(hash).cloned().map(|func| FnResolutionCacheEntry { func, - source: m.id_raw().clone(), + source: m.id().map(|s| Box::new(s.into())), }) }) }); @@ -232,8 +232,7 @@ impl Engine { _global.get_qualified_fn(hash).map(|(func, source)| { FnResolutionCacheEntry { func: func.clone(), - source: source - .map_or_else(|| Identifier::new_const(), Into::into), + source: source.map(|s| Box::new(s.into())), } }) }) @@ -242,7 +241,7 @@ impl Engine { m.get_qualified_fn(hash).cloned().map(|func| { FnResolutionCacheEntry { func, - source: m.id_raw().clone(), + source: m.id().map(|s| Box::new(s.into())), } }) }) @@ -250,7 +249,7 @@ impl Engine { match func { // Specific version found - Some(f) => return Some(Box::new(f)), + Some(f) => return Some(f), // Stop when all permutations are exhausted None if bitmask >= max_bitmask => { @@ -265,7 +264,7 @@ impl Engine { func: CallableFunction::from_method( Box::new(f) as Box ), - source: Identifier::new_const(), + source: None, } }) } else { @@ -276,10 +275,9 @@ impl Engine { func: CallableFunction::from_method( Box::new(f) as Box ), - source: Identifier::new_const(), + source: None, }) } - .map(Box::new) }); } @@ -308,7 +306,7 @@ impl Engine { } }); - result.as_ref().map(Box::as_ref) + result.as_ref() } /// # Main Entry-Point @@ -370,9 +368,10 @@ impl Engine { backup.change_first_arg_to_copy(args); } - let source = match (source.as_str(), parent_source.as_str()) { - ("", "") => None, - ("", s) | (s, ..) => Some(s), + let source = match (source, parent_source.as_str()) { + (None, "") => None, + (None, s) => Some(s), + (Some(s), ..) => Some(s.as_str()), }; #[cfg(feature = "debugging")] @@ -626,7 +625,7 @@ impl Engine { // Script-defined function call? #[cfg(not(feature = "no_function"))] - if let Some(FnResolutionCacheEntry { func, mut source }) = self + if let Some(FnResolutionCacheEntry { func, ref source }) = self .resolve_fn( global, caches, @@ -657,7 +656,13 @@ impl Engine { } }; - mem::swap(&mut global.source, &mut source); + let orig_source = mem::replace( + &mut global.source, + source + .as_ref() + .map(|s| (**s).clone()) + .unwrap_or(Identifier::new_const()), + ); let result = if _is_method_call { // Method call of script function - map first argument to `this` @@ -695,7 +700,7 @@ impl Engine { }; // Restore the original source - mem::swap(&mut global.source, &mut source); + global.source = orig_source; return Ok((result?, false)); } @@ -757,7 +762,7 @@ impl Engine { // Recalculate hashes let new_hash = calc_fn_hash(fn_name, args_len).into(); // Arguments are passed as-is, adding the curried arguments - let mut curry = FnArgsVec::with_capacity(fn_ptr.num_curried()); + let mut curry = FnArgsVec::with_capacity(fn_ptr.curry().len()); curry.extend(fn_ptr.curry().iter().cloned()); let mut args = FnArgsVec::with_capacity(curry.len() + call_args.len()); args.extend(curry.iter_mut()); @@ -792,7 +797,7 @@ impl Engine { calc_fn_hash(fn_name, args_len + 1), ); // Replace the first argument with the object pointer, adding the curried arguments - let mut curry = FnArgsVec::with_capacity(fn_ptr.num_curried()); + let mut curry = FnArgsVec::with_capacity(fn_ptr.curry().len()); curry.extend(fn_ptr.curry().iter().cloned()); let mut args = FnArgsVec::with_capacity(curry.len() + call_args.len() + 1); args.push(target.as_mut()); diff --git a/src/lib.rs b/src/lib.rs index f10e2b20..f4a7bfff 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -44,7 +44,7 @@ //! # #[cfg(not(target_family = "wasm"))] //! # //! // Evaluate the script, expecting a 'bool' result -//! let result = engine.eval_file::("my_script.rhai".into())?; +//! let result: bool = engine.eval_file("my_script.rhai".into())?; //! //! assert_eq!(result, true); //! diff --git a/src/module/mod.rs b/src/module/mod.rs index 5ad8e48e..c3b68758 100644 --- a/src/module/mod.rs +++ b/src/module/mod.rs @@ -81,7 +81,7 @@ impl Ord for FnMetadata { #[derive(Debug, Clone)] pub struct FuncInfo { /// Function instance. - pub func: Shared, + pub func: CallableFunction, /// Parameter types (if applicable). pub param_types: StaticVec, /// Function metadata. @@ -246,7 +246,7 @@ pub struct Module { functions: BTreeMap>, /// Flattened collection of all external Rust functions, native or scripted. /// including those in sub-modules. - all_functions: BTreeMap>, + all_functions: BTreeMap, /// Iterator functions, keyed by the type producing the iterator. type_iterators: BTreeMap>, /// Flattened collection of iterator functions, including those in sub-modules. @@ -1452,7 +1452,7 @@ impl Module { #[must_use] pub(crate) fn get_fn(&self, hash_fn: u64) -> Option<&CallableFunction> { if !self.functions.is_empty() { - self.functions.get(&hash_fn).map(|f| f.func.as_ref()) + self.functions.get(&hash_fn).map(|f| &f.func) } else { None } @@ -1479,9 +1479,7 @@ impl Module { #[must_use] pub(crate) fn get_qualified_fn(&self, hash_qualified_fn: u64) -> Option<&CallableFunction> { if !self.all_functions.is_empty() { - self.all_functions - .get(&hash_qualified_fn) - .map(|f| f.as_ref()) + self.all_functions.get(&hash_qualified_fn) } else { None } @@ -1932,7 +1930,7 @@ impl Module { module: &'a Module, path: &mut Vec<&'a str>, variables: &mut BTreeMap, - functions: &mut BTreeMap>, + functions: &mut BTreeMap, type_iterators: &mut BTreeMap>, ) -> bool { let mut contains_indexed_global_functions = false; From b4fea634b0ab0f1149eee454eebefb9d967ee581 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Sat, 7 May 2022 16:29:20 +0800 Subject: [PATCH 03/19] Avoid unnecessary allocations. --- src/api/json.rs | 6 ++++-- src/packages/string_basic.rs | 8 ++++++-- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/src/api/json.rs b/src/api/json.rs index f8a8c2ce..675ae427 100644 --- a/src/api/json.rs +++ b/src/api/json.rs @@ -156,11 +156,13 @@ pub fn format_map_as_json(map: &Map) -> String { let mut result = String::from('{'); for (key, value) in map { + use std::fmt::Write; + if result.len() > 1 { result.push(','); } - result.push_str(&format!("{:?}", key)); + write!(result, "{:?}", key).unwrap(); result.push(':'); if let Some(val) = value.read_lock::() { @@ -171,7 +173,7 @@ pub fn format_map_as_json(map: &Map) -> String { if value.is::<()>() { result.push_str("null"); } else { - result.push_str(&format!("{:?}", value)); + write!(result, "{:?}", value).unwrap(); } } diff --git a/src/packages/string_basic.rs b/src/packages/string_basic.rs index c817a24c..a64317b2 100644 --- a/src/packages/string_basic.rs +++ b/src/packages/string_basic.rs @@ -195,12 +195,16 @@ mod print_debug_functions { result.push_str("#{"); map.iter_mut().enumerate().for_each(|(i, (k, v))| { - result.push_str(&format!( + use std::fmt::Write; + + write!( + result, "{:?}: {}{}", k, &print_with_func(FUNC_TO_DEBUG, &ctx, v), if i < len - 1 { ", " } else { "" } - )); + ) + .unwrap(); }); result.push('}'); From c7aea45d4b04478f10c6da2c32ee5f675cbbe166 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Mon, 9 May 2022 14:20:33 +0800 Subject: [PATCH 04/19] Add to_int for decimal. --- CHANGELOG.md | 1 + src/packages/math_basic.rs | 33 +++++++++++++++++++++++++++++---- 2 files changed, 30 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ad7c33d5..0615876e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ Bug fixes --------- * Self-contained `AST` now works properly with `Engine::call_fn`. +* Missing `to_int` from `Decimal` is added. Deprecated API's ---------------- diff --git a/src/packages/math_basic.rs b/src/packages/math_basic.rs index 45fd3790..604ef992 100644 --- a/src/packages/math_basic.rs +++ b/src/packages/math_basic.rs @@ -311,10 +311,10 @@ mod float_functions { pub fn is_infinite(x: FLOAT) -> bool { x.is_infinite() } - /// Return the integral part of the floating-point number. + /// Convert the floating-point number into an integer. #[rhai_fn(name = "to_int", return_raw)] pub fn f32_to_int(x: f32) -> RhaiResultOf { - if cfg!(not(feature = "unchecked")) && x > (INT::MAX as f32) { + if cfg!(not(feature = "unchecked")) && (x > (INT::MAX as f32) || x < (INT::MIN as f32)) { Err( ERR::ErrorArithmetic(format!("Integer overflow: to_int({})", x), Position::NONE) .into(), @@ -323,10 +323,10 @@ mod float_functions { Ok(x.trunc() as INT) } } - /// Return the integral part of the floating-point number. + /// Convert the floating-point number into an integer. #[rhai_fn(name = "to_int", return_raw)] pub fn f64_to_int(x: f64) -> RhaiResultOf { - if cfg!(not(feature = "unchecked")) && x > (INT::MAX as f64) { + if cfg!(not(feature = "unchecked")) && (x > (INT::MAX as f64) || x < (INT::MIN as f64)) { Err( ERR::ErrorArithmetic(format!("Integer overflow: to_int({})", x), Position::NONE) .into(), @@ -365,6 +365,7 @@ mod float_functions { #[cfg(feature = "decimal")] #[export_module] mod decimal_functions { + use num_traits::ToPrimitive; use rust_decimal::{ prelude::{FromStr, RoundingStrategy}, Decimal, MathematicalOps, @@ -553,6 +554,30 @@ mod decimal_functions { Ok(x.round_dp_with_strategy(digits as u32, RoundingStrategy::MidpointTowardZero)) } + /// Convert the decimal number into an integer. + #[rhai_fn(return_raw)] + pub fn to_int(x: Decimal) -> RhaiResultOf { + let n = x.to_i64().and_then(|n| { + #[cfg(feature = "only_i32")] + return if n > (INT::MAX as i64) || n < (INT::MIN as i64) { + None + } else { + Some(n as i32) + }; + + #[cfg(not(feature = "only_i32"))] + return Some(n); + }); + + match n { + Some(n) => Ok(n), + _ => Err(ERR::ErrorArithmetic( + format!("Integer overflow: to_int({})", x), + Position::NONE, + ) + .into()), + } + } /// Return the integral part of the decimal number. #[rhai_fn(name = "int", get = "int")] pub fn int(x: Decimal) -> Decimal { From 04df4d254757c16aae8d5aeded7b4df4a15ee0ee Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Tue, 17 May 2022 11:06:34 +0800 Subject: [PATCH 05/19] Fix indexing parsing. --- CHANGELOG.md | 1 + src/ast/expr.rs | 12 +--- src/parser.rs | 171 ++++++++++++++++++++++-------------------------- 3 files changed, 84 insertions(+), 100 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0615876e..c0a03bf8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ Bug fixes * Self-contained `AST` now works properly with `Engine::call_fn`. * Missing `to_int` from `Decimal` is added. +* Parsing of index expressions is relaxed and many cases no longer result in an index-type error to allow for custom indexers. Deprecated API's ---------------- diff --git a/src/ast/expr.rs b/src/ast/expr.rs index 1ec892a0..263b4eca 100644 --- a/src/ast/expr.rs +++ b/src/ast/expr.rs @@ -799,6 +799,8 @@ impl Expr { match token { #[cfg(not(feature = "no_object"))] Token::Period => return true, + #[cfg(not(feature = "no_index"))] + Token::LeftBracket => return true, _ => (), } @@ -823,15 +825,9 @@ impl Expr { | Self::Index(..) | Self::Array(..) | Self::Map(..) - | Self::Custom(..) => match token { - #[cfg(not(feature = "no_index"))] - Token::LeftBracket => true, - _ => false, - }, + | Self::Custom(..) => false, Self::Variable(..) => match token { - #[cfg(not(feature = "no_index"))] - Token::LeftBracket => true, Token::LeftParen => true, Token::Unit => true, Token::Bang => true, @@ -840,8 +836,6 @@ impl Expr { }, Self::Property(..) => match token { - #[cfg(not(feature = "no_index"))] - Token::LeftBracket => true, Token::LeftParen => true, _ => false, }, diff --git a/src/parser.rs b/src/parser.rs index 758cb63a..5ab4b0b1 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -631,6 +631,7 @@ impl Engine { state: &mut ParseState, lib: &mut FnLib, lhs: Expr, + chained: bool, settings: ParseSettings, ) -> ParseResult { #[cfg(not(feature = "unchecked"))] @@ -640,113 +641,100 @@ impl Engine { let idx_expr = self.parse_expr(input, state, lib, settings.level_up())?; - // Check type of indexing - must be integer or string - match idx_expr { - Expr::IntegerConstant(.., pos) => match lhs { - Expr::IntegerConstant(..) - | Expr::Array(..) - | Expr::StringConstant(..) - | Expr::InterpolatedString(..) => (), + // Check types of indexing that cannot be overridden + // - arrays, maps, strings, bit-fields + match lhs { + _ if chained => (), - Expr::Map(..) => { + Expr::Map(..) => match idx_expr { + // lhs[int] + Expr::IntegerConstant(..) => { return Err(PERR::MalformedIndexExpr( - "Object map access expects string index, not a number".into(), - ) - .into_err(pos)) - } - - #[cfg(not(feature = "no_float"))] - Expr::FloatConstant(..) => { - return Err(PERR::MalformedIndexExpr( - "Only arrays, object maps and strings can be indexed".into(), - ) - .into_err(lhs.start_position())) - } - - Expr::CharConstant(..) - | Expr::And(..) - | Expr::Or(..) - | Expr::BoolConstant(..) - | Expr::Unit(..) => { - return Err(PERR::MalformedIndexExpr( - "Only arrays, object maps and strings can be indexed".into(), - ) - .into_err(lhs.start_position())) - } - - _ => (), - }, - - // lhs[string] - Expr::StringConstant(..) | Expr::InterpolatedString(..) => match lhs { - Expr::Map(..) => (), - - Expr::Array(..) | Expr::StringConstant(..) | Expr::InterpolatedString(..) => { - return Err(PERR::MalformedIndexExpr( - "Array or string expects numeric index, not a string".into(), + "Object map expects string index, not a number".into(), ) .into_err(idx_expr.start_position())) } + // lhs[string] + Expr::StringConstant(..) | Expr::InterpolatedString(..) => (), + + // lhs[float] #[cfg(not(feature = "no_float"))] Expr::FloatConstant(..) => { return Err(PERR::MalformedIndexExpr( - "Only arrays, object maps and strings can be indexed".into(), + "Object map expects string index, not a float".into(), ) - .into_err(lhs.start_position())) + .into_err(idx_expr.start_position())) } - - Expr::CharConstant(..) - | Expr::And(..) - | Expr::Or(..) - | Expr::BoolConstant(..) - | Expr::Unit(..) => { + // lhs[char] + Expr::CharConstant(..) => { return Err(PERR::MalformedIndexExpr( - "Only arrays, object maps and strings can be indexed".into(), + "Object map expects string index, not a character".into(), ) - .into_err(lhs.start_position())) + .into_err(idx_expr.start_position())) + } + // lhs[()] + Expr::Unit(..) => { + return Err(PERR::MalformedIndexExpr( + "Object map expects string index, not ()".into(), + ) + .into_err(idx_expr.start_position())) + } + // lhs[??? && ???], lhs[??? || ???], lhs[true], lhs[false] + Expr::And(..) | Expr::Or(..) | Expr::BoolConstant(..) => { + return Err(PERR::MalformedIndexExpr( + "Object map expects string index, not a boolean".into(), + ) + .into_err(idx_expr.start_position())) } - _ => (), }, - // lhs[float] - #[cfg(not(feature = "no_float"))] - x @ Expr::FloatConstant(..) => { - return Err(PERR::MalformedIndexExpr( - "Array access expects integer index, not a float".into(), - ) - .into_err(x.start_position())) - } - // lhs[char] - x @ Expr::CharConstant(..) => { - return Err(PERR::MalformedIndexExpr( - "Array access expects integer index, not a character".into(), - ) - .into_err(x.start_position())) - } - // lhs[()] - x @ Expr::Unit(..) => { - return Err(PERR::MalformedIndexExpr( - "Array access expects integer index, not ()".into(), - ) - .into_err(x.start_position())) - } - // lhs[??? && ???], lhs[??? || ???] - x @ Expr::And(..) | x @ Expr::Or(..) => { - return Err(PERR::MalformedIndexExpr( - "Array access expects integer index, not a boolean".into(), - ) - .into_err(x.start_position())) - } - // lhs[true], lhs[false] - x @ Expr::BoolConstant(..) => { - return Err(PERR::MalformedIndexExpr( - "Array access expects integer index, not a boolean".into(), - ) - .into_err(x.start_position())) - } - // All other expressions + Expr::IntegerConstant(..) + | Expr::Array(..) + | Expr::StringConstant(..) + | Expr::InterpolatedString(..) => match idx_expr { + // lhs[int] + Expr::IntegerConstant(..) => (), + + // lhs[string] + Expr::StringConstant(..) | Expr::InterpolatedString(..) => { + return Err(PERR::MalformedIndexExpr( + "Array, string or bit-field expects numeric index, not a string".into(), + ) + .into_err(idx_expr.start_position())) + } + // lhs[float] + #[cfg(not(feature = "no_float"))] + Expr::FloatConstant(..) => { + return Err(PERR::MalformedIndexExpr( + "Array, string or bit-field expects integer index, not a float".into(), + ) + .into_err(idx_expr.start_position())) + } + // lhs[char] + Expr::CharConstant(..) => { + return Err(PERR::MalformedIndexExpr( + "Array, string or bit-field expects integer index, not a character".into(), + ) + .into_err(idx_expr.start_position())) + } + // lhs[()] + Expr::Unit(..) => { + return Err(PERR::MalformedIndexExpr( + "Array, string or bit-field expects integer index, not ()".into(), + ) + .into_err(idx_expr.start_position())) + } + // lhs[??? && ???], lhs[??? || ???], lhs[true], lhs[false] + Expr::And(..) | Expr::Or(..) | Expr::BoolConstant(..) => { + return Err(PERR::MalformedIndexExpr( + "Array, string or bit-field expects integer index, not a boolean".into(), + ) + .into_err(idx_expr.start_position())) + } + _ => (), + }, _ => (), } @@ -767,6 +755,7 @@ impl Engine { state, lib, idx_expr, + true, settings.level_up(), )?; // Indexing binds to right @@ -1628,7 +1617,7 @@ impl Engine { // Indexing #[cfg(not(feature = "no_index"))] (expr, Token::LeftBracket) => { - self.parse_index_chain(input, state, lib, expr, settings.level_up())? + self.parse_index_chain(input, state, lib, expr, false, settings.level_up())? } // Property access #[cfg(not(feature = "no_object"))] From 591f7d73623119579e251fd3f878a512138d4aeb Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Tue, 17 May 2022 15:20:32 +0800 Subject: [PATCH 06/19] Add tests for index type checks. --- tests/arrays.rs | 49 +++++++++++++++++++++++++++++++++++++++++++++++- tests/maps.rs | 50 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 98 insertions(+), 1 deletion(-) diff --git a/tests/arrays.rs b/tests/arrays.rs index e0dc764d..fb6e4a4d 100644 --- a/tests/arrays.rs +++ b/tests/arrays.rs @@ -1,5 +1,5 @@ #![cfg(not(feature = "no_index"))] -use rhai::{Array, Dynamic, Engine, EvalAltResult, INT}; +use rhai::{Array, Dynamic, Engine, EvalAltResult, ParseErrorType, INT}; #[test] fn test_arrays() -> Result<(), Box> { @@ -172,6 +172,53 @@ fn test_arrays() -> Result<(), Box> { Ok(()) } +#[test] +fn test_array_index_types() -> Result<(), Box> { + let engine = Engine::new(); + + engine.compile("[1, 2, 3][0]['x']")?; + + assert!(matches!( + *engine + .compile("[1, 2, 3]['x']") + .expect_err("should error") + .0, + ParseErrorType::MalformedIndexExpr(..) + )); + + #[cfg(not(feature = "no_float"))] + assert!(matches!( + *engine + .compile("[1, 2, 3][123.456]") + .expect_err("should error") + .0, + ParseErrorType::MalformedIndexExpr(..) + )); + + assert!(matches!( + *engine.compile("[1, 2, 3][()]").expect_err("should error").0, + ParseErrorType::MalformedIndexExpr(..) + )); + + assert!(matches!( + *engine + .compile(r#"[1, 2, 3]["hello"]"#) + .expect_err("should error") + .0, + ParseErrorType::MalformedIndexExpr(..) + )); + + assert!(matches!( + *engine + .compile("[1, 2, 3][true && false]") + .expect_err("should error") + .0, + ParseErrorType::MalformedIndexExpr(..) + )); + + Ok(()) +} + #[test] #[cfg(not(feature = "no_object"))] fn test_array_with_structs() -> Result<(), Box> { diff --git a/tests/maps.rs b/tests/maps.rs index 9a4414f1..daf8b051 100644 --- a/tests/maps.rs +++ b/tests/maps.rs @@ -124,6 +124,56 @@ fn test_map_prop() -> Result<(), Box> { Ok(()) } +#[test] +fn test_map_index_types() -> Result<(), Box> { + let engine = Engine::new(); + + engine.compile(r#"#{a:1, b:2, c:3}["a"]['x']"#)?; + + assert!(matches!( + *engine + .compile("#{a:1, b:2, c:3}['x']") + .expect_err("should error") + .0, + ParseErrorType::MalformedIndexExpr(..) + )); + + assert!(matches!( + *engine + .compile("#{a:1, b:2, c:3}[1]") + .expect_err("should error") + .0, + ParseErrorType::MalformedIndexExpr(..) + )); + + #[cfg(not(feature = "no_float"))] + assert!(matches!( + *engine + .compile("#{a:1, b:2, c:3}[123.456]") + .expect_err("should error") + .0, + ParseErrorType::MalformedIndexExpr(..) + )); + + assert!(matches!( + *engine + .compile("#{a:1, b:2, c:3}[()]") + .expect_err("should error") + .0, + ParseErrorType::MalformedIndexExpr(..) + )); + + assert!(matches!( + *engine + .compile("#{a:1, b:2, c:3}[true && false]") + .expect_err("should error") + .0, + ParseErrorType::MalformedIndexExpr(..) + )); + + Ok(()) +} + #[test] fn test_map_assign() -> Result<(), Box> { let engine = Engine::new(); From 7c8c6659aeece1c9e806905c4f6c7c4b24b45db6 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Tue, 17 May 2022 16:21:17 +0800 Subject: [PATCH 07/19] Better encapsulate EvalContext. --- src/api/custom_syntax.rs | 29 ---------------- src/eval/debugger.rs | 10 +----- src/eval/eval_context.rs | 71 +++++++++++++++++++++++++++++++++------- src/eval/expr.rs | 21 ++---------- src/eval/stmt.rs | 10 +----- 5 files changed, 64 insertions(+), 77 deletions(-) diff --git a/src/api/custom_syntax.rs b/src/api/custom_syntax.rs index 76b96443..3982aee6 100644 --- a/src/api/custom_syntax.rs +++ b/src/api/custom_syntax.rs @@ -1,7 +1,6 @@ //! Module implementing custom syntax for [`Engine`]. use crate::ast::Expr; -use crate::eval::Caches; use crate::func::native::SendSync; use crate::parser::ParseResult; use crate::tokenizer::{is_valid_identifier, Token}; @@ -140,34 +139,6 @@ impl Deref for Expression<'_> { } } -impl EvalContext<'_, '_, '_, '_, '_, '_, '_, '_> { - /// Evaluate an [expression tree][Expression] within this [evaluation context][`EvalContext`]. - /// - /// # WARNING - Low Level API - /// - /// This function is very low level. It evaluates an expression from an [`AST`][crate::AST]. - #[inline(always)] - pub fn eval_expression_tree(&mut self, expr: &Expression) -> RhaiResult { - let mut caches; - - self.engine.eval_expr( - self.scope, - self.global, - match self.caches.as_mut() { - Some(c) => c, - None => { - caches = Caches::new(); - &mut caches - } - }, - self.lib, - self.this_ptr, - expr, - self.level, - ) - } -} - /// Definition of a custom syntax definition. pub struct CustomSyntax { /// A parsing function to return the next token in a custom syntax based on the diff --git a/src/eval/debugger.rs b/src/eval/debugger.rs index a9ece2a5..a6344a85 100644 --- a/src/eval/debugger.rs +++ b/src/eval/debugger.rs @@ -496,15 +496,7 @@ impl Engine { Some(source.as_str()) }; - let context = crate::EvalContext { - engine: self, - scope, - global, - caches: None, - lib, - this_ptr, - level, - }; + let context = crate::EvalContext::new(self, scope, global, None, lib, this_ptr, level); if let Some((.., ref on_debugger)) = self.debugger { let command = on_debugger(context, event, node, source, node.position())?; diff --git a/src/eval/eval_context.rs b/src/eval/eval_context.rs index ed93f8ff..42041fb7 100644 --- a/src/eval/eval_context.rs +++ b/src/eval/eval_context.rs @@ -1,7 +1,7 @@ //! Evaluation context. use super::{Caches, GlobalRuntimeState}; -use crate::{Dynamic, Engine, Module, Scope}; +use crate::{Dynamic, Engine, Expression, Module, RhaiResult, Scope}; #[cfg(feature = "no_std")] use std::prelude::v1::*; @@ -9,26 +9,48 @@ use std::prelude::v1::*; #[derive(Debug)] pub struct EvalContext<'a, 's, 'ps, 'g, 'pg, 'c, 't, 'pt> { /// The current [`Engine`]. - pub(crate) engine: &'a Engine, + engine: &'a Engine, /// The current [`Scope`]. - pub(crate) scope: &'s mut Scope<'ps>, + scope: &'s mut Scope<'ps>, /// The current [`GlobalRuntimeState`]. - pub(crate) global: &'g mut GlobalRuntimeState<'pg>, + global: &'g mut GlobalRuntimeState<'pg>, /// The current [caches][Caches], if available. - pub(crate) caches: Option<&'c mut Caches>, + caches: Option<&'c mut Caches>, /// The current stack of imported [modules][Module]. - pub(crate) lib: &'a [&'a Module], + lib: &'a [&'a Module], /// The current bound `this` pointer, if any. - pub(crate) this_ptr: &'t mut Option<&'pt mut Dynamic>, + this_ptr: &'t mut Option<&'pt mut Dynamic>, /// The current nesting level of function calls. - pub(crate) level: usize, + level: usize, } -impl<'s, 'ps, 'g, 'pg, 'pt> EvalContext<'_, 's, 'ps, 'g, 'pg, '_, '_, 'pt> { +impl<'a, 's, 'ps, 'g, 'pg, 'c, 't, 'pt> EvalContext<'a, 's, 'ps, 'g, 'pg, 'c, 't, 'pt> { + /// Create a new [`EvalContext`]. + #[inline(always)] + #[must_use] + pub fn new( + engine: &'a Engine, + scope: &'s mut Scope<'ps>, + global: &'g mut GlobalRuntimeState<'pg>, + caches: Option<&'c mut Caches>, + lib: &'a [&'a Module], + this_ptr: &'t mut Option<&'pt mut Dynamic>, + level: usize, + ) -> Self { + Self { + engine, + scope, + global, + caches, + lib, + this_ptr, + level, + } + } /// The current [`Engine`]. #[inline(always)] #[must_use] - pub const fn engine(&self) -> &Engine { + pub const fn engine(&self) -> &'a Engine { self.engine } /// The current source. @@ -110,8 +132,8 @@ impl<'s, 'ps, 'g, 'pg, 'pt> EvalContext<'_, 's, 'ps, 'g, 'pg, '_, '_, 'pt> { /// Mutable reference to the current bound `this` pointer, if any. #[inline(always)] #[must_use] - pub fn this_ptr_mut(&mut self) -> Option<&mut &'pt mut Dynamic> { - self.this_ptr.as_mut() + pub fn this_ptr_mut(&mut self) -> &mut Option<&'pt mut Dynamic> { + &mut self.this_ptr } /// The current nesting level of function calls. #[inline(always)] @@ -119,4 +141,29 @@ impl<'s, 'ps, 'g, 'pg, 'pt> EvalContext<'_, 's, 'ps, 'g, 'pg, '_, '_, 'pt> { pub const fn call_level(&self) -> usize { self.level } + + /// Evaluate an [expression tree][Expression] within this [evaluation context][`EvalContext`]. + /// + /// # WARNING - Low Level API + /// + /// This function is very low level. It evaluates an expression from an [`AST`][crate::AST]. + #[inline(always)] + pub fn eval_expression_tree(&mut self, expr: &Expression) -> RhaiResult { + let mut new_caches = Caches::new(); + + let caches = match self.caches.as_mut() { + Some(c) => c, + None => &mut new_caches, + }; + + self.engine.eval_expr( + self.scope, + self.global, + caches, + self.lib, + self.this_ptr, + expr, + self.level, + ) + } } diff --git a/src/eval/expr.rs b/src/eval/expr.rs index 6785bbbc..d0dce935 100644 --- a/src/eval/expr.rs +++ b/src/eval/expr.rs @@ -151,15 +151,7 @@ impl Engine { // Check the variable resolver, if any if let Some(ref resolve_var) = self.resolve_var { - let context = EvalContext { - engine: self, - scope, - global, - caches: None, - lib, - this_ptr, - level, - }; + let context = EvalContext::new(self, scope, global, None, lib, this_ptr, level); let var_name = expr.get_variable_name(true).expect("`Expr::Variable`"); match resolve_var(var_name, index, context) { Ok(Some(mut result)) => { @@ -480,15 +472,8 @@ impl Engine { *pos, )) })?; - let mut context = EvalContext { - engine: self, - scope, - global, - caches: Some(caches), - lib, - this_ptr, - level, - }; + let mut context = + EvalContext::new(self, scope, global, Some(caches), lib, this_ptr, level); let result = (custom_def.func)(&mut context, &expressions); diff --git a/src/eval/stmt.rs b/src/eval/stmt.rs index 6b3b1dea..b8d11bd7 100644 --- a/src/eval/stmt.rs +++ b/src/eval/stmt.rs @@ -831,15 +831,7 @@ impl Engine { nesting_level, will_shadow, }; - let context = EvalContext { - engine: self, - scope, - global, - caches: None, - lib, - this_ptr, - level, - }; + let context = EvalContext::new(self, scope, global, None, lib, this_ptr, level); match filter(true, info, context) { Ok(true) => None, From a53bcc2e1db7d5c78e82882b9ae88f2a71996961 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Thu, 19 May 2022 10:02:12 +0800 Subject: [PATCH 08/19] Add EvalAltResult::IndexNotFound. --- CHANGELOG.md | 5 +++++ src/types/error.rs | 39 ++++++++++++++++++++++++--------------- tests/get_set.rs | 9 ++++----- 3 files changed, 33 insertions(+), 20 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c0a03bf8..0cb30e7c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,11 @@ Deprecated API's * `FnPtr::num_curried` is deprecated in favor of `FnPtr::curry().len()`. +Enhancements +------------ + +* `EvalAltResult::IndexNotFound` is added to aid in raising errors for indexers. + Version 1.7.0 ============= diff --git a/src/types/error.rs b/src/types/error.rs index 10dfe851..4d307daf 100644 --- a/src/types/error.rs +++ b/src/types/error.rs @@ -42,6 +42,8 @@ pub enum EvalAltResult { ErrorVariableNotFound(String, Position), /// Access of an unknown object map property. Wrapped value is the property name. ErrorPropertyNotFound(String, Position), + /// Access of an invalid index. Wrapped value is the index name. + ErrorIndexNotFound(Dynamic, Position), /// Call to an unknown function. Wrapped value is the function signature. ErrorFunctionNotFound(String, Position), /// Usage of an unknown [module][crate::Module]. Wrapped value is the [module][crate::Module] name. @@ -149,10 +151,11 @@ impl fmt::Display for EvalAltResult { } Self::ErrorInModule(s, err, ..) => write!(f, "Error in module '{}' > {}", s, err)?, - Self::ErrorVariableExists(s, ..) => write!(f, "Variable is already defined: {}", s)?, + Self::ErrorVariableExists(s, ..) => write!(f, "Variable already defined: {}", s)?, Self::ErrorForbiddenVariable(s, ..) => write!(f, "Forbidden variable name: {}", s)?, Self::ErrorVariableNotFound(s, ..) => write!(f, "Variable not found: {}", s)?, Self::ErrorPropertyNotFound(s, ..) => write!(f, "Property not found: {}", s)?, + Self::ErrorIndexNotFound(s, ..) => write!(f, "Invalid index: {}", s)?, Self::ErrorFunctionNotFound(s, ..) => write!(f, "Function not found: {}", s)?, Self::ErrorModuleNotFound(s, ..) => write!(f, "Module not found: {}", s)?, Self::ErrorDataRace(s, ..) => { @@ -162,9 +165,9 @@ impl fmt::Display for EvalAltResult { "" => f.write_str("Malformed dot expression"), s => f.write_str(s), }?, - Self::ErrorIndexingType(s, ..) => write!(f, "Indexer not registered: {}", s)?, - Self::ErrorUnboundThis(..) => f.write_str("'this' is not bound")?, - Self::ErrorFor(..) => f.write_str("For loop expects a type that is iterable")?, + Self::ErrorIndexingType(s, ..) => write!(f, "Indexer unavailable: {}", s)?, + Self::ErrorUnboundThis(..) => f.write_str("'this' not bound")?, + Self::ErrorFor(..) => f.write_str("For loop expects an iterable type")?, Self::ErrorTooManyOperations(..) => f.write_str("Too many operations")?, Self::ErrorTooManyModules(..) => f.write_str("Too many modules imported")?, Self::ErrorStackOverflow(..) => f.write_str("Stack overflow")?, @@ -181,14 +184,14 @@ impl fmt::Display for EvalAltResult { Self::ErrorAssignmentToConstant(s, ..) => write!(f, "Cannot modify constant: {}", s)?, Self::ErrorMismatchOutputType(e, a, ..) => match (a.as_str(), e.as_str()) { - ("", e) => write!(f, "Output type is incorrect, expecting {}", e), - (a, "") => write!(f, "Output type is incorrect: {}", a), - (a, e) => write!(f, "Output type is incorrect: {} (expecting {})", a, e), + ("", e) => write!(f, "Output type incorrect, expecting {}", e), + (a, "") => write!(f, "Output type incorrect: {}", a), + (a, e) => write!(f, "Output type incorrect: {} (expecting {})", a, e), }?, Self::ErrorMismatchDataType(e, a, ..) => match (a.as_str(), e.as_str()) { - ("", e) => write!(f, "Data type is incorrect, expecting {}", e), - (a, "") => write!(f, "Data type is incorrect: {}", a), - (a, e) => write!(f, "Data type is incorrect: {} (expecting {})", a, e), + ("", e) => write!(f, "Data type incorrect, expecting {}", e), + (a, "") => write!(f, "Data type incorrect: {}", a), + (a, e) => write!(f, "Data type incorrect: {} (expecting {})", a, e), }?, Self::ErrorArithmetic(s, ..) => match s.as_str() { "" => f.write_str("Arithmetic error"), @@ -204,12 +207,12 @@ impl fmt::Display for EvalAltResult { 0 => write!(f, "Array index {} out of bounds: array is empty", index), 1 => write!( f, - "Array index {} out of bounds: only 1 element in the array", + "Array index {} out of bounds: only 1 element in array", index ), _ => write!( f, - "Array index {} out of bounds: only {} elements in the array", + "Array index {} out of bounds: only {} elements in array", index, max ), }?, @@ -217,18 +220,18 @@ impl fmt::Display for EvalAltResult { 0 => write!(f, "String index {} out of bounds: string is empty", index), 1 => write!( f, - "String index {} out of bounds: only 1 character in the string", + "String index {} out of bounds: only 1 character in string", index ), _ => write!( f, - "String index {} out of bounds: only {} characters in the string", + "String index {} out of bounds: only {} characters in string", index, max ), }?, Self::ErrorBitFieldBounds(max, index, ..) => write!( f, - "Bit-field index {} out of bounds: only {} bits in the bit-field", + "Bit-field index {} out of bounds: only {} bits in bit-field", index, max )?, Self::ErrorDataTooLarge(typ, ..) => write!(f, "{} exceeds maximum limit", typ)?, @@ -291,6 +294,7 @@ impl EvalAltResult { | Self::ErrorForbiddenVariable(..) | Self::ErrorVariableNotFound(..) | Self::ErrorPropertyNotFound(..) + | Self::ErrorIndexNotFound(..) | Self::ErrorModuleNotFound(..) | Self::ErrorDataRace(..) | Self::ErrorAssignmentToConstant(..) @@ -388,6 +392,9 @@ impl EvalAltResult { | Self::ErrorAssignmentToConstant(v, ..) => { map.insert("variable".into(), v.into()); } + Self::ErrorIndexNotFound(v, ..) => { + map.insert("index".into(), v.clone()); + } Self::ErrorModuleNotFound(m, ..) => { map.insert("module".into(), m.into()); } @@ -448,6 +455,7 @@ impl EvalAltResult { | Self::ErrorForbiddenVariable(.., pos) | Self::ErrorVariableNotFound(.., pos) | Self::ErrorPropertyNotFound(.., pos) + | Self::ErrorIndexNotFound(.., pos) | Self::ErrorModuleNotFound(.., pos) | Self::ErrorDataRace(.., pos) | Self::ErrorAssignmentToConstant(.., pos) @@ -499,6 +507,7 @@ impl EvalAltResult { | Self::ErrorForbiddenVariable(.., pos) | Self::ErrorVariableNotFound(.., pos) | Self::ErrorPropertyNotFound(.., pos) + | Self::ErrorIndexNotFound(.., pos) | Self::ErrorModuleNotFound(.., pos) | Self::ErrorDataRace(.., pos) | Self::ErrorAssignmentToConstant(.., pos) diff --git a/tests/get_set.rs b/tests/get_set.rs index 9e428f30..c7efc3e1 100644 --- a/tests/get_set.rs +++ b/tests/get_set.rs @@ -314,14 +314,13 @@ fn test_get_set_indexer() -> Result<(), Box> { .register_type_with_name::("MyMap") .register_fn("new_map", || MyMap::new()) .register_indexer_get_result(|map: &mut MyMap, index: &str| { - map.get(index) - .cloned() - .ok_or_else(|| format!("Index `{}` not found!", index).into()) + map.get(index).cloned().ok_or_else(|| { + EvalAltResult::ErrorIndexNotFound(index.into(), rhai::Position::NONE).into() + }) }) .register_indexer_set(|map: &mut MyMap, index: &str, value: INT| { map.insert(index.to_string(), value); - }) - .register_fn("to_string", |map: &mut MyMap| format!("{:?}", map)); + }); assert_eq!( engine.eval::( From 47d0d014e32d59665102d501c92908fcb2899732 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Thu, 19 May 2022 14:32:43 +0800 Subject: [PATCH 09/19] Reduce cloning. --- src/packages/array_basic.rs | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/src/packages/array_basic.rs b/src/packages/array_basic.rs index c7556865..273f42bf 100644 --- a/src/packages/array_basic.rs +++ b/src/packages/array_basic.rs @@ -639,15 +639,15 @@ pub mod array_functions { /// /// print(y); // prints "[0, 2, 6, 12, 20]" /// ``` - #[rhai_fn(return_raw, pure)] - pub fn map(ctx: NativeCallContext, array: &mut Array, mapper: FnPtr) -> RhaiResultOf { + #[rhai_fn(return_raw)] + pub fn map(ctx: NativeCallContext, array: Array, mapper: FnPtr) -> RhaiResultOf { if array.is_empty() { - return Ok(array.clone()); + return Ok(array); } let mut ar = Array::with_capacity(array.len()); - for (i, item) in array.iter().enumerate() { + for (i, item) in array.into_iter().enumerate() { ar.push( mapper .call_raw(&ctx, None, [item.clone()]) @@ -655,7 +655,7 @@ pub mod array_functions { ERR::ErrorFunctionNotFound(fn_sig, ..) if fn_sig.starts_with(mapper.fn_name()) => { - mapper.call_raw(&ctx, None, [item.clone(), (i as INT).into()]) + mapper.call_raw(&ctx, None, [item, (i as INT).into()]) } _ => Err(err), }) @@ -699,10 +699,10 @@ pub mod array_functions { /// /// print(y); // prints "[0, 2, 6, 12, 20]" /// ``` - #[rhai_fn(name = "map", return_raw, pure)] + #[rhai_fn(name = "map", return_raw)] pub fn map_by_fn_name( ctx: NativeCallContext, - array: &mut Array, + array: Array, mapper: &str, ) -> RhaiResultOf { map(ctx, array, FnPtr::new(mapper)?) @@ -729,15 +729,15 @@ pub mod array_functions { /// /// print(y); // prints "[12, 20]" /// ``` - #[rhai_fn(return_raw, pure)] - pub fn filter(ctx: NativeCallContext, array: &mut Array, filter: FnPtr) -> RhaiResultOf { + #[rhai_fn(return_raw)] + pub fn filter(ctx: NativeCallContext, array: Array, filter: FnPtr) -> RhaiResultOf { if array.is_empty() { - return Ok(array.clone()); + return Ok(array); } let mut ar = Array::new(); - for (i, item) in array.iter().enumerate() { + for (i, item) in array.into_iter().enumerate() { if filter .call_raw(&ctx, None, [item.clone()]) .or_else(|err| match *err { @@ -759,7 +759,7 @@ pub mod array_functions { .as_bool() .unwrap_or(false) { - ar.push(item.clone()); + ar.push(item); } } @@ -790,10 +790,10 @@ pub mod array_functions { /// /// print(y); // prints "[12, 20]" /// ``` - #[rhai_fn(name = "filter", return_raw, pure)] + #[rhai_fn(name = "filter", return_raw)] pub fn filter_by_fn_name( ctx: NativeCallContext, - array: &mut Array, + array: Array, filter_func: &str, ) -> RhaiResultOf { filter(ctx, array, FnPtr::new(filter_func)?) From dd8c18369bacbc9e4923bf594d4b3c92a892fdb5 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Thu, 19 May 2022 14:36:58 +0800 Subject: [PATCH 10/19] Use call_native_fn. --- src/eval/chaining.rs | 45 +++++++++++++++++++++----------------------- 1 file changed, 21 insertions(+), 24 deletions(-) diff --git a/src/eval/chaining.rs b/src/eval/chaining.rs index 22e1d8b5..2d9899c5 100644 --- a/src/eval/chaining.rs +++ b/src/eval/chaining.rs @@ -335,12 +335,11 @@ impl Engine { let (mut new_val, op_info) = new_val.expect("`Some`"); if op_info.is_op_assignment() { - let hash = crate::ast::FnCallHashes::from_native(*hash_get); let args = &mut [target.as_mut()]; let (mut orig_val, ..) = self - .exec_fn_call( - None, global, caches, lib, getter, hash, args, is_ref_mut, - true, *pos, level, + .call_native_fn( + global, caches, lib, getter, *hash_get, args, is_ref_mut, + false, *pos, level, ) .or_else(|err| match *err { // Try an indexer if property does not exist @@ -371,10 +370,9 @@ impl Engine { new_val = orig_val; } - let hash = crate::ast::FnCallHashes::from_native(*hash_set); let args = &mut [target.as_mut(), &mut new_val]; - self.exec_fn_call( - None, global, caches, lib, setter, hash, args, is_ref_mut, true, *pos, + self.call_native_fn( + global, caches, lib, setter, *hash_set, args, is_ref_mut, false, *pos, level, ) .or_else(|err| match *err { @@ -399,10 +397,9 @@ impl Engine { self.run_debugger(scope, global, lib, this_ptr, rhs, level)?; let ((getter, hash_get), _, name) = x.as_ref(); - let hash = crate::ast::FnCallHashes::from_native(*hash_get); let args = &mut [target.as_mut()]; - self.exec_fn_call( - None, global, caches, lib, getter, hash, args, is_ref_mut, true, *pos, + self.call_native_fn( + global, caches, lib, getter, *hash_get, args, is_ref_mut, false, *pos, level, ) .map_or_else( @@ -488,16 +485,14 @@ impl Engine { let ((getter, hash_get), (setter, hash_set), name) = p.as_ref(); let rhs_chain = rhs.into(); - let hash_get = crate::ast::FnCallHashes::from_native(*hash_get); - let hash_set = crate::ast::FnCallHashes::from_native(*hash_set); let mut arg_values = [target.as_mut(), &mut Dynamic::UNIT.clone()]; let args = &mut arg_values[..1]; // Assume getters are always pure let (mut val, ..) = self - .exec_fn_call( - None, global, caches, lib, getter, hash_get, args, - is_ref_mut, true, pos, level, + .call_native_fn( + global, caches, lib, getter, *hash_get, args, is_ref_mut, + false, pos, level, ) .or_else(|err| match *err { // Try an indexer if property does not exist @@ -531,9 +526,9 @@ impl Engine { // Re-use args because the first &mut parameter will not be consumed let mut arg_values = [target.as_mut(), val.as_mut()]; let args = &mut arg_values; - self.exec_fn_call( - None, global, caches, lib, setter, hash_set, args, - is_ref_mut, true, pos, level, + self.call_native_fn( + global, caches, lib, setter, *hash_set, args, is_ref_mut, + false, pos, level, ) .or_else( |err| match *err { @@ -813,12 +808,13 @@ impl Engine { level: usize, ) -> RhaiResultOf { let args = &mut [target, idx]; - let hash_get = crate::ast::FnCallHashes::from_native(global.hash_idx_get()); + let hash = global.hash_idx_get(); let fn_name = crate::engine::FN_IDX_GET; let pos = Position::NONE; + let level = level + 1; - self.exec_fn_call( - None, global, caches, lib, fn_name, hash_get, args, true, true, pos, level, + self.call_native_fn( + global, caches, lib, fn_name, hash, args, true, false, pos, level, ) .map(|(r, ..)| r) } @@ -837,13 +833,14 @@ impl Engine { is_ref_mut: bool, level: usize, ) -> RhaiResultOf<(Dynamic, bool)> { - let hash_set = crate::ast::FnCallHashes::from_native(global.hash_idx_set()); + let hash = global.hash_idx_set(); let args = &mut [target, idx, new_val]; let fn_name = crate::engine::FN_IDX_SET; let pos = Position::NONE; + let level = level + 1; - self.exec_fn_call( - None, global, caches, lib, fn_name, hash_set, args, is_ref_mut, true, pos, level, + self.call_native_fn( + global, caches, lib, fn_name, hash, args, is_ref_mut, false, pos, level, ) } From 857ae7a64a425e7cf9931cb83af1e7193f83af28 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Thu, 19 May 2022 14:41:48 +0800 Subject: [PATCH 11/19] Comments update. --- src/api/call_fn.rs | 2 +- src/ast/namespace.rs | 4 +-- src/func/call.rs | 72 ++++++++++++++++++++++---------------------- src/parser.rs | 27 +++++++++-------- 4 files changed, 54 insertions(+), 51 deletions(-) diff --git a/src/api/call_fn.rs b/src/api/call_fn.rs index d6290163..1f2ec085 100644 --- a/src/api/call_fn.rs +++ b/src/api/call_fn.rs @@ -187,7 +187,7 @@ impl Engine { /// /// # WARNING - Low Level API /// - /// This function is very low level. + /// This function is _extremely_ low level. /// /// A [`GlobalRuntimeState`] and [`Caches`] need to be passed into the function, which can be /// created via [`GlobalRuntimeState::new`] and [`Caches::new`]. diff --git a/src/ast/namespace.rs b/src/ast/namespace.rs index 22103d5d..9bf328dd 100644 --- a/src/ast/namespace.rs +++ b/src/ast/namespace.rs @@ -17,8 +17,8 @@ use std::{ /// /// Not available under `no_module`. /// -/// A [`u64`] offset to the current [stack of imported modules][crate::GlobalRuntimeState] is -/// cached for quick search purposes. +/// A [`u64`] offset to the current stack of imported [modules][crate::Module] in the +/// [global runtime state][crate::GlobalRuntimeState] is cached for quick search purposes. /// /// A [`StaticVec`] is used because the vast majority of namespace-qualified access contains only /// one level, and it is wasteful to always allocate a [`Vec`] with one element. diff --git a/src/func/call.rs b/src/func/call.rs index cfeebacb..edc07ae3 100644 --- a/src/func/call.rs +++ b/src/func/call.rs @@ -736,6 +736,42 @@ impl Engine { }) } + /// Evaluate an argument. + #[inline] + pub(crate) fn get_arg_value( + &self, + scope: &mut Scope, + global: &mut GlobalRuntimeState, + caches: &mut Caches, + lib: &[&Module], + this_ptr: &mut Option<&mut Dynamic>, + arg_expr: &Expr, + level: usize, + ) -> RhaiResultOf<(Dynamic, Position)> { + #[cfg(feature = "debugging")] + if self.debugger.is_some() { + if let Some(value) = arg_expr.get_literal_value() { + #[cfg(feature = "debugging")] + self.run_debugger(scope, global, lib, this_ptr, arg_expr, level)?; + return Ok((value, arg_expr.start_position())); + } + } + + // Do not match function exit for arguments + #[cfg(feature = "debugging")] + let reset_debugger = global.debugger.clear_status_if(|status| { + matches!(status, crate::eval::DebuggerStatus::FunctionExit(..)) + }); + + let result = self.eval_expr(scope, global, caches, lib, this_ptr, arg_expr, level); + + // Restore function exit status + #[cfg(feature = "debugging")] + global.debugger.reset_status(reset_debugger); + + Ok((result?, arg_expr.start_position())) + } + /// Call a dot method. #[cfg(not(feature = "no_object"))] pub(crate) fn make_method_call( @@ -892,42 +928,6 @@ impl Engine { Ok((result, updated)) } - /// Evaluate an argument. - #[inline] - pub(crate) fn get_arg_value( - &self, - scope: &mut Scope, - global: &mut GlobalRuntimeState, - caches: &mut Caches, - lib: &[&Module], - this_ptr: &mut Option<&mut Dynamic>, - arg_expr: &Expr, - level: usize, - ) -> RhaiResultOf<(Dynamic, Position)> { - #[cfg(feature = "debugging")] - if self.debugger.is_some() { - if let Some(value) = arg_expr.get_literal_value() { - #[cfg(feature = "debugging")] - self.run_debugger(scope, global, lib, this_ptr, arg_expr, level)?; - return Ok((value, arg_expr.start_position())); - } - } - - // Do not match function exit for arguments - #[cfg(feature = "debugging")] - let reset_debugger = global.debugger.clear_status_if(|status| { - matches!(status, crate::eval::DebuggerStatus::FunctionExit(..)) - }); - - let result = self.eval_expr(scope, global, caches, lib, this_ptr, arg_expr, level); - - // Restore function exit status - #[cfg(feature = "debugging")] - global.debugger.reset_status(reset_debugger); - - Ok((result?, arg_expr.start_position())) - } - /// Call a function in normal function-call style. pub(crate) fn make_function_call( &self, diff --git a/src/parser.rs b/src/parser.rs index 5ab4b0b1..6726d1fe 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -387,6 +387,7 @@ fn match_token(input: &mut TokenStream, token: Token) -> (bool, Position) { } /// Parse a variable name. +#[inline] fn parse_var_name(input: &mut TokenStream) -> ParseResult<(SmartString, Position)> { match input.next().expect(NEVER_ENDS) { // Variable name @@ -403,6 +404,7 @@ fn parse_var_name(input: &mut TokenStream) -> ParseResult<(SmartString, Position } /// Parse a symbol. +#[inline] fn parse_symbol(input: &mut TokenStream) -> ParseResult<(SmartString, Position)> { match input.next().expect(NEVER_ENDS) { // Symbol @@ -631,7 +633,7 @@ impl Engine { state: &mut ParseState, lib: &mut FnLib, lhs: Expr, - chained: bool, + check_index_type: bool, settings: ParseSettings, ) -> ParseResult { #[cfg(not(feature = "unchecked"))] @@ -644,7 +646,7 @@ impl Engine { // Check types of indexing that cannot be overridden // - arrays, maps, strings, bit-fields match lhs { - _ if chained => (), + _ if !check_index_type => (), Expr::Map(..) => match idx_expr { // lhs[int] @@ -755,7 +757,7 @@ impl Engine { state, lib, idx_expr, - true, + false, settings.level_up(), )?; // Indexing binds to right @@ -1617,7 +1619,7 @@ impl Engine { // Indexing #[cfg(not(feature = "no_index"))] (expr, Token::LeftBracket) => { - self.parse_index_chain(input, state, lib, expr, false, settings.level_up())? + self.parse_index_chain(input, state, lib, expr, true, settings.level_up())? } // Property access #[cfg(not(feature = "no_object"))] @@ -2704,15 +2706,16 @@ impl Engine { nesting_level: level, will_shadow, }; - let context = EvalContext { - engine: self, - scope: &mut state.stack, - global: &mut state.global, - caches: None, - lib: &[], - this_ptr: &mut None, + let mut this_ptr = None; + let context = EvalContext::new( + self, + &mut state.stack, + &mut state.global, + None, + &[], + &mut this_ptr, level, - }; + ); match filter(false, info, context) { Ok(true) => (), From 42d2718e2478c7fcd7be6c179c861e8e4a564ec5 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Thu, 19 May 2022 16:56:22 +0800 Subject: [PATCH 12/19] Fix test. --- tests/maps.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/maps.rs b/tests/maps.rs index daf8b051..6c3a53d0 100644 --- a/tests/maps.rs +++ b/tests/maps.rs @@ -124,6 +124,7 @@ fn test_map_prop() -> Result<(), Box> { Ok(()) } +#[cfg(not(feature = "no_index"))] #[test] fn test_map_index_types() -> Result<(), Box> { let engine = Engine::new(); From 130b93d029f1b5dba61e24ad407a8c89f8fee892 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Thu, 19 May 2022 21:40:22 +0800 Subject: [PATCH 13/19] Use bit-flags for options. --- src/api/compile.rs | 8 +-- src/api/eval.rs | 2 +- src/api/optimize.rs | 4 +- src/api/options.rs | 121 ++++++++++++++++++++------------------------ src/api/run.rs | 6 +-- src/ast/flags.rs | 2 +- src/engine.rs | 17 +++++-- src/parser.rs | 85 ++++++++++++++++++------------- 8 files changed, 124 insertions(+), 121 deletions(-) diff --git a/src/api/compile.rs b/src/api/compile.rs index ab82f90a..2250b641 100644 --- a/src/api/compile.rs +++ b/src/api/compile.rs @@ -201,11 +201,7 @@ impl Engine { scope: &Scope, scripts: impl AsRef<[S]>, ) -> ParseResult { - self.compile_with_scope_and_optimization_level( - scope, - scripts, - self.options.optimization_level, - ) + self.compile_with_scope_and_optimization_level(scope, scripts, self.optimization_level) } /// Join a list of strings and compile into an [`AST`] using own scope at a specific optimization level. /// @@ -296,6 +292,6 @@ impl Engine { let mut peekable = stream.peekable(); let mut state = ParseState::new(self, scope, tokenizer_control); - self.parse_global_expr(&mut peekable, &mut state, self.options.optimization_level) + self.parse_global_expr(&mut peekable, &mut state, self.optimization_level) } } diff --git a/src/api/eval.rs b/src/api/eval.rs index 0abb366c..aec12d99 100644 --- a/src/api/eval.rs +++ b/src/api/eval.rs @@ -67,7 +67,7 @@ impl Engine { let ast = self.compile_with_scope_and_optimization_level( scope, &[script], - self.options.optimization_level, + self.optimization_level, )?; self.eval_ast_with_scope(scope, &ast) } diff --git a/src/api/optimize.rs b/src/api/optimize.rs index dd0ca118..7589c2fd 100644 --- a/src/api/optimize.rs +++ b/src/api/optimize.rs @@ -9,7 +9,7 @@ impl Engine { /// Not available under `no_optimize`. #[inline(always)] pub fn set_optimization_level(&mut self, optimization_level: OptimizationLevel) -> &mut Self { - self.options.optimization_level = optimization_level; + self.optimization_level = optimization_level; self } @@ -20,7 +20,7 @@ impl Engine { #[inline(always)] #[must_use] pub const fn optimization_level(&self) -> OptimizationLevel { - self.options.optimization_level + self.optimization_level } /// Optimize the [`AST`] with constants defined in an external Scope. diff --git a/src/api/options.rs b/src/api/options.rs index dba11f4b..1a098926 100644 --- a/src/api/options.rs +++ b/src/api/options.rs @@ -1,62 +1,47 @@ //! Settings for [`Engine`]'s language options. -use crate::{Engine, OptimizationLevel}; +use crate::Engine; +use bitflags::bitflags; #[cfg(feature = "no_std")] use std::prelude::v1::*; -/// A type containing all language options for the [`Engine`]. -#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] -pub struct LanguageOptions { - /// Script optimization level. - pub optimization_level: OptimizationLevel, - /// Is `if`-expression allowed? - pub allow_if_expr: bool, - /// Is `switch` expression allowed? - pub allow_switch_expr: bool, - /// Is statement-expression allowed? - pub allow_stmt_expr: bool, - /// Is anonymous function allowed? - #[cfg(not(feature = "no_function"))] - pub allow_anonymous_fn: bool, - /// Is looping allowed? - pub allow_looping: bool, - /// Is variables shadowing allowed? - pub allow_shadowing: bool, - /// Strict variables mode? - pub strict_var: bool, - /// Raise error if an object map property does not exist? - /// Returns `()` if `false`. - #[cfg(not(feature = "no_object"))] - pub fail_on_invalid_map_property: bool, -} - -impl LanguageOptions { - /// Create a new [`Options`] with default values. - #[inline(always)] - pub const fn new() -> Self { - Self { - #[cfg(not(feature = "no_optimize"))] - optimization_level: OptimizationLevel::Simple, - #[cfg(feature = "no_optimize")] - optimization_level: (), - - allow_if_expr: true, - allow_switch_expr: true, - allow_stmt_expr: true, - #[cfg(not(feature = "no_function"))] - allow_anonymous_fn: true, - allow_looping: true, - strict_var: false, - allow_shadowing: true, - #[cfg(not(feature = "no_object"))] - fail_on_invalid_map_property: false, - } +bitflags! { + /// Bit-flags containing all language options for the [`Engine`]. + pub struct LangOptions: u8 { + /// Is `if`-expression allowed? + const IF_EXPR = 0b_00000001; + /// Is `switch` expression allowed? + const SWITCH_EXPR = 0b_00000010; + /// Is statement-expression allowed? + const STMT_EXPR = 0b_00000100; + /// Is anonymous function allowed? + #[cfg(not(feature = "no_function"))] + const ANON_FN = 0b_00001000; + /// Is looping allowed? + const LOOPING = 0b_00010000; + /// Is variables shadowing allowed? + const SHADOW = 0b_00100000; + /// Strict variables mode? + const STRICT_VAR = 0b_01000000; + /// Raise error if an object map property does not exist? + /// Returns `()` if `false`. + #[cfg(not(feature = "no_object"))] + const FAIL_ON_INVALID_MAP_PROPERTY = 0b_10000000; } } -impl Default for LanguageOptions { - fn default() -> Self { - Self::new() +impl LangOptions { + /// Create a new [`Options`] with default values. + #[inline(always)] + pub fn new() -> Self { + Self::IF_EXPR | Self::SWITCH_EXPR | Self::STMT_EXPR | Self::LOOPING | Self::SHADOW | { + #[cfg(not(feature = "no_function"))] + { + Self::ANON_FN + } + #[cfg(feature = "no_function")] + 0 + } } } @@ -65,34 +50,34 @@ impl Engine { /// Default is `true`. #[inline(always)] pub const fn allow_if_expression(&self) -> bool { - self.options.allow_if_expr + self.options.contains(LangOptions::IF_EXPR) } /// Set whether `if`-expression is allowed. #[inline(always)] pub fn set_allow_if_expression(&mut self, enable: bool) { - self.options.allow_if_expr = enable; + self.options.set(LangOptions::IF_EXPR, enable) } /// Is `switch` expression allowed? /// Default is `true`. #[inline(always)] pub const fn allow_switch_expression(&self) -> bool { - self.options.allow_switch_expr + self.options.contains(LangOptions::SWITCH_EXPR) } /// Set whether `switch` expression is allowed. #[inline(always)] pub fn set_allow_switch_expression(&mut self, enable: bool) { - self.options.allow_switch_expr = enable; + self.options.set(LangOptions::SWITCH_EXPR, enable); } /// Is statement-expression allowed? /// Default is `true`. #[inline(always)] pub const fn allow_statement_expression(&self) -> bool { - self.options.allow_stmt_expr + self.options.contains(LangOptions::STMT_EXPR) } /// Set whether statement-expression is allowed. #[inline(always)] pub fn set_allow_statement_expression(&mut self, enable: bool) { - self.options.allow_stmt_expr = enable; + self.options.set(LangOptions::STMT_EXPR, enable); } /// Is anonymous function allowed? /// Default is `true`. @@ -101,7 +86,7 @@ impl Engine { #[cfg(not(feature = "no_function"))] #[inline(always)] pub const fn allow_anonymous_fn(&self) -> bool { - self.options.allow_anonymous_fn + self.options.contains(LangOptions::ANON_FN) } /// Set whether anonymous function is allowed. /// @@ -109,40 +94,40 @@ impl Engine { #[cfg(not(feature = "no_function"))] #[inline(always)] pub fn set_allow_anonymous_fn(&mut self, enable: bool) { - self.options.allow_anonymous_fn = enable; + self.options.set(LangOptions::ANON_FN, enable); } /// Is looping allowed? /// Default is `true`. #[inline(always)] pub const fn allow_looping(&self) -> bool { - self.options.allow_looping + self.options.contains(LangOptions::LOOPING) } /// Set whether looping is allowed. #[inline(always)] pub fn set_allow_looping(&mut self, enable: bool) { - self.options.allow_looping = enable; + self.options.set(LangOptions::LOOPING, enable); } /// Is variables shadowing allowed? /// Default is `true`. #[inline(always)] pub const fn allow_shadowing(&self) -> bool { - self.options.allow_shadowing + self.options.contains(LangOptions::SHADOW) } /// Set whether variables shadowing is allowed. #[inline(always)] pub fn set_allow_shadowing(&mut self, enable: bool) { - self.options.allow_shadowing = enable; + self.options.set(LangOptions::SHADOW, enable); } /// Is strict variables mode enabled? /// Default is `false`. #[inline(always)] pub const fn strict_variables(&self) -> bool { - self.options.strict_var + self.options.contains(LangOptions::STRICT_VAR) } /// Set whether strict variables mode is enabled. #[inline(always)] pub fn set_strict_variables(&mut self, enable: bool) { - self.options.strict_var = enable; + self.options.set(LangOptions::STRICT_VAR, enable); } /// Raise error if an object map property does not exist? /// Default is `false`. @@ -151,7 +136,8 @@ impl Engine { #[cfg(not(feature = "no_object"))] #[inline(always)] pub const fn fail_on_invalid_map_property(&self) -> bool { - self.options.fail_on_invalid_map_property + self.options + .contains(LangOptions::FAIL_ON_INVALID_MAP_PROPERTY) } /// Set whether to raise error if an object map property does not exist. /// @@ -159,6 +145,7 @@ impl Engine { #[cfg(not(feature = "no_object"))] #[inline(always)] pub fn set_fail_on_invalid_map_property(&mut self, enable: bool) { - self.options.fail_on_invalid_map_property = enable; + self.options + .set(LangOptions::FAIL_ON_INVALID_MAP_PROPERTY, enable); } } diff --git a/src/api/run.rs b/src/api/run.rs index b79a168d..673fa19e 100644 --- a/src/api/run.rs +++ b/src/api/run.rs @@ -26,11 +26,7 @@ impl Engine { self.lex_raw(&scripts, self.token_mapper.as_ref().map(Box::as_ref)); let mut state = ParseState::new(self, scope, tokenizer_control); - let ast = self.parse( - &mut stream.peekable(), - &mut state, - self.options.optimization_level, - )?; + let ast = self.parse(&mut stream.peekable(), &mut state, self.optimization_level)?; self.run_ast_with_scope(scope, &ast) } diff --git a/src/ast/flags.rs b/src/ast/flags.rs index 34a3bbaa..837a9d80 100644 --- a/src/ast/flags.rs +++ b/src/ast/flags.rs @@ -15,7 +15,7 @@ pub enum FnAccess { } bitflags! { - /// _(internals)_ A type that holds a configuration option with bit-flags. + /// _(internals)_ Bit-flags containing [`AST`][crate::AST] node configuration options. /// Exported under the `internals` feature only. pub struct ASTFlags: u8 { /// No options for the [`AST`][crate::AST] node. diff --git a/src/engine.rs b/src/engine.rs index da83207e..8a4e8841 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -1,7 +1,7 @@ //! Main module defining the script evaluation [`Engine`]. use crate::api::custom_syntax::CustomSyntax; -use crate::api::options::LanguageOptions; +use crate::api::options::LangOptions; use crate::func::native::{ OnDebugCallback, OnDefVarCallback, OnParseTokenCallback, OnPrintCallback, OnVarCallback, }; @@ -9,7 +9,8 @@ use crate::packages::{Package, StandardPackage}; use crate::tokenizer::Token; use crate::types::dynamic::Union; use crate::{ - Dynamic, Identifier, ImmutableString, Module, Position, RhaiResult, Shared, StaticVec, + Dynamic, Identifier, ImmutableString, Module, OptimizationLevel, Position, RhaiResult, Shared, + StaticVec, }; #[cfg(feature = "no_std")] use std::prelude::v1::*; @@ -129,7 +130,10 @@ pub struct Engine { pub(crate) progress: Option>, /// Language options. - pub(crate) options: LanguageOptions, + pub(crate) options: LangOptions, + + /// Script optimization level. + pub optimization_level: OptimizationLevel, /// Max limits. #[cfg(not(feature = "unchecked"))] @@ -274,7 +278,12 @@ impl Engine { #[cfg(not(feature = "unchecked"))] progress: None, - options: LanguageOptions::new(), + options: LangOptions::new(), + + #[cfg(not(feature = "no_optimize"))] + optimization_level: OptimizationLevel::Simple, + #[cfg(feature = "no_optimize")] + optimization_level: (), #[cfg(not(feature = "unchecked"))] limits: crate::api::limits::Limits::new(), diff --git a/src/parser.rs b/src/parser.rs index 6726d1fe..0086f813 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -2,7 +2,7 @@ use crate::api::custom_syntax::{markers::*, CustomSyntax}; use crate::api::events::VarDefInfo; -use crate::api::options::LanguageOptions; +use crate::api::options::LangOptions; use crate::ast::{ ASTFlags, BinaryExpr, ConditionalStmtBlock, CustomExpr, Expr, FnCallExpr, FnCallHashes, Ident, OpAssignment, ScriptFnDef, Stmt, StmtBlock, StmtBlockContainer, SwitchCases, TryCatchBlock, @@ -215,7 +215,7 @@ struct ParseSettings { /// Is the current position inside a loop? is_breakable: bool, /// Language options in effect (overrides Engine options). - options: LanguageOptions, + options: LangOptions, /// Current expression nesting level. level: usize, /// Current position. @@ -501,7 +501,10 @@ impl Engine { #[cfg(feature = "no_function")] let relax = false; - if !relax && settings.options.strict_var && index.is_none() { + if !relax + && settings.options.contains(LangOptions::STRICT_VAR) + && index.is_none() + { return Err(PERR::ModuleUndefined(namespace.root().to_string()) .into_err(namespace.position())); } @@ -561,7 +564,10 @@ impl Engine { #[cfg(feature = "no_function")] let relax = false; - if !relax && settings.options.strict_var && index.is_none() { + if !relax + && settings.options.contains(LangOptions::STRICT_VAR) + && index.is_none() + { return Err(PERR::ModuleUndefined(namespace.root().to_string()) .into_err(namespace.position())); } @@ -1234,7 +1240,7 @@ impl Engine { } // { - block statement as expression - Token::LeftBrace if settings.options.allow_stmt_expr => { + Token::LeftBrace if settings.options.contains(LangOptions::STMT_EXPR) => { match self.parse_block(input, state, lib, settings.level_up())? { block @ Stmt::Block(..) => Expr::Stmt(Box::new(block.into())), stmt => unreachable!("Stmt::Block expected but gets {:?}", stmt), @@ -1245,19 +1251,21 @@ impl Engine { Token::LeftParen => self.parse_paren_expr(input, state, lib, settings.level_up())?, // If statement is allowed to act as expressions - Token::If if settings.options.allow_if_expr => Expr::Stmt(Box::new( + Token::If if settings.options.contains(LangOptions::IF_EXPR) => Expr::Stmt(Box::new( self.parse_if(input, state, lib, settings.level_up())? .into(), )), // Switch statement is allowed to act as expressions - Token::Switch if settings.options.allow_switch_expr => Expr::Stmt(Box::new( - self.parse_switch(input, state, lib, settings.level_up())? - .into(), - )), + Token::Switch if settings.options.contains(LangOptions::SWITCH_EXPR) => { + Expr::Stmt(Box::new( + self.parse_switch(input, state, lib, settings.level_up())? + .into(), + )) + } // | ... #[cfg(not(feature = "no_function"))] - Token::Pipe | Token::Or if settings.options.allow_anonymous_fn => { + Token::Pipe | Token::Or if settings.options.contains(LangOptions::ANON_FN) => { let mut new_state = ParseState::new(self, state.scope, state.tokenizer_control.clone()); @@ -1266,6 +1274,17 @@ impl Engine { new_state.max_expr_depth = self.max_function_expr_depth(); } + let mut options = self.options; + options.set( + LangOptions::STRICT_VAR, + if cfg!(feature = "no_closure") { + settings.options.contains(LangOptions::STRICT_VAR) + } else { + // A capturing closure can access variables not defined locally + false + }, + ); + let new_settings = ParseSettings { is_global: false, is_function_scope: true, @@ -1273,15 +1292,7 @@ impl Engine { is_closure_scope: true, is_breakable: false, level: 0, - options: LanguageOptions { - strict_var: if cfg!(feature = "no_closure") { - settings.options.strict_var - } else { - // A capturing closure can access variables not defined locally - false - }, - ..self.options - }, + options, ..settings }; @@ -1292,7 +1303,7 @@ impl Engine { |crate::ast::Ident { name, pos }| { let index = state.access_var(name, *pos); - if settings.options.strict_var + if settings.options.contains(LangOptions::STRICT_VAR) && !settings.is_closure_scope && index.is_none() && !state.scope.contains(name) @@ -1439,7 +1450,7 @@ impl Engine { _ => { let index = state.access_var(&s, settings.pos); - if settings.options.strict_var + if settings.options.contains(LangOptions::STRICT_VAR) && index.is_none() && !state.scope.contains(&s) { @@ -1674,7 +1685,10 @@ impl Engine { #[cfg(feature = "no_function")] let relax = false; - if !relax && settings.options.strict_var && index.is_none() { + if !relax + && settings.options.contains(LangOptions::STRICT_VAR) + && index.is_none() + { return Err(PERR::ModuleUndefined(namespace.root().to_string()) .into_err(namespace.position())); } @@ -3072,6 +3086,12 @@ impl Engine { new_state.max_expr_depth = self.max_function_expr_depth(); } + let mut options = self.options; + options.set( + LangOptions::STRICT_VAR, + settings.options.contains(LangOptions::STRICT_VAR), + ); + let new_settings = ParseSettings { is_global: false, is_function_scope: true, @@ -3079,10 +3099,7 @@ impl Engine { is_closure_scope: false, is_breakable: false, level: 0, - options: LanguageOptions { - strict_var: settings.options.strict_var, - ..self.options - }, + options, pos, ..settings }; @@ -3541,6 +3558,11 @@ impl Engine { ) -> ParseResult { let mut functions = BTreeMap::new(); + let mut options = self.options; + options.remove(LangOptions::IF_EXPR | LangOptions::SWITCH_EXPR | LangOptions::STMT_EXPR); + #[cfg(not(feature = "no_function"))] + options.remove(LangOptions::ANON_FN); + let settings = ParseSettings { is_global: true, #[cfg(not(feature = "no_function"))] @@ -3550,14 +3572,7 @@ impl Engine { is_closure_scope: false, is_breakable: false, level: 0, - options: LanguageOptions { - allow_if_expr: false, - allow_switch_expr: false, - allow_stmt_expr: false, - #[cfg(not(feature = "no_function"))] - allow_anonymous_fn: false, - ..self.options - }, + options, pos: Position::NONE, }; let expr = self.parse_expr(input, state, &mut functions, settings)?; From 8f73796110b1edd9481f0561e3a388ae425f9a90 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Thu, 19 May 2022 21:49:19 +0800 Subject: [PATCH 14/19] Fix builds. --- src/api/options.rs | 4 +++- src/func/call.rs | 6 +++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/api/options.rs b/src/api/options.rs index 1a098926..940439aa 100644 --- a/src/api/options.rs +++ b/src/api/options.rs @@ -40,7 +40,9 @@ impl LangOptions { Self::ANON_FN } #[cfg(feature = "no_function")] - 0 + { + Self::empty() + } } } } diff --git a/src/func/call.rs b/src/func/call.rs index edc07ae3..ea1355d4 100644 --- a/src/func/call.rs +++ b/src/func/call.rs @@ -12,8 +12,8 @@ use crate::engine::{ use crate::eval::{Caches, FnResolutionCacheEntry, GlobalRuntimeState}; use crate::{ calc_fn_hash, calc_fn_params_hash, combine_hashes, Dynamic, Engine, FnArgsVec, FnPtr, - Identifier, ImmutableString, Module, OptimizationLevel, Position, RhaiError, RhaiResult, - RhaiResultOf, Scope, ERR, + ImmutableString, Module, OptimizationLevel, Position, RhaiError, RhaiResult, RhaiResultOf, + Scope, ERR, }; #[cfg(feature = "no_std")] use std::prelude::v1::*; @@ -661,7 +661,7 @@ impl Engine { source .as_ref() .map(|s| (**s).clone()) - .unwrap_or(Identifier::new_const()), + .unwrap_or(crate::Identifier::new_const()), ); let result = if _is_method_call { From 46c1d86221bcf123fbc132aa011aa2425348f7be Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Fri, 20 May 2022 21:49:27 +0800 Subject: [PATCH 15/19] Fix merge AST with self-contained AST. --- CHANGELOG.md | 1 + src/ast/ast.rs | 26 +++++++++++++++++++++++++- 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0cb30e7c..462bc2ab 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ Bug fixes * Self-contained `AST` now works properly with `Engine::call_fn`. * Missing `to_int` from `Decimal` is added. * Parsing of index expressions is relaxed and many cases no longer result in an index-type error to allow for custom indexers. +* Merging a self-contained `AST` into another `AST` now works properly. Deprecated API's ---------------- diff --git a/src/ast/ast.rs b/src/ast/ast.rs index 10484cf6..08099edf 100644 --- a/src/ast/ast.rs +++ b/src/ast/ast.rs @@ -506,7 +506,7 @@ impl AST { lib }; - if !other.source.is_empty() { + let mut _ast = if !other.source.is_empty() { Self::new_with_source( merged, #[cfg(not(feature = "no_function"))] @@ -519,7 +519,31 @@ impl AST { #[cfg(not(feature = "no_function"))] lib, ) + }; + + #[cfg(not(feature = "no_module"))] + match ( + self.resolver().map_or(0, |r| r.len()), + other.resolver().map_or(0, |r| r.len()), + ) { + (0, 0) => (), + (_, 0) => { + _ast.set_resolver(self.resolver().unwrap().clone()); + } + (0, _) => { + _ast.set_resolver(other.resolver().unwrap().clone()); + } + (_, _) => { + let mut resolver = (**self.resolver().unwrap()).clone(); + let other_resolver = (**other.resolver().unwrap()).clone(); + for (k, v) in other_resolver { + resolver.insert(k, crate::func::shared_take_or_clone(v)); + } + _ast.set_resolver(resolver); + } } + + _ast } /// Combine one [`AST`] with another. The second [`AST`] is consumed. /// From 9c1a49da0b044ff3bb392ef72da1e687a3129ce4 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Sat, 21 May 2022 11:31:15 +0800 Subject: [PATCH 16/19] Fix AST combine. --- src/ast/ast.rs | 21 +++++++++++++++++++++ tests/modules.rs | 13 +++++++++++++ 2 files changed, 34 insertions(+) diff --git a/src/ast/ast.rs b/src/ast/ast.rs index 08099edf..0b9ec418 100644 --- a/src/ast/ast.rs +++ b/src/ast/ast.rs @@ -618,6 +618,27 @@ impl AST { crate::func::native::shared_make_mut(&mut self.lib) .merge_filtered(&other.lib, &_filter); } + + #[cfg(not(feature = "no_module"))] + match ( + self.resolver.as_ref().map_or(0, |r| r.len()), + other.resolver.as_ref().map_or(0, |r| r.len()), + ) { + (_, 0) => (), + (0, _) => { + self.set_resolver(other.resolver.unwrap()); + } + (_, _) => { + let resolver = + crate::func::native::shared_make_mut(self.resolver.as_mut().unwrap()); + let other_resolver = + crate::func::native::shared_take_or_clone(other.resolver.unwrap()); + for (k, v) in other_resolver { + resolver.insert(k, crate::func::shared_take_or_clone(v)); + } + } + } + self } /// Filter out the functions, retaining only some based on a filter predicate. diff --git a/tests/modules.rs b/tests/modules.rs index 69e2ef82..d6bacf3f 100644 --- a/tests/modules.rs +++ b/tests/modules.rs @@ -282,6 +282,19 @@ fn test_module_resolver() -> Result<(), Box> { let result: INT = engine.call_fn(&mut Scope::new(), &ast, "foo", (2 as INT,))?; assert_eq!(result, 84); + + let mut ast2 = engine.compile("fn foo(x) { 42 }")?; + + let len = ast.resolver().unwrap().len(); + + ast2 += ast; + + assert!(ast2.resolver().is_some()); + assert_eq!(ast2.resolver().unwrap().len(), len); + + let result: INT = engine.call_fn(&mut Scope::new(), &ast2, "foo", (2 as INT,))?; + + assert_eq!(result, 84); } Ok(()) From 5435fdb8c8b4875149e5e9d8cbd8b34eb4988e82 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Sat, 21 May 2022 11:57:23 +0800 Subject: [PATCH 17/19] Fix tests. --- CHANGELOG.md | 2 +- tests/modules.rs | 8 ++++++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 462bc2ab..0d0b8329 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,7 +10,7 @@ Bug fixes * Self-contained `AST` now works properly with `Engine::call_fn`. * Missing `to_int` from `Decimal` is added. * Parsing of index expressions is relaxed and many cases no longer result in an index-type error to allow for custom indexers. -* Merging a self-contained `AST` into another `AST` now works properly. +* Merging or combining a self-contained `AST` into another `AST` now works properly. Deprecated API's ---------------- diff --git a/tests/modules.rs b/tests/modules.rs index d6bacf3f..d80fcfba 100644 --- a/tests/modules.rs +++ b/tests/modules.rs @@ -285,12 +285,16 @@ fn test_module_resolver() -> Result<(), Box> { let mut ast2 = engine.compile("fn foo(x) { 42 }")?; + #[cfg(feature = "internals")] let len = ast.resolver().unwrap().len(); ast2 += ast; - assert!(ast2.resolver().is_some()); - assert_eq!(ast2.resolver().unwrap().len(), len); + #[cfg(feature = "internals")] + { + assert!(ast2.resolver().is_some()); + assert_eq!(ast2.resolver().unwrap().len(), len); + } let result: INT = engine.call_fn(&mut Scope::new(), &ast2, "foo", (2 as INT,))?; From 1abec0a8a8b3d4a26bea8b74438936f4938e87cb Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Sat, 21 May 2022 21:44:12 +0800 Subject: [PATCH 18/19] Allow initialization of EvalState tag and separate debugger state into separate variable. --- CHANGELOG.md | 2 ++ src/api/compile.rs | 4 ++-- src/api/eval.rs | 2 +- src/api/mod.rs | 21 ++++++++++++++++++++- src/api/run.rs | 6 +++++- src/bin/rhai-dbg.rs | 7 ++++++- src/engine.rs | 7 ++++++- src/eval/debugger.rs | 22 +++++++++++++++++++++- src/eval/global_state.rs | 28 +++++++++++++--------------- tests/debugging.rs | 12 ++++++++++-- 10 files changed, 86 insertions(+), 25 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0d0b8329..20169aff 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,8 @@ Enhancements ------------ * `EvalAltResult::IndexNotFound` is added to aid in raising errors for indexers. +* `Engine::def_tag`, `Engine::def_tag_mut` and `Engine::set_tag` are added to manage a default value for the custom evaluation state, accessible via `EvalState::tag()` (which is the same as `NativeCallContext::tag()`). +* Originally, the debugger's custom state uses the same state as `EvalState::tag()` (which is the same as `NativeCallContext::tag()`). It is now split into its own variable accessible under `Debugger::state()`. Version 1.7.0 diff --git a/src/api/compile.rs b/src/api/compile.rs index 2250b641..750972e9 100644 --- a/src/api/compile.rs +++ b/src/api/compile.rs @@ -201,7 +201,7 @@ impl Engine { scope: &Scope, scripts: impl AsRef<[S]>, ) -> ParseResult { - self.compile_with_scope_and_optimization_level(scope, scripts, self.optimization_level) + self.compile_with_scope_and_optimization_level(scope, scripts, self.optimization_level()) } /// Join a list of strings and compile into an [`AST`] using own scope at a specific optimization level. /// @@ -292,6 +292,6 @@ impl Engine { let mut peekable = stream.peekable(); let mut state = ParseState::new(self, scope, tokenizer_control); - self.parse_global_expr(&mut peekable, &mut state, self.optimization_level) + self.parse_global_expr(&mut peekable, &mut state, self.optimization_level()) } } diff --git a/src/api/eval.rs b/src/api/eval.rs index aec12d99..65ef8c37 100644 --- a/src/api/eval.rs +++ b/src/api/eval.rs @@ -67,7 +67,7 @@ impl Engine { let ast = self.compile_with_scope_and_optimization_level( scope, &[script], - self.optimization_level, + self.optimization_level(), )?; self.eval_ast_with_scope(scope, &ast) } diff --git a/src/api/mod.rs b/src/api/mod.rs index 554f48d5..b8e7084e 100644 --- a/src/api/mod.rs +++ b/src/api/mod.rs @@ -30,7 +30,7 @@ pub mod deprecated; use crate::engine::Precedence; use crate::tokenizer::Token; -use crate::{Engine, Identifier}; +use crate::{Dynamic, Engine, Identifier}; #[cfg(feature = "no_std")] use std::prelude::v1::*; @@ -195,4 +195,23 @@ impl Engine { Ok(self) } + + /// Get the default value of the custom state for each evaluation run. + #[inline(always)] + #[must_use] + pub fn default_tag(&self) -> &Dynamic { + &self.def_tag + } + /// Get a mutable reference to the default value of the custom state for each evaluation run. + #[inline(always)] + #[must_use] + pub fn default_tag_mut(&mut self) -> &mut Dynamic { + &mut self.def_tag + } + /// Set the default value of the custom state for each evaluation run. + #[inline(always)] + pub fn set_default_tag(&mut self, value: impl Into) -> &mut Self { + self.def_tag = value.into(); + self + } } diff --git a/src/api/run.rs b/src/api/run.rs index 673fa19e..93a0a38b 100644 --- a/src/api/run.rs +++ b/src/api/run.rs @@ -26,7 +26,11 @@ impl Engine { self.lex_raw(&scripts, self.token_mapper.as_ref().map(Box::as_ref)); let mut state = ParseState::new(self, scope, tokenizer_control); - let ast = self.parse(&mut stream.peekable(), &mut state, self.optimization_level)?; + let ast = self.parse( + &mut stream.peekable(), + &mut state, + self.optimization_level(), + )?; self.run_ast_with_scope(scope, &ast) } diff --git a/src/bin/rhai-dbg.rs b/src/bin/rhai-dbg.rs index db285f7a..93bc6b6d 100644 --- a/src/bin/rhai-dbg.rs +++ b/src/bin/rhai-dbg.rs @@ -60,7 +60,12 @@ fn print_current_source( lines: &[String], window: (usize, usize), ) { - let current_source = &mut *context.tag_mut().write_lock::().unwrap(); + let current_source = &mut *context + .global_runtime_state_mut() + .debugger + .state_mut() + .write_lock::() + .unwrap(); let src = source.unwrap_or(""); if src != current_source { println!( diff --git a/src/engine.rs b/src/engine.rs index 8a4e8841..2af23bac 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -132,8 +132,11 @@ pub struct Engine { /// Language options. pub(crate) options: LangOptions, + /// Default value for the custom state. + pub(crate) def_tag: Dynamic, + /// Script optimization level. - pub optimization_level: OptimizationLevel, + pub(crate) optimization_level: OptimizationLevel, /// Max limits. #[cfg(not(feature = "unchecked"))] @@ -280,6 +283,8 @@ impl Engine { options: LangOptions::new(), + def_tag: Dynamic::UNIT, + #[cfg(not(feature = "no_optimize"))] optimization_level: OptimizationLevel::Simple, #[cfg(feature = "no_optimize")] diff --git a/src/eval/debugger.rs b/src/eval/debugger.rs index a6344a85..11b52bf8 100644 --- a/src/eval/debugger.rs +++ b/src/eval/debugger.rs @@ -253,17 +253,20 @@ pub struct Debugger { break_points: Vec, /// The current function call stack. call_stack: Vec, + /// The current state. + state: Dynamic, } impl Debugger { /// Create a new [`Debugger`]. #[inline(always)] #[must_use] - pub fn new(status: DebuggerStatus) -> Self { + pub fn new(status: DebuggerStatus, state: Dynamic) -> Self { Self { status, break_points: Vec::new(), call_stack: Vec::new(), + state, } } /// Get the current call stack. @@ -374,6 +377,23 @@ impl Debugger { pub fn break_points_mut(&mut self) -> &mut Vec { &mut self.break_points } + /// Get the custom state. + #[inline(always)] + #[must_use] + pub fn state(&self) -> &Dynamic { + &self.state + } + /// Get a mutable reference to the custom state. + #[inline(always)] + #[must_use] + pub fn state_mut(&mut self) -> &mut Dynamic { + &mut self.state + } + /// Set the custom state. + #[inline(always)] + pub fn set_state(&mut self, state: impl Into) { + self.state = state.into(); + } } impl Engine { diff --git a/src/eval/global_state.rs b/src/eval/global_state.rs index bdcbb2ca..3c0a8c4a 100644 --- a/src/eval/global_state.rs +++ b/src/eval/global_state.rs @@ -78,8 +78,6 @@ impl GlobalRuntimeState<'_> { #[inline(always)] #[must_use] pub fn new(engine: &Engine) -> Self { - let _engine = engine; - Self { #[cfg(not(feature = "no_module"))] keys: crate::StaticVec::new_const(), @@ -98,21 +96,21 @@ impl GlobalRuntimeState<'_> { #[cfg(not(feature = "no_function"))] constants: None, - #[cfg(not(feature = "debugging"))] - tag: Dynamic::UNIT, - #[cfg(feature = "debugging")] - tag: if let Some((ref init, ..)) = engine.debugger { - init() - } else { - Dynamic::UNIT - }, + tag: engine.default_tag().clone(), #[cfg(feature = "debugging")] - debugger: crate::eval::Debugger::new(if engine.debugger.is_some() { - crate::eval::DebuggerStatus::Init - } else { - crate::eval::DebuggerStatus::CONTINUE - }), + debugger: crate::eval::Debugger::new( + if engine.debugger.is_some() { + crate::eval::DebuggerStatus::Init + } else { + crate::eval::DebuggerStatus::CONTINUE + }, + if let Some((ref init, ..)) = engine.debugger { + init() + } else { + Dynamic::UNIT + }, + ), dummy: PhantomData::default(), } diff --git a/tests/debugging.rs b/tests/debugging.rs index 8f5b8cf9..48f8d946 100644 --- a/tests/debugging.rs +++ b/tests/debugging.rs @@ -57,10 +57,18 @@ fn test_debugger_state() -> Result<(), Box> { }, |mut context, _, _, _, _| { // Print debugger state - which is an object map - println!("Current state = {}", context.tag()); + println!( + "Current state = {}", + context.global_runtime_state_mut().debugger.state() + ); // Modify state - let mut state = context.tag_mut().write_lock::().unwrap(); + let mut state = context + .global_runtime_state_mut() + .debugger + .state_mut() + .write_lock::() + .unwrap(); let hello = state.get("hello").unwrap().as_int().unwrap(); state.insert("hello".into(), (hello + 1).into()); state.insert("foo".into(), true.into()); From ee886fc719a2043dc49e76efa7dc442906147a41 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Sat, 21 May 2022 22:13:02 +0800 Subject: [PATCH 19/19] Fix builds. --- src/api/compile.rs | 4 ++-- src/api/eval.rs | 2 +- src/api/run.rs | 6 +----- 3 files changed, 4 insertions(+), 8 deletions(-) diff --git a/src/api/compile.rs b/src/api/compile.rs index 750972e9..2250b641 100644 --- a/src/api/compile.rs +++ b/src/api/compile.rs @@ -201,7 +201,7 @@ impl Engine { scope: &Scope, scripts: impl AsRef<[S]>, ) -> ParseResult { - self.compile_with_scope_and_optimization_level(scope, scripts, self.optimization_level()) + self.compile_with_scope_and_optimization_level(scope, scripts, self.optimization_level) } /// Join a list of strings and compile into an [`AST`] using own scope at a specific optimization level. /// @@ -292,6 +292,6 @@ impl Engine { let mut peekable = stream.peekable(); let mut state = ParseState::new(self, scope, tokenizer_control); - self.parse_global_expr(&mut peekable, &mut state, self.optimization_level()) + self.parse_global_expr(&mut peekable, &mut state, self.optimization_level) } } diff --git a/src/api/eval.rs b/src/api/eval.rs index 65ef8c37..aec12d99 100644 --- a/src/api/eval.rs +++ b/src/api/eval.rs @@ -67,7 +67,7 @@ impl Engine { let ast = self.compile_with_scope_and_optimization_level( scope, &[script], - self.optimization_level(), + self.optimization_level, )?; self.eval_ast_with_scope(scope, &ast) } diff --git a/src/api/run.rs b/src/api/run.rs index 93a0a38b..673fa19e 100644 --- a/src/api/run.rs +++ b/src/api/run.rs @@ -26,11 +26,7 @@ impl Engine { self.lex_raw(&scripts, self.token_mapper.as_ref().map(Box::as_ref)); let mut state = ParseState::new(self, scope, tokenizer_control); - let ast = self.parse( - &mut stream.peekable(), - &mut state, - self.optimization_level(), - )?; + let ast = self.parse(&mut stream.peekable(), &mut state, self.optimization_level)?; self.run_ast_with_scope(scope, &ast) }