From 855cb762462af2b7b1b12bc877218057b88ab95c Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Sat, 16 Apr 2022 16:36:53 +0800 Subject: [PATCH 01/13] Rename EvalState to Caches. --- src/api/call_fn.rs | 8 +-- src/api/custom_syntax.rs | 4 +- src/api/eval.rs | 6 +- src/api/run.rs | 6 +- src/eval/cache.rs | 69 ++++++++++++++++++++ src/eval/chaining.rs | 132 +++++++++++++++++++-------------------- src/eval/debugger.rs | 18 +++--- src/eval/eval_context.rs | 10 +-- src/eval/eval_state.rs | 72 --------------------- src/eval/expr.rs | 63 +++++++++---------- src/eval/global_state.rs | 27 ++++++-- src/eval/mod.rs | 4 +- src/eval/stmt.rs | 127 +++++++++++++++++++------------------ src/func/call.rs | 117 +++++++++++++++------------------- src/func/native.rs | 6 +- src/func/script.rs | 21 ++++--- src/lib.rs | 5 +- src/optimizer.rs | 4 +- src/parser.rs | 4 +- 19 files changed, 349 insertions(+), 354 deletions(-) create mode 100644 src/eval/cache.rs delete mode 100644 src/eval/eval_state.rs diff --git a/src/api/call_fn.rs b/src/api/call_fn.rs index cfad57f9..6ca6f59d 100644 --- a/src/api/call_fn.rs +++ b/src/api/call_fn.rs @@ -1,7 +1,7 @@ //! Module that defines the `call_fn` API of [`Engine`]. #![cfg(not(feature = "no_function"))] -use crate::eval::{EvalState, GlobalRuntimeState}; +use crate::eval::{Caches, GlobalRuntimeState}; use crate::types::dynamic::Variant; use crate::{ Dynamic, Engine, FuncArgs, Position, RhaiResult, RhaiResultOf, Scope, StaticVec, AST, ERR, @@ -149,7 +149,7 @@ impl Engine { this_ptr: Option<&mut Dynamic>, arg_values: impl AsMut<[Dynamic]>, ) -> RhaiResult { - let state = &mut EvalState::new(); + let caches = &mut Caches::new(); let global = &mut GlobalRuntimeState::new(self); let statements = ast.statements(); @@ -157,7 +157,7 @@ impl Engine { let orig_scope_len = scope.len(); if eval_ast && !statements.is_empty() { - self.eval_global_statements(scope, global, state, statements, &[ast.as_ref()], 0)?; + self.eval_global_statements(scope, global, caches, statements, &[ast.as_ref()], 0)?; if rewind_scope { scope.rewind(orig_scope_len); @@ -181,7 +181,7 @@ impl Engine { self.call_script_fn( scope, global, - state, + caches, &[ast.as_ref()], &mut this_ptr, fn_def, diff --git a/src/api/custom_syntax.rs b/src/api/custom_syntax.rs index d3411fce..32be7d54 100644 --- a/src/api/custom_syntax.rs +++ b/src/api/custom_syntax.rs @@ -130,7 +130,7 @@ impl Deref for Expression<'_> { } } -impl EvalContext<'_, '_, '_, '_, '_, '_, '_, '_, '_, '_> { +impl EvalContext<'_, '_, '_, '_, '_, '_, '_, '_, '_> { /// Evaluate an [expression tree][Expression]. /// /// # WARNING - Low Level API @@ -141,7 +141,7 @@ impl EvalContext<'_, '_, '_, '_, '_, '_, '_, '_, '_, '_> { self.engine.eval_expr( self.scope, self.global, - self.state, + self.caches, self.lib, self.this_ptr, expr, diff --git a/src/api/eval.rs b/src/api/eval.rs index f83da77f..49e26243 100644 --- a/src/api/eval.rs +++ b/src/api/eval.rs @@ -1,6 +1,6 @@ //! Module that defines the public evaluation API of [`Engine`]. -use crate::eval::{EvalState, GlobalRuntimeState}; +use crate::eval::{Caches, GlobalRuntimeState}; use crate::parser::ParseState; use crate::types::dynamic::Variant; use crate::{ @@ -208,7 +208,7 @@ impl Engine { ast: &'a AST, level: usize, ) -> RhaiResult { - let mut state = EvalState::new(); + let mut caches = Caches::new(); global.source = ast.source_raw().clone(); #[cfg(not(feature = "no_module"))] @@ -231,6 +231,6 @@ impl Engine { } else { &lib[..] }; - self.eval_global_statements(scope, global, &mut state, statements, lib, level) + self.eval_global_statements(scope, global, &mut caches, statements, lib, level) } } diff --git a/src/api/run.rs b/src/api/run.rs index 25fd69c7..77097d29 100644 --- a/src/api/run.rs +++ b/src/api/run.rs @@ -1,6 +1,6 @@ //! Module that defines the public evaluation API of [`Engine`]. -use crate::eval::{EvalState, GlobalRuntimeState}; +use crate::eval::{Caches, GlobalRuntimeState}; use crate::parser::ParseState; use crate::{Engine, Module, RhaiResultOf, Scope, AST}; #[cfg(feature = "no_std")] @@ -43,7 +43,7 @@ impl Engine { /// Evaluate an [`AST`] with own scope, returning any error (if any). #[inline] pub fn run_ast_with_scope(&self, scope: &mut Scope, ast: &AST) -> RhaiResultOf<()> { - let state = &mut EvalState::new(); + let caches = &mut Caches::new(); let global = &mut GlobalRuntimeState::new(self); global.source = ast.source_raw().clone(); @@ -63,7 +63,7 @@ impl Engine { } else { &lib }; - self.eval_global_statements(scope, global, state, statements, lib, 0)?; + self.eval_global_statements(scope, global, caches, statements, lib, 0)?; } Ok(()) } diff --git a/src/eval/cache.rs b/src/eval/cache.rs new file mode 100644 index 00000000..64c9e0fb --- /dev/null +++ b/src/eval/cache.rs @@ -0,0 +1,69 @@ +//! System caches. + +use crate::func::CallableFunction; +use crate::{Identifier, StaticVec}; +use std::collections::BTreeMap; +#[cfg(feature = "no_std")] +use std::prelude::v1::*; + +/// _(internals)_ An entry in a function resolution cache. +/// Exported under the `internals` feature only. +#[derive(Debug, Clone)] +pub struct FnResolutionCacheEntry { + /// Function. + pub func: CallableFunction, + /// Optional source. + /// No source if the string is empty. + pub source: Identifier, +} + +/// _(internals)_ A function resolution cache. +/// Exported under the `internals` feature only. +/// +/// [`FnResolutionCacheEntry`] is [`Box`]ed in order to pack as many entries inside a single B-Tree +/// level as possible. +pub type FnResolutionCache = BTreeMap>>; + +/// _(internals)_ A type containing system-wide caches. +/// Exported under the `internals` feature only. +/// +/// The following caches are contained inside this type: +/// * A stack of [function resolution caches][FnResolutionCache] +#[derive(Debug, Clone)] +pub struct Caches(StaticVec); + +impl Caches { + /// Create an empty [`Caches`]. + #[inline(always)] + #[must_use] + pub const fn new() -> Self { + Self(StaticVec::new_const()) + } + /// Get the number of function resolution cache(s) in the stack. + #[inline(always)] + #[must_use] + pub fn fn_resolution_caches_len(&self) -> usize { + self.0.len() + } + /// Get a mutable reference to the current function resolution cache. + #[inline] + #[must_use] + pub fn fn_resolution_cache_mut(&mut self) -> &mut FnResolutionCache { + if self.0.is_empty() { + // Push a new function resolution cache if the stack is empty + self.push_fn_resolution_cache(); + } + self.0.last_mut().unwrap() + } + /// Push an empty function resolution cache onto the stack and make it current. + #[allow(dead_code)] + #[inline(always)] + pub fn push_fn_resolution_cache(&mut self) { + self.0.push(BTreeMap::new()); + } + /// Rewind the function resolution caches stack to a particular size. + #[inline(always)] + pub fn rewind_fn_resolution_caches(&mut self, len: usize) { + self.0.truncate(len); + } +} diff --git a/src/eval/chaining.rs b/src/eval/chaining.rs index 56d3aba3..ee566328 100644 --- a/src/eval/chaining.rs +++ b/src/eval/chaining.rs @@ -1,7 +1,7 @@ //! Types to support chaining operations (i.e. indexing and dotting). #![cfg(any(not(feature = "no_index"), not(feature = "no_object")))] -use super::{EvalState, GlobalRuntimeState, Target}; +use super::{Caches, GlobalRuntimeState, Target}; use crate::ast::{ASTFlags, Expr, OpAssignment}; use crate::types::dynamic::Union; use crate::{Dynamic, Engine, Module, Position, RhaiResult, RhaiResultOf, Scope, StaticVec, ERR}; @@ -122,7 +122,7 @@ impl Engine { fn eval_dot_index_chain_helper( &self, global: &mut GlobalRuntimeState, - state: &mut EvalState, + caches: &mut Caches, lib: &[&Module], this_ptr: &mut Option<&mut Dynamic>, target: &mut Target, @@ -155,7 +155,7 @@ impl Engine { if !_parent_options.contains(ASTFlags::BREAK) => { #[cfg(feature = "debugging")] - self.run_debugger(scope, global, state, lib, this_ptr, _parent, level)?; + self.run_debugger(scope, global, caches, lib, this_ptr, _parent, level)?; let mut idx_val_for_setter = idx_val.clone(); let idx_pos = x.lhs.start_position(); @@ -163,14 +163,14 @@ impl Engine { let (try_setter, result) = { let mut obj = self.get_indexed_mut( - global, state, lib, target, idx_val, idx_pos, false, true, level, + global, caches, lib, target, idx_val, idx_pos, false, true, level, )?; let is_obj_temp_val = obj.is_temp_value(); let obj_ptr = &mut obj; match self.eval_dot_index_chain_helper( - global, state, lib, this_ptr, obj_ptr, root, rhs, &x.rhs, *options, - idx_values, rhs_chain, level, new_val, + global, caches, lib, this_ptr, obj_ptr, root, rhs, &x.rhs, + *options, idx_values, rhs_chain, level, new_val, ) { Ok((result, true)) if is_obj_temp_val => { (Some(obj.take_or_clone()), (result, true)) @@ -185,7 +185,7 @@ impl Engine { let idx = &mut idx_val_for_setter; let new_val = &mut new_val; self.call_indexer_set( - global, state, lib, target, idx, new_val, is_ref_mut, level, + global, caches, lib, target, idx, new_val, is_ref_mut, level, ) .or_else(|e| match *e { ERR::ErrorIndexingType(..) => Ok((Dynamic::UNIT, false)), @@ -198,18 +198,18 @@ impl Engine { // xxx[rhs] op= new_val _ if new_val.is_some() => { #[cfg(feature = "debugging")] - self.run_debugger(scope, global, state, lib, this_ptr, _parent, level)?; + self.run_debugger(scope, global, caches, lib, this_ptr, _parent, level)?; let ((new_val, new_pos), (op_info, op_pos)) = new_val.expect("`Some`"); let mut idx_val2 = idx_val.clone(); let try_setter = match self.get_indexed_mut( - global, state, lib, target, idx_val, pos, true, false, level, + global, caches, lib, target, idx_val, pos, true, false, level, ) { // Indexed value is not a temp value - update directly Ok(ref mut obj_ptr) => { self.eval_op_assignment( - global, state, lib, op_info, op_pos, obj_ptr, root, new_val, + global, caches, lib, op_info, op_pos, obj_ptr, root, new_val, level, ) .map_err(|err| err.fill_position(new_pos))?; @@ -232,12 +232,12 @@ impl Engine { let idx = &mut idx.clone(); // Call the index getter to get the current value if let Ok(val) = - self.call_indexer_get(global, state, lib, target, idx, level) + self.call_indexer_get(global, caches, lib, target, idx, level) { let mut res = val.into(); // Run the op-assignment self.eval_op_assignment( - global, state, lib, op_info, op_pos, &mut res, root, + global, caches, lib, op_info, op_pos, &mut res, root, new_val, level, ) .map_err(|err| err.fill_position(new_pos))?; @@ -251,7 +251,7 @@ impl Engine { // Try to call index setter let new_val = &mut new_val; self.call_indexer_set( - global, state, lib, target, idx, new_val, is_ref_mut, level, + global, caches, lib, target, idx, new_val, is_ref_mut, level, )?; } @@ -260,10 +260,10 @@ impl Engine { // xxx[rhs] _ => { #[cfg(feature = "debugging")] - self.run_debugger(scope, global, state, lib, this_ptr, _parent, level)?; + self.run_debugger(scope, global, caches, lib, this_ptr, _parent, level)?; self.get_indexed_mut( - global, state, lib, target, idx_val, pos, false, true, level, + global, caches, lib, target, idx_val, pos, false, true, level, ) .map(|v| (v.take_or_clone(), false)) } @@ -280,11 +280,11 @@ impl Engine { #[cfg(feature = "debugging")] let reset_debugger = self.run_debugger_with_reset( - scope, global, state, lib, this_ptr, rhs, level, + scope, global, caches, lib, this_ptr, rhs, level, )?; let result = self.make_method_call( - global, state, lib, name, *hashes, target, call_args, *pos, level, + global, caches, lib, name, *hashes, target, call_args, *pos, level, ); #[cfg(feature = "debugging")] @@ -303,16 +303,16 @@ impl Engine { // {xxx:map}.id op= ??? Expr::Property(x, pos) if target.is::() && new_val.is_some() => { #[cfg(feature = "debugging")] - self.run_debugger(scope, global, state, lib, this_ptr, rhs, level)?; + self.run_debugger(scope, global, caches, lib, this_ptr, rhs, level)?; let index = x.2.clone().into(); let ((new_val, new_pos), (op_info, op_pos)) = new_val.expect("`Some`"); { let val_target = &mut self.get_indexed_mut( - global, state, lib, target, index, *pos, true, false, level, + global, caches, lib, target, index, *pos, true, false, level, )?; self.eval_op_assignment( - global, state, lib, op_info, op_pos, val_target, root, new_val, + global, caches, lib, op_info, op_pos, val_target, root, new_val, level, ) .map_err(|err| err.fill_position(new_pos))?; @@ -324,18 +324,18 @@ impl Engine { // {xxx:map}.id Expr::Property(x, pos) if target.is::() => { #[cfg(feature = "debugging")] - self.run_debugger(scope, global, state, lib, this_ptr, rhs, level)?; + self.run_debugger(scope, global, caches, lib, this_ptr, rhs, level)?; let index = x.2.clone().into(); let val = self.get_indexed_mut( - global, state, lib, target, index, *pos, false, false, level, + global, caches, lib, target, index, *pos, false, false, level, )?; Ok((val.take_or_clone(), false)) } // xxx.id op= ??? Expr::Property(x, pos) if new_val.is_some() => { #[cfg(feature = "debugging")] - self.run_debugger(scope, global, state, lib, this_ptr, rhs, level)?; + self.run_debugger(scope, global, caches, lib, this_ptr, rhs, level)?; let ((getter, hash_get), (setter, hash_set), name) = x.as_ref(); let ((mut new_val, new_pos), (op_info, op_pos)) = new_val.expect("`Some`"); @@ -345,15 +345,15 @@ impl Engine { let args = &mut [target.as_mut()]; let (mut orig_val, ..) = self .exec_fn_call( - None, global, state, lib, getter, hash, args, is_ref_mut, true, - *pos, level, + None, global, caches, lib, getter, hash, args, is_ref_mut, + true, *pos, level, ) .or_else(|err| match *err { // Try an indexer if property does not exist ERR::ErrorDotExpr(..) => { let mut prop = name.into(); self.call_indexer_get( - global, state, lib, target, &mut prop, level, + global, caches, lib, target, &mut prop, level, ) .map(|r| (r, false)) .map_err(|e| { @@ -370,7 +370,7 @@ impl Engine { let orig_val = &mut (&mut orig_val).into(); self.eval_op_assignment( - global, state, lib, op_info, op_pos, orig_val, root, new_val, + global, caches, lib, op_info, op_pos, orig_val, root, new_val, level, ) .map_err(|err| err.fill_position(new_pos))?; @@ -382,7 +382,7 @@ impl Engine { let hash = crate::ast::FnCallHashes::from_native(*hash_set); let args = &mut [target.as_mut(), &mut new_val]; self.exec_fn_call( - None, global, state, lib, setter, hash, args, is_ref_mut, true, *pos, + None, global, caches, lib, setter, hash, args, is_ref_mut, true, *pos, level, ) .or_else(|err| match *err { @@ -391,7 +391,7 @@ impl Engine { let idx = &mut name.into(); let new_val = &mut new_val; self.call_indexer_set( - global, state, lib, target, idx, new_val, is_ref_mut, level, + global, caches, lib, target, idx, new_val, is_ref_mut, level, ) .map_err(|e| match *e { ERR::ErrorIndexingType(..) => err, @@ -404,13 +404,13 @@ impl Engine { // xxx.id Expr::Property(x, pos) => { #[cfg(feature = "debugging")] - self.run_debugger(scope, global, state, lib, this_ptr, rhs, level)?; + self.run_debugger(scope, global, caches, 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, state, lib, getter, hash, args, is_ref_mut, true, *pos, + None, global, caches, lib, getter, hash, args, is_ref_mut, true, *pos, level, ) .map_or_else( @@ -419,7 +419,7 @@ impl Engine { ERR::ErrorDotExpr(..) => { let mut prop = name.into(); self.call_indexer_get( - global, state, lib, target, &mut prop, level, + global, caches, lib, target, &mut prop, level, ) .map(|r| (r, false)) .map_err(|e| match *e { @@ -443,12 +443,12 @@ impl Engine { Expr::Property(ref p, pos) => { #[cfg(feature = "debugging")] self.run_debugger( - scope, global, state, lib, this_ptr, _node, level, + scope, global, caches, lib, this_ptr, _node, level, )?; let index = p.2.clone().into(); self.get_indexed_mut( - global, state, lib, target, index, pos, false, true, level, + global, caches, lib, target, index, pos, false, true, level, )? } // {xxx:map}.fn_name(arg_expr_list)[expr] | {xxx:map}.fn_name(arg_expr_list).expr @@ -458,11 +458,11 @@ impl Engine { #[cfg(feature = "debugging")] let reset_debugger = self.run_debugger_with_reset( - scope, global, state, lib, this_ptr, _node, level, + scope, global, caches, lib, this_ptr, _node, level, )?; let result = self.make_method_call( - global, state, lib, name, *hashes, target, call_args, pos, + global, caches, lib, name, *hashes, target, call_args, pos, level, ); @@ -481,7 +481,7 @@ impl Engine { let rhs_chain = rhs.into(); self.eval_dot_index_chain_helper( - global, state, lib, this_ptr, val_target, root, rhs, &x.rhs, *options, + global, caches, lib, this_ptr, val_target, root, rhs, &x.rhs, *options, idx_values, rhs_chain, level, new_val, ) .map_err(|err| err.fill_position(*x_pos)) @@ -495,7 +495,7 @@ impl Engine { Expr::Property(ref p, pos) => { #[cfg(feature = "debugging")] self.run_debugger( - scope, global, state, lib, this_ptr, _node, level, + scope, global, caches, lib, this_ptr, _node, level, )?; let ((getter, hash_get), (setter, hash_set), name) = p.as_ref(); @@ -508,7 +508,7 @@ impl Engine { // Assume getters are always pure let (mut val, ..) = self .exec_fn_call( - None, global, state, lib, getter, hash_get, args, + None, global, caches, lib, getter, hash_get, args, is_ref_mut, true, pos, level, ) .or_else(|err| match *err { @@ -516,7 +516,7 @@ impl Engine { ERR::ErrorDotExpr(..) => { let mut prop = name.into(); self.call_indexer_get( - global, state, lib, target, &mut prop, level, + global, caches, lib, target, &mut prop, level, ) .map(|r| (r, false)) .map_err( @@ -533,7 +533,7 @@ impl Engine { let (result, may_be_changed) = self .eval_dot_index_chain_helper( - global, state, lib, this_ptr, val, root, rhs, &x.rhs, + global, caches, lib, this_ptr, val, root, rhs, &x.rhs, *options, idx_values, rhs_chain, level, new_val, ) .map_err(|err| err.fill_position(*x_pos))?; @@ -544,7 +544,7 @@ impl Engine { let mut arg_values = [target.as_mut(), val.as_mut()]; let args = &mut arg_values; self.exec_fn_call( - None, global, state, lib, setter, hash_set, args, + None, global, caches, lib, setter, hash_set, args, is_ref_mut, true, pos, level, ) .or_else( @@ -554,7 +554,7 @@ impl Engine { let idx = &mut name.into(); let new_val = val; self.call_indexer_set( - global, state, lib, target, idx, new_val, + global, caches, lib, target, idx, new_val, is_ref_mut, level, ) .or_else(|e| match *e { @@ -581,11 +581,11 @@ impl Engine { #[cfg(feature = "debugging")] let reset_debugger = self.run_debugger_with_reset( - scope, global, state, lib, this_ptr, _node, level, + scope, global, caches, lib, this_ptr, _node, level, )?; let result = self.make_method_call( - global, state, lib, name, *hashes, target, args, pos, level, + global, caches, lib, name, *hashes, target, args, pos, level, ); #[cfg(feature = "debugging")] @@ -595,8 +595,8 @@ impl Engine { let val = &mut val.into(); self.eval_dot_index_chain_helper( - global, state, lib, this_ptr, val, root, rhs, &x.rhs, *options, - idx_values, rhs_chain, level, new_val, + global, caches, lib, this_ptr, val, root, rhs, &x.rhs, + *options, idx_values, rhs_chain, level, new_val, ) .map_err(|err| err.fill_position(pos)) } @@ -620,7 +620,7 @@ impl Engine { &self, scope: &mut Scope, global: &mut GlobalRuntimeState, - state: &mut EvalState, + caches: &mut Caches, lib: &[&Module], this_ptr: &mut Option<&mut Dynamic>, expr: &Expr, @@ -638,7 +638,7 @@ impl Engine { let idx_values = &mut StaticVec::new_const(); self.eval_dot_index_chain_arguments( - scope, global, state, lib, this_ptr, rhs, options, chain_type, idx_values, 0, level, + scope, global, caches, lib, this_ptr, rhs, options, chain_type, idx_values, 0, level, )?; let is_assignment = new_val.is_some(); @@ -647,19 +647,19 @@ impl Engine { // id.??? or id[???] Expr::Variable(x, .., var_pos) => { #[cfg(feature = "debugging")] - self.run_debugger(scope, global, state, lib, this_ptr, lhs, level)?; + self.run_debugger(scope, global, caches, lib, this_ptr, lhs, level)?; #[cfg(not(feature = "unchecked"))] self.inc_operations(&mut global.num_operations, *var_pos)?; let (mut target, ..) = - self.search_namespace(scope, global, state, lib, this_ptr, lhs, level)?; + self.search_namespace(scope, global, caches, lib, this_ptr, lhs, level)?; let obj_ptr = &mut target; let root = (x.3.as_str(), *var_pos); self.eval_dot_index_chain_helper( - global, state, lib, &mut None, obj_ptr, root, expr, rhs, options, idx_values, + global, caches, lib, &mut None, obj_ptr, root, expr, rhs, options, idx_values, chain_type, level, new_val, ) .map(|(v, ..)| v) @@ -669,11 +669,11 @@ impl Engine { _ if is_assignment => unreachable!("cannot assign to an expression"), // {expr}.??? or {expr}[???] expr => { - let value = self.eval_expr(scope, global, state, lib, this_ptr, expr, level)?; + let value = self.eval_expr(scope, global, caches, lib, this_ptr, expr, level)?; let obj_ptr = &mut value.into(); let root = ("", expr.start_position()); self.eval_dot_index_chain_helper( - global, state, lib, this_ptr, obj_ptr, root, expr, rhs, options, idx_values, + global, caches, lib, this_ptr, obj_ptr, root, expr, rhs, options, idx_values, chain_type, level, new_val, ) .map(|(v, ..)| if is_assignment { Dynamic::UNIT } else { v }) @@ -689,7 +689,7 @@ impl Engine { &self, scope: &mut Scope, global: &mut GlobalRuntimeState, - state: &mut EvalState, + caches: &mut Caches, lib: &[&Module], this_ptr: &mut Option<&mut Dynamic>, expr: &Expr, @@ -713,7 +713,7 @@ impl Engine { (crate::FnArgsVec::with_capacity(args.len()), Position::NONE), |(mut values, mut pos), expr| { let (value, arg_pos) = - self.get_arg_value(scope, global, state, lib, this_ptr, expr, level)?; + self.get_arg_value(scope, global, caches, lib, this_ptr, expr, level)?; if values.is_empty() { pos = arg_pos; } @@ -758,7 +758,7 @@ impl Engine { (crate::FnArgsVec::with_capacity(args.len()), Position::NONE), |(mut values, mut pos), expr| { let (value, arg_pos) = self.get_arg_value( - scope, global, state, lib, this_ptr, expr, level, + scope, global, caches, lib, this_ptr, expr, level, )?; if values.is_empty() { pos = arg_pos @@ -779,7 +779,7 @@ impl Engine { } #[cfg(not(feature = "no_index"))] _ if _parent_chain_type == ChainType::Indexing => self - .eval_expr(scope, global, state, lib, this_ptr, lhs, level) + .eval_expr(scope, global, caches, lib, this_ptr, lhs, level) .map(|v| { super::ChainArgument::from_index_value( v.flatten(), @@ -793,7 +793,7 @@ impl Engine { let chain_type = expr.into(); self.eval_dot_index_chain_arguments( - scope, global, state, lib, this_ptr, rhs, *options, chain_type, idx_values, + scope, global, caches, lib, this_ptr, rhs, *options, chain_type, idx_values, size, level, )?; @@ -806,7 +806,7 @@ impl Engine { } #[cfg(not(feature = "no_index"))] _ if _parent_chain_type == ChainType::Indexing => idx_values.push( - self.eval_expr(scope, global, state, lib, this_ptr, expr, level) + self.eval_expr(scope, global, caches, lib, this_ptr, expr, level) .map(|v| { super::ChainArgument::from_index_value(v.flatten(), expr.start_position()) })?, @@ -823,7 +823,7 @@ impl Engine { fn call_indexer_get( &self, global: &mut GlobalRuntimeState, - state: &mut EvalState, + caches: &mut Caches, lib: &[&Module], target: &mut Dynamic, idx: &mut Dynamic, @@ -835,7 +835,7 @@ impl Engine { let pos = Position::NONE; self.exec_fn_call( - None, global, state, lib, fn_name, hash_get, args, true, true, pos, level, + None, global, caches, lib, fn_name, hash_get, args, true, true, pos, level, ) .map(|(r, ..)| r) } @@ -846,7 +846,7 @@ impl Engine { fn call_indexer_set( &self, global: &mut GlobalRuntimeState, - state: &mut EvalState, + caches: &mut Caches, lib: &[&Module], target: &mut Dynamic, idx: &mut Dynamic, @@ -860,7 +860,7 @@ impl Engine { let pos = Position::NONE; self.exec_fn_call( - None, global, state, lib, fn_name, hash_set, args, is_ref_mut, true, pos, level, + None, global, caches, lib, fn_name, hash_set, args, is_ref_mut, true, pos, level, ) } @@ -869,7 +869,7 @@ impl Engine { fn get_indexed_mut<'t>( &self, global: &mut GlobalRuntimeState, - state: &mut EvalState, + caches: &mut Caches, lib: &[&Module], target: &'t mut Dynamic, mut idx: Dynamic, @@ -1064,7 +1064,7 @@ impl Engine { } _ if use_indexers => self - .call_indexer_get(global, state, lib, target, &mut idx, level) + .call_indexer_get(global, caches, lib, target, &mut idx, level) .map(Into::into), _ => Err(ERR::ErrorIndexingType( diff --git a/src/eval/debugger.rs b/src/eval/debugger.rs index e0dd361e..f79dd398 100644 --- a/src/eval/debugger.rs +++ b/src/eval/debugger.rs @@ -1,7 +1,7 @@ //! Module defining the debugging interface. #![cfg(feature = "debugging")] -use super::{EvalContext, EvalState, GlobalRuntimeState}; +use super::{Caches, EvalContext, GlobalRuntimeState}; use crate::ast::{ASTNode, Expr, Stmt}; use crate::{Dynamic, Engine, EvalAltResult, Identifier, Module, Position, RhaiResultOf, Scope}; #[cfg(feature = "no_std")] @@ -406,7 +406,7 @@ impl Engine { &self, scope: &mut Scope, global: &mut GlobalRuntimeState, - state: &mut EvalState, + caches: &mut Caches, lib: &[&Module], this_ptr: &mut Option<&mut Dynamic>, node: impl Into>, @@ -414,7 +414,7 @@ impl Engine { ) -> RhaiResultOf<()> { if self.debugger.is_some() { if let Some(cmd) = - self.run_debugger_with_reset_raw(scope, global, state, lib, this_ptr, node, level)? + self.run_debugger_with_reset_raw(scope, global, caches, lib, this_ptr, node, level)? { global.debugger.status = cmd; } @@ -434,14 +434,14 @@ impl Engine { &self, scope: &mut Scope, global: &mut GlobalRuntimeState, - state: &mut EvalState, + caches: &mut Caches, lib: &[&Module], this_ptr: &mut Option<&mut Dynamic>, node: impl Into>, level: usize, ) -> RhaiResultOf> { if self.debugger.is_some() { - self.run_debugger_with_reset_raw(scope, global, state, lib, this_ptr, node, level) + self.run_debugger_with_reset_raw(scope, global, caches, lib, this_ptr, node, level) } else { Ok(None) } @@ -458,7 +458,7 @@ impl Engine { &self, scope: &mut Scope, global: &mut GlobalRuntimeState, - state: &mut EvalState, + caches: &mut Caches, lib: &[&Module], this_ptr: &mut Option<&mut Dynamic>, node: impl Into>, @@ -490,7 +490,7 @@ impl Engine { } }; - self.run_debugger_raw(scope, global, state, lib, this_ptr, node, event, level) + self.run_debugger_raw(scope, global, caches, lib, this_ptr, node, event, level) } /// Run the debugger callback unconditionally. /// @@ -504,7 +504,7 @@ impl Engine { &self, scope: &mut Scope, global: &mut GlobalRuntimeState, - state: &mut EvalState, + caches: &mut Caches, lib: &[&Module], this_ptr: &mut Option<&mut Dynamic>, node: ASTNode<'a>, @@ -522,7 +522,7 @@ impl Engine { engine: self, scope, global, - state, + caches, lib, this_ptr, level, diff --git a/src/eval/eval_context.rs b/src/eval/eval_context.rs index f378076b..e05b9c60 100644 --- a/src/eval/eval_context.rs +++ b/src/eval/eval_context.rs @@ -1,21 +1,21 @@ //! Evaluation context. -use super::{EvalState, GlobalRuntimeState}; +use super::{Caches, GlobalRuntimeState}; use crate::{Dynamic, Engine, Module, Scope}; #[cfg(feature = "no_std")] use std::prelude::v1::*; /// Context of a script evaluation process. #[derive(Debug)] -pub struct EvalContext<'a, 'x, 'px, 'm, 'pm, 's, 'ps, 'b, 't, 'pt> { +pub struct EvalContext<'a, 'x, 'px, 'm, 'pm, 'c, 'b, 't, 'pt> { /// The current [`Engine`]. pub(crate) engine: &'a Engine, /// The current [`Scope`]. pub(crate) scope: &'x mut Scope<'px>, /// The current [`GlobalRuntimeState`]. pub(crate) global: &'m mut GlobalRuntimeState<'pm>, - /// The current [evaluation state][EvalState]. - pub(crate) state: &'s mut EvalState<'ps>, + /// The current [caches][Caches]. + pub(crate) caches: &'c mut Caches, /// The current stack of imported [modules][Module]. pub(crate) lib: &'b [&'b Module], /// The current bound `this` pointer, if any. @@ -24,7 +24,7 @@ pub struct EvalContext<'a, 'x, 'px, 'm, 'pm, 's, 'ps, 'b, 't, 'pt> { pub(crate) level: usize, } -impl<'x, 'px, 'm, 'pm, 'pt> EvalContext<'_, 'x, 'px, 'm, 'pm, '_, '_, '_, '_, 'pt> { +impl<'x, 'px, 'm, 'pm, 'pt> EvalContext<'_, 'x, 'px, 'm, 'pm, '_, '_, '_, 'pt> { /// The current [`Engine`]. #[inline(always)] #[must_use] diff --git a/src/eval/eval_state.rs b/src/eval/eval_state.rs deleted file mode 100644 index 13acd9b5..00000000 --- a/src/eval/eval_state.rs +++ /dev/null @@ -1,72 +0,0 @@ -//! Evaluation state. - -use crate::func::call::FnResolutionCache; -use crate::StaticVec; -#[cfg(feature = "no_std")] -use std::prelude::v1::*; -use std::{collections::BTreeMap, marker::PhantomData}; - -/// _(internals)_ A type that holds all the current states of the [`Engine`][crate::Engine]. -/// Exported under the `internals` feature only. -#[derive(Debug, Clone)] -pub struct EvalState<'a> { - /// Force a [`Scope`][crate::Scope] search by name. - /// - /// Normally, access to variables are parsed with a relative offset into the - /// [`Scope`][crate::Scope] to avoid a lookup. - /// - /// In some situation, e.g. after running an `eval` statement, or after a custom syntax - /// statement, subsequent offsets may become mis-aligned. - /// - /// When that happens, this flag is turned on. - pub always_search_scope: bool, - /// Level of the current scope. - /// - /// The global (root) level is zero, a new block (or function call) is one level higher, and so on. - pub scope_level: usize, - /// Stack of function resolution caches. - fn_resolution_caches: StaticVec, - /// Take care of the lifetime parameter - dummy: PhantomData<&'a ()>, -} - -impl EvalState<'_> { - /// Create a new [`EvalState`]. - #[inline(always)] - #[must_use] - pub fn new() -> Self { - Self { - always_search_scope: false, - scope_level: 0, - fn_resolution_caches: StaticVec::new_const(), - dummy: Default::default(), - } - } - /// Get the number of function resolution cache(s) in the stack. - #[inline(always)] - #[must_use] - pub fn fn_resolution_caches_len(&self) -> usize { - self.fn_resolution_caches.len() - } - /// Get a mutable reference to the current function resolution cache. - #[inline] - #[must_use] - pub fn fn_resolution_cache_mut(&mut self) -> &mut FnResolutionCache { - if self.fn_resolution_caches.is_empty() { - // Push a new function resolution cache if the stack is empty - self.push_fn_resolution_cache(); - } - self.fn_resolution_caches.last_mut().unwrap() - } - /// Push an empty function resolution cache onto the stack and make it current. - #[allow(dead_code)] - #[inline(always)] - pub fn push_fn_resolution_cache(&mut self) { - self.fn_resolution_caches.push(BTreeMap::new()); - } - /// Rewind the function resolution caches stack to a particular size. - #[inline(always)] - pub fn rewind_fn_resolution_caches(&mut self, len: usize) { - self.fn_resolution_caches.truncate(len); - } -} diff --git a/src/eval/expr.rs b/src/eval/expr.rs index c2d35b32..5d28f5c2 100644 --- a/src/eval/expr.rs +++ b/src/eval/expr.rs @@ -1,6 +1,6 @@ //! Module defining functions for evaluating an expression. -use super::{EvalContext, EvalState, GlobalRuntimeState, Target}; +use super::{Caches, EvalContext, GlobalRuntimeState, Target}; use crate::ast::{Expr, FnCallExpr, OpAssignment}; use crate::engine::{KEYWORD_THIS, OP_CONCAT}; use crate::types::dynamic::AccessMode; @@ -17,7 +17,6 @@ impl Engine { pub(crate) fn search_imports( &self, global: &GlobalRuntimeState, - state: &mut EvalState, namespace: &crate::ast::Namespace, ) -> Option> { assert!(!namespace.is_empty()); @@ -25,7 +24,7 @@ impl Engine { let root = namespace.root(); // Qualified - check if the root module is directly indexed - let index = if state.always_search_scope { + let index = if global.always_search_scope { None } else { namespace.index() @@ -48,7 +47,7 @@ impl Engine { &self, scope: &'s mut Scope, global: &mut GlobalRuntimeState, - state: &mut EvalState, + caches: &mut Caches, lib: &[&Module], this_ptr: &'s mut Option<&mut Dynamic>, expr: &Expr, @@ -56,24 +55,24 @@ impl Engine { ) -> RhaiResultOf<(Target<'s>, Position)> { match expr { Expr::Variable(_, Some(_), _) => { - self.search_scope_only(scope, global, state, lib, this_ptr, expr, level) + self.search_scope_only(scope, global, caches, lib, this_ptr, expr, level) } Expr::Variable(v, None, _var_pos) => match v.as_ref() { // Normal variable access #[cfg(not(feature = "no_module"))] (_, ns, ..) if ns.is_empty() => { - self.search_scope_only(scope, global, state, lib, this_ptr, expr, level) + self.search_scope_only(scope, global, caches, lib, this_ptr, expr, level) } #[cfg(feature = "no_module")] (_, (), ..) => { - self.search_scope_only(scope, global, state, lib, this_ptr, expr, level) + self.search_scope_only(scope, global, caches, lib, this_ptr, expr, level) } // Qualified variable access #[cfg(not(feature = "no_module"))] (_, namespace, hash_var, var_name) => { // foo:bar::baz::VARIABLE - if let Some(module) = self.search_imports(global, state, namespace) { + if let Some(module) = self.search_imports(global, namespace) { return if let Some(mut target) = module.get_qualified_var(*hash_var) { // Module variables are constant target.set_access_mode(AccessMode::ReadOnly); @@ -131,7 +130,7 @@ impl Engine { &self, scope: &'s mut Scope, global: &mut GlobalRuntimeState, - state: &mut EvalState, + caches: &mut Caches, lib: &[&Module], this_ptr: &'s mut Option<&mut Dynamic>, expr: &Expr, @@ -148,7 +147,7 @@ impl Engine { Err(ERR::ErrorUnboundThis(*pos).into()) } } - _ if state.always_search_scope => (0, expr.start_position()), + _ if global.always_search_scope => (0, expr.start_position()), Expr::Variable(.., Some(i), pos) => (i.get() as usize, *pos), Expr::Variable(v, None, pos) => (v.0.map(NonZeroUsize::get).unwrap_or(0), *pos), _ => unreachable!("Expr::Variable expected but gets {:?}", expr), @@ -160,7 +159,7 @@ impl Engine { engine: self, scope, global, - state, + caches, lib, this_ptr, level, @@ -205,7 +204,7 @@ impl Engine { &self, scope: &mut Scope, global: &mut GlobalRuntimeState, - state: &mut EvalState, + caches: &mut Caches, lib: &[&Module], this_ptr: &mut Option<&mut Dynamic>, expr: &FnCallExpr, @@ -228,7 +227,7 @@ impl Engine { let hash = hashes.native; return self.make_qualified_function_call( - scope, global, state, lib, this_ptr, namespace, name, args, hash, pos, level, + scope, global, caches, lib, this_ptr, namespace, name, args, hash, pos, level, ); } @@ -239,7 +238,7 @@ impl Engine { ); self.make_function_call( - scope, global, state, lib, this_ptr, name, first_arg, args, *hashes, *capture, pos, + scope, global, caches, lib, this_ptr, name, first_arg, args, *hashes, *capture, pos, level, ) } @@ -256,7 +255,7 @@ impl Engine { &self, scope: &mut Scope, global: &mut GlobalRuntimeState, - state: &mut EvalState, + caches: &mut Caches, lib: &[&Module], this_ptr: &mut Option<&mut Dynamic>, expr: &Expr, @@ -270,13 +269,13 @@ impl Engine { if let Expr::FnCall(x, ..) = expr { #[cfg(feature = "debugging")] let reset_debugger = - self.run_debugger_with_reset(scope, global, state, lib, this_ptr, expr, level)?; + self.run_debugger_with_reset(scope, global, caches, lib, this_ptr, expr, level)?; #[cfg(not(feature = "unchecked"))] self.inc_operations(&mut global.num_operations, expr.position())?; let result = - self.eval_fn_call_expr(scope, global, state, lib, this_ptr, x, x.pos, level); + self.eval_fn_call_expr(scope, global, caches, lib, this_ptr, x, x.pos, level); #[cfg(feature = "debugging")] global.debugger.reset_status(reset_debugger); @@ -289,7 +288,7 @@ impl Engine { // will cost more than the mis-predicted `match` branch. if let Expr::Variable(x, index, var_pos) = expr { #[cfg(feature = "debugging")] - self.run_debugger(scope, global, state, lib, this_ptr, expr, level)?; + self.run_debugger(scope, global, caches, lib, this_ptr, expr, level)?; #[cfg(not(feature = "unchecked"))] self.inc_operations(&mut global.num_operations, expr.position())?; @@ -300,14 +299,14 @@ impl Engine { .cloned() .ok_or_else(|| ERR::ErrorUnboundThis(*var_pos).into()) } else { - self.search_namespace(scope, global, state, lib, this_ptr, expr, level) + self.search_namespace(scope, global, caches, lib, this_ptr, expr, level) .map(|(val, ..)| val.take_or_clone()) }; } #[cfg(feature = "debugging")] let reset_debugger = - self.run_debugger_with_reset(scope, global, state, lib, this_ptr, expr, level)?; + self.run_debugger_with_reset(scope, global, caches, lib, this_ptr, expr, level)?; #[cfg(not(feature = "unchecked"))] self.inc_operations(&mut global.num_operations, expr.position())?; @@ -330,7 +329,7 @@ impl Engine { for expr in x.iter() { let item = - match self.eval_expr(scope, global, state, lib, this_ptr, expr, level) { + match self.eval_expr(scope, global, caches, lib, this_ptr, expr, level) { Ok(r) => r, err => { result = err; @@ -340,7 +339,7 @@ impl Engine { if let Err(err) = self.eval_op_assignment( global, - state, + caches, lib, Some(OpAssignment::new(OP_CONCAT)), expr.start_position(), @@ -367,7 +366,7 @@ impl Engine { for item_expr in x.iter() { let value = match self - .eval_expr(scope, global, state, lib, this_ptr, item_expr, level) + .eval_expr(scope, global, caches, lib, this_ptr, item_expr, level) { Ok(r) => r.flatten(), err => { @@ -405,7 +404,7 @@ impl Engine { for (key, value_expr) in x.0.iter() { let value = match self - .eval_expr(scope, global, state, lib, this_ptr, value_expr, level) + .eval_expr(scope, global, caches, lib, this_ptr, value_expr, level) { Ok(r) => r.flatten(), err => { @@ -431,7 +430,7 @@ impl Engine { Expr::And(x, ..) => { let lhs = self - .eval_expr(scope, global, state, lib, this_ptr, &x.lhs, level) + .eval_expr(scope, global, caches, lib, this_ptr, &x.lhs, level) .and_then(|v| { v.as_bool().map_err(|typ| { self.make_type_mismatch_err::(typ, x.lhs.position()) @@ -439,7 +438,7 @@ impl Engine { }); if let Ok(true) = lhs { - self.eval_expr(scope, global, state, lib, this_ptr, &x.rhs, level) + self.eval_expr(scope, global, caches, lib, this_ptr, &x.rhs, level) .and_then(|v| { v.as_bool() .map_err(|typ| { @@ -454,7 +453,7 @@ impl Engine { Expr::Or(x, ..) => { let lhs = self - .eval_expr(scope, global, state, lib, this_ptr, &x.lhs, level) + .eval_expr(scope, global, caches, lib, this_ptr, &x.lhs, level) .and_then(|v| { v.as_bool().map_err(|typ| { self.make_type_mismatch_err::(typ, x.lhs.position()) @@ -462,7 +461,7 @@ impl Engine { }); if let Ok(false) = lhs { - self.eval_expr(scope, global, state, lib, this_ptr, &x.rhs, level) + self.eval_expr(scope, global, caches, lib, this_ptr, &x.rhs, level) .and_then(|v| { v.as_bool() .map_err(|typ| { @@ -491,7 +490,7 @@ impl Engine { engine: self, scope, global, - state, + caches, lib, this_ptr, level, @@ -504,17 +503,17 @@ impl Engine { Expr::Stmt(x) if x.is_empty() => Ok(Dynamic::UNIT), Expr::Stmt(x) => { - self.eval_stmt_block(scope, global, state, lib, this_ptr, x, true, level) + self.eval_stmt_block(scope, global, caches, lib, this_ptr, x, true, level) } #[cfg(not(feature = "no_index"))] Expr::Index(..) => { - self.eval_dot_index_chain(scope, global, state, lib, this_ptr, expr, level, None) + self.eval_dot_index_chain(scope, global, caches, lib, this_ptr, expr, level, None) } #[cfg(not(feature = "no_object"))] Expr::Dot(..) => { - self.eval_dot_index_chain(scope, global, state, lib, this_ptr, expr, level, None) + self.eval_dot_index_chain(scope, global, caches, lib, this_ptr, expr, level, None) } _ => unreachable!("expression cannot be evaluated: {:?}", expr), diff --git a/src/eval/global_state.rs b/src/eval/global_state.rs index a7d6aeb6..ae02a67d 100644 --- a/src/eval/global_state.rs +++ b/src/eval/global_state.rs @@ -30,16 +30,31 @@ pub struct GlobalRuntimeState<'a> { #[cfg(not(feature = "no_module"))] modules: crate::StaticVec>, /// Source of the current context. + /// /// No source if the string is empty. pub source: Identifier, /// Number of operations performed. pub num_operations: u64, /// Number of modules loaded. pub num_modules_loaded: usize, + /// Level of the current scope. + /// + /// The global (root) level is zero, a new block (or function call) is one level higher, and so on. + pub scope_level: usize, + /// Force a [`Scope`][crate::Scope] search by name. + /// + /// Normally, access to variables are parsed with a relative offset into the + /// [`Scope`][crate::Scope] to avoid a lookup. + /// + /// In some situation, e.g. after running an `eval` statement, or after a custom syntax + /// statement, subsequent offsets may become mis-aligned. + /// + /// When that happens, this flag is turned on. + pub always_search_scope: bool, /// Function call hashes to index getters and setters. #[cfg(any(not(feature = "no_index"), not(feature = "no_object")))] fn_hash_indexing: (u64, u64), - /// Embedded [crate::Module][crate::Module] resolver. + /// Embedded [module][crate::Module] resolver. #[cfg(not(feature = "no_module"))] pub embedded_module_resolver: Option>, @@ -71,6 +86,8 @@ impl GlobalRuntimeState<'_> { source: Identifier::new_const(), num_operations: 0, num_modules_loaded: 0, + scope_level: 0, + always_search_scope: false, #[cfg(not(feature = "no_module"))] embedded_module_resolver: None, #[cfg(any(not(feature = "no_index"), not(feature = "no_object")))] @@ -92,7 +109,7 @@ impl GlobalRuntimeState<'_> { pub fn num_imports(&self) -> usize { self.keys.len() } - /// Get the globally-imported [crate::Module][crate::Module] at a particular index. + /// Get the globally-imported [module][crate::Module] at a particular index. /// /// Not available under `no_module`. #[cfg(not(feature = "no_module"))] @@ -101,7 +118,7 @@ impl GlobalRuntimeState<'_> { pub fn get_shared_import(&self, index: usize) -> Option> { self.modules.get(index).cloned() } - /// Get a mutable reference to the globally-imported [crate::Module][crate::Module] at a + /// Get a mutable reference to the globally-imported [module][crate::Module] at a /// particular index. /// /// Not available under `no_module`. @@ -115,7 +132,7 @@ impl GlobalRuntimeState<'_> { ) -> Option<&mut crate::Shared> { self.modules.get_mut(index) } - /// Get the index of a globally-imported [crate::Module][crate::Module] by name. + /// Get the index of a globally-imported [module][crate::Module] by name. /// /// Not available under `no_module`. #[cfg(not(feature = "no_module"))] @@ -132,7 +149,7 @@ impl GlobalRuntimeState<'_> { } }) } - /// Push an imported [crate::Module][crate::Module] onto the stack. + /// Push an imported [module][crate::Module] onto the stack. /// /// Not available under `no_module`. #[cfg(not(feature = "no_module"))] diff --git a/src/eval/mod.rs b/src/eval/mod.rs index 0b87a2dd..ea11a5d8 100644 --- a/src/eval/mod.rs +++ b/src/eval/mod.rs @@ -1,13 +1,14 @@ +mod cache; mod chaining; mod data_check; mod debugger; mod eval_context; -mod eval_state; mod expr; mod global_state; mod stmt; mod target; +pub use cache::{Caches, FnResolutionCache, FnResolutionCacheEntry}; #[cfg(any(not(feature = "no_index"), not(feature = "no_object")))] pub use chaining::{ChainArgument, ChainType}; #[cfg(feature = "debugging")] @@ -16,7 +17,6 @@ pub use debugger::{ OnDebuggerCallback, OnDebuggingInit, }; pub use eval_context::EvalContext; -pub use eval_state::EvalState; #[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_function"))] pub use global_state::GlobalConstants; diff --git a/src/eval/stmt.rs b/src/eval/stmt.rs index 4e33f28a..20d473d1 100644 --- a/src/eval/stmt.rs +++ b/src/eval/stmt.rs @@ -1,6 +1,6 @@ //! Module defining functions for evaluating a statement. -use super::{EvalContext, EvalState, GlobalRuntimeState, Target}; +use super::{Caches, EvalContext, GlobalRuntimeState, Target}; use crate::api::events::VarDefInfo; use crate::ast::{ ASTFlags, BinaryExpr, Expr, Ident, OpAssignment, Stmt, SwitchCases, TryCatchBlock, @@ -25,7 +25,7 @@ impl Engine { &self, scope: &mut Scope, global: &mut GlobalRuntimeState, - state: &mut EvalState, + caches: &mut Caches, lib: &[&Module], this_ptr: &mut Option<&mut Dynamic>, statements: &[Stmt], @@ -36,14 +36,14 @@ impl Engine { return Ok(Dynamic::UNIT); } - let orig_always_search_scope = state.always_search_scope; + let orig_always_search_scope = global.always_search_scope; let orig_scope_len = scope.len(); #[cfg(not(feature = "no_module"))] let orig_imports_len = global.num_imports(); - let orig_fn_resolution_caches_len = state.fn_resolution_caches_len(); + let orig_fn_resolution_caches_len = caches.fn_resolution_caches_len(); if restore_orig_state { - state.scope_level += 1; + global.scope_level += 1; } let mut result = Ok(Dynamic::UNIT); @@ -55,7 +55,7 @@ impl Engine { result = self.eval_stmt( scope, global, - state, + caches, lib, this_ptr, stmt, @@ -76,34 +76,34 @@ impl Engine { .skip(imports_len) .any(|(.., m)| m.contains_indexed_global_functions()) { - if state.fn_resolution_caches_len() > orig_fn_resolution_caches_len { + if caches.fn_resolution_caches_len() > orig_fn_resolution_caches_len { // When new module is imported with global functions and there is already // a new cache, clear it - notice that this is expensive as all function // resolutions must start again - state.fn_resolution_cache_mut().clear(); + caches.fn_resolution_cache_mut().clear(); } else if restore_orig_state { // When new module is imported with global functions, push a new cache - state.push_fn_resolution_cache(); + caches.push_fn_resolution_cache(); } else { // When the block is to be evaluated in-place, just clear the current cache - state.fn_resolution_cache_mut().clear(); + caches.fn_resolution_cache_mut().clear(); } } } } // If imports list is modified, pop the functions lookup cache - state.rewind_fn_resolution_caches(orig_fn_resolution_caches_len); + caches.rewind_fn_resolution_caches(orig_fn_resolution_caches_len); if restore_orig_state { scope.rewind(orig_scope_len); - state.scope_level -= 1; + global.scope_level -= 1; #[cfg(not(feature = "no_module"))] global.truncate_imports(orig_imports_len); // The impact of new local variables goes away at the end of a block // because any new variables introduced will go out of scope - state.always_search_scope = orig_always_search_scope; + global.always_search_scope = orig_always_search_scope; } result @@ -114,7 +114,7 @@ impl Engine { pub(crate) fn eval_op_assignment( &self, global: &mut GlobalRuntimeState, - state: &mut EvalState, + caches: &mut Caches, lib: &[&Module], op_info: Option, op_pos: Position, @@ -157,7 +157,7 @@ impl Engine { let level = level + 1; match self.call_native_fn( - global, state, lib, op_assign, hash, args, true, true, op_pos, level, + global, caches, lib, op_assign, hash, args, true, true, op_pos, level, ) { Ok(_) => { #[cfg(not(feature = "unchecked"))] @@ -167,7 +167,7 @@ impl Engine { { // Expand to `var = var op rhs` let (value, ..) = self.call_native_fn( - global, state, lib, op, hash_op, args, true, false, op_pos, level, + global, caches, lib, op, hash_op, args, true, false, op_pos, level, )?; #[cfg(not(feature = "unchecked"))] @@ -197,7 +197,7 @@ impl Engine { &self, scope: &mut Scope, global: &mut GlobalRuntimeState, - state: &mut EvalState, + caches: &mut Caches, lib: &[&Module], this_ptr: &mut Option<&mut Dynamic>, stmt: &Stmt, @@ -206,7 +206,7 @@ impl Engine { ) -> RhaiResult { #[cfg(feature = "debugging")] let reset_debugger = - self.run_debugger_with_reset(scope, global, state, lib, this_ptr, stmt, level)?; + self.run_debugger_with_reset(scope, global, caches, lib, this_ptr, stmt, level)?; // Coded this way for better branch prediction. // Popular branches are lifted out of the `match` statement into their own branches. @@ -217,7 +217,7 @@ impl Engine { self.inc_operations(&mut global.num_operations, stmt.position())?; let result = - self.eval_fn_call_expr(scope, global, state, lib, this_ptr, x, x.pos, level); + self.eval_fn_call_expr(scope, global, caches, lib, this_ptr, x, x.pos, level); #[cfg(feature = "debugging")] global.debugger.reset_status(reset_debugger); @@ -236,12 +236,12 @@ impl Engine { let (op_info, BinaryExpr { lhs, rhs }) = x.as_ref(); let rhs_result = self - .eval_expr(scope, global, state, lib, this_ptr, rhs, level) + .eval_expr(scope, global, caches, lib, this_ptr, rhs, level) .map(Dynamic::flatten); if let Ok(rhs_val) = rhs_result { let search_result = - self.search_namespace(scope, global, state, lib, this_ptr, lhs, level); + self.search_namespace(scope, global, caches, lib, this_ptr, lhs, level); if let Ok(search_val) = search_result { let (mut lhs_ptr, pos) = search_val; @@ -261,7 +261,7 @@ impl Engine { let lhs_ptr = &mut lhs_ptr; self.eval_op_assignment( - global, state, lib, *op_info, *op_pos, lhs_ptr, root, rhs_val, level, + global, caches, lib, *op_info, *op_pos, lhs_ptr, root, rhs_val, level, ) .map_err(|err| err.fill_position(rhs.start_position())) .map(|_| Dynamic::UNIT) @@ -275,7 +275,7 @@ impl Engine { let (op_info, BinaryExpr { lhs, rhs }) = x.as_ref(); let rhs_result = self - .eval_expr(scope, global, state, lib, this_ptr, rhs, level) + .eval_expr(scope, global, caches, lib, this_ptr, rhs, level) .map(Dynamic::flatten); if let Ok(rhs_val) = rhs_result { @@ -291,14 +291,14 @@ impl Engine { #[cfg(not(feature = "no_index"))] Expr::Index(..) => self .eval_dot_index_chain( - scope, global, state, lib, this_ptr, lhs, level, _new_val, + scope, global, caches, lib, this_ptr, lhs, level, _new_val, ) .map(|_| Dynamic::UNIT), // dot_lhs.dot_rhs op= rhs #[cfg(not(feature = "no_object"))] Expr::Dot(..) => self .eval_dot_index_chain( - scope, global, state, lib, this_ptr, lhs, level, _new_val, + scope, global, caches, lib, this_ptr, lhs, level, _new_val, ) .map(|_| Dynamic::UNIT), _ => unreachable!("cannot assign to expression: {:?}", lhs), @@ -323,21 +323,21 @@ impl Engine { // Expression as statement Stmt::Expr(expr) => self - .eval_expr(scope, global, state, lib, this_ptr, expr, level) + .eval_expr(scope, global, caches, lib, this_ptr, expr, level) .map(Dynamic::flatten), // Block scope Stmt::Block(statements, ..) if statements.is_empty() => Ok(Dynamic::UNIT), - Stmt::Block(statements, ..) => { - self.eval_stmt_block(scope, global, state, lib, this_ptr, statements, true, level) - } + Stmt::Block(statements, ..) => self.eval_stmt_block( + scope, global, caches, lib, this_ptr, statements, true, level, + ), // If statement Stmt::If(x, ..) => { let (expr, if_block, else_block) = x.as_ref(); let guard_val = self - .eval_expr(scope, global, state, lib, this_ptr, expr, level) + .eval_expr(scope, global, caches, lib, this_ptr, expr, level) .and_then(|v| { v.as_bool().map_err(|typ| { self.make_type_mismatch_err::(typ, expr.position()) @@ -348,7 +348,7 @@ impl Engine { Ok(true) => { if !if_block.is_empty() { self.eval_stmt_block( - scope, global, state, lib, this_ptr, if_block, true, level, + scope, global, caches, lib, this_ptr, if_block, true, level, ) } else { Ok(Dynamic::UNIT) @@ -357,7 +357,7 @@ impl Engine { Ok(false) => { if !else_block.is_empty() { self.eval_stmt_block( - scope, global, state, lib, this_ptr, else_block, true, level, + scope, global, caches, lib, this_ptr, else_block, true, level, ) } else { Ok(Dynamic::UNIT) @@ -378,7 +378,8 @@ impl Engine { }, ) = x.as_ref(); - let value_result = self.eval_expr(scope, global, state, lib, this_ptr, expr, level); + let value_result = + self.eval_expr(scope, global, caches, lib, this_ptr, expr, level); if let Ok(value) = value_result { let stmt_block_result = if value.is_hashable() { @@ -392,15 +393,17 @@ impl Engine { .condition .as_ref() .map(|cond| { - self.eval_expr(scope, global, state, lib, this_ptr, cond, level) - .and_then(|v| { - v.as_bool().map_err(|typ| { - self.make_type_mismatch_err::( - typ, - cond.position(), - ) - }) + self.eval_expr( + scope, global, caches, lib, this_ptr, cond, level, + ) + .and_then(|v| { + v.as_bool().map_err(|typ| { + self.make_type_mismatch_err::( + typ, + cond.position(), + ) }) + }) }) .unwrap_or(Ok(true)); @@ -425,7 +428,7 @@ impl Engine { .as_ref() .map(|cond| { self.eval_expr( - scope, global, state, lib, this_ptr, cond, level, + scope, global, caches, lib, this_ptr, cond, level, ) .and_then(|v| { v.as_bool().map_err(|typ| { @@ -460,7 +463,7 @@ impl Engine { if let Ok(Some(statements)) = stmt_block_result { if !statements.is_empty() { self.eval_stmt_block( - scope, global, state, lib, this_ptr, statements, true, level, + scope, global, caches, lib, this_ptr, statements, true, level, ) } else { Ok(Dynamic::UNIT) @@ -469,7 +472,7 @@ impl Engine { // Default match clause if !def_case.is_empty() { self.eval_stmt_block( - scope, global, state, lib, this_ptr, def_case, true, level, + scope, global, caches, lib, this_ptr, def_case, true, level, ) } else { Ok(Dynamic::UNIT) @@ -488,7 +491,7 @@ impl Engine { if !body.is_empty() { match self - .eval_stmt_block(scope, global, state, lib, this_ptr, body, true, level) + .eval_stmt_block(scope, global, caches, lib, this_ptr, body, true, level) { Ok(_) => (), Err(err) => match *err { @@ -508,7 +511,7 @@ impl Engine { let (expr, body) = x.as_ref(); let condition = self - .eval_expr(scope, global, state, lib, this_ptr, expr, level) + .eval_expr(scope, global, caches, lib, this_ptr, expr, level) .and_then(|v| { v.as_bool().map_err(|typ| { self.make_type_mismatch_err::(typ, expr.position()) @@ -519,9 +522,9 @@ impl Engine { Ok(false) => break Ok(Dynamic::UNIT), Ok(true) if body.is_empty() => (), Ok(true) => { - match self - .eval_stmt_block(scope, global, state, lib, this_ptr, body, true, level) - { + match self.eval_stmt_block( + scope, global, caches, lib, this_ptr, body, true, level, + ) { Ok(_) => (), Err(err) => match *err { ERR::LoopBreak(false, ..) => (), @@ -541,7 +544,7 @@ impl Engine { if !body.is_empty() { match self - .eval_stmt_block(scope, global, state, lib, this_ptr, body, true, level) + .eval_stmt_block(scope, global, caches, lib, this_ptr, body, true, level) { Ok(_) => (), Err(err) => match *err { @@ -553,7 +556,7 @@ impl Engine { } let condition = self - .eval_expr(scope, global, state, lib, this_ptr, &expr, level) + .eval_expr(scope, global, caches, lib, this_ptr, &expr, level) .and_then(|v| { v.as_bool().map_err(|typ| { self.make_type_mismatch_err::(typ, expr.position()) @@ -572,7 +575,7 @@ impl Engine { let (var_name, counter, expr, statements) = x.as_ref(); let iter_result = self - .eval_expr(scope, global, state, lib, this_ptr, expr, level) + .eval_expr(scope, global, caches, lib, this_ptr, expr, level) .map(Dynamic::flatten); if let Ok(iter_obj) = iter_result { @@ -671,7 +674,7 @@ impl Engine { } let result = self.eval_stmt_block( - scope, global, state, lib, this_ptr, statements, true, level, + scope, global, caches, lib, this_ptr, statements, true, level, ); match result { @@ -715,7 +718,7 @@ impl Engine { } = x.as_ref(); let result = self - .eval_stmt_block(scope, global, state, lib, this_ptr, try_block, true, level) + .eval_stmt_block(scope, global, caches, lib, this_ptr, try_block, true, level) .map(|_| Dynamic::UNIT); match result { @@ -767,7 +770,7 @@ impl Engine { let result = self.eval_stmt_block( scope, global, - state, + caches, lib, this_ptr, catch_block, @@ -794,7 +797,7 @@ impl Engine { // Throw value Stmt::Return(Some(expr), options, pos) if options.contains(ASTFlags::BREAK) => self - .eval_expr(scope, global, state, lib, this_ptr, expr, level) + .eval_expr(scope, global, caches, lib, this_ptr, expr, level) .and_then(|v| Err(ERR::ErrorRuntime(v.flatten(), *pos).into())), // Empty throw @@ -804,7 +807,7 @@ impl Engine { // Return value Stmt::Return(Some(expr), .., pos) => self - .eval_expr(scope, global, state, lib, this_ptr, expr, level) + .eval_expr(scope, global, caches, lib, this_ptr, expr, level) .and_then(|v| Err(ERR::Return(v.flatten(), *pos).into())), // Empty return @@ -828,7 +831,7 @@ impl Engine { // Check variable definition filter let result = if let Some(ref filter) = self.def_var_filter { let will_shadow = scope.contains(var_name); - let nesting_level = state.scope_level; + let nesting_level = global.scope_level; let is_const = access == AccessMode::ReadOnly; let info = VarDefInfo { name: var_name, @@ -840,7 +843,7 @@ impl Engine { engine: self, scope, global, - state, + caches, lib, this_ptr, level, @@ -864,7 +867,7 @@ impl Engine { } else { // Evaluate initial value let value_result = self - .eval_expr(scope, global, state, lib, this_ptr, expr, level) + .eval_expr(scope, global, caches, lib, this_ptr, expr, level) .map(Dynamic::flatten); if let Ok(mut value) = value_result { @@ -872,7 +875,7 @@ impl Engine { // Put global constants into global module #[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_module"))] - if state.scope_level == 0 + if global.scope_level == 0 && access == AccessMode::ReadOnly && lib.iter().any(|&m| !m.is_empty()) { @@ -927,7 +930,7 @@ impl Engine { } let path_result = self - .eval_expr(scope, global, state, lib, this_ptr, &expr, level) + .eval_expr(scope, global, caches, lib, this_ptr, &expr, level) .and_then(|v| { let typ = v.type_name(); v.try_cast::().ok_or_else(|| { diff --git a/src/func/call.rs b/src/func/call.rs index 190a29c7..c74beef5 100644 --- a/src/func/call.rs +++ b/src/func/call.rs @@ -9,7 +9,7 @@ use crate::engine::{ KEYWORD_DEBUG, KEYWORD_EVAL, KEYWORD_FN_PTR, KEYWORD_FN_PTR_CALL, KEYWORD_FN_PTR_CURRY, KEYWORD_IS_DEF_VAR, KEYWORD_PRINT, KEYWORD_TYPE_OF, }; -use crate::eval::{EvalState, GlobalRuntimeState}; +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, @@ -19,7 +19,6 @@ use crate::{ use std::prelude::v1::*; use std::{ any::{type_name, TypeId}, - collections::BTreeMap, convert::TryFrom, mem, }; @@ -128,24 +127,6 @@ pub fn ensure_no_data_race( Ok(()) } -/// _(internals)_ An entry in a function resolution cache. -/// Exported under the `internals` feature only. -#[derive(Debug, Clone)] -pub struct FnResolutionCacheEntry { - /// Function. - pub func: CallableFunction, - /// Optional source. - /// No source if the string is empty. - pub source: Identifier, -} - -/// _(internals)_ A function resolution cache. -/// Exported under the `internals` feature only. -/// -/// [`FnResolutionCacheEntry`] is [`Box`]ed in order to pack as many entries inside a single B-Tree -/// level as possible. -pub type FnResolutionCache = BTreeMap>>; - impl Engine { /// Generate the signature for a function call. #[inline] @@ -196,7 +177,7 @@ impl Engine { fn resolve_fn<'s>( &self, _global: &GlobalRuntimeState, - state: &'s mut EvalState, + state: &'s mut Caches, lib: &[&Module], fn_name: &str, hash_script: u64, @@ -344,7 +325,7 @@ impl Engine { pub(crate) fn call_native_fn( &self, global: &mut GlobalRuntimeState, - state: &mut EvalState, + caches: &mut Caches, lib: &[&Module], name: &str, hash: u64, @@ -362,7 +343,7 @@ impl Engine { // Check if function access already in the cache let func = self.resolve_fn( global, - state, + caches, lib, name, hash, @@ -439,7 +420,7 @@ impl Engine { Err(ref err) => crate::eval::DebuggerEvent::FunctionExitWithError(err), }; match self - .run_debugger_raw(scope, global, state, lib, &mut None, node, event, level) + .run_debugger_raw(scope, global, caches, lib, &mut None, node, event, level) { Ok(_) => (), Err(err) => _result = Err(err), @@ -576,7 +557,7 @@ impl Engine { &self, _scope: Option<&mut Scope>, global: &mut GlobalRuntimeState, - state: &mut EvalState, + caches: &mut Caches, lib: &[&Module], fn_name: &str, hashes: FnCallHashes, @@ -619,7 +600,7 @@ impl Engine { false } else { let hash_script = calc_fn_hash(fn_name.as_str(), num_params as usize); - self.has_script_fn(Some(global), state, lib, hash_script) + self.has_script_fn(Some(global), caches, lib, hash_script) } .into(), false, @@ -650,7 +631,7 @@ impl Engine { if let Some(FnResolutionCacheEntry { func, mut source }) = self .resolve_fn( global, - state, + caches, lib, fn_name, hashes.script, @@ -687,7 +668,7 @@ impl Engine { self.call_script_fn( scope, global, - state, + caches, lib, &mut Some(*first_arg), func, @@ -706,7 +687,7 @@ impl Engine { } let result = self.call_script_fn( - scope, global, state, lib, &mut None, func, args, true, pos, level, + scope, global, caches, lib, &mut None, func, args, true, pos, level, ); // Restore the original reference @@ -724,7 +705,7 @@ impl Engine { // Native function call let hash = hashes.native; self.call_native_fn( - global, state, lib, fn_name, hash, args, is_ref_mut, false, pos, level, + global, caches, lib, fn_name, hash, args, is_ref_mut, false, pos, level, ) } @@ -735,13 +716,13 @@ impl Engine { &self, scope: &mut Scope, global: &mut GlobalRuntimeState, - state: &mut EvalState, + caches: &mut Caches, statements: &[Stmt], lib: &[&Module], level: usize, ) -> RhaiResult { self.eval_stmt_block( - scope, global, state, lib, &mut None, statements, false, level, + scope, global, caches, lib, &mut None, statements, false, level, ) .or_else(|err| match *err { ERR::Return(out, ..) => Ok(out), @@ -757,7 +738,7 @@ impl Engine { pub(crate) fn make_method_call( &self, global: &mut GlobalRuntimeState, - state: &mut EvalState, + caches: &mut Caches, lib: &[&Module], fn_name: &str, mut hash: FnCallHashes, @@ -786,7 +767,7 @@ impl Engine { // Map it to name(args) in function-call style self.exec_fn_call( - None, global, state, lib, fn_name, new_hash, &mut args, false, false, pos, + None, global, caches, lib, fn_name, new_hash, &mut args, false, false, pos, level, ) } @@ -822,7 +803,7 @@ impl Engine { // Map it to name(args) in function-call style self.exec_fn_call( - None, global, state, lib, fn_name, new_hash, &mut args, is_ref_mut, true, pos, + None, global, caches, lib, fn_name, new_hash, &mut args, is_ref_mut, true, pos, level, ) } @@ -892,7 +873,7 @@ impl Engine { args.extend(call_args.iter_mut()); self.exec_fn_call( - None, global, state, lib, fn_name, hash, &mut args, is_ref_mut, true, pos, + None, global, caches, lib, fn_name, hash, &mut args, is_ref_mut, true, pos, level, ) } @@ -914,7 +895,7 @@ impl Engine { &self, scope: &mut Scope, global: &mut GlobalRuntimeState, - state: &mut EvalState, + caches: &mut Caches, lib: &[&Module], this_ptr: &mut Option<&mut Dynamic>, arg_expr: &Expr, @@ -924,7 +905,7 @@ impl Engine { if self.debugger.is_some() { if let Some(value) = arg_expr.get_literal_value() { #[cfg(feature = "debugging")] - self.run_debugger(scope, global, state, lib, this_ptr, arg_expr, level)?; + self.run_debugger(scope, global, caches, lib, this_ptr, arg_expr, level)?; return Ok((value, arg_expr.start_position())); } } @@ -935,7 +916,7 @@ impl Engine { matches!(status, crate::eval::DebuggerStatus::FunctionExit(..)) }); - let result = self.eval_expr(scope, global, state, lib, this_ptr, arg_expr, level); + let result = self.eval_expr(scope, global, caches, lib, this_ptr, arg_expr, level); // Restore function exit status #[cfg(feature = "debugging")] @@ -949,7 +930,7 @@ impl Engine { &self, scope: &mut Scope, global: &mut GlobalRuntimeState, - state: &mut EvalState, + caches: &mut Caches, lib: &[&Module], this_ptr: &mut Option<&mut Dynamic>, fn_name: &str, @@ -973,7 +954,7 @@ impl Engine { KEYWORD_FN_PTR_CALL if total_args >= 1 => { let arg = first_arg.unwrap(); let (arg_value, arg_pos) = - self.get_arg_value(scope, global, state, lib, this_ptr, arg, level)?; + self.get_arg_value(scope, global, caches, lib, this_ptr, arg, level)?; if !arg_value.is::() { let typ = self.map_type_name(arg_value.type_name()); @@ -1006,7 +987,7 @@ impl Engine { KEYWORD_FN_PTR if total_args == 1 => { let arg = first_arg.unwrap(); let (arg_value, arg_pos) = - self.get_arg_value(scope, global, state, lib, this_ptr, arg, level)?; + self.get_arg_value(scope, global, caches, lib, this_ptr, arg, level)?; // Fn - only in function call style return arg_value @@ -1021,7 +1002,7 @@ impl Engine { KEYWORD_FN_PTR_CURRY if total_args > 1 => { let first = first_arg.unwrap(); let (arg_value, arg_pos) = - self.get_arg_value(scope, global, state, lib, this_ptr, first, level)?; + self.get_arg_value(scope, global, caches, lib, this_ptr, first, level)?; if !arg_value.is::() { let typ = self.map_type_name(arg_value.type_name()); @@ -1033,7 +1014,7 @@ impl Engine { // Append the new curried arguments to the existing list. let fn_curry = a_expr.iter().try_fold(fn_curry, |mut curried, expr| { let (value, ..) = - self.get_arg_value(scope, global, state, lib, this_ptr, expr, level)?; + self.get_arg_value(scope, global, caches, lib, this_ptr, expr, level)?; curried.push(value); Ok::<_, RhaiError>(curried) })?; @@ -1046,7 +1027,7 @@ impl Engine { crate::engine::KEYWORD_IS_SHARED if total_args == 1 => { let arg = first_arg.unwrap(); let (arg_value, ..) = - self.get_arg_value(scope, global, state, lib, this_ptr, arg, level)?; + self.get_arg_value(scope, global, caches, lib, this_ptr, arg, level)?; return Ok(arg_value.is_shared().into()); } @@ -1055,14 +1036,14 @@ impl Engine { crate::engine::KEYWORD_IS_DEF_FN if total_args == 2 => { let first = first_arg.unwrap(); let (arg_value, arg_pos) = - self.get_arg_value(scope, global, state, lib, this_ptr, first, level)?; + self.get_arg_value(scope, global, caches, lib, this_ptr, first, level)?; let fn_name = arg_value .into_immutable_string() .map_err(|typ| self.make_type_mismatch_err::(typ, arg_pos))?; let (arg_value, arg_pos) = - self.get_arg_value(scope, global, state, lib, this_ptr, &a_expr[0], level)?; + self.get_arg_value(scope, global, caches, lib, this_ptr, &a_expr[0], level)?; let num_params = arg_value .as_int() @@ -1072,7 +1053,7 @@ impl Engine { false } else { let hash_script = calc_fn_hash(&fn_name, num_params as usize); - self.has_script_fn(Some(global), state, lib, hash_script) + self.has_script_fn(Some(global), caches, lib, hash_script) } .into()); } @@ -1081,7 +1062,7 @@ impl Engine { KEYWORD_IS_DEF_VAR if total_args == 1 => { let arg = first_arg.unwrap(); let (arg_value, arg_pos) = - self.get_arg_value(scope, global, state, lib, this_ptr, arg, level)?; + self.get_arg_value(scope, global, caches, lib, this_ptr, arg, level)?; let var_name = arg_value .into_immutable_string() .map_err(|typ| self.make_type_mismatch_err::(typ, arg_pos))?; @@ -1094,14 +1075,14 @@ impl Engine { let orig_scope_len = scope.len(); let arg = first_arg.unwrap(); let (arg_value, pos) = - self.get_arg_value(scope, global, state, lib, this_ptr, arg, level)?; + self.get_arg_value(scope, global, caches, lib, this_ptr, arg, level)?; let script = &arg_value .into_immutable_string() .map_err(|typ| self.make_type_mismatch_err::(typ, pos))?; let result = self.eval_script_expr_in_place( scope, global, - state, + caches, lib, script, pos, @@ -1111,7 +1092,7 @@ impl Engine { // IMPORTANT! If the eval defines new variables in the current scope, // all variable offsets from this point on will be mis-aligned. if scope.len() != orig_scope_len { - state.always_search_scope = true; + global.always_search_scope = true; } return result.map_err(|err| { @@ -1143,7 +1124,7 @@ impl Engine { .map(|&v| v) .chain(a_expr.iter()) .try_for_each(|expr| { - self.get_arg_value(scope, global, state, lib, this_ptr, expr, level) + self.get_arg_value(scope, global, caches, lib, this_ptr, expr, level) .map(|(value, ..)| arg_values.push(value.flatten())) })?; args.extend(curry.iter_mut()); @@ -1154,7 +1135,7 @@ impl Engine { return self .exec_fn_call( - scope, global, state, lib, name, hashes, &mut args, is_ref_mut, false, pos, + scope, global, caches, lib, name, hashes, &mut args, is_ref_mut, false, pos, level, ) .map(|(v, ..)| v); @@ -1171,16 +1152,16 @@ impl Engine { let first_expr = first_arg.unwrap(); #[cfg(feature = "debugging")] - self.run_debugger(scope, global, state, lib, this_ptr, first_expr, level)?; + self.run_debugger(scope, global, caches, lib, this_ptr, first_expr, level)?; // func(x, ...) -> x.func(...) a_expr.iter().try_for_each(|expr| { - self.get_arg_value(scope, global, state, lib, this_ptr, expr, level) + self.get_arg_value(scope, global, caches, lib, this_ptr, expr, level) .map(|(value, ..)| arg_values.push(value.flatten())) })?; let (mut target, _pos) = - self.search_namespace(scope, global, state, lib, this_ptr, first_expr, level)?; + self.search_namespace(scope, global, caches, lib, this_ptr, first_expr, level)?; if target.as_ref().is_read_only() { target = target.into_owned(); @@ -1210,7 +1191,7 @@ impl Engine { .into_iter() .chain(a_expr.iter()) .try_for_each(|expr| { - self.get_arg_value(scope, global, state, lib, this_ptr, expr, level) + self.get_arg_value(scope, global, caches, lib, this_ptr, expr, level) .map(|(value, ..)| arg_values.push(value.flatten())) })?; args.extend(curry.iter_mut()); @@ -1219,7 +1200,7 @@ impl Engine { } self.exec_fn_call( - None, global, state, lib, name, hashes, &mut args, is_ref_mut, false, pos, level, + None, global, caches, lib, name, hashes, &mut args, is_ref_mut, false, pos, level, ) .map(|(v, ..)| v) } @@ -1230,7 +1211,7 @@ impl Engine { &self, scope: &mut Scope, global: &mut GlobalRuntimeState, - state: &mut EvalState, + caches: &mut Caches, lib: &[&Module], this_ptr: &mut Option<&mut Dynamic>, namespace: &crate::ast::Namespace, @@ -1252,20 +1233,20 @@ impl Engine { // and avoid cloning the value if !args_expr.is_empty() && args_expr[0].is_variable_access(true) { #[cfg(feature = "debugging")] - self.run_debugger(scope, global, state, lib, this_ptr, &args_expr[0], level)?; + self.run_debugger(scope, global, caches, lib, this_ptr, &args_expr[0], level)?; // func(x, ...) -> x.func(...) arg_values.push(Dynamic::UNIT); args_expr.iter().skip(1).try_for_each(|expr| { - self.get_arg_value(scope, global, state, lib, this_ptr, expr, level) + self.get_arg_value(scope, global, caches, lib, this_ptr, expr, level) .map(|(value, ..)| arg_values.push(value.flatten())) })?; // Get target reference to first argument let first_arg = &args_expr[0]; let (target, _pos) = - self.search_scope_only(scope, global, state, lib, this_ptr, first_arg, level)?; + self.search_scope_only(scope, global, caches, lib, this_ptr, first_arg, level)?; #[cfg(not(feature = "unchecked"))] self.inc_operations(&mut global.num_operations, _pos)?; @@ -1289,7 +1270,7 @@ impl Engine { } else { // func(..., ...) or func(mod::x, ...) args_expr.iter().try_for_each(|expr| { - self.get_arg_value(scope, global, state, lib, this_ptr, expr, level) + self.get_arg_value(scope, global, caches, lib, this_ptr, expr, level) .map(|(value, ..)| arg_values.push(value.flatten())) })?; args.extend(arg_values.iter_mut()); @@ -1298,7 +1279,7 @@ impl Engine { // Search for the root namespace let module = self - .search_imports(global, state, namespace) + .search_imports(global, namespace) .ok_or_else(|| ERR::ErrorModuleNotFound(namespace.to_string(), namespace.position()))?; // First search script-defined functions in namespace (can override built-in) @@ -1370,7 +1351,7 @@ impl Engine { mem::swap(&mut global.source, &mut source); let result = self.call_script_fn( - new_scope, global, state, lib, &mut None, fn_def, &mut args, true, pos, level, + new_scope, global, caches, lib, &mut None, fn_def, &mut args, true, pos, level, ); global.source = source; @@ -1410,7 +1391,7 @@ impl Engine { &self, scope: &mut Scope, global: &mut GlobalRuntimeState, - state: &mut EvalState, + caches: &mut Caches, lib: &[&Module], script: &str, _pos: Position, @@ -1448,6 +1429,6 @@ impl Engine { } // Evaluate the AST - self.eval_global_statements(scope, global, state, statements, lib, level) + self.eval_global_statements(scope, global, caches, statements, lib, level) } } diff --git a/src/func/native.rs b/src/func/native.rs index d3e1e3a7..8e1a27eb 100644 --- a/src/func/native.rs +++ b/src/func/native.rs @@ -3,7 +3,7 @@ use super::call::FnCallArgs; use crate::api::events::VarDefInfo; use crate::ast::FnCallHashes; -use crate::eval::{EvalState, GlobalRuntimeState}; +use crate::eval::{Caches, GlobalRuntimeState}; use crate::plugin::PluginFunction; use crate::tokenizer::{Token, TokenizeState}; use crate::types::dynamic::Variant; @@ -311,7 +311,7 @@ impl<'a> NativeCallContext<'a> { .global .cloned() .unwrap_or_else(|| GlobalRuntimeState::new(self.engine())); - let mut state = EvalState::new(); + let mut caches = Caches::new(); let fn_name = fn_name.as_ref(); let args_len = args.len(); @@ -330,7 +330,7 @@ impl<'a> NativeCallContext<'a> { .exec_fn_call( None, &mut global, - &mut state, + &mut caches, self.lib, fn_name, hash, diff --git a/src/func/script.rs b/src/func/script.rs index 97bcdaba..fb024849 100644 --- a/src/func/script.rs +++ b/src/func/script.rs @@ -3,7 +3,7 @@ use super::call::FnCallArgs; use crate::ast::ScriptFnDef; -use crate::eval::{EvalState, GlobalRuntimeState}; +use crate::eval::{Caches, GlobalRuntimeState}; use crate::{Dynamic, Engine, Module, Position, RhaiError, RhaiResult, Scope, ERR}; use std::mem; #[cfg(feature = "no_std")] @@ -26,7 +26,7 @@ impl Engine { &self, scope: &mut Scope, global: &mut GlobalRuntimeState, - state: &mut EvalState, + caches: &mut Caches, lib: &[&Module], this_ptr: &mut Option<&mut Dynamic>, fn_def: &ScriptFnDef, @@ -109,7 +109,7 @@ impl Engine { } // Merge in encapsulated environment, if any - let orig_fn_resolution_caches_len = state.fn_resolution_caches_len(); + let orig_fn_resolution_caches_len = caches.fn_resolution_caches_len(); #[cfg(not(feature = "no_module"))] let mut lib_merged = crate::StaticVec::with_capacity(lib.len() + 1); @@ -128,7 +128,7 @@ impl Engine { if fn_lib.is_empty() { lib } else { - state.push_fn_resolution_cache(); + caches.push_fn_resolution_cache(); lib_merged.push(fn_lib.as_ref()); lib_merged.extend(lib.iter().cloned()); &lib_merged @@ -142,7 +142,7 @@ impl Engine { #[cfg(feature = "debugging")] { let node = crate::ast::Stmt::Noop(fn_def.body.position()); - self.run_debugger(scope, global, state, lib, this_ptr, &node, level)?; + self.run_debugger(scope, global, caches, lib, this_ptr, &node, level)?; } // Evaluate the function @@ -150,7 +150,7 @@ impl Engine { .eval_stmt_block( scope, global, - state, + caches, lib, this_ptr, &fn_def.body, @@ -193,7 +193,8 @@ impl Engine { Ok(ref r) => crate::eval::DebuggerEvent::FunctionExitWithValue(r), Err(ref err) => crate::eval::DebuggerEvent::FunctionExitWithError(err), }; - match self.run_debugger_raw(scope, global, state, lib, this_ptr, node, event, level) + match self + .run_debugger_raw(scope, global, caches, lib, this_ptr, node, event, level) { Ok(_) => (), Err(err) => _result = Err(err), @@ -221,7 +222,7 @@ impl Engine { } // Restore state - state.rewind_fn_resolution_caches(orig_fn_resolution_caches_len); + caches.rewind_fn_resolution_caches(orig_fn_resolution_caches_len); _result } @@ -231,11 +232,11 @@ impl Engine { pub(crate) fn has_script_fn( &self, _global: Option<&GlobalRuntimeState>, - state: &mut EvalState, + caches: &mut Caches, lib: &[&Module], hash_script: u64, ) -> bool { - let cache = state.fn_resolution_cache_mut(); + let cache = caches.fn_resolution_cache_mut(); if let Some(result) = cache.get(&hash_script).map(|v| v.is_some()) { return result; diff --git a/src/lib.rs b/src/lib.rs index 5e6c444a..56958dd3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -294,10 +294,7 @@ pub use ast::EncapsulatedEnviron; pub use ast::FloatWrapper; #[cfg(feature = "internals")] -pub use eval::{EvalState, GlobalRuntimeState}; - -#[cfg(feature = "internals")] -pub use func::call::{FnResolutionCache, FnResolutionCacheEntry}; +pub use eval::{Caches, FnResolutionCache, FnResolutionCacheEntry, GlobalRuntimeState}; /// Alias to [`smallvec::SmallVec<[T; 3]>`](https://crates.io/crates/smallvec), which is a /// specialized [`Vec`] backed by a small, inline, fixed-size array when there are ≤ 3 items stored. diff --git a/src/optimizer.rs b/src/optimizer.rs index fe13e94f..f68d84d5 100644 --- a/src/optimizer.rs +++ b/src/optimizer.rs @@ -3,7 +3,7 @@ use crate::ast::{ASTFlags, Expr, OpAssignment, Stmt, StmtBlock, StmtBlockContainer, SwitchCases}; use crate::engine::{KEYWORD_DEBUG, KEYWORD_EVAL, KEYWORD_FN_PTR, KEYWORD_PRINT, KEYWORD_TYPE_OF}; -use crate::eval::{EvalState, GlobalRuntimeState}; +use crate::eval::{Caches, GlobalRuntimeState}; use crate::func::builtin::get_builtin_binary_op_fn; use crate::func::hashing::get_hasher; use crate::tokenizer::{Span, Token}; @@ -139,7 +139,7 @@ impl<'a> OptimizerState<'a> { self.engine .call_native_fn( &mut GlobalRuntimeState::new(&self.engine), - &mut EvalState::new(), + &mut Caches::new(), lib, fn_name, calc_fn_hash(&fn_name, arg_values.len()), diff --git a/src/parser.rs b/src/parser.rs index dd6659d4..db975a12 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -8,7 +8,7 @@ use crate::ast::{ OpAssignment, ScriptFnDef, Stmt, StmtBlock, StmtBlockContainer, SwitchCases, TryCatchBlock, }; use crate::engine::{Precedence, KEYWORD_THIS, OP_CONTAINS}; -use crate::eval::{EvalState, GlobalRuntimeState}; +use crate::eval::{Caches, GlobalRuntimeState}; use crate::func::hashing::get_hasher; use crate::tokenizer::{ is_keyword_function, is_valid_function_name, is_valid_identifier, Token, TokenStream, @@ -2686,7 +2686,7 @@ impl Engine { engine: self, scope: &mut state.stack, global: &mut GlobalRuntimeState::new(self), - state: &mut EvalState::new(), + caches: &mut Caches::new(), lib: &[], this_ptr: &mut None, level, From daf73d5341ba297d7121ce144562fb1853cfc5aa Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Sat, 16 Apr 2022 23:32:14 +0800 Subject: [PATCH 02/13] Make caches optional for EvalContext. --- src/api/custom_syntax.rs | 13 +++++++++++-- src/eval/chaining.rs | 35 +++++++++++++++-------------------- src/eval/debugger.rs | 14 +++++--------- src/eval/eval_context.rs | 16 ++++++++-------- src/eval/expr.rs | 18 ++++++++---------- src/eval/global_state.rs | 2 +- src/eval/stmt.rs | 6 +++--- src/func/call.rs | 14 ++++++-------- src/func/script.rs | 6 ++---- src/parser.rs | 4 ++-- 10 files changed, 61 insertions(+), 67 deletions(-) diff --git a/src/api/custom_syntax.rs b/src/api/custom_syntax.rs index 32be7d54..978ba920 100644 --- a/src/api/custom_syntax.rs +++ b/src/api/custom_syntax.rs @@ -1,6 +1,7 @@ //! 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}; @@ -130,7 +131,7 @@ impl Deref for Expression<'_> { } } -impl EvalContext<'_, '_, '_, '_, '_, '_, '_, '_, '_> { +impl EvalContext<'_, '_, '_, '_, '_, '_, '_, '_> { /// Evaluate an [expression tree][Expression]. /// /// # WARNING - Low Level API @@ -138,10 +139,18 @@ impl EvalContext<'_, '_, '_, '_, '_, '_, '_, '_, '_> { /// 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, - self.caches, + match self.caches.as_mut() { + Some(c) => c, + None => { + caches = Caches::new(); + &mut caches + } + }, self.lib, self.this_ptr, expr, diff --git a/src/eval/chaining.rs b/src/eval/chaining.rs index ee566328..ed9d8b9e 100644 --- a/src/eval/chaining.rs +++ b/src/eval/chaining.rs @@ -155,7 +155,7 @@ impl Engine { if !_parent_options.contains(ASTFlags::BREAK) => { #[cfg(feature = "debugging")] - self.run_debugger(scope, global, caches, lib, this_ptr, _parent, level)?; + self.run_debugger(scope, global, lib, this_ptr, _parent, level)?; let mut idx_val_for_setter = idx_val.clone(); let idx_pos = x.lhs.start_position(); @@ -198,7 +198,7 @@ impl Engine { // xxx[rhs] op= new_val _ if new_val.is_some() => { #[cfg(feature = "debugging")] - self.run_debugger(scope, global, caches, lib, this_ptr, _parent, level)?; + self.run_debugger(scope, global, lib, this_ptr, _parent, level)?; let ((new_val, new_pos), (op_info, op_pos)) = new_val.expect("`Some`"); let mut idx_val2 = idx_val.clone(); @@ -260,7 +260,7 @@ impl Engine { // xxx[rhs] _ => { #[cfg(feature = "debugging")] - self.run_debugger(scope, global, caches, lib, this_ptr, _parent, level)?; + self.run_debugger(scope, global, lib, this_ptr, _parent, level)?; self.get_indexed_mut( global, caches, lib, target, idx_val, pos, false, true, level, @@ -279,9 +279,8 @@ impl Engine { let call_args = &mut idx_val.into_fn_call_args(); #[cfg(feature = "debugging")] - let reset_debugger = self.run_debugger_with_reset( - scope, global, caches, lib, this_ptr, rhs, level, - )?; + let reset_debugger = + self.run_debugger_with_reset(scope, global, lib, this_ptr, rhs, level)?; let result = self.make_method_call( global, caches, lib, name, *hashes, target, call_args, *pos, level, @@ -303,7 +302,7 @@ impl Engine { // {xxx:map}.id op= ??? Expr::Property(x, pos) if target.is::() && new_val.is_some() => { #[cfg(feature = "debugging")] - self.run_debugger(scope, global, caches, lib, this_ptr, rhs, level)?; + self.run_debugger(scope, global, lib, this_ptr, rhs, level)?; let index = x.2.clone().into(); let ((new_val, new_pos), (op_info, op_pos)) = new_val.expect("`Some`"); @@ -324,7 +323,7 @@ impl Engine { // {xxx:map}.id Expr::Property(x, pos) if target.is::() => { #[cfg(feature = "debugging")] - self.run_debugger(scope, global, caches, lib, this_ptr, rhs, level)?; + self.run_debugger(scope, global, lib, this_ptr, rhs, level)?; let index = x.2.clone().into(); let val = self.get_indexed_mut( @@ -335,7 +334,7 @@ impl Engine { // xxx.id op= ??? Expr::Property(x, pos) if new_val.is_some() => { #[cfg(feature = "debugging")] - self.run_debugger(scope, global, caches, lib, this_ptr, rhs, level)?; + self.run_debugger(scope, global, lib, this_ptr, rhs, level)?; let ((getter, hash_get), (setter, hash_set), name) = x.as_ref(); let ((mut new_val, new_pos), (op_info, op_pos)) = new_val.expect("`Some`"); @@ -404,7 +403,7 @@ impl Engine { // xxx.id Expr::Property(x, pos) => { #[cfg(feature = "debugging")] - self.run_debugger(scope, global, caches, lib, this_ptr, rhs, level)?; + 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); @@ -442,9 +441,7 @@ impl Engine { let val_target = &mut match x.lhs { Expr::Property(ref p, pos) => { #[cfg(feature = "debugging")] - self.run_debugger( - scope, global, caches, lib, this_ptr, _node, level, - )?; + self.run_debugger(scope, global, lib, this_ptr, _node, level)?; let index = p.2.clone().into(); self.get_indexed_mut( @@ -458,7 +455,7 @@ impl Engine { #[cfg(feature = "debugging")] let reset_debugger = self.run_debugger_with_reset( - scope, global, caches, lib, this_ptr, _node, level, + scope, global, lib, this_ptr, _node, level, )?; let result = self.make_method_call( @@ -494,9 +491,7 @@ impl Engine { // xxx.prop[expr] | xxx.prop.expr Expr::Property(ref p, pos) => { #[cfg(feature = "debugging")] - self.run_debugger( - scope, global, caches, lib, this_ptr, _node, level, - )?; + self.run_debugger(scope, global, lib, this_ptr, _node, level)?; let ((getter, hash_get), (setter, hash_set), name) = p.as_ref(); let rhs_chain = rhs.into(); @@ -581,7 +576,7 @@ impl Engine { #[cfg(feature = "debugging")] let reset_debugger = self.run_debugger_with_reset( - scope, global, caches, lib, this_ptr, _node, level, + scope, global, lib, this_ptr, _node, level, )?; let result = self.make_method_call( @@ -647,13 +642,13 @@ impl Engine { // id.??? or id[???] Expr::Variable(x, .., var_pos) => { #[cfg(feature = "debugging")] - self.run_debugger(scope, global, caches, lib, this_ptr, lhs, level)?; + self.run_debugger(scope, global, lib, this_ptr, lhs, level)?; #[cfg(not(feature = "unchecked"))] self.inc_operations(&mut global.num_operations, *var_pos)?; let (mut target, ..) = - self.search_namespace(scope, global, caches, lib, this_ptr, lhs, level)?; + self.search_namespace(scope, global, lib, this_ptr, lhs, level)?; let obj_ptr = &mut target; let root = (x.3.as_str(), *var_pos); diff --git a/src/eval/debugger.rs b/src/eval/debugger.rs index f79dd398..96387a64 100644 --- a/src/eval/debugger.rs +++ b/src/eval/debugger.rs @@ -1,7 +1,7 @@ //! Module defining the debugging interface. #![cfg(feature = "debugging")] -use super::{Caches, EvalContext, GlobalRuntimeState}; +use super::{EvalContext, GlobalRuntimeState}; use crate::ast::{ASTNode, Expr, Stmt}; use crate::{Dynamic, Engine, EvalAltResult, Identifier, Module, Position, RhaiResultOf, Scope}; #[cfg(feature = "no_std")] @@ -406,7 +406,6 @@ impl Engine { &self, scope: &mut Scope, global: &mut GlobalRuntimeState, - caches: &mut Caches, lib: &[&Module], this_ptr: &mut Option<&mut Dynamic>, node: impl Into>, @@ -414,7 +413,7 @@ impl Engine { ) -> RhaiResultOf<()> { if self.debugger.is_some() { if let Some(cmd) = - self.run_debugger_with_reset_raw(scope, global, caches, lib, this_ptr, node, level)? + self.run_debugger_with_reset_raw(scope, global, lib, this_ptr, node, level)? { global.debugger.status = cmd; } @@ -434,14 +433,13 @@ impl Engine { &self, scope: &mut Scope, global: &mut GlobalRuntimeState, - caches: &mut Caches, lib: &[&Module], this_ptr: &mut Option<&mut Dynamic>, node: impl Into>, level: usize, ) -> RhaiResultOf> { if self.debugger.is_some() { - self.run_debugger_with_reset_raw(scope, global, caches, lib, this_ptr, node, level) + self.run_debugger_with_reset_raw(scope, global, lib, this_ptr, node, level) } else { Ok(None) } @@ -458,7 +456,6 @@ impl Engine { &self, scope: &mut Scope, global: &mut GlobalRuntimeState, - caches: &mut Caches, lib: &[&Module], this_ptr: &mut Option<&mut Dynamic>, node: impl Into>, @@ -490,7 +487,7 @@ impl Engine { } }; - self.run_debugger_raw(scope, global, caches, lib, this_ptr, node, event, level) + self.run_debugger_raw(scope, global, lib, this_ptr, node, event, level) } /// Run the debugger callback unconditionally. /// @@ -504,7 +501,6 @@ impl Engine { &self, scope: &mut Scope, global: &mut GlobalRuntimeState, - caches: &mut Caches, lib: &[&Module], this_ptr: &mut Option<&mut Dynamic>, node: ASTNode<'a>, @@ -522,7 +518,7 @@ impl Engine { engine: self, scope, global, - caches, + caches: None, lib, this_ptr, level, diff --git a/src/eval/eval_context.rs b/src/eval/eval_context.rs index e05b9c60..ea9137f4 100644 --- a/src/eval/eval_context.rs +++ b/src/eval/eval_context.rs @@ -7,24 +7,24 @@ use std::prelude::v1::*; /// Context of a script evaluation process. #[derive(Debug)] -pub struct EvalContext<'a, 'x, 'px, 'm, 'pm, 'c, 'b, 't, 'pt> { +pub struct EvalContext<'a, 's, 'ps, 'm, 'pm, 'c, 't, 'pt> { /// The current [`Engine`]. pub(crate) engine: &'a Engine, /// The current [`Scope`]. - pub(crate) scope: &'x mut Scope<'px>, + pub(crate) scope: &'s mut Scope<'ps>, /// The current [`GlobalRuntimeState`]. pub(crate) global: &'m mut GlobalRuntimeState<'pm>, - /// The current [caches][Caches]. - pub(crate) caches: &'c mut Caches, + /// The current [caches][Caches], if available. + pub(crate) caches: Option<&'c mut Caches>, /// The current stack of imported [modules][Module]. - pub(crate) lib: &'b [&'b Module], + pub(crate) lib: &'a [&'a Module], /// The current bound `this` pointer, if any. pub(crate) this_ptr: &'t mut Option<&'pt mut Dynamic>, /// The current nesting level of function calls. pub(crate) level: usize, } -impl<'x, 'px, 'm, 'pm, 'pt> EvalContext<'_, 'x, 'px, 'm, 'pm, '_, '_, '_, 'pt> { +impl<'s, 'ps, 'm, 'pm, 'pt> EvalContext<'_, 's, 'ps, 'm, 'pm, '_, '_, 'pt> { /// The current [`Engine`]. #[inline(always)] #[must_use] @@ -44,13 +44,13 @@ impl<'x, 'px, 'm, 'pm, 'pt> EvalContext<'_, 'x, 'px, 'm, 'pm, '_, '_, '_, 'pt> { /// The current [`Scope`]. #[inline(always)] #[must_use] - pub const fn scope(&self) -> &Scope<'px> { + pub const fn scope(&self) -> &Scope<'ps> { self.scope } /// Get a mutable reference to the current [`Scope`]. #[inline(always)] #[must_use] - pub fn scope_mut(&mut self) -> &mut &'x mut Scope<'px> { + pub fn scope_mut(&mut self) -> &mut &'s mut Scope<'ps> { &mut self.scope } /// Get an iterator over the current set of modules imported via `import` statements, diff --git a/src/eval/expr.rs b/src/eval/expr.rs index 5d28f5c2..61b18991 100644 --- a/src/eval/expr.rs +++ b/src/eval/expr.rs @@ -47,7 +47,6 @@ impl Engine { &self, scope: &'s mut Scope, global: &mut GlobalRuntimeState, - caches: &mut Caches, lib: &[&Module], this_ptr: &'s mut Option<&mut Dynamic>, expr: &Expr, @@ -55,13 +54,13 @@ impl Engine { ) -> RhaiResultOf<(Target<'s>, Position)> { match expr { Expr::Variable(_, Some(_), _) => { - self.search_scope_only(scope, global, caches, lib, this_ptr, expr, level) + self.search_scope_only(scope, global, lib, this_ptr, expr, level) } Expr::Variable(v, None, _var_pos) => match v.as_ref() { // Normal variable access #[cfg(not(feature = "no_module"))] (_, ns, ..) if ns.is_empty() => { - self.search_scope_only(scope, global, caches, lib, this_ptr, expr, level) + self.search_scope_only(scope, global, lib, this_ptr, expr, level) } #[cfg(feature = "no_module")] (_, (), ..) => { @@ -130,7 +129,6 @@ impl Engine { &self, scope: &'s mut Scope, global: &mut GlobalRuntimeState, - caches: &mut Caches, lib: &[&Module], this_ptr: &'s mut Option<&mut Dynamic>, expr: &Expr, @@ -159,7 +157,7 @@ impl Engine { engine: self, scope, global, - caches, + caches: None, lib, this_ptr, level, @@ -269,7 +267,7 @@ impl Engine { if let Expr::FnCall(x, ..) = expr { #[cfg(feature = "debugging")] let reset_debugger = - self.run_debugger_with_reset(scope, global, caches, lib, this_ptr, expr, level)?; + self.run_debugger_with_reset(scope, global, lib, this_ptr, expr, level)?; #[cfg(not(feature = "unchecked"))] self.inc_operations(&mut global.num_operations, expr.position())?; @@ -288,7 +286,7 @@ impl Engine { // will cost more than the mis-predicted `match` branch. if let Expr::Variable(x, index, var_pos) = expr { #[cfg(feature = "debugging")] - self.run_debugger(scope, global, caches, lib, this_ptr, expr, level)?; + self.run_debugger(scope, global, lib, this_ptr, expr, level)?; #[cfg(not(feature = "unchecked"))] self.inc_operations(&mut global.num_operations, expr.position())?; @@ -299,14 +297,14 @@ impl Engine { .cloned() .ok_or_else(|| ERR::ErrorUnboundThis(*var_pos).into()) } else { - self.search_namespace(scope, global, caches, lib, this_ptr, expr, level) + self.search_namespace(scope, global, lib, this_ptr, expr, level) .map(|(val, ..)| val.take_or_clone()) }; } #[cfg(feature = "debugging")] let reset_debugger = - self.run_debugger_with_reset(scope, global, caches, lib, this_ptr, expr, level)?; + self.run_debugger_with_reset(scope, global, lib, this_ptr, expr, level)?; #[cfg(not(feature = "unchecked"))] self.inc_operations(&mut global.num_operations, expr.position())?; @@ -490,7 +488,7 @@ impl Engine { engine: self, scope, global, - caches, + caches: Some(caches), lib, this_ptr, level, diff --git a/src/eval/global_state.rs b/src/eval/global_state.rs index ae02a67d..08ed06df 100644 --- a/src/eval/global_state.rs +++ b/src/eval/global_state.rs @@ -63,7 +63,7 @@ pub struct GlobalRuntimeState<'a> { /// Interior mutability is needed because it is shared in order to aid in cloning. #[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_function"))] - pub(crate) constants: Option, + pub constants: Option, /// Debugging interface. #[cfg(feature = "debugging")] pub debugger: super::Debugger, diff --git a/src/eval/stmt.rs b/src/eval/stmt.rs index 20d473d1..4af2a105 100644 --- a/src/eval/stmt.rs +++ b/src/eval/stmt.rs @@ -206,7 +206,7 @@ impl Engine { ) -> RhaiResult { #[cfg(feature = "debugging")] let reset_debugger = - self.run_debugger_with_reset(scope, global, caches, lib, this_ptr, stmt, level)?; + self.run_debugger_with_reset(scope, global, lib, this_ptr, stmt, level)?; // Coded this way for better branch prediction. // Popular branches are lifted out of the `match` statement into their own branches. @@ -241,7 +241,7 @@ impl Engine { if let Ok(rhs_val) = rhs_result { let search_result = - self.search_namespace(scope, global, caches, lib, this_ptr, lhs, level); + self.search_namespace(scope, global, lib, this_ptr, lhs, level); if let Ok(search_val) = search_result { let (mut lhs_ptr, pos) = search_val; @@ -843,7 +843,7 @@ impl Engine { engine: self, scope, global, - caches, + caches: None, lib, this_ptr, level, diff --git a/src/func/call.rs b/src/func/call.rs index c74beef5..caec732a 100644 --- a/src/func/call.rs +++ b/src/func/call.rs @@ -419,9 +419,7 @@ impl Engine { Ok(ref r) => crate::eval::DebuggerEvent::FunctionExitWithValue(r), Err(ref err) => crate::eval::DebuggerEvent::FunctionExitWithError(err), }; - match self - .run_debugger_raw(scope, global, caches, lib, &mut None, node, event, level) - { + match self.run_debugger_raw(scope, global, lib, &mut None, node, event, level) { Ok(_) => (), Err(err) => _result = Err(err), } @@ -905,7 +903,7 @@ impl Engine { if self.debugger.is_some() { if let Some(value) = arg_expr.get_literal_value() { #[cfg(feature = "debugging")] - self.run_debugger(scope, global, caches, lib, this_ptr, arg_expr, level)?; + self.run_debugger(scope, global, lib, this_ptr, arg_expr, level)?; return Ok((value, arg_expr.start_position())); } } @@ -1152,7 +1150,7 @@ impl Engine { let first_expr = first_arg.unwrap(); #[cfg(feature = "debugging")] - self.run_debugger(scope, global, caches, lib, this_ptr, first_expr, level)?; + self.run_debugger(scope, global, lib, this_ptr, first_expr, level)?; // func(x, ...) -> x.func(...) a_expr.iter().try_for_each(|expr| { @@ -1161,7 +1159,7 @@ impl Engine { })?; let (mut target, _pos) = - self.search_namespace(scope, global, caches, lib, this_ptr, first_expr, level)?; + self.search_namespace(scope, global, lib, this_ptr, first_expr, level)?; if target.as_ref().is_read_only() { target = target.into_owned(); @@ -1233,7 +1231,7 @@ impl Engine { // and avoid cloning the value if !args_expr.is_empty() && args_expr[0].is_variable_access(true) { #[cfg(feature = "debugging")] - self.run_debugger(scope, global, caches, lib, this_ptr, &args_expr[0], level)?; + self.run_debugger(scope, global, lib, this_ptr, &args_expr[0], level)?; // func(x, ...) -> x.func(...) arg_values.push(Dynamic::UNIT); @@ -1246,7 +1244,7 @@ impl Engine { // Get target reference to first argument let first_arg = &args_expr[0]; let (target, _pos) = - self.search_scope_only(scope, global, caches, lib, this_ptr, first_arg, level)?; + self.search_scope_only(scope, global, lib, this_ptr, first_arg, level)?; #[cfg(not(feature = "unchecked"))] self.inc_operations(&mut global.num_operations, _pos)?; diff --git a/src/func/script.rs b/src/func/script.rs index fb024849..659f88d5 100644 --- a/src/func/script.rs +++ b/src/func/script.rs @@ -142,7 +142,7 @@ impl Engine { #[cfg(feature = "debugging")] { let node = crate::ast::Stmt::Noop(fn_def.body.position()); - self.run_debugger(scope, global, caches, lib, this_ptr, &node, level)?; + self.run_debugger(scope, global, lib, this_ptr, &node, level)?; } // Evaluate the function @@ -193,9 +193,7 @@ impl Engine { Ok(ref r) => crate::eval::DebuggerEvent::FunctionExitWithValue(r), Err(ref err) => crate::eval::DebuggerEvent::FunctionExitWithError(err), }; - match self - .run_debugger_raw(scope, global, caches, lib, this_ptr, node, event, level) - { + match self.run_debugger_raw(scope, global, lib, this_ptr, node, event, level) { Ok(_) => (), Err(err) => _result = Err(err), } diff --git a/src/parser.rs b/src/parser.rs index db975a12..eacfc13e 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -8,7 +8,7 @@ use crate::ast::{ OpAssignment, ScriptFnDef, Stmt, StmtBlock, StmtBlockContainer, SwitchCases, TryCatchBlock, }; use crate::engine::{Precedence, KEYWORD_THIS, OP_CONTAINS}; -use crate::eval::{Caches, GlobalRuntimeState}; +use crate::eval::GlobalRuntimeState; use crate::func::hashing::get_hasher; use crate::tokenizer::{ is_keyword_function, is_valid_function_name, is_valid_identifier, Token, TokenStream, @@ -2686,7 +2686,7 @@ impl Engine { engine: self, scope: &mut state.stack, global: &mut GlobalRuntimeState::new(self), - caches: &mut Caches::new(), + caches: None, lib: &[], this_ptr: &mut None, level, From 3f74e5e67487bad9619e5006b848b4f12f098482 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Mon, 18 Apr 2022 17:34:53 +0800 Subject: [PATCH 03/13] Use &Path as source path. --- src/module/resolvers/file.rs | 24 ++++++++---------------- 1 file changed, 8 insertions(+), 16 deletions(-) diff --git a/src/module/resolvers/file.rs b/src/module/resolvers/file.rs index 5de00416..561dfaae 100644 --- a/src/module/resolvers/file.rs +++ b/src/module/resolvers/file.rs @@ -201,17 +201,15 @@ impl FileModuleResolver { /// Is a particular path cached? #[inline] #[must_use] - pub fn is_cached(&self, path: impl AsRef, source_path: Option<&str>) -> bool { + pub fn is_cached(&self, path: impl AsRef) -> bool { if !self.cache_enabled { return false; } - let file_path = self.get_file_path(path.as_ref(), source_path); - let cache = locked_read(&self.cache); if !cache.is_empty() { - cache.contains_key(&file_path) + cache.contains_key(path.as_ref()) } else { false } @@ -227,20 +225,14 @@ impl FileModuleResolver { /// The next time this path is resolved, the script file will be loaded once again. #[inline] #[must_use] - pub fn clear_cache_for_path( - &mut self, - path: impl AsRef, - source_path: Option>, - ) -> Option> { - let file_path = self.get_file_path(path.as_ref(), source_path.as_ref().map(<_>::as_ref)); - + pub fn clear_cache_for_path(&mut self, path: impl AsRef) -> Option> { locked_write(&self.cache) - .remove_entry(&file_path) + .remove_entry(path.as_ref()) .map(|(.., v)| v) } /// Construct a full file path. #[must_use] - fn get_file_path(&self, path: &str, source_path: Option<&str>) -> PathBuf { + pub fn get_file_path(&self, path: &str, source_path: Option<&Path>) -> PathBuf { let path = Path::new(path); let mut file_path; @@ -274,9 +266,9 @@ impl FileModuleResolver { .as_ref() .and_then(|g| g.source()) .or(source) - .and_then(|p| Path::new(p).parent().map(|p| p.to_string_lossy())); + .and_then(|p| Path::new(p).parent()); - let file_path = self.get_file_path(path, source_path.as_ref().map(|p| p.as_ref())); + let file_path = self.get_file_path(path, source_path); if self.is_cache_enabled() { #[cfg(not(feature = "sync"))] @@ -351,7 +343,7 @@ impl ModuleResolver for FileModuleResolver { pos: Position, ) -> Option> { // Construct the script file path - let file_path = self.get_file_path(path, source_path); + let file_path = self.get_file_path(path, source_path.map(|s| Path::new(s))); // Load the script file and compile it Some( From 60a933862e089c910482944a6728346bad8d91c2 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Mon, 18 Apr 2022 23:12:47 +0800 Subject: [PATCH 04/13] Streamline op-assignments. --- src/ast/stmt.rs | 48 +++++++++++++++++++++++++------- src/eval/chaining.rs | 66 ++++++++++++++++++-------------------------- src/eval/expr.rs | 26 ++++++++--------- src/eval/stmt.rs | 39 ++++++++++++++------------ src/eval/target.rs | 2 ++ src/optimizer.rs | 4 +-- src/parser.rs | 27 +++++++++--------- 7 files changed, 116 insertions(+), 96 deletions(-) diff --git a/src/ast/stmt.rs b/src/ast/stmt.rs index 33fbd75a..d855e32b 100644 --- a/src/ast/stmt.rs +++ b/src/ast/stmt.rs @@ -17,6 +17,8 @@ use std::{ /// _(internals)_ An op-assignment operator. /// Exported under the `internals` feature only. +/// +/// This type may hold a straight assignment (i.e. not an op-assignment). #[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)] pub struct OpAssignment<'a> { /// Hash of the op-assignment call. @@ -27,9 +29,29 @@ pub struct OpAssignment<'a> { pub op_assign: &'a str, /// Underlying operator. pub op: &'a str, + /// [Position] of the op-assignment operator. + pub pos: Position, } impl OpAssignment<'_> { + /// Create a new [`OpAssignment`] that is only a straight assignment. + #[must_use] + #[inline(always)] + pub const fn new_assignment(pos: Position) -> Self { + Self { + hash_op_assign: 0, + hash_op: 0, + op_assign: "=", + op: "=", + pos, + } + } + /// Is this an op-assignment? + #[must_use] + #[inline(always)] + pub const fn is_op_assignment(&self) -> bool { + self.hash_op_assign != 0 || self.hash_op != 0 + } /// Create a new [`OpAssignment`]. /// /// # Panics @@ -37,8 +59,8 @@ impl OpAssignment<'_> { /// Panics if the name is not an op-assignment operator. #[must_use] #[inline(always)] - pub fn new(name: &str) -> Self { - Self::new_from_token(Token::lookup_from_syntax(name).expect("operator")) + pub fn new_op_assignment(name: &str, pos: Position) -> Self { + Self::new_op_assignment_from_token(Token::lookup_from_syntax(name).expect("operator"), pos) } /// Create a new [`OpAssignment`] from a [`Token`]. /// @@ -46,7 +68,7 @@ impl OpAssignment<'_> { /// /// Panics if the token is not an op-assignment operator. #[must_use] - pub fn new_from_token(op: Token) -> Self { + pub fn new_op_assignment_from_token(op: Token, pos: Position) -> Self { let op_raw = op .get_base_op_from_assignment() .expect("op-assignment operator") @@ -56,6 +78,7 @@ impl OpAssignment<'_> { hash_op: calc_fn_hash(op_raw, 2), op_assign: op.literal_syntax(), op: op_raw, + pos, } } /// Create a new [`OpAssignment`] from a base operator. @@ -65,8 +88,11 @@ impl OpAssignment<'_> { /// Panics if the name is not an operator that can be converted into an op-operator. #[must_use] #[inline(always)] - pub fn new_from_base(name: &str) -> Self { - Self::new_from_base_token(Token::lookup_from_syntax(name).expect("operator")) + pub fn new_op_assignment_from_base(name: &str, pos: Position) -> Self { + Self::new_op_assignment_from_base_token( + Token::lookup_from_syntax(name).expect("operator"), + pos, + ) } /// Convert a [`Token`] into a new [`OpAssignment`]. /// @@ -75,8 +101,8 @@ impl OpAssignment<'_> { /// Panics if the token is cannot be converted into an op-assignment operator. #[inline(always)] #[must_use] - pub fn new_from_base_token(op: Token) -> Self { - Self::new_from_token(op.convert_to_op_assignment().expect("operator")) + pub fn new_op_assignment_from_base_token(op: Token, pos: Position) -> Self { + Self::new_op_assignment_from_token(op.convert_to_op_assignment().expect("operator"), pos) } } @@ -375,7 +401,7 @@ pub enum Stmt { /// * [`CONSTANT`][ASTFlags::CONSTANT] = `const` Var(Box<(Ident, Expr, Option)>, ASTFlags, Position), /// expr op`=` expr - Assignment(Box<(Option>, BinaryExpr)>, Position), + Assignment(Box<(OpAssignment<'static>, BinaryExpr)>), /// func `(` expr `,` ... `)` /// /// Note - this is a duplicate of [`Expr::FnCall`] to cover the very common pattern of a single @@ -464,7 +490,6 @@ impl Stmt { match self { Self::Noop(pos) | Self::BreakLoop(.., pos) - | Self::Assignment(.., pos) | Self::FnCall(.., pos) | Self::If(.., pos) | Self::Switch(.., pos) @@ -475,6 +500,8 @@ impl Stmt { | Self::Var(.., pos) | Self::TryCatch(.., pos) => *pos, + Self::Assignment(x) => x.0.pos, + Self::Block(x) => x.position(), Self::Expr(x) => x.start_position(), @@ -493,7 +520,6 @@ impl Stmt { match self { Self::Noop(pos) | Self::BreakLoop(.., pos) - | Self::Assignment(.., pos) | Self::FnCall(.., pos) | Self::If(.., pos) | Self::Switch(.., pos) @@ -504,6 +530,8 @@ impl Stmt { | Self::Var(.., pos) | Self::TryCatch(.., pos) => *pos = new_pos, + Self::Assignment(x) => x.0.pos = new_pos, + Self::Block(x) => x.set_position(new_pos, x.end_position()), Self::Expr(x) => { diff --git a/src/eval/chaining.rs b/src/eval/chaining.rs index ed9d8b9e..22e1d8b5 100644 --- a/src/eval/chaining.rs +++ b/src/eval/chaining.rs @@ -130,10 +130,10 @@ impl Engine { _parent: &Expr, rhs: &Expr, _parent_options: ASTFlags, - idx_values: &mut StaticVec, + idx_values: &mut StaticVec, chain_type: ChainType, level: usize, - new_val: Option<((Dynamic, Position), (Option, Position))>, + new_val: Option<(Dynamic, OpAssignment)>, ) -> RhaiResultOf<(Dynamic, bool)> { let is_ref_mut = target.is_ref(); @@ -200,7 +200,7 @@ impl Engine { #[cfg(feature = "debugging")] self.run_debugger(scope, global, lib, this_ptr, _parent, level)?; - let ((new_val, new_pos), (op_info, op_pos)) = new_val.expect("`Some`"); + let (new_val, op_info) = new_val.expect("`Some`"); let mut idx_val2 = idx_val.clone(); let try_setter = match self.get_indexed_mut( @@ -209,12 +209,10 @@ impl Engine { // Indexed value is not a temp value - update directly Ok(ref mut obj_ptr) => { self.eval_op_assignment( - global, caches, lib, op_info, op_pos, obj_ptr, root, new_val, - level, - ) - .map_err(|err| err.fill_position(new_pos))?; + global, caches, lib, op_info, obj_ptr, root, new_val, level, + )?; #[cfg(not(feature = "unchecked"))] - self.check_data_size(obj_ptr, new_pos)?; + self.check_data_size(obj_ptr, op_info.pos)?; None } // Indexed value cannot be referenced - use indexer @@ -228,7 +226,7 @@ impl Engine { let idx = &mut idx_val2; // Is this an op-assignment? - if op_info.is_some() { + if op_info.is_op_assignment() { let idx = &mut idx.clone(); // Call the index getter to get the current value if let Ok(val) = @@ -237,14 +235,13 @@ impl Engine { let mut res = val.into(); // Run the op-assignment self.eval_op_assignment( - global, caches, lib, op_info, op_pos, &mut res, root, - new_val, level, - ) - .map_err(|err| err.fill_position(new_pos))?; + global, caches, lib, op_info, &mut res, root, new_val, + level, + )?; // Replace new value new_val = res.take_or_clone(); #[cfg(not(feature = "unchecked"))] - self.check_data_size(&new_val, new_pos)?; + self.check_data_size(&new_val, op_info.pos)?; } } @@ -305,19 +302,17 @@ impl Engine { self.run_debugger(scope, global, lib, this_ptr, rhs, level)?; let index = x.2.clone().into(); - let ((new_val, new_pos), (op_info, op_pos)) = new_val.expect("`Some`"); + let (new_val, op_info) = new_val.expect("`Some`"); { let val_target = &mut self.get_indexed_mut( global, caches, lib, target, index, *pos, true, false, level, )?; self.eval_op_assignment( - global, caches, lib, op_info, op_pos, val_target, root, new_val, - level, - ) - .map_err(|err| err.fill_position(new_pos))?; + global, caches, lib, op_info, val_target, root, new_val, level, + )?; } #[cfg(not(feature = "unchecked"))] - self.check_data_size(target.source(), new_pos)?; + self.check_data_size(target.source(), op_info.pos)?; Ok((Dynamic::UNIT, true)) } // {xxx:map}.id @@ -337,9 +332,9 @@ impl Engine { self.run_debugger(scope, global, lib, this_ptr, rhs, level)?; let ((getter, hash_get), (setter, hash_set), name) = x.as_ref(); - let ((mut new_val, new_pos), (op_info, op_pos)) = new_val.expect("`Some`"); + let (mut new_val, op_info) = new_val.expect("`Some`"); - if op_info.is_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 @@ -369,10 +364,8 @@ impl Engine { let orig_val = &mut (&mut orig_val).into(); self.eval_op_assignment( - global, caches, lib, op_info, op_pos, orig_val, root, new_val, - level, - ) - .map_err(|err| err.fill_position(new_pos))?; + global, caches, lib, op_info, orig_val, root, new_val, level, + )?; } new_val = orig_val; @@ -620,7 +613,7 @@ impl Engine { this_ptr: &mut Option<&mut Dynamic>, expr: &Expr, level: usize, - new_val: Option<((Dynamic, Position), (Option, Position))>, + new_val: Option<(Dynamic, OpAssignment)>, ) -> RhaiResult { let (crate::ast::BinaryExpr { lhs, rhs }, chain_type, options, op_pos) = match expr { #[cfg(not(feature = "no_index"))] @@ -690,7 +683,7 @@ impl Engine { expr: &Expr, parent_options: ASTFlags, _parent_chain_type: ChainType, - idx_values: &mut StaticVec, + idx_values: &mut StaticVec, size: usize, level: usize, ) -> RhaiResultOf<()> { @@ -717,7 +710,7 @@ impl Engine { }, )?; - idx_values.push(super::ChainArgument::from_fn_call_args(values, pos)); + idx_values.push(ChainArgument::from_fn_call_args(values, pos)); } #[cfg(not(feature = "no_object"))] Expr::MethodCall(..) if _parent_chain_type == ChainType::Dotting => { @@ -726,7 +719,7 @@ impl Engine { #[cfg(not(feature = "no_object"))] Expr::Property(.., pos) if _parent_chain_type == ChainType::Dotting => { - idx_values.push(super::ChainArgument::Property(*pos)) + idx_values.push(ChainArgument::Property(*pos)) } Expr::Property(..) => unreachable!("unexpected Expr::Property for indexing"), @@ -739,7 +732,7 @@ impl Engine { let lhs_arg_val = match lhs { #[cfg(not(feature = "no_object"))] Expr::Property(.., pos) if _parent_chain_type == ChainType::Dotting => { - super::ChainArgument::Property(*pos) + ChainArgument::Property(*pos) } Expr::Property(..) => unreachable!("unexpected Expr::Property for indexing"), @@ -762,7 +755,7 @@ impl Engine { Ok::<_, crate::RhaiError>((values, pos)) }, )?; - super::ChainArgument::from_fn_call_args(values, pos) + ChainArgument::from_fn_call_args(values, pos) } #[cfg(not(feature = "no_object"))] Expr::MethodCall(..) if _parent_chain_type == ChainType::Dotting => { @@ -776,10 +769,7 @@ impl Engine { _ if _parent_chain_type == ChainType::Indexing => self .eval_expr(scope, global, caches, lib, this_ptr, lhs, level) .map(|v| { - super::ChainArgument::from_index_value( - v.flatten(), - lhs.start_position(), - ) + ChainArgument::from_index_value(v.flatten(), lhs.start_position()) })?, expr => unreachable!("unknown chained expression: {:?}", expr), }; @@ -802,9 +792,7 @@ impl Engine { #[cfg(not(feature = "no_index"))] _ if _parent_chain_type == ChainType::Indexing => idx_values.push( self.eval_expr(scope, global, caches, lib, this_ptr, expr, level) - .map(|v| { - super::ChainArgument::from_index_value(v.flatten(), expr.start_position()) - })?, + .map(|v| ChainArgument::from_index_value(v.flatten(), expr.start_position()))?, ), _ => unreachable!("unknown chained expression: {:?}", expr), } diff --git a/src/eval/expr.rs b/src/eval/expr.rs index 61b18991..74ef43a4 100644 --- a/src/eval/expr.rs +++ b/src/eval/expr.rs @@ -322,9 +322,13 @@ impl Engine { // `... ${...} ...` Expr::InterpolatedString(x, _) => { - let mut concat: Dynamic = self.const_empty_string().into(); + let mut concat = self.const_empty_string().into(); + let target = &mut concat; let mut result = Ok(Dynamic::UNIT); + let mut op_info = OpAssignment::new_op_assignment(OP_CONCAT, Position::NONE); + let root = ("", Position::NONE); + for expr in x.iter() { let item = match self.eval_expr(scope, global, caches, lib, this_ptr, expr, level) { @@ -335,23 +339,17 @@ impl Engine { } }; - if let Err(err) = self.eval_op_assignment( - global, - caches, - lib, - Some(OpAssignment::new(OP_CONCAT)), - expr.start_position(), - &mut (&mut concat).into(), - ("", Position::NONE), - item, - level, - ) { - result = Err(err.fill_position(expr.start_position())); + op_info.pos = expr.start_position(); + + if let Err(err) = self + .eval_op_assignment(global, caches, lib, op_info, target, root, item, level) + { + result = Err(err); break; } } - result.map(|_| concat) + result.map(|_| concat.take_or_clone()) } #[cfg(not(feature = "no_index"))] diff --git a/src/eval/stmt.rs b/src/eval/stmt.rs index 4af2a105..5e8bc8f0 100644 --- a/src/eval/stmt.rs +++ b/src/eval/stmt.rs @@ -110,14 +110,12 @@ impl Engine { } /// Evaluate an op-assignment statement. - /// [`Position`] in [`EvalAltResult`] is [`NONE`][Position::NONE] and should be set afterwards. pub(crate) fn eval_op_assignment( &self, global: &mut GlobalRuntimeState, caches: &mut Caches, lib: &[&Module], - op_info: Option, - op_pos: Position, + op_info: OpAssignment, target: &mut Target, root: (&str, Position), new_val: Dynamic, @@ -130,13 +128,15 @@ impl Engine { let mut new_val = new_val; - if let Some(OpAssignment { - hash_op_assign, - hash_op, - op_assign, - op, - }) = op_info - { + if op_info.is_op_assignment() { + let OpAssignment { + hash_op_assign, + hash_op, + op_assign, + op, + pos: op_pos, + } = op_info; + let mut lock_guard; let lhs_ptr_inner; @@ -166,9 +166,11 @@ impl Engine { Err(err) if matches!(*err, ERR::ErrorFunctionNotFound(ref f, ..) if f.starts_with(op_assign)) => { // Expand to `var = var op rhs` - let (value, ..) = self.call_native_fn( - global, caches, lib, op, hash_op, args, true, false, op_pos, level, - )?; + let (value, ..) = self + .call_native_fn( + global, caches, lib, op, hash_op, args, true, false, op_pos, level, + ) + .map_err(|err| err.fill_position(op_info.pos))?; #[cfg(not(feature = "unchecked"))] self.check_data_size(&value, root.1)?; @@ -182,7 +184,9 @@ impl Engine { *target.as_mut() = new_val; } - target.propagate_changed_value() + target + .propagate_changed_value() + .map_err(|err| err.fill_position(op_info.pos)) } /// Evaluate a statement. @@ -228,7 +232,7 @@ impl Engine { // Then assignments. // We shouldn't do this for too many variants because, soon or later, the added comparisons // will cost more than the mis-predicted `match` branch. - if let Stmt::Assignment(x, op_pos) = stmt { + if let Stmt::Assignment(x, ..) = stmt { #[cfg(not(feature = "unchecked"))] self.inc_operations(&mut global.num_operations, stmt.position())?; @@ -261,9 +265,8 @@ impl Engine { let lhs_ptr = &mut lhs_ptr; self.eval_op_assignment( - global, caches, lib, *op_info, *op_pos, lhs_ptr, root, rhs_val, level, + global, caches, lib, *op_info, lhs_ptr, root, rhs_val, level, ) - .map_err(|err| err.fill_position(rhs.start_position())) .map(|_| Dynamic::UNIT) } else { search_result.map(|_| Dynamic::UNIT) @@ -279,7 +282,7 @@ impl Engine { .map(Dynamic::flatten); if let Ok(rhs_val) = rhs_result { - let _new_val = Some(((rhs_val, rhs.start_position()), (*op_info, *op_pos))); + let _new_val = Some((rhs_val, *op_info)); // Must be either `var[index] op= val` or `var.prop op= val` match lhs { diff --git a/src/eval/target.rs b/src/eval/target.rs index eb046be2..69d67440 100644 --- a/src/eval/target.rs +++ b/src/eval/target.rs @@ -272,6 +272,8 @@ impl<'a> Target<'a> { } /// Propagate a changed value back to the original source. /// This has no effect for direct references. + /// + /// [`Position`] in [`EvalAltResult`] is [`NONE`][Position::NONE] and should be set afterwards. #[inline] pub fn propagate_changed_value(&mut self) -> RhaiResultOf<()> { match self { diff --git a/src/optimizer.rs b/src/optimizer.rs index f68d84d5..3936b91c 100644 --- a/src/optimizer.rs +++ b/src/optimizer.rs @@ -426,7 +426,7 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b match stmt { // var = var op expr => var op= expr Stmt::Assignment(x, ..) - if x.0.is_none() + if !x.0.is_op_assignment() && x.1.lhs.is_variable_access(true) && matches!(&x.1.rhs, Expr::FnCall(x2, ..) if Token::lookup_from_syntax(&x2.name).map(|t| t.has_op_assignment()).unwrap_or(false) @@ -437,7 +437,7 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b match x.1.rhs { Expr::FnCall(ref mut x2, ..) => { state.set_dirty(); - x.0 = Some(OpAssignment::new_from_base(&x2.name)); + x.0 = OpAssignment::new_op_assignment_from_base(&x2.name, x2.pos); x.1.rhs = mem::take(&mut x2.args[1]); } ref expr => unreachable!("Expr::FnCall expected but gets {:?}", expr), diff --git a/src/parser.rs b/src/parser.rs index eacfc13e..1d684960 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -1808,7 +1808,11 @@ impl Engine { } } - let op_info = op.map(OpAssignment::new_from_token); + let op_info = if let Some(op) = op { + OpAssignment::new_op_assignment_from_token(op, op_pos) + } else { + OpAssignment::new_assignment(op_pos) + }; match lhs { // const_expr = rhs @@ -1816,10 +1820,9 @@ impl Engine { Err(PERR::AssignmentToConstant("".into()).into_err(lhs.start_position())) } // var (non-indexed) = rhs - Expr::Variable(ref x, None, _) if x.0.is_none() => Ok(Stmt::Assignment( - (op_info, (lhs, rhs).into()).into(), - op_pos, - )), + Expr::Variable(ref x, None, _) if x.0.is_none() => { + Ok(Stmt::Assignment((op_info, (lhs, rhs).into()).into())) + } // var (indexed) = rhs Expr::Variable(ref x, i, var_pos) => { let (index, .., name) = x.as_ref(); @@ -1832,10 +1835,9 @@ impl Engine { .get_mut_by_index(state.stack.len() - index) .access_mode() { - AccessMode::ReadWrite => Ok(Stmt::Assignment( - (op_info, (lhs, rhs).into()).into(), - op_pos, - )), + AccessMode::ReadWrite => { + Ok(Stmt::Assignment((op_info, (lhs, rhs).into()).into())) + } // Constant values cannot be assigned to AccessMode::ReadOnly => { Err(PERR::AssignmentToConstant(name.to_string()).into_err(var_pos)) @@ -1854,10 +1856,9 @@ impl Engine { None => { match x.lhs { // var[???] = rhs, var.??? = rhs - Expr::Variable(..) => Ok(Stmt::Assignment( - (op_info, (lhs, rhs).into()).into(), - op_pos, - )), + Expr::Variable(..) => { + Ok(Stmt::Assignment((op_info, (lhs, rhs).into()).into())) + } // expr[???] = rhs, expr.??? = rhs ref expr => Err(PERR::AssignmentToInvalidLHS("".to_string()) .into_err(expr.position())), From f9ee0c29be1bedbf8c1e413be344e36002e196ff Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Mon, 18 Apr 2022 23:24:08 +0800 Subject: [PATCH 05/13] Fix builds. --- src/eval/expr.rs | 4 +--- src/func/register.rs | 11 ++++++----- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/src/eval/expr.rs b/src/eval/expr.rs index 74ef43a4..44af2fcf 100644 --- a/src/eval/expr.rs +++ b/src/eval/expr.rs @@ -63,9 +63,7 @@ impl Engine { self.search_scope_only(scope, global, lib, this_ptr, expr, level) } #[cfg(feature = "no_module")] - (_, (), ..) => { - self.search_scope_only(scope, global, caches, lib, this_ptr, expr, level) - } + (_, (), ..) => self.search_scope_only(scope, global, lib, this_ptr, expr, level), // Qualified variable access #[cfg(not(feature = "no_module"))] diff --git a/src/func/register.rs b/src/func/register.rs index 84d1d728..5b08ab31 100644 --- a/src/func/register.rs +++ b/src/func/register.rs @@ -5,9 +5,8 @@ use super::call::FnCallArgs; use super::callable_function::CallableFunction; use super::native::{FnAny, SendSync}; -use crate::tokenizer::Position; use crate::types::dynamic::{DynamicWriteLock, Variant}; -use crate::{reify, Dynamic, NativeCallContext, RhaiResultOf, ERR}; +use crate::{reify, Dynamic, NativeCallContext, RhaiResultOf}; #[cfg(feature = "no_std")] use std::prelude::v1::*; use std::{any::TypeId, mem}; @@ -102,9 +101,11 @@ macro_rules! check_constant { _ => false, }; if deny { - return Err( - ERR::ErrorAssignmentToConstant(String::new(), Position::NONE).into(), - ); + return Err(crate::ERR::ErrorAssignmentToConstant( + String::new(), + crate::Position::NONE, + ) + .into()); } } } From 40c49063362205e4c096ce9a22415e59c2e6b7ec Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Tue, 19 Apr 2022 08:28:59 +0800 Subject: [PATCH 06/13] Make Module::eval_ast_as_new_raw public. --- CHANGELOG.md | 5 +++++ src/module/mod.rs | 18 ++++++++++++++---- 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 670fde5b..81b7cd2b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,11 @@ Bug fixes * Compound assignments now work properly with indexers. +Enhancements +------------ + +* `Module::eval_ast_as_new_raw` is made public as a low-level API. + Version 1.6.1 ============= diff --git a/src/module/mod.rs b/src/module/mod.rs index 1b1051af..ea9b7ebe 100644 --- a/src/module/mod.rs +++ b/src/module/mod.rs @@ -1742,9 +1742,12 @@ impl Module { /// Create a new [`Module`] by evaluating an [`AST`][crate::AST]. /// - /// The entire [`AST`][crate::AST] is encapsulated into each function, allowing functions - /// to cross-call each other. Functions in the global namespace, plus all functions - /// defined in the [`Module`], are _merged_ into a _unified_ namespace before each call. + /// The entire [`AST`][crate::AST] is encapsulated into each function, allowing functions to + /// cross-call each other. + /// + /// Functions in the global namespace, plus all functions defined in the [`Module`], are + /// _merged_ into a _unified_ namespace before each call. + /// /// Therefore, all functions will be found. /// /// # Example @@ -1781,8 +1784,15 @@ impl Module { /// _merged_ into a _unified_ namespace before each call. /// /// Therefore, all functions will be found. + /// + /// # WARNING - Low Level API + /// + /// This function is very low level. + /// + /// In particular, the [`global`][crate::eval::GlobalRuntimeState] parameter allows the + /// entire calling environment to be encapsulated, including automatic global constants. #[cfg(not(feature = "no_module"))] - pub(crate) fn eval_ast_as_new_raw( + pub fn eval_ast_as_new_raw( engine: &crate::Engine, scope: crate::Scope, global: &mut crate::eval::GlobalRuntimeState, From 770b2e04ccefc9e633cecf24df949c7b09011917 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Tue, 19 Apr 2022 16:20:43 +0800 Subject: [PATCH 07/13] Simplify switch condition. --- src/ast/stmt.rs | 63 +++++++------------ src/eval/stmt.rs | 39 +++++------- src/optimizer.rs | 159 +++++++++++++++++++++++++++-------------------- src/parser.rs | 6 +- 4 files changed, 131 insertions(+), 136 deletions(-) diff --git a/src/ast/stmt.rs b/src/ast/stmt.rs index d855e32b..3b73a91a 100644 --- a/src/ast/stmt.rs +++ b/src/ast/stmt.rs @@ -19,7 +19,7 @@ use std::{ /// Exported under the `internals` feature only. /// /// This type may hold a straight assignment (i.e. not an op-assignment). -#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)] +#[derive(Clone, Copy, Eq, PartialEq, Hash)] pub struct OpAssignment<'a> { /// Hash of the op-assignment call. pub hash_op_assign: u64, @@ -106,11 +106,27 @@ impl OpAssignment<'_> { } } +impl fmt::Debug for OpAssignment<'_> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + if self.is_op_assignment() { + f.debug_struct("OpAssignment") + .field("hash_op_assign", &self.hash_op_assign) + .field("hash_op", &self.hash_op) + .field("op_assign", &self.op_assign) + .field("op", &self.op) + .field("pos", &self.pos) + .finish() + } else { + fmt::Debug::fmt(&self.pos, f) + } + } +} + /// A statements block with an optional condition. #[derive(Debug, Clone, Hash)] pub struct ConditionalStmtBlock { - /// Optional condition. - pub condition: Option, + /// Condition. + pub condition: Expr, /// Statements block. pub statements: StmtBlock, } @@ -119,7 +135,7 @@ impl> From for ConditionalStmtBlock { #[inline(always)] fn from(value: B) -> Self { Self { - condition: None, + condition: Expr::BoolConstant(true, Position::NONE), statements: value.into(), } } @@ -128,16 +144,6 @@ impl> From for ConditionalStmtBlock { impl> From<(Expr, B)> for ConditionalStmtBlock { #[inline(always)] fn from(value: (Expr, B)) -> Self { - Self { - condition: Some(value.0), - statements: value.1.into(), - } - } -} - -impl> From<(Option, B)> for ConditionalStmtBlock { - #[inline(always)] - fn from(value: (Option, B)) -> Self { Self { condition: value.0, statements: value.1.into(), @@ -145,15 +151,6 @@ impl> From<(Option, B)> for ConditionalStmtBlock { } } -impl ConditionalStmtBlock { - /// Does the condition exist? - #[inline(always)] - #[must_use] - pub const fn has_condition(&self) -> bool { - self.condition.is_some() - } -} - /// _(internals)_ A type containing all cases for a `switch` statement. /// Exported under the `internals` feature only. #[derive(Debug, Clone, Hash)] @@ -621,12 +618,10 @@ impl Stmt { Self::Switch(x, ..) => { x.0.is_pure() && x.1.cases.values().all(|block| { - block.condition.as_ref().map(Expr::is_pure).unwrap_or(true) - && block.statements.iter().all(Stmt::is_pure) + block.condition.is_pure() && block.statements.iter().all(Stmt::is_pure) }) && x.1.ranges.iter().all(|(.., block)| { - block.condition.as_ref().map(Expr::is_pure).unwrap_or(true) - && block.statements.iter().all(Stmt::is_pure) + block.condition.is_pure() && block.statements.iter().all(Stmt::is_pure) }) && x.1.def_case.iter().all(Stmt::is_pure) } @@ -768,12 +763,7 @@ impl Stmt { return false; } for b in x.1.cases.values() { - if !b - .condition - .as_ref() - .map(|e| e.walk(path, on_node)) - .unwrap_or(true) - { + if !b.condition.walk(path, on_node) { return false; } for s in b.statements.iter() { @@ -783,12 +773,7 @@ impl Stmt { } } for (.., b) in &x.1.ranges { - if !b - .condition - .as_ref() - .map(|e| e.walk(path, on_node)) - .unwrap_or(true) - { + if !b.condition.walk(path, on_node) { return false; } for s in b.statements.iter() { diff --git a/src/eval/stmt.rs b/src/eval/stmt.rs index 5e8bc8f0..bbdf5f41 100644 --- a/src/eval/stmt.rs +++ b/src/eval/stmt.rs @@ -392,23 +392,16 @@ impl Engine { // First check hashes if let Some(case_block) = cases.get(&hash) { - let cond_result = case_block - .condition - .as_ref() - .map(|cond| { - self.eval_expr( - scope, global, caches, lib, this_ptr, cond, level, - ) + let cond_result = match case_block.condition { + Expr::BoolConstant(b, ..) => Ok(b), + ref c => self + .eval_expr(scope, global, caches, lib, this_ptr, c, level) .and_then(|v| { v.as_bool().map_err(|typ| { - self.make_type_mismatch_err::( - typ, - cond.position(), - ) + self.make_type_mismatch_err::(typ, c.position()) }) - }) - }) - .unwrap_or(Ok(true)); + }), + }; match cond_result { Ok(true) => Ok(Some(&case_block.statements)), @@ -426,23 +419,19 @@ impl Engine { || (inclusive && (start..=end).contains(&value)) }) { - let cond_result = block - .condition - .as_ref() - .map(|cond| { - self.eval_expr( - scope, global, caches, lib, this_ptr, cond, level, - ) + let cond_result = match block.condition { + Expr::BoolConstant(b, ..) => Ok(b), + ref c => self + .eval_expr(scope, global, caches, lib, this_ptr, c, level) .and_then(|v| { v.as_bool().map_err(|typ| { self.make_type_mismatch_err::( typ, - cond.position(), + c.position(), ) }) - }) - }) - .unwrap_or(Ok(true)); + }), + }; match cond_result { Ok(true) => result = Ok(Some(&block.statements)), diff --git a/src/optimizer.rs b/src/optimizer.rs index 3936b91c..403b1564 100644 --- a/src/optimizer.rs +++ b/src/optimizer.rs @@ -531,35 +531,38 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b // First check hashes if let Some(block) = cases.get_mut(&hash) { - if let Some(mut condition) = mem::take(&mut block.condition) { - // switch const { case if condition => stmt, _ => def } => if condition { stmt } else { def } - optimize_expr(&mut condition, state, false); - - let def_stmt = - optimize_stmt_block(mem::take(def_case), state, true, true, false); - - *stmt = Stmt::If( - ( - condition, + match mem::take(&mut block.condition) { + Expr::BoolConstant(true, ..) => { + // Promote the matched case + let statements = optimize_stmt_block( mem::take(&mut block.statements), - StmtBlock::new_with_span( - def_stmt, - def_case.span_or_else(*pos, Position::NONE), - ), - ) - .into(), - match_expr.start_position(), - ); - } else { - // Promote the matched case - let statements = optimize_stmt_block( - mem::take(&mut block.statements), - state, - true, - true, - false, - ); - *stmt = (statements, block.statements.span()).into(); + state, + true, + true, + false, + ); + *stmt = (statements, block.statements.span()).into(); + } + mut condition => { + // switch const { case if condition => stmt, _ => def } => if condition { stmt } else { def } + optimize_expr(&mut condition, state, false); + + let def_stmt = + optimize_stmt_block(mem::take(def_case), state, true, true, false); + + *stmt = Stmt::If( + ( + condition, + mem::take(&mut block.statements), + StmtBlock::new_with_span( + def_stmt, + def_case.span_or_else(*pos, Position::NONE), + ), + ) + .into(), + match_expr.start_position(), + ); + } } state.set_dirty(); @@ -571,7 +574,11 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b let value = value.as_int().expect("`INT`"); // Only one range or all ranges without conditions - if ranges.len() == 1 || ranges.iter().all(|(.., c)| !c.has_condition()) { + if ranges.len() == 1 + || ranges + .iter() + .all(|(.., c)| matches!(c.condition, Expr::BoolConstant(true, ..))) + { for (.., block) in ranges .iter_mut() @@ -580,30 +587,38 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b || (inclusive && (start..=end).contains(&value)) }) { - if let Some(mut condition) = mem::take(&mut block.condition) { - // switch const { range if condition => stmt, _ => def } => if condition { stmt } else { def } - optimize_expr(&mut condition, state, false); + match mem::take(&mut block.condition) { + Expr::BoolConstant(true, ..) => { + // Promote the matched case + let statements = mem::take(&mut *block.statements); + let statements = + optimize_stmt_block(statements, state, true, true, false); + *stmt = (statements, block.statements.span()).into(); + } + mut condition => { + // switch const { range if condition => stmt, _ => def } => if condition { stmt } else { def } + optimize_expr(&mut condition, state, false); - let def_stmt = - optimize_stmt_block(mem::take(def_case), state, true, true, false); - *stmt = Stmt::If( - ( - condition, - mem::take(&mut block.statements), - StmtBlock::new_with_span( - def_stmt, - def_case.span_or_else(*pos, Position::NONE), - ), - ) - .into(), - match_expr.start_position(), - ); - } else { - // Promote the matched case - let statements = mem::take(&mut *block.statements); - let statements = - optimize_stmt_block(statements, state, true, true, false); - *stmt = (statements, block.statements.span()).into(); + let def_stmt = optimize_stmt_block( + mem::take(def_case), + state, + true, + true, + false, + ); + *stmt = Stmt::If( + ( + condition, + mem::take(&mut block.statements), + StmtBlock::new_with_span( + def_stmt, + def_case.span_or_else(*pos, Position::NONE), + ), + ) + .into(), + match_expr.start_position(), + ); + } } state.set_dirty(); @@ -632,12 +647,14 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b *block.statements = optimize_stmt_block(statements, state, preserve_result, true, false); - if let Some(mut condition) = mem::take(&mut block.condition) { - optimize_expr(&mut condition, state, false); - match condition { - Expr::Unit(..) | Expr::BoolConstant(true, ..) => state.set_dirty(), - _ => block.condition = Some(condition), + optimize_expr(&mut block.condition, state, false); + + match block.condition { + Expr::Unit(pos) => { + block.condition = Expr::BoolConstant(true, pos); + state.set_dirty() } + _ => (), } } return; @@ -669,18 +686,20 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b *block.statements = optimize_stmt_block(statements, state, preserve_result, true, false); - if let Some(mut condition) = mem::take(&mut block.condition) { - optimize_expr(&mut condition, state, false); - match condition { - Expr::Unit(..) | Expr::BoolConstant(true, ..) => state.set_dirty(), - _ => block.condition = Some(condition), + optimize_expr(&mut block.condition, state, false); + + match block.condition { + Expr::Unit(pos) => { + block.condition = Expr::BoolConstant(true, pos); + state.set_dirty(); } + _ => (), } } // Remove false cases cases.retain(|_, block| match block.condition { - Some(Expr::BoolConstant(false, ..)) => { + Expr::BoolConstant(false, ..) => { state.set_dirty(); false } @@ -693,18 +712,20 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut OptimizerState, preserve_result: b *block.statements = optimize_stmt_block(statements, state, preserve_result, true, false); - if let Some(mut condition) = mem::take(&mut block.condition) { - optimize_expr(&mut condition, state, false); - match condition { - Expr::Unit(..) | Expr::BoolConstant(true, ..) => state.set_dirty(), - _ => block.condition = Some(condition), + optimize_expr(&mut block.condition, state, false); + + match block.condition { + Expr::Unit(pos) => { + block.condition = Expr::BoolConstant(true, pos); + state.set_dirty(); } + _ => (), } } // Remove false ranges ranges.retain(|(.., block)| match block.condition { - Some(Expr::BoolConstant(false, ..)) => { + Expr::BoolConstant(false, ..) => { state.set_dirty(); false } diff --git a/src/parser.rs b/src/parser.rs index 1d684960..33c315f8 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -1041,7 +1041,7 @@ impl Engine { return Err(PERR::WrongSwitchCaseCondition.into_err(if_pos)); } - (None, None) + (None, Expr::BoolConstant(true, Position::NONE)) } (Token::Underscore, pos) => return Err(PERR::DuplicatedSwitchCase.into_err(*pos)), @@ -1054,9 +1054,9 @@ impl Engine { Some(self.parse_expr(input, state, lib, settings.level_up())?); let condition = if match_token(input, Token::If).0 { - Some(self.parse_expr(input, state, lib, settings.level_up())?) + self.parse_expr(input, state, lib, settings.level_up())? } else { - None + Expr::BoolConstant(true, Position::NONE) }; (case_expr, condition) } From 299d6ef308ac5642b1dfc07247ad69b1a547fd6e Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Tue, 19 Apr 2022 21:45:11 +0800 Subject: [PATCH 08/13] Type checking in switch case condition. --- CHANGELOG.md | 1 + src/ast/stmt.rs | 4 +++- src/parser.rs | 7 ++++++- 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 81b7cd2b..e5edd37d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ Enhancements ------------ * `Module::eval_ast_as_new_raw` is made public as a low-level API. +* Improper `switch` case condition syntax is now caught at parse time. Version 1.6.1 diff --git a/src/ast/stmt.rs b/src/ast/stmt.rs index 3b73a91a..788ff368 100644 --- a/src/ast/stmt.rs +++ b/src/ast/stmt.rs @@ -122,7 +122,9 @@ impl fmt::Debug for OpAssignment<'_> { } } -/// A statements block with an optional condition. +/// A statements block with a condition. +/// +/// The condition may simply be [`Expr::BoolConstant`] with `true` if there is actually no condition. #[derive(Debug, Clone, Hash)] pub struct ConditionalStmtBlock { /// Condition. diff --git a/src/parser.rs b/src/parser.rs index 33c315f8..6abb4586 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -1054,7 +1054,12 @@ impl Engine { Some(self.parse_expr(input, state, lib, settings.level_up())?); let condition = if match_token(input, Token::If).0 { - self.parse_expr(input, state, lib, settings.level_up())? + ensure_not_statement_expr(input, "a boolean")?; + let guard = self + .parse_expr(input, state, lib, settings.level_up())? + .ensure_bool_expr()?; + ensure_not_assignment(input)?; + guard } else { Expr::BoolConstant(true, Position::NONE) }; From 5e4f27ae253abff76c2ed80a561950b99ef75f6a Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Thu, 21 Apr 2022 10:04:46 +0800 Subject: [PATCH 09/13] Add Token::Unit. --- src/ast/expr.rs | 1 + src/parser.rs | 87 +++++++++++++++++++++++++--------------- src/tokenizer.rs | 10 +++++ src/types/parse_error.rs | 3 +- tests/unit.rs | 2 +- 5 files changed, 69 insertions(+), 34 deletions(-) diff --git a/src/ast/expr.rs b/src/ast/expr.rs index 0f8a1562..d6a417f6 100644 --- a/src/ast/expr.rs +++ b/src/ast/expr.rs @@ -832,6 +832,7 @@ impl Expr { #[cfg(not(feature = "no_index"))] Token::LeftBracket => true, Token::LeftParen => true, + Token::Unit => true, Token::Bang => true, Token::DoubleColon => true, _ => false, diff --git a/src/parser.rs b/src/parser.rs index 6abb4586..a4b6fa60 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -426,10 +426,6 @@ impl Engine { let mut settings = settings; settings.pos = eat_token(input, Token::LeftParen); - if match_token(input, Token::RightParen).0 { - return Ok(Expr::Unit(settings.pos)); - } - let expr = self.parse_expr(input, state, lib, settings.level_up())?; match input.next().expect(NEVER_ENDS) { @@ -453,6 +449,7 @@ impl Engine { state: &mut ParseState, lib: &mut FnLib, id: Identifier, + no_args: bool, capture_parent_scope: bool, #[cfg(not(feature = "no_module"))] namespace: crate::ast::Namespace, settings: ParseSettings, @@ -460,7 +457,11 @@ impl Engine { #[cfg(not(feature = "unchecked"))] settings.ensure_level_within_max_limit(state.max_expr_depth)?; - let (token, token_pos) = input.peek().expect(NEVER_ENDS); + let (token, token_pos) = if no_args { + &(Token::RightParen, Position::NONE) + } else { + input.peek().expect(NEVER_ENDS) + }; #[cfg(not(feature = "no_module"))] let mut namespace = namespace; @@ -479,7 +480,9 @@ impl Engine { Token::LexError(err) => return Err(err.clone().into_err(*token_pos)), // id() Token::RightParen => { - eat_token(input, Token::RightParen); + if !no_args { + eat_token(input, Token::RightParen); + } #[cfg(not(feature = "no_module"))] let hash = if !namespace.is_empty() { @@ -897,7 +900,7 @@ impl Engine { } let (name, pos) = match input.next().expect(NEVER_ENDS) { - (Token::Identifier(s), pos) | (Token::StringConstant(s), pos) => { + (Token::Identifier(s) | Token::StringConstant(s), pos) => { if map.iter().any(|(p, ..)| **p == s) { return Err(PERR::DuplicatedProperty(s.to_string()).into_err(pos)); } @@ -1201,6 +1204,11 @@ impl Engine { let root_expr = match token { Token::EOF => return Err(PERR::UnexpectedEOF.into_err(settings.pos)), + Token::Unit => { + input.next(); + Expr::Unit(settings.pos) + } + Token::IntegerConstant(..) | Token::CharConstant(..) | Token::StringConstant(..) @@ -1218,13 +1226,13 @@ impl Engine { #[cfg(not(feature = "no_float"))] Token::FloatConstant(x) => { let x = *x; - input.next().expect(NEVER_ENDS); + input.next(); Expr::FloatConstant(x, settings.pos) } #[cfg(feature = "decimal")] Token::DecimalConstant(x) => { let x = (*x).into(); - input.next().expect(NEVER_ENDS); + input.next(); Expr::DynamicConstant(Box::new(x), settings.pos) } @@ -1235,6 +1243,7 @@ impl Engine { stmt => unreachable!("Stmt::Block expected but gets {:?}", stmt), } } + // ( - grouped expression Token::LeftParen => self.parse_paren_expr(input, state, lib, settings.level_up())?, @@ -1401,7 +1410,7 @@ impl Engine { match input.peek().expect(NEVER_ENDS).0 { // Function call - Token::LeftParen | Token::Bang => { + Token::LeftParen | Token::Bang | Token::Unit => { #[cfg(not(feature = "no_closure"))] { // Once the identifier consumed we must enable next variables capturing @@ -1467,11 +1476,13 @@ impl Engine { match input.peek().expect(NEVER_ENDS).0 { // Function call is allowed to have reserved keyword - Token::LeftParen | Token::Bang if is_keyword_function(&s) => Expr::Variable( - (None, ns, 0, state.get_identifier("", s)).into(), - None, - settings.pos, - ), + Token::LeftParen | Token::Bang | Token::Unit if is_keyword_function(&s) => { + Expr::Variable( + (None, ns, 0, state.get_identifier("", s)).into(), + None, + settings.pos, + ) + } // Access to `this` as a variable is OK within a function scope #[cfg(not(feature = "no_function"))] _ if &*s == KEYWORD_THIS && settings.is_function_scope => Expr::Variable( @@ -1531,30 +1542,33 @@ impl Engine { // Qualified function call with ! #[cfg(not(feature = "no_module"))] (Expr::Variable(x, ..), Token::Bang) if !x.1.is_empty() => { - return if !match_token(input, Token::LeftParen).0 { - Err(LexError::UnexpectedInput(Token::Bang.syntax().to_string()) - .into_err(tail_pos)) - } else { - Err(LexError::ImproperSymbol( + return match input.peek().expect(NEVER_ENDS) { + (Token::LeftParen | Token::Unit, ..) => { + Err(LexError::UnexpectedInput(Token::Bang.syntax().to_string()) + .into_err(tail_pos)) + } + _ => Err(LexError::ImproperSymbol( "!".to_string(), "'!' cannot be used to call module functions".to_string(), ) - .into_err(tail_pos)) + .into_err(tail_pos)), }; } // Function call with ! (Expr::Variable(x, .., pos), Token::Bang) => { - match match_token(input, Token::LeftParen) { - (false, pos) => { + match input.peek().expect(NEVER_ENDS) { + (Token::LeftParen | Token::Unit, ..) => (), + (_, pos) => { return Err(PERR::MissingToken( Token::LeftParen.syntax().into(), "to start arguments list of function call".into(), ) - .into_err(pos)) + .into_err(*pos)) } - _ => (), } + let no_args = input.next().expect(NEVER_ENDS).0 == Token::Unit; + let (.., _ns, _, name) = *x; settings.pos = pos; self.parse_fn_call( @@ -1562,6 +1576,7 @@ impl Engine { state, lib, name, + no_args, true, #[cfg(not(feature = "no_module"))] _ns, @@ -1569,7 +1584,7 @@ impl Engine { )? } // Function call - (Expr::Variable(x, .., pos), Token::LeftParen) => { + (Expr::Variable(x, .., pos), t @ (Token::LeftParen | Token::Unit)) => { let (.., _ns, _, name) = *x; settings.pos = pos; self.parse_fn_call( @@ -1577,6 +1592,7 @@ impl Engine { state, lib, name, + t == Token::Unit, false, #[cfg(not(feature = "no_module"))] _ns, @@ -1992,7 +2008,7 @@ impl Engine { )) } // lhs.dot_lhs.dot_rhs or lhs.dot_lhs[idx_rhs] - (lhs, rhs @ Expr::Dot(..)) | (lhs, rhs @ Expr::Index(..)) => { + (lhs, rhs @ (Expr::Dot(..) | Expr::Index(..))) => { let (x, term, pos, is_dot) = match rhs { Expr::Dot(x, term, pos) => (x, term, pos, true), Expr::Index(x, term, pos) => (x, term, pos, false), @@ -2324,7 +2340,7 @@ impl Engine { } } CUSTOM_SYNTAX_MARKER_BOOL => match input.next().expect(NEVER_ENDS) { - (b @ Token::True, pos) | (b @ Token::False, pos) => { + (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_identifier("", CUSTOM_SYNTAX_MARKER_BOOL)); @@ -2999,7 +3015,7 @@ impl Engine { comments.push(comment); match input.peek().expect(NEVER_ENDS) { - (Token::Fn, ..) | (Token::Private, ..) => break, + (Token::Fn | Token::Private, ..) => break, (Token::Comment(..), ..) => (), _ => return Err(PERR::WrongDocComment.into_err(comments_pos)), } @@ -3269,14 +3285,21 @@ impl Engine { Err(_) => return Err(PERR::FnMissingName.into_err(pos)), }; - match input.peek().expect(NEVER_ENDS) { - (Token::LeftParen, ..) => eat_token(input, Token::LeftParen), + let no_params = match input.peek().expect(NEVER_ENDS) { + (Token::LeftParen, ..) => { + eat_token(input, Token::LeftParen); + match_token(input, Token::RightParen).0 + } + (Token::Unit, ..) => { + eat_token(input, Token::Unit); + true + } (.., pos) => return Err(PERR::FnMissingParams(name.to_string()).into_err(*pos)), }; let mut params = StaticVec::new_const(); - if !match_token(input, Token::RightParen).0 { + if !no_params { let sep_err = format!("to separate the parameters of function '{}'", name); loop { diff --git a/src/tokenizer.rs b/src/tokenizer.rs index 98456c47..0a3d7dee 100644 --- a/src/tokenizer.rs +++ b/src/tokenizer.rs @@ -382,6 +382,8 @@ pub enum Token { LeftBracket, /// `]` RightBracket, + /// `()` + Unit, /// `+` Plus, /// `+` (unary) @@ -558,6 +560,7 @@ impl Token { RightParen => ")", LeftBracket => "[", RightBracket => "]", + Unit => "()", Plus => "+", UnaryPlus => "+", Minus => "-", @@ -754,6 +757,7 @@ impl Token { ")" => RightParen, "[" => LeftBracket, "]" => RightBracket, + "()" => Unit, "+" => Plus, "-" => Minus, "*" => Multiply, @@ -1702,6 +1706,12 @@ fn get_next_token_inner( ('{', ..) => return Some((Token::LeftBrace, start_pos)), ('}', ..) => return Some((Token::RightBrace, start_pos)), + // Unit + ('(', ')') => { + eat_next(stream, pos); + return Some((Token::Unit, start_pos)); + } + // Parentheses ('(', '*') => { eat_next(stream, pos); diff --git a/src/types/parse_error.rs b/src/types/parse_error.rs index 0a86d8e9..266ddc34 100644 --- a/src/types/parse_error.rs +++ b/src/types/parse_error.rs @@ -51,7 +51,8 @@ impl fmt::Display for LexError { Self::ImproperSymbol(s, d) if d.is_empty() => { write!(f, "Invalid symbol encountered: '{}'", s) } - Self::ImproperSymbol(.., d) => f.write_str(d), + Self::ImproperSymbol(s, d) if s.is_empty() => f.write_str(d), + Self::ImproperSymbol(s, d) => write!(f, "{}: '{}'", d, s), } } } diff --git a/tests/unit.rs b/tests/unit.rs index edfcf61c..bb9d1496 100644 --- a/tests/unit.rs +++ b/tests/unit.rs @@ -17,6 +17,6 @@ fn test_unit_eq() -> Result<(), Box> { #[test] fn test_unit_with_spaces() -> Result<(), Box> { let engine = Engine::new(); - engine.run("let x = ( ); x")?; + engine.run("let x = ( ); x").expect_err("should error"); Ok(()) } From 4f2764d2337fd91be7fc87a9076b3f8b96241512 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Thu, 21 Apr 2022 10:04:57 +0800 Subject: [PATCH 10/13] Revise `parse_json`. --- CHANGELOG.md | 1 + src/api/compile.rs | 156 ++++++++++++++++++++++++++------------------- tests/maps.rs | 51 +++++++++++---- 3 files changed, 128 insertions(+), 80 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e5edd37d..cb3e08b5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ Enhancements * `Module::eval_ast_as_new_raw` is made public as a low-level API. * Improper `switch` case condition syntax is now caught at parse time. +* `Engine::parse_json` now natively handles nested JSON inputs (using a token remap filter) without needing to replace `{` with `#{`. Version 1.6.1 diff --git a/src/api/compile.rs b/src/api/compile.rs index 53402446..7688a89c 100644 --- a/src/api/compile.rs +++ b/src/api/compile.rs @@ -314,19 +314,12 @@ impl Engine { /// /// Not available under `no_object`. /// - /// The JSON string must be an object hash. It cannot be a simple scalar value. + /// The JSON string must be an object hash. It cannot be a simple primitive value. /// /// Set `has_null` to `true` in order to map `null` values to `()`. - /// Setting it to `false` will cause an [`ErrorVariableNotFound`][crate::EvalAltResult::ErrorVariableNotFound] error during parsing. + /// Setting it to `false` causes a syntax error for any `null` value. /// - /// # JSON With Sub-Objects - /// - /// This method assumes no sub-objects in the JSON string. That is because the syntax - /// of a JSON sub-object (or object hash), `{ .. }`, is different from Rhai's syntax, `#{ .. }`. - /// Parsing a JSON string with sub-objects will cause a syntax error. - /// - /// If it is certain that the character `{` never appears in any text string within the JSON object, - /// which is a valid assumption for many use cases, then globally replace `{` with `#{` before calling this method. + /// JSON sub-objects are handled transparently. /// /// # Example /// @@ -336,18 +329,27 @@ impl Engine { /// /// let engine = Engine::new(); /// - /// let map = engine.parse_json( - /// r#"{"a":123, "b":42, "c":{"x":false, "y":true}, "d":null}"# - /// .replace("{", "#{").as_str(), - /// true)?; + /// let map = engine.parse_json(r#" + /// { + /// "a": 123, + /// "b": 42, + /// "c": { + /// "x": false, + /// "y": true, + /// "z": '$' + /// }, + /// "d": null + /// }"#, true)?; /// /// assert_eq!(map.len(), 4); /// assert_eq!(map["a"].as_int().expect("a should exist"), 123); /// assert_eq!(map["b"].as_int().expect("b should exist"), 42); - /// assert!(map["d"].is::<()>()); + /// assert_eq!(map["d"].as_unit().expect("d should exist"), ()); /// /// let c = map["c"].read_lock::().expect("c should exist"); /// assert_eq!(c["x"].as_bool().expect("x should be bool"), false); + /// assert_eq!(c["y"].as_bool().expect("y should be bool"), true); + /// assert_eq!(c["z"].as_char().expect("z should be char"), '$'); /// # Ok(()) /// # } /// ``` @@ -358,60 +360,80 @@ impl Engine { json: impl AsRef, has_null: bool, ) -> crate::RhaiResultOf { - use crate::tokenizer::Token; + use crate::{tokenizer::Token, LexError}; - fn parse_json_inner( - engine: &Engine, - json: &str, - has_null: bool, - ) -> crate::RhaiResultOf { - let mut scope = Scope::new(); - let json_text = json.trim_start(); - let scripts = if json_text.starts_with(Token::MapStart.literal_syntax()) { - [json_text, ""] - } else if json_text.starts_with(Token::LeftBrace.literal_syntax()) { - ["#", json_text] - } else { - return Err(crate::PERR::MissingToken( - Token::LeftBrace.syntax().into(), - "to start a JSON object hash".into(), - ) - .into_err(crate::Position::new( - 1, - (json.len() - json_text.len() + 1) as u16, - )) - .into()); - }; - let (stream, tokenizer_control) = engine.lex_raw( - &scripts, - if has_null { - Some(&|token, _, _| { - match token { - // If `null` is present, make sure `null` is treated as a variable - Token::Reserved(s) if &*s == "null" => Token::Identifier(s), - _ => token, - } - }) - } else { - None - }, - ); - let mut state = ParseState::new(engine, tokenizer_control); - let ast = engine.parse_global_expr( - &mut stream.peekable(), - &mut state, - &scope, - #[cfg(not(feature = "no_optimize"))] - OptimizationLevel::None, - #[cfg(feature = "no_optimize")] - OptimizationLevel::default(), - )?; + let scripts = [json.as_ref()]; + + let (stream, tokenizer_control) = self.lex_raw( + &scripts, if has_null { - scope.push_constant("null", ()); - } - engine.eval_ast_with_scope(&mut scope, &ast) - } + Some(&|token, _, _| { + match token { + // `null` => `()` + Token::Reserved(s) if &*s == "null" => Token::Unit, + // `{` => `#{` + Token::LeftBrace => Token::MapStart, + // Disallowed syntax + t @ (Token::Unit | Token::MapStart) => Token::LexError( + LexError::ImproperSymbol( + t.literal_syntax().to_string(), + "".to_string(), + ) + .into(), + ), + Token::InterpolatedString(..) => Token::LexError( + LexError::ImproperSymbol( + "interpolated string".to_string(), + "".to_string(), + ) + .into(), + ), + // All others + _ => token, + } + }) + } else { + Some(&|token, _, _| { + match token { + Token::Reserved(s) if &*s == "null" => Token::LexError( + LexError::ImproperSymbol("null".to_string(), "".to_string()).into(), + ), + // `{` => `#{` + Token::LeftBrace => Token::MapStart, + // Disallowed syntax + t @ (Token::Unit | Token::MapStart) => Token::LexError( + LexError::ImproperSymbol( + t.literal_syntax().to_string(), + "".to_string(), + ) + .into(), + ), + Token::InterpolatedString(..) => Token::LexError( + LexError::ImproperSymbol( + "interpolated string".to_string(), + "".to_string(), + ) + .into(), + ), + // All others + _ => token, + } + }) + }, + ); - parse_json_inner(self, json.as_ref(), has_null) + let mut state = ParseState::new(self, tokenizer_control); + + let ast = self.parse_global_expr( + &mut stream.peekable(), + &mut state, + &Scope::new(), + #[cfg(not(feature = "no_optimize"))] + OptimizationLevel::None, + #[cfg(feature = "no_optimize")] + OptimizationLevel::default(), + )?; + + self.eval_ast(&ast) } } diff --git a/tests/maps.rs b/tests/maps.rs index 36ff7e84..9a4414f1 100644 --- a/tests/maps.rs +++ b/tests/maps.rs @@ -130,8 +130,8 @@ fn test_map_assign() -> Result<(), Box> { let x = engine.eval::(r#"let x = #{a: 1, b: true, "c$": "hello"}; x"#)?; - assert_eq!(x["a"].clone_cast::(), 1); - assert_eq!(x["b"].clone_cast::(), true); + assert_eq!(x["a"].as_int().unwrap(), 1); + assert_eq!(x["b"].as_bool().unwrap(), true); assert_eq!(x["c$"].clone_cast::(), "hello"); Ok(()) @@ -143,8 +143,8 @@ fn test_map_return() -> Result<(), Box> { let x = engine.eval::(r#"#{a: 1, b: true, "c$": "hello"}"#)?; - assert_eq!(x["a"].clone_cast::(), 1); - assert_eq!(x["b"].clone_cast::(), true); + assert_eq!(x["a"].as_int().unwrap(), 1); + assert_eq!(x["b"].as_bool().unwrap(), true); assert_eq!(x["c$"].clone_cast::(), "hello"); Ok(()) @@ -182,17 +182,17 @@ fn test_map_for() -> Result<(), Box> { fn test_map_json() -> Result<(), Box> { let engine = Engine::new(); - let json = r#"{"a":1, "b":true, "c":42, "$d e f!":"hello", "z":null}"#; + let json = r#"{"a":1, "b":true, "c":41+1, "$d e f!":"hello", "z":null}"#; let map = engine.parse_json(json, true)?; assert!(!map.contains_key("x")); - assert_eq!(map["a"].clone_cast::(), 1); - assert_eq!(map["b"].clone_cast::(), true); - assert_eq!(map["c"].clone_cast::(), 42); + assert_eq!(map["a"].as_int().unwrap(), 1); + assert_eq!(map["b"].as_bool().unwrap(), true); + assert_eq!(map["c"].as_int().unwrap(), 42); assert_eq!(map["$d e f!"].clone_cast::(), "hello"); - assert_eq!(map["z"].clone_cast::<()>(), ()); + assert_eq!(map["z"].as_unit().unwrap(), ()); #[cfg(not(feature = "no_index"))] { @@ -218,12 +218,37 @@ fn test_map_json() -> Result<(), Box> { ); } - engine.parse_json(&format!("#{}", json), true)?; + engine.parse_json(json, true)?; assert!(matches!( - *engine.parse_json(" 123", true).expect_err("should error"), - EvalAltResult::ErrorParsing(ParseErrorType::MissingToken(token, ..), ..) - if token == "{" + *engine.parse_json("123", true).expect_err("should error"), + EvalAltResult::ErrorMismatchOutputType(..) + )); + + assert!(matches!( + *engine + .parse_json("#{a:123}", true) + .expect_err("should error"), + EvalAltResult::ErrorParsing(..) + )); + + assert!(matches!( + *engine.parse_json("{a:()}", true).expect_err("should error"), + EvalAltResult::ErrorParsing(..) + )); + + assert!(matches!( + *engine + .parse_json("#{a:123+456}", true) + .expect_err("should error"), + EvalAltResult::ErrorParsing(..) + )); + + assert!(matches!( + *engine + .parse_json("{a:`hello${world}`}", true) + .expect_err("should error"), + EvalAltResult::ErrorParsing(..) )); Ok(()) From c3d013bddcd2ba51374868aa3da1a708663aec52 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Thu, 21 Apr 2022 12:15:21 +0800 Subject: [PATCH 11/13] Add to_json for maps. --- CHANGELOG.md | 2 + src/api/compile.rs | 128 ---------------------------- src/api/json.rs | 175 ++++++++++++++++++++++++++++++++++++++ src/api/mod.rs | 2 + src/lib.rs | 3 + src/packages/map_basic.rs | 24 +++++- 6 files changed, 205 insertions(+), 129 deletions(-) create mode 100644 src/api/json.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index cb3e08b5..a2b740dd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,8 @@ Enhancements * `Module::eval_ast_as_new_raw` is made public as a low-level API. * Improper `switch` case condition syntax is now caught at parse time. * `Engine::parse_json` now natively handles nested JSON inputs (using a token remap filter) without needing to replace `{` with `#{`. +* `to_json` is added to object maps to cheaply convert it to JSON format (`()` is mapped to `null`, all other data types must be supported by JSON) +* A global function `format_map_as_json` is provided which is the same as `to_json` for object maps. Version 1.6.1 diff --git a/src/api/compile.rs b/src/api/compile.rs index 7688a89c..2259ec92 100644 --- a/src/api/compile.rs +++ b/src/api/compile.rs @@ -308,132 +308,4 @@ impl Engine { self.options.optimization_level, ) } - /// Parse a JSON string into an [object map][crate::Map]. - /// This is a light-weight alternative to using, say, - /// [`serde_json`](https://crates.io/crates/serde_json) to deserialize the JSON. - /// - /// Not available under `no_object`. - /// - /// The JSON string must be an object hash. It cannot be a simple primitive value. - /// - /// Set `has_null` to `true` in order to map `null` values to `()`. - /// Setting it to `false` causes a syntax error for any `null` value. - /// - /// JSON sub-objects are handled transparently. - /// - /// # Example - /// - /// ``` - /// # fn main() -> Result<(), Box> { - /// use rhai::{Engine, Map}; - /// - /// let engine = Engine::new(); - /// - /// let map = engine.parse_json(r#" - /// { - /// "a": 123, - /// "b": 42, - /// "c": { - /// "x": false, - /// "y": true, - /// "z": '$' - /// }, - /// "d": null - /// }"#, true)?; - /// - /// assert_eq!(map.len(), 4); - /// assert_eq!(map["a"].as_int().expect("a should exist"), 123); - /// assert_eq!(map["b"].as_int().expect("b should exist"), 42); - /// assert_eq!(map["d"].as_unit().expect("d should exist"), ()); - /// - /// let c = map["c"].read_lock::().expect("c should exist"); - /// assert_eq!(c["x"].as_bool().expect("x should be bool"), false); - /// assert_eq!(c["y"].as_bool().expect("y should be bool"), true); - /// assert_eq!(c["z"].as_char().expect("z should be char"), '$'); - /// # Ok(()) - /// # } - /// ``` - #[cfg(not(feature = "no_object"))] - #[inline(always)] - pub fn parse_json( - &self, - json: impl AsRef, - has_null: bool, - ) -> crate::RhaiResultOf { - use crate::{tokenizer::Token, LexError}; - - let scripts = [json.as_ref()]; - - let (stream, tokenizer_control) = self.lex_raw( - &scripts, - if has_null { - Some(&|token, _, _| { - match token { - // `null` => `()` - Token::Reserved(s) if &*s == "null" => Token::Unit, - // `{` => `#{` - Token::LeftBrace => Token::MapStart, - // Disallowed syntax - t @ (Token::Unit | Token::MapStart) => Token::LexError( - LexError::ImproperSymbol( - t.literal_syntax().to_string(), - "".to_string(), - ) - .into(), - ), - Token::InterpolatedString(..) => Token::LexError( - LexError::ImproperSymbol( - "interpolated string".to_string(), - "".to_string(), - ) - .into(), - ), - // All others - _ => token, - } - }) - } else { - Some(&|token, _, _| { - match token { - Token::Reserved(s) if &*s == "null" => Token::LexError( - LexError::ImproperSymbol("null".to_string(), "".to_string()).into(), - ), - // `{` => `#{` - Token::LeftBrace => Token::MapStart, - // Disallowed syntax - t @ (Token::Unit | Token::MapStart) => Token::LexError( - LexError::ImproperSymbol( - t.literal_syntax().to_string(), - "".to_string(), - ) - .into(), - ), - Token::InterpolatedString(..) => Token::LexError( - LexError::ImproperSymbol( - "interpolated string".to_string(), - "".to_string(), - ) - .into(), - ), - // All others - _ => token, - } - }) - }, - ); - - let mut state = ParseState::new(self, tokenizer_control); - - let ast = self.parse_global_expr( - &mut stream.peekable(), - &mut state, - &Scope::new(), - #[cfg(not(feature = "no_optimize"))] - OptimizationLevel::None, - #[cfg(feature = "no_optimize")] - OptimizationLevel::default(), - )?; - - self.eval_ast(&ast) - } } diff --git a/src/api/json.rs b/src/api/json.rs new file mode 100644 index 00000000..4eba3eb6 --- /dev/null +++ b/src/api/json.rs @@ -0,0 +1,175 @@ +//! Module that defines JSON manipulation functions for [`Engine`]. +#![cfg(not(feature = "no_object"))] + +use crate::{Engine, LexError, Map, OptimizationLevel, ParseState, RhaiResultOf, Scope, Token}; + +impl Engine { + /// Parse a JSON string into an [object map][Map]. + /// + /// This is a light-weight alternative to using, say, [`serde_json`](https://crates.io/crates/serde_json) + /// to deserialize the JSON. + /// + /// Not available under `no_object`. + /// + /// The JSON string must be an object hash. It cannot be a simple primitive value. + /// + /// Set `has_null` to `true` in order to map `null` values to `()`. + /// Setting it to `false` causes a syntax error for any `null` value. + /// + /// JSON sub-objects are handled transparently. + /// + /// This function can be used together with [`format_map_as_json`] to work with JSON texts + /// without using the [`serde`](https://crates.io/crates/serde) crate (which is heavy). + /// + /// # Example + /// + /// ``` + /// # fn main() -> Result<(), Box> { + /// use rhai::{Engine, Map}; + /// + /// let engine = Engine::new(); + /// + /// let map = engine.parse_json(r#" + /// { + /// "a": 123, + /// "b": 42, + /// "c": { + /// "x": false, + /// "y": true, + /// "z": '$' + /// }, + /// "d": null + /// }"#, true)?; + /// + /// assert_eq!(map.len(), 4); + /// assert_eq!(map["a"].as_int().expect("a should exist"), 123); + /// assert_eq!(map["b"].as_int().expect("b should exist"), 42); + /// assert_eq!(map["d"].as_unit().expect("d should exist"), ()); + /// + /// let c = map["c"].read_lock::().expect("c should exist"); + /// assert_eq!(c["x"].as_bool().expect("x should be bool"), false); + /// assert_eq!(c["y"].as_bool().expect("y should be bool"), true); + /// assert_eq!(c["z"].as_char().expect("z should be char"), '$'); + /// # Ok(()) + /// # } + /// ``` + #[cfg(not(feature = "no_object"))] + #[inline(always)] + pub fn parse_json(&self, json: impl AsRef, has_null: bool) -> RhaiResultOf { + let scripts = [json.as_ref()]; + + let (stream, tokenizer_control) = self.lex_raw( + &scripts, + if has_null { + Some(&|token, _, _| { + match token { + // `null` => `()` + Token::Reserved(s) if &*s == "null" => Token::Unit, + // `{` => `#{` + Token::LeftBrace => Token::MapStart, + // Disallowed syntax + t @ (Token::Unit | Token::MapStart) => Token::LexError( + LexError::ImproperSymbol( + t.literal_syntax().to_string(), + "".to_string(), + ) + .into(), + ), + Token::InterpolatedString(..) => Token::LexError( + LexError::ImproperSymbol( + "interpolated string".to_string(), + "".to_string(), + ) + .into(), + ), + // All others + _ => token, + } + }) + } else { + Some(&|token, _, _| { + match token { + Token::Reserved(s) if &*s == "null" => Token::LexError( + LexError::ImproperSymbol("null".to_string(), "".to_string()).into(), + ), + // `{` => `#{` + Token::LeftBrace => Token::MapStart, + // Disallowed syntax + t @ (Token::Unit | Token::MapStart) => Token::LexError( + LexError::ImproperSymbol( + t.literal_syntax().to_string(), + "Invalid JSON syntax".to_string(), + ) + .into(), + ), + Token::InterpolatedString(..) => Token::LexError( + LexError::ImproperSymbol( + "interpolated string".to_string(), + "Invalid JSON syntax".to_string(), + ) + .into(), + ), + // All others + _ => token, + } + }) + }, + ); + + let mut state = ParseState::new(self, tokenizer_control); + + let ast = self.parse_global_expr( + &mut stream.peekable(), + &mut state, + &Scope::new(), + #[cfg(not(feature = "no_optimize"))] + OptimizationLevel::None, + #[cfg(feature = "no_optimize")] + OptimizationLevel::default(), + )?; + + self.eval_ast(&ast) + } +} + +/// Return the JSON representation of an [object map][Map]. +/// +/// This function can be used together with [`Engine::parse_json`] to work with JSON texts +/// without using the [`serde`](https://crates.io/crates/serde) crate (which is heavy). +/// +/// # Data types +/// +/// Only the following data types should be kept inside the object map: [`INT`][crate::INT], +/// [`FLOAT`][crate::FLOAT], [`ImmutableString`][crate::ImmutableString], `char`, `bool`, `()`, +/// [`Array`][crate::Array], [`Map`]. +/// +/// # Errors +/// +/// Data types not supported by JSON serialize into formats that may invalidate the result. +pub fn format_map_as_json(map: &Map) -> String { + let mut result = String::from('{'); + + for (key, value) in map { + if result.len() > 1 { + result.push(','); + } + + result.push_str(&format!("{:?}", key)); + result.push(':'); + + if let Some(val) = value.read_lock::() { + result.push_str(&format_map_as_json(&*val)); + continue; + } + + if value.is::<()>() { + result.push_str("null"); + } else { + result.push_str(&format!("{:?}", value)); + } + } + + result.push('}'); + + result +} diff --git a/src/api/mod.rs b/src/api/mod.rs index 25431175..554f48d5 100644 --- a/src/api/mod.rs +++ b/src/api/mod.rs @@ -8,6 +8,8 @@ pub mod run; pub mod compile; +pub mod json; + pub mod files; pub mod register; diff --git a/src/lib.rs b/src/lib.rs index 56958dd3..f10e2b20 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -237,6 +237,9 @@ pub type Blob = Vec; #[cfg(not(feature = "no_object"))] pub type Map = std::collections::BTreeMap; +#[cfg(not(feature = "no_object"))] +pub use api::json::format_map_as_json; + #[cfg(not(feature = "no_module"))] pub use module::ModuleResolver; diff --git a/src/packages/map_basic.rs b/src/packages/map_basic.rs index a3b0931f..771bc7aa 100644 --- a/src/packages/map_basic.rs +++ b/src/packages/map_basic.rs @@ -2,7 +2,7 @@ use crate::engine::OP_EQUALS; use crate::plugin::*; -use crate::{def_package, Dynamic, ImmutableString, Map, RhaiResultOf, INT}; +use crate::{def_package, format_map_as_json, Dynamic, ImmutableString, Map, RhaiResultOf, INT}; #[cfg(feature = "no_std")] use std::prelude::v1::*; @@ -266,4 +266,26 @@ mod map_functions { map.values().cloned().collect() } } + /// Return the JSON representation of the object map. + /// + /// # Data types + /// + /// Only the following data types should be kept inside the object map: + /// `INT`, `FLOAT`, `ImmutableString`, `char`, `bool`, `()`, `Array`, `Map`. + /// + /// # Errors + /// + /// Data types not supported by JSON serialize into formats that may + /// invalidate the result. + /// + /// # Example + /// + /// ```rhai + /// let m = #{a:1, b:2, c:3}; + /// + /// print(m.to_json()); // prints {"a":1, "b":2, "c":3} + /// ``` + pub fn to_json(map: &mut Map) -> String { + format_map_as_json(map) + } } From 6b8ddd925bc2f979eb0f0faf8d7d077549a06a03 Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Thu, 21 Apr 2022 13:21:53 +0800 Subject: [PATCH 12/13] Allow scope constants in strict variables mode. --- CHANGELOG.md | 5 +++++ src/api/compile.rs | 18 ++++-------------- src/api/eval.rs | 3 +-- src/api/json.rs | 4 ++-- src/api/run.rs | 3 +-- src/parser.rs | 26 ++++++++++++++++++-------- tests/options.rs | 13 +++++++++++++ 7 files changed, 44 insertions(+), 28 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a2b740dd..e47dc084 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,11 @@ Bug fixes * Compound assignments now work properly with indexers. +Script-breaking changes +----------------------- + +* _Strict Variables Mode_ no longer returns an error when an undeclared variable matches a constant in the provided external `Scope`. + Enhancements ------------ diff --git a/src/api/compile.rs b/src/api/compile.rs index 2259ec92..ab82f90a 100644 --- a/src/api/compile.rs +++ b/src/api/compile.rs @@ -225,13 +225,8 @@ impl Engine { scripts.as_ref(), self.token_mapper.as_ref().map(Box::as_ref), ); - let mut state = ParseState::new(self, tokenizer_control); - self.parse( - &mut stream.peekable(), - &mut state, - scope, - optimization_level, - ) + let mut state = ParseState::new(self, scope, tokenizer_control); + self.parse(&mut stream.peekable(), &mut state, optimization_level) } /// Compile a string containing an expression into an [`AST`], /// which can be used later for evaluation. @@ -300,12 +295,7 @@ impl Engine { self.lex_raw(&scripts, self.token_mapper.as_ref().map(Box::as_ref)); let mut peekable = stream.peekable(); - let mut state = ParseState::new(self, tokenizer_control); - self.parse_global_expr( - &mut peekable, - &mut state, - scope, - self.options.optimization_level, - ) + let mut state = ParseState::new(self, scope, tokenizer_control); + self.parse_global_expr(&mut peekable, &mut state, self.options.optimization_level) } } diff --git a/src/api/eval.rs b/src/api/eval.rs index 49e26243..094fd117 100644 --- a/src/api/eval.rs +++ b/src/api/eval.rs @@ -116,13 +116,12 @@ impl Engine { let scripts = [script]; let (stream, tokenizer_control) = self.lex_raw(&scripts, self.token_mapper.as_ref().map(Box::as_ref)); - let mut state = ParseState::new(self, tokenizer_control); + let mut state = ParseState::new(self, scope, tokenizer_control); // No need to optimize a lone expression let ast = self.parse_global_expr( &mut stream.peekable(), &mut state, - scope, #[cfg(not(feature = "no_optimize"))] OptimizationLevel::None, #[cfg(feature = "no_optimize")] diff --git a/src/api/json.rs b/src/api/json.rs index 4eba3eb6..b495c4a0 100644 --- a/src/api/json.rs +++ b/src/api/json.rs @@ -116,12 +116,12 @@ impl Engine { }, ); - let mut state = ParseState::new(self, tokenizer_control); + let scope = &Scope::new(); + let mut state = ParseState::new(self, scope, tokenizer_control); let ast = self.parse_global_expr( &mut stream.peekable(), &mut state, - &Scope::new(), #[cfg(not(feature = "no_optimize"))] OptimizationLevel::None, #[cfg(feature = "no_optimize")] diff --git a/src/api/run.rs b/src/api/run.rs index 77097d29..cbda58c7 100644 --- a/src/api/run.rs +++ b/src/api/run.rs @@ -24,12 +24,11 @@ impl Engine { let scripts = [script]; let (stream, tokenizer_control) = self.lex_raw(&scripts, self.token_mapper.as_ref().map(Box::as_ref)); - let mut state = ParseState::new(self, tokenizer_control); + let mut state = ParseState::new(self, scope, tokenizer_control); let ast = self.parse( &mut stream.peekable(), &mut state, - scope, self.options.optimization_level, )?; diff --git a/src/parser.rs b/src/parser.rs index a4b6fa60..e33ece2b 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -47,6 +47,8 @@ pub struct ParseState<'e> { pub tokenizer_control: TokenizerControl, /// Interned strings. interned_strings: StringsInterner, + /// External [scope][Scope] with constants. + pub scope: &'e Scope<'e>, /// Encapsulates a local stack with variable names to simulate an actual runtime scope. pub stack: Scope<'e>, /// Size of the local variables stack upon entry of the current block scope. @@ -72,7 +74,7 @@ impl<'e> ParseState<'e> { /// Create a new [`ParseState`]. #[inline(always)] #[must_use] - pub fn new(engine: &Engine, tokenizer_control: TokenizerControl) -> Self { + pub fn new(engine: &Engine, scope: &'e Scope, tokenizer_control: TokenizerControl) -> Self { Self { tokenizer_control, #[cfg(not(feature = "no_closure"))] @@ -80,6 +82,7 @@ impl<'e> ParseState<'e> { #[cfg(not(feature = "no_closure"))] allow_capture: true, interned_strings: StringsInterner::new(), + scope, stack: Scope::new(), block_stack_len: 0, #[cfg(not(feature = "no_module"))] @@ -1261,7 +1264,8 @@ impl Engine { // | ... #[cfg(not(feature = "no_function"))] Token::Pipe | Token::Or if settings.options.allow_anonymous_fn => { - let mut new_state = ParseState::new(self, state.tokenizer_control.clone()); + let mut new_state = + ParseState::new(self, state.scope, state.tokenizer_control.clone()); #[cfg(not(feature = "unchecked"))] { @@ -1297,6 +1301,10 @@ impl Engine { if settings.options.strict_var && !settings.is_closure_scope && index.is_none() + && !matches!( + state.scope.get_index(name), + Some((_, AccessMode::ReadOnly)) + ) { // If the parent scope is not inside another capturing closure // then we can conclude that the captured variable doesn't exist. @@ -1440,7 +1448,10 @@ impl Engine { _ => { let index = state.access_var(&s, settings.pos); - if settings.options.strict_var && index.is_none() { + if settings.options.strict_var + && index.is_none() + && !matches!(state.scope.get_index(&s), Some((_, AccessMode::ReadOnly))) + { return Err( PERR::VariableUndefined(s.to_string()).into_err(settings.pos) ); @@ -3061,7 +3072,8 @@ impl Engine { match input.next().expect(NEVER_ENDS) { (Token::Fn, pos) => { - let mut new_state = ParseState::new(self, state.tokenizer_control.clone()); + let mut new_state = + ParseState::new(self, state.scope, state.tokenizer_control.clone()); #[cfg(not(feature = "unchecked"))] { @@ -3533,7 +3545,6 @@ impl Engine { &self, input: &mut TokenStream, state: &mut ParseState, - _scope: &Scope, _optimization_level: OptimizationLevel, ) -> ParseResult { let mut functions = BTreeMap::new(); @@ -3575,7 +3586,7 @@ impl Engine { #[cfg(not(feature = "no_optimize"))] return Ok(crate::optimizer::optimize_into_ast( self, - _scope, + state.scope, statements, #[cfg(not(feature = "no_function"))] StaticVec::new_const(), @@ -3657,7 +3668,6 @@ impl Engine { &self, input: &mut TokenStream, state: &mut ParseState, - _scope: &Scope, _optimization_level: OptimizationLevel, ) -> ParseResult { let (statements, _lib) = self.parse_global_level(input, state)?; @@ -3665,7 +3675,7 @@ impl Engine { #[cfg(not(feature = "no_optimize"))] return Ok(crate::optimizer::optimize_into_ast( self, - _scope, + state.scope, statements, #[cfg(not(feature = "no_function"))] _lib, diff --git a/tests/options.rs b/tests/options.rs index 803a5802..f4154d72 100644 --- a/tests/options.rs +++ b/tests/options.rs @@ -56,6 +56,10 @@ fn test_options_allow() -> Result<(), Box> { fn test_options_strict_var() -> Result<(), Box> { let mut engine = Engine::new(); + let mut scope = Scope::new(); + scope.push_constant("x", 42 as INT); + scope.push_constant("y", 0 as INT); + engine.compile("let x = if y { z } else { w };")?; #[cfg(not(feature = "no_function"))] @@ -79,6 +83,11 @@ fn test_options_strict_var() -> Result<(), Box> { assert!(engine.compile("let x = if y { z } else { w };").is_err()); engine.compile("let y = 42; let x = y;")?; + assert_eq!( + engine.eval_with_scope::(&mut scope, "{ let y = 42; x * y }")?, + 42 * 42 + ); + #[cfg(not(feature = "no_function"))] assert!(engine.compile("fn foo(x) { x + y }").is_err()); @@ -103,6 +112,10 @@ fn test_options_strict_var() -> Result<(), Box> { engine.compile("let x = 42; let f = |y| { || x + y };")?; assert!(engine.compile("fn foo() { |y| { || x + y } }").is_err()); } + assert_eq!( + engine.eval_with_scope::(&mut scope, "fn foo(z) { x * y + z } foo(1)")?, + 1 + ); } Ok(()) From 6f4cc91451df2bc9ba1ed4b110b378325ece4f4c Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Thu, 21 Apr 2022 16:01:20 +0800 Subject: [PATCH 13/13] Fix builds. --- src/api/json.rs | 12 +++++++++--- tests/options.rs | 1 + 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/src/api/json.rs b/src/api/json.rs index b495c4a0..856e25f4 100644 --- a/src/api/json.rs +++ b/src/api/json.rs @@ -1,7 +1,11 @@ //! Module that defines JSON manipulation functions for [`Engine`]. #![cfg(not(feature = "no_object"))] -use crate::{Engine, LexError, Map, OptimizationLevel, ParseState, RhaiResultOf, Scope, Token}; +use crate::parser::ParseState; +use crate::tokenizer::Token; +use crate::{Engine, LexError, Map, OptimizationLevel, RhaiResultOf, Scope}; +#[cfg(feature = "no_std")] +use std::prelude::v1::*; impl Engine { /// Parse a JSON string into an [object map][Map]. @@ -53,8 +57,7 @@ impl Engine { /// # Ok(()) /// # } /// ``` - #[cfg(not(feature = "no_object"))] - #[inline(always)] + #[inline] pub fn parse_json(&self, json: impl AsRef, has_null: bool) -> RhaiResultOf { let scripts = [json.as_ref()]; @@ -134,6 +137,8 @@ impl Engine { /// Return the JSON representation of an [object map][Map]. /// +/// Not available under `no_std`. +/// /// This function can be used together with [`Engine::parse_json`] to work with JSON texts /// without using the [`serde`](https://crates.io/crates/serde) crate (which is heavy). /// @@ -146,6 +151,7 @@ impl Engine { /// # Errors /// /// Data types not supported by JSON serialize into formats that may invalidate the result. +#[inline] pub fn format_map_as_json(map: &Map) -> String { let mut result = String::from('{'); diff --git a/tests/options.rs b/tests/options.rs index f4154d72..aa9b250d 100644 --- a/tests/options.rs +++ b/tests/options.rs @@ -112,6 +112,7 @@ fn test_options_strict_var() -> Result<(), Box> { engine.compile("let x = 42; let f = |y| { || x + y };")?; assert!(engine.compile("fn foo() { |y| { || x + y } }").is_err()); } + #[cfg(not(feature = "no_optimize"))] assert_eq!( engine.eval_with_scope::(&mut scope, "fn foo(z) { x * y + z } foo(1)")?, 1