diff --git a/RELEASES.md b/RELEASES.md index ffdca9a9..4fb86c9e 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -24,6 +24,11 @@ New features * Low-level API for custom syntax allowing more flexibility in designing the syntax. * `Module::fill_with` to poly-fill a module with another. +Enhancements +------------ + +* AST data structures are optimized to maximize cache friendliness. This may have speed impacts on large, complex scripts (benchmarks wanted!). + Version 0.19.3 ============== diff --git a/src/engine.rs b/src/engine.rs index 2bc062f4..dcd786e4 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -6,7 +6,7 @@ use crate::fn_native::{Callback, FnPtr, OnVarCallback}; use crate::module::{Module, ModuleRef}; use crate::optimize::OptimizationLevel; use crate::packages::{Package, PackagesCollection, StandardPackage}; -use crate::parser::{BinaryExpr, Expr, ReturnType, Stmt}; +use crate::parser::{BinaryExpr, Expr, Ident, ReturnType, Stmt}; use crate::r#unsafe::unsafe_cast_var_name_to_lifetime; use crate::result::EvalAltResult; use crate::scope::{EntryType as ScopeEntryType, Scope}; @@ -33,6 +33,7 @@ use crate::any::DynamicWriteLock; use crate::stdlib::{ any::type_name, + borrow::Cow, boxed::Box, collections::{HashMap, HashSet}, fmt, format, @@ -393,6 +394,11 @@ impl State { pub fn new() -> Self { Default::default() } + /// Is the state currently at global (root) level? + #[inline(always)] + pub fn is_global(&self) -> bool { + self.scope_level == 0 + } } /// _[INTERNALS]_ A type containing all the limits imposed by the `Engine`. @@ -764,7 +770,7 @@ impl Engine { match expr { Expr::Variable(v) => match v.as_ref() { // Qualified variable - ((name, pos), Some(modules), hash_var, _) => { + (Ident { name, pos }, Some(modules), hash_var, _) => { let module = search_imports_mut(mods, state, modules)?; let target = module.get_qualified_var_mut(*hash_var).map_err(|mut err| { match *err { @@ -796,7 +802,7 @@ impl Engine { this_ptr: &'s mut Option<&mut Dynamic>, expr: &'a Expr, ) -> Result<(Target<'s>, &'a str, ScopeEntryType, Position), Box> { - let ((name, pos), _, _, index) = match expr { + let (Ident { name, pos }, _, _, index) = match expr { Expr::Variable(v) => v.as_ref(), _ => unreachable!(), }; @@ -1187,7 +1193,10 @@ impl Engine { match dot_lhs { // id.??? or id[???] Expr::Variable(x) => { - let (var_name, var_pos) = &x.0; + let Ident { + name: var_name, + pos: var_pos, + } = &x.0; self.inc_operations(state) .map_err(|err| err.fill_position(*var_pos))?; @@ -1491,14 +1500,16 @@ impl Engine { Expr::IntegerConstant(x) => Ok(x.0.into()), #[cfg(not(feature = "no_float"))] Expr::FloatConstant(x) => Ok(x.0.into()), - Expr::StringConstant(x) => Ok(x.0.to_string().into()), + Expr::StringConstant(x) => Ok(x.name.to_string().into()), Expr::CharConstant(x) => Ok(x.0.into()), - Expr::FnPointer(x) => Ok(FnPtr::new_unchecked(x.0.clone(), Default::default()).into()), - Expr::Variable(x) if (x.0).0 == KEYWORD_THIS => { + Expr::FnPointer(x) => { + Ok(FnPtr::new_unchecked(x.name.clone(), Default::default()).into()) + } + Expr::Variable(x) if (x.0).name == KEYWORD_THIS => { if let Some(val) = this_ptr { Ok(val.clone()) } else { - EvalAltResult::ErrorUnboundThis((x.0).1).into() + EvalAltResult::ErrorUnboundThis((x.0).pos).into() } } Expr::Variable(_) => { @@ -1533,9 +1544,9 @@ impl Engine { #[cfg(not(feature = "no_object"))] Expr::Map(x) => Ok(Dynamic(Union::Map(Box::new( x.0.iter() - .map(|((key, _), expr)| { + .map(|(key, expr)| { self.eval_expr(scope, mods, state, lib, this_ptr, expr, level) - .map(|val| (key.clone(), val)) + .map(|val| (key.name.clone(), val)) }) .collect::, _>>()?, )))), @@ -1885,7 +1896,11 @@ impl Engine { if let Some(func) = func { // Add the loop variable - let var_name = unsafe_cast_var_name_to_lifetime(name, &state); + let var_name: Cow<'_, str> = if state.is_global() { + name.clone().into() + } else { + unsafe_cast_var_name_to_lifetime(name).into() + }; scope.push(var_name, ()); let index = scope.len() - 1; state.scope_level += 1; @@ -1929,7 +1944,7 @@ impl Engine { // Try/Catch statement Stmt::TryCatch(x) => { - let ((try_body, _), var_def, (catch_body, _)) = x.as_ref(); + let (try_body, var_def, catch_body, _) = x.as_ref(); let result = self .eval_stmt(scope, mods, state, lib, this_ptr, try_body, level) @@ -1951,8 +1966,12 @@ impl Engine { let orig_scope_len = scope.len(); state.scope_level += 1; - if let Some((var_name, _)) = var_def { - let var_name = unsafe_cast_var_name_to_lifetime(var_name, &state); + if let Some(Ident { name, .. }) = var_def { + let var_name: Cow<'_, str> = if state.is_global() { + name.clone().into() + } else { + unsafe_cast_var_name_to_lifetime(name).into() + }; scope.push(var_name, value); } @@ -2016,7 +2035,11 @@ impl Engine { } else { ().into() }; - let var_name = unsafe_cast_var_name_to_lifetime(&var_def.0, &state); + let var_name: Cow<'_, str> = if state.is_global() { + var_def.name.clone().into() + } else { + unsafe_cast_var_name_to_lifetime(&var_def.name).into() + }; scope.push_dynamic_value(var_name, entry_type, val, false); Ok(Default::default()) } @@ -2039,7 +2062,7 @@ impl Engine { if let Some(name_def) = alias { module.index_all_sub_modules(); - mods.push((name_def.0.clone(), module)); + mods.push((name_def.name.clone(), module)); } state.modules += 1; @@ -2059,13 +2082,13 @@ impl Engine { // Export statement #[cfg(not(feature = "no_module"))] Stmt::Export(list, _) => { - for ((id, id_pos), rename) in list.iter() { + for (Ident { name, pos: id_pos }, rename) in list.iter() { // Mark scope variables as public - if let Some(index) = scope.get_index(id).map(|(i, _)| i) { - let alias = rename.as_ref().map(|(n, _)| n).unwrap_or_else(|| id); + if let Some(index) = scope.get_index(name).map(|(i, _)| i) { + let alias = rename.as_ref().map(|x| &x.name).unwrap_or_else(|| name); scope.set_entry_alias(index, alias.clone()); } else { - return EvalAltResult::ErrorVariableNotFound(id.into(), *id_pos).into(); + return EvalAltResult::ErrorVariableNotFound(name.into(), *id_pos).into(); } } Ok(Default::default()) @@ -2073,7 +2096,7 @@ impl Engine { // Share statement #[cfg(not(feature = "no_closure"))] - Stmt::Share(var_name, _) => { + Stmt::Share(Ident { name: var_name, .. }) => { match scope.get_index(var_name) { Some((index, ScopeEntryType::Normal)) => { let (val, _) = scope.get_mut(index); diff --git a/src/fn_call.rs b/src/fn_call.rs index efd563b6..531a9284 100644 --- a/src/fn_call.rs +++ b/src/fn_call.rs @@ -41,6 +41,7 @@ use crate::scope::Entry as ScopeEntry; use crate::stdlib::{ any::{type_name, TypeId}, + borrow::Cow, boxed::Box, convert::TryFrom, format, @@ -376,7 +377,7 @@ impl Engine { .iter() .zip(args.iter_mut().map(|v| mem::take(*v))) .map(|(name, value)| { - let var_name = unsafe_cast_var_name_to_lifetime(name.as_str(), state); + let var_name: Cow<'_, str> = unsafe_cast_var_name_to_lifetime(name).into(); (var_name, ScopeEntryType::Normal, value) }), ); diff --git a/src/lib.rs b/src/lib.rs index e2973ae0..d1896d9f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -149,7 +149,9 @@ pub use token::{get_next_token, parse_string_literal, InputStream, Token, Tokeni #[cfg(feature = "internals")] #[deprecated(note = "this type is volatile and may change")] -pub use parser::{CustomExpr, Expr, FloatWrapper, ReturnType, ScriptFnDef, Stmt}; +pub use parser::{ + BinaryExpr, CustomExpr, Expr, FloatWrapper, Ident, IdentX, ReturnType, ScriptFnDef, Stmt, +}; #[cfg(feature = "internals")] #[deprecated(note = "this type is volatile and may change")] diff --git a/src/optimize.rs b/src/optimize.rs index 8fe5c876..de031742 100644 --- a/src/optimize.rs +++ b/src/optimize.rs @@ -7,7 +7,7 @@ use crate::engine::{ }; use crate::fn_call::run_builtin_binary_op; use crate::module::Module; -use crate::parser::{map_dynamic_to_expr, BinaryExpr, Expr, ScriptFnDef, Stmt, AST}; +use crate::parser::{map_dynamic_to_expr, BinaryExpr, CustomExpr, Expr, ScriptFnDef, Stmt, AST}; use crate::scope::{Entry as ScopeEntry, Scope}; use crate::token::{is_valid_identifier, Position}; use crate::{calc_fn_hash, StaticVec}; @@ -15,9 +15,6 @@ use crate::{calc_fn_hash, StaticVec}; #[cfg(not(feature = "no_function"))] use crate::parser::ReturnType; -#[cfg(feature = "internals")] -use crate::parser::CustomExpr; - use crate::stdlib::{ boxed::Box, iter::empty, @@ -283,18 +280,18 @@ fn optimize_stmt(stmt: Stmt, state: &mut State, preserve_result: bool) -> Stmt { .into_iter() .map(|stmt| match stmt { // Add constant literals into the state - Stmt::Const(name, Some(expr), pos) if expr.is_literal() => { + Stmt::Const(var_def, Some(expr), pos) if expr.is_literal() => { state.set_dirty(); - state.push_constant(&name.0, expr); + state.push_constant(&var_def.name, expr); Stmt::Noop(pos) // No need to keep constants } - Stmt::Const(name, Some(expr), pos) if expr.is_literal() => { + Stmt::Const(var_def, Some(expr), pos) if expr.is_literal() => { let expr = optimize_expr(expr, state); - Stmt::Const(name, Some(expr), pos) + Stmt::Const(var_def, Some(expr), pos) } - Stmt::Const(name, None, pos) => { + Stmt::Const(var_def, None, pos) => { state.set_dirty(); - state.push_constant(&name.0, Expr::Unit(name.1)); + state.push_constant(&var_def.name, Expr::Unit(var_def.pos)); Stmt::Noop(pos) // No need to keep constants } // Optimize the statement @@ -389,22 +386,25 @@ fn optimize_stmt(stmt: Stmt, state: &mut State, preserve_result: bool) -> Stmt { } } // try { block } catch ( var ) { block } - Stmt::TryCatch(x) if (x.0).0.is_pure() => { + Stmt::TryCatch(x) if x.0.is_pure() => { // If try block is pure, there will never be any exceptions state.set_dirty(); - let pos = (x.0).0.position(); - let mut statements: Vec<_> = Default::default(); - statements.push(optimize_stmt((x.0).0, state, preserve_result)); + let pos = x.0.position(); + let mut statements = match optimize_stmt(x.0, state, preserve_result) { + Stmt::Block(statements, _) => statements, + stmt => vec![stmt], + }; statements.push(Stmt::Noop(pos)); Stmt::Block(statements, pos) } // try { block } catch ( var ) { block } Stmt::TryCatch(x) => { - let ((try_block, try_pos), var_name, (catch_block, catch_pos)) = *x; + let (try_block, var_name, catch_block, pos) = *x; Stmt::TryCatch(Box::new(( - (optimize_stmt(try_block, state, false), try_pos), + optimize_stmt(try_block, state, false), var_name, - (optimize_stmt(catch_block, state, false), catch_pos), + optimize_stmt(catch_block, state, false), + pos, ))) } // expr; @@ -463,7 +463,7 @@ fn optimize_expr(expr: Expr, state: &mut State) -> Expr { // All other items can be thrown away. state.set_dirty(); let pos = m.1; - m.0.into_iter().find(|((name, _), _)| name == prop) + m.0.into_iter().find(|(x, _)| &x.name == prop) .map(|(_, mut expr)| { expr.set_position(pos); expr }) .unwrap_or_else(|| Expr::Unit(pos)) } @@ -495,15 +495,15 @@ fn optimize_expr(expr: Expr, state: &mut State) -> Expr { // All other items can be thrown away. state.set_dirty(); let pos = m.1; - m.0.into_iter().find(|((name, _), _)| *name == s.0) + m.0.into_iter().find(|(x, _)| x.name == s.name) .map(|(_, mut expr)| { expr.set_position(pos); expr }) .unwrap_or_else(|| Expr::Unit(pos)) } // string[int] - (Expr::StringConstant(s), Expr::IntegerConstant(i)) if i.0 >= 0 && (i.0 as usize) < s.0.chars().count() => { + (Expr::StringConstant(s), Expr::IntegerConstant(i)) if i.0 >= 0 && (i.0 as usize) < s.name.chars().count() => { // String literal indexing - get the character state.set_dirty(); - Expr::CharConstant(Box::new((s.0.chars().nth(i.0 as usize).unwrap(), s.1))) + Expr::CharConstant(Box::new((s.name.chars().nth(i.0 as usize).unwrap(), s.pos))) } // lhs[rhs] (lhs, rhs) => Expr::Index(Box::new(BinaryExpr { @@ -520,27 +520,27 @@ fn optimize_expr(expr: Expr, state: &mut State) -> Expr { // [ items .. ] #[cfg(not(feature = "no_object"))] Expr::Map(m) => Expr::Map(Box::new((m.0 - .into_iter().map(|((key, pos), expr)| ((key, pos), optimize_expr(expr, state))) + .into_iter().map(|(key, expr)| (key, optimize_expr(expr, state))) .collect(), m.1))), // lhs in rhs Expr::In(x) => match (x.lhs, x.rhs) { // "xxx" in "xxxxx" (Expr::StringConstant(a), Expr::StringConstant(b)) => { state.set_dirty(); - if b.0.contains(a.0.as_str()) { Expr::True(a.1) } else { Expr::False(a.1) } + if b.name.contains(a.name.as_str()) { Expr::True(a.pos) } else { Expr::False(a.pos) } } // 'x' in "xxxxx" (Expr::CharConstant(a), Expr::StringConstant(b)) => { state.set_dirty(); - if b.0.contains(a.0) { Expr::True(a.1) } else { Expr::False(a.1) } + if b.name.contains(a.0) { Expr::True(a.1) } else { Expr::False(a.1) } } // "xxx" in #{...} (Expr::StringConstant(a), Expr::Map(b)) => { state.set_dirty(); - if b.0.iter().find(|((name, _), _)| *name == a.0).is_some() { - Expr::True(a.1) + if b.0.iter().find(|(x, _)| x.name == a.name).is_some() { + Expr::True(a.pos) } else { - Expr::False(a.1) + Expr::False(a.pos) } } // 'x' in #{...} @@ -548,7 +548,7 @@ fn optimize_expr(expr: Expr, state: &mut State) -> Expr { state.set_dirty(); let ch = a.0.to_string(); - if b.0.iter().find(|((name, _), _)| name == &ch).is_some() { + if b.0.iter().find(|(x, _)| x.name == &ch).is_some() { Expr::True(a.1) } else { Expr::False(a.1) @@ -697,24 +697,20 @@ fn optimize_expr(expr: Expr, state: &mut State) -> Expr { } // constant-name - Expr::Variable(x) if x.1.is_none() && state.contains_constant(&(x.0).0) => { - let (name, pos) = x.0; + Expr::Variable(x) if x.1.is_none() && state.contains_constant(&x.0.name) => { state.set_dirty(); // Replace constant with value - let mut expr = state.find_constant(&name).unwrap().clone(); - expr.set_position(pos); + let mut expr = state.find_constant(&x.0.name).unwrap().clone(); + expr.set_position(x.0.pos); expr } // Custom syntax - #[cfg(feature = "internals")] - Expr::Custom(x) => Expr::Custom(Box::new(( - CustomExpr( - (x.0).0.into_iter().map(|expr| optimize_expr(expr, state)).collect(), - (x.0).1), - x.1 - ))), + Expr::Custom(x) => Expr::Custom(Box::new(CustomExpr { + keywords: x.keywords.into_iter().map(|expr| optimize_expr(expr, state)).collect(), + ..*x + })), // All other expressions - skip expr => expr, @@ -776,7 +772,7 @@ fn optimize( let expr = optimize_expr(expr, &mut state); if expr.is_literal() { - state.push_constant(&var_def.0, expr.clone()); + state.push_constant(&var_def.name, expr.clone()); } // Keep it in the global scope @@ -788,7 +784,7 @@ fn optimize( } } Stmt::Const(ref var_def, None, _) => { - state.push_constant(&var_def.0, Expr::Unit(var_def.1)); + state.push_constant(&var_def.name, Expr::Unit(var_def.pos)); // Keep it in the global scope stmt diff --git a/src/parser.rs b/src/parser.rs index 4329de2a..84a8e6d9 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -733,6 +733,37 @@ impl ParseSettings { } } +/// An identifier containing a string name and a position. +#[derive(Debug, Clone, Hash)] +pub struct Ident { + pub name: String, + pub pos: Position, +} + +impl Ident { + /// Create a new `Identifier`. + pub fn new(name: String, pos: Position) -> Self { + Self { name, pos } + } +} + +/// An identifier containing an immutable name and a position. +#[derive(Debug, Clone, Hash)] +pub struct IdentX { + pub name: ImmutableString, + pub pos: Position, +} + +impl IdentX { + /// Create a new `Identifier`. + pub fn new(name: impl Into, pos: Position) -> Self { + Self { + name: name.into(), + pos, + } + } +} + /// _[INTERNALS]_ A Rhai statement. /// Exported under the `internals` feature only. /// @@ -751,21 +782,15 @@ pub enum Stmt { /// for id in expr { stmt } For(Expr, Box<(String, Stmt)>, Position), /// let id = expr - Let(Box<(String, Position)>, Option, Position), + Let(Box, Option, Position), /// const id = expr - Const(Box<(String, Position)>, Option, Position), + Const(Box, Option, Position), /// expr op= expr Assignment(Box<(Expr, Cow<'static, str>, Expr)>, Position), /// { stmt; ... } Block(Vec, Position), /// try { stmt; ... } catch ( var ) { stmt; ... } - TryCatch( - Box<( - (Stmt, Position), - Option<(String, Position)>, - (Stmt, Position), - )>, - ), + TryCatch(Box<(Stmt, Option, Stmt, (Position, Position))>), /// expr Expr(Expr), /// continue @@ -776,16 +801,13 @@ pub enum Stmt { ReturnWithVal((ReturnType, Position), Option, Position), /// import expr as var #[cfg(not(feature = "no_module"))] - Import(Expr, Option>, Position), + Import(Expr, Option>, Position), /// export var as var, ... #[cfg(not(feature = "no_module"))] - Export( - Vec<((String, Position), Option<(String, Position)>)>, - Position, - ), + Export(Vec<(Ident, Option)>, Position), /// Convert a variable to shared. #[cfg(not(feature = "no_closure"))] - Share(String, Position), + Share(Ident), } impl Default for Stmt { @@ -818,8 +840,8 @@ impl Stmt { | Self::For(_, _, pos) | Self::ReturnWithVal((_, pos), _, _) => *pos, - Self::Let(x, _, _) | Self::Const(x, _, _) => x.1, - Self::TryCatch(x) => (x.0).1, + Self::Let(x, _, _) | Self::Const(x, _, _) => x.pos, + Self::TryCatch(x) => (x.3).0, Self::Expr(x) => x.position(), @@ -829,7 +851,7 @@ impl Stmt { Self::Export(_, pos) => *pos, #[cfg(not(feature = "no_closure"))] - Self::Share(_, pos) => *pos, + Self::Share(Ident { pos, .. }) => *pos, } } @@ -847,8 +869,8 @@ impl Stmt { | Self::For(_, _, pos) | Self::ReturnWithVal((_, pos), _, _) => *pos = new_pos, - Self::Let(x, _, _) | Self::Const(x, _, _) => x.1 = new_pos, - Self::TryCatch(x) => (x.0).1 = new_pos, + Self::Let(x, _, _) | Self::Const(x, _, _) => x.pos = new_pos, + Self::TryCatch(x) => (x.3).0 = new_pos, Self::Expr(x) => { x.set_position(new_pos); @@ -860,7 +882,7 @@ impl Stmt { Self::Export(_, pos) => *pos = new_pos, #[cfg(not(feature = "no_closure"))] - Self::Share(_, pos) => *pos = new_pos, + Self::Share(Ident { pos, .. }) => *pos = new_pos, } self @@ -891,7 +913,7 @@ impl Stmt { Self::Import(_, _, _) | Self::Export(_, _) => false, #[cfg(not(feature = "no_closure"))] - Self::Share(_, _) => false, + Self::Share(_) => false, } } @@ -910,7 +932,7 @@ impl Stmt { Self::Let(_, _, _) | Self::Const(_, _, _) | Self::Assignment(_, _) => false, Self::Block(block, _) => block.iter().all(|stmt| stmt.is_pure()), Self::Continue(_) | Self::Break(_) | Self::ReturnWithVal(_, _, _) => false, - Self::TryCatch(x) => (x.0).0.is_pure() && (x.2).0.is_pure(), + Self::TryCatch(x) => x.0.is_pure() && x.2.is_pure(), #[cfg(not(feature = "no_module"))] Self::Import(_, _, _) => false, @@ -918,7 +940,7 @@ impl Stmt { Self::Export(_, _) => false, #[cfg(not(feature = "no_closure"))] - Self::Share(_, _) => false, + Self::Share(_) => false, } } } @@ -931,9 +953,9 @@ impl Stmt { /// This type is volatile and may change. #[derive(Clone)] pub struct CustomExpr { - keywords: StaticVec, - func: Shared, - pos: Position, + pub(crate) keywords: StaticVec, + pub(crate) func: Shared, + pub(crate) pos: Position, } impl fmt::Debug for CustomExpr { @@ -990,6 +1012,7 @@ impl Hash for FloatWrapper { } } +/// A binary expression structure. #[derive(Debug, Clone, Hash)] pub struct BinaryExpr { pub lhs: Expr, @@ -1016,18 +1039,11 @@ pub enum Expr { /// Character constant. CharConstant(Box<(char, Position)>), /// String constant. - StringConstant(Box<(ImmutableString, Position)>), + StringConstant(Box), /// FnPtr constant. - FnPointer(Box<(ImmutableString, Position)>), + FnPointer(Box), /// Variable access - ((variable name, position), optional modules, hash, optional index) - Variable( - Box<( - (String, Position), - Option>, - u64, - Option, - )>, - ), + Variable(Box<(Ident, Option>, u64, Option)>), /// Property access. Property(Box<((ImmutableString, String, String), Position)>), /// { stmt } @@ -1053,7 +1069,7 @@ pub enum Expr { /// [ expr, ... ] Array(Box<(StaticVec, Position)>), /// #{ name:expr, ... } - Map(Box<(StaticVec<((ImmutableString, Position), Expr)>, Position)>), + Map(Box<(StaticVec<(IdentX, Expr)>, Position)>), /// lhs in rhs In(Box), /// lhs && rhs @@ -1117,9 +1133,9 @@ impl Expr { #[cfg(not(feature = "no_float"))] Self::FloatConstant(x) => x.0.into(), Self::CharConstant(x) => x.0.into(), - Self::StringConstant(x) => x.0.clone().into(), + Self::StringConstant(x) => x.name.clone().into(), Self::FnPointer(x) => Dynamic(Union::FnPtr(Box::new(FnPtr::new_unchecked( - x.0.clone(), + x.name.clone(), Default::default(), )))), Self::True(_) => true.into(), @@ -1137,7 +1153,7 @@ impl Expr { Self::Map(x) if x.0.iter().all(|(_, v)| v.is_constant()) => { Dynamic(Union::Map(Box::new( x.0.iter() - .map(|((k, _), v)| (k.clone(), v.get_constant_value().unwrap())) + .map(|(k, v)| (k.name.clone(), v.get_constant_value().unwrap())) .collect(), ))) } @@ -1149,7 +1165,7 @@ impl Expr { /// Is the expression a simple variable access? pub(crate) fn get_variable_access(&self, non_qualified: bool) -> Option<&str> { match self { - Self::Variable(x) if !non_qualified || x.1.is_none() => Some((x.0).0.as_str()), + Self::Variable(x) if !non_qualified || x.1.is_none() => Some((x.0).name.as_str()), _ => None, } } @@ -1164,13 +1180,13 @@ impl Expr { Self::IntegerConstant(x) => x.1, Self::CharConstant(x) => x.1, - Self::StringConstant(x) => x.1, - Self::FnPointer(x) => x.1, + Self::StringConstant(x) => x.pos, + Self::FnPointer(x) => x.pos, Self::Array(x) => x.1, Self::Map(x) => x.1, Self::Property(x) => x.1, Self::Stmt(x) => x.1, - Self::Variable(x) => (x.0).1, + Self::Variable(x) => (x.0).pos, Self::FnCall(x) => (x.0).3, Self::And(x) | Self::Or(x) | Self::In(x) => x.pos, @@ -1195,11 +1211,11 @@ impl Expr { Self::IntegerConstant(x) => x.1 = new_pos, Self::CharConstant(x) => x.1 = new_pos, - Self::StringConstant(x) => x.1 = new_pos, - Self::FnPointer(x) => x.1 = new_pos, + Self::StringConstant(x) => x.pos = new_pos, + Self::FnPointer(x) => x.pos = new_pos, Self::Array(x) => x.1 = new_pos, Self::Map(x) => x.1 = new_pos, - Self::Variable(x) => (x.0).1 = new_pos, + Self::Variable(x) => (x.0).pos = new_pos, Self::Property(x) => x.1 = new_pos, Self::Stmt(x) => x.1 = new_pos, Self::FnCall(x) => (x.0).3 = new_pos, @@ -1364,7 +1380,7 @@ impl Expr { pub(crate) fn into_property(self) -> Self { match self { Self::Variable(x) if x.1.is_none() => { - let (name, pos) = x.0; + let Ident { name, pos } = x.0; let getter = make_getter(&name); let setter = make_setter(&name); Self::Property(Box::new(((name.into(), getter, setter), pos))) @@ -1622,7 +1638,7 @@ fn parse_index_chain( return Err(PERR::MalformedIndexExpr( "Array or string expects numeric index, not a string".into(), ) - .into_err(x.1)) + .into_err(x.pos)) } #[cfg(not(feature = "no_float"))] @@ -1872,7 +1888,7 @@ fn parse_map_literal( } let expr = parse_expr(input, state, lib, settings.level_up())?; - map.push(((Into::::into(name), pos), expr)); + map.push((IdentX::new(name, pos), expr)); match input.peek().unwrap() { (Token::Comma, _) => { @@ -1899,11 +1915,11 @@ fn parse_map_literal( // Check for duplicating properties map.iter() .enumerate() - .try_for_each(|(i, ((k1, _), _))| { + .try_for_each(|(i, (IdentX { name: k1, .. }, _))| { map.iter() .skip(i + 1) - .find(|((k2, _), _)| k2 == k1) - .map_or_else(|| Ok(()), |((k2, pos), _)| Err((k2, *pos))) + .find(|(IdentX { name: k2, .. }, _)| k2 == k1) + .map_or_else(|| Ok(()), |(IdentX { name: k2, pos }, _)| Err((k2, *pos))) }) .map_err(|(key, pos)| PERR::DuplicatedProperty(key.to_string()).into_err(pos))?; @@ -1940,7 +1956,7 @@ fn parse_primary( #[cfg(not(feature = "no_float"))] Token::FloatConstant(x) => Expr::FloatConstant(Box::new(FloatWrapper(x, settings.pos))), Token::CharConstant(c) => Expr::CharConstant(Box::new((c, settings.pos))), - Token::StringConstant(s) => Expr::StringConstant(Box::new((s.into(), settings.pos))), + Token::StringConstant(s) => Expr::StringConstant(Box::new(IdentX::new(s, settings.pos))), // Function call Token::Identifier(s) if *next_token == Token::LeftParen || *next_token == Token::Bang => { @@ -1949,7 +1965,7 @@ fn parse_primary( { state.allow_capture = true; } - Expr::Variable(Box::new(((s, settings.pos), None, 0, None))) + Expr::Variable(Box::new((Ident::new(s, settings.pos), None, 0, None))) } // Module qualification #[cfg(not(feature = "no_module"))] @@ -1959,18 +1975,18 @@ fn parse_primary( { state.allow_capture = true; } - Expr::Variable(Box::new(((s, settings.pos), None, 0, None))) + Expr::Variable(Box::new((Ident::new(s, settings.pos), None, 0, None))) } // Normal variable access Token::Identifier(s) => { let index = state.access_var(&s, settings.pos); - Expr::Variable(Box::new(((s, settings.pos), None, 0, index))) + Expr::Variable(Box::new((Ident::new(s, settings.pos), None, 0, index))) } // Function call is allowed to have reserved keyword Token::Reserved(s) if *next_token == Token::LeftParen || *next_token == Token::Bang => { if is_keyword_function(&s) { - Expr::Variable(Box::new(((s, settings.pos), None, 0, None))) + Expr::Variable(Box::new((Ident::new(s, settings.pos), None, 0, None))) } else { return Err(PERR::Reserved(s).into_err(settings.pos)); } @@ -1984,7 +2000,7 @@ fn parse_primary( .into_err(settings.pos), ); } else { - Expr::Variable(Box::new(((s, settings.pos), None, 0, None))) + Expr::Variable(Box::new((Ident::new(s, settings.pos), None, 0, None))) } } @@ -2040,13 +2056,13 @@ fn parse_primary( .into_err(pos)); } - let ((name, pos), modules, _, _) = *x; + let (Ident { name, pos }, modules, _, _) = *x; settings.pos = pos; parse_fn_call(input, state, lib, name, true, modules, settings.level_up())? } // Function call (Expr::Variable(x), Token::LeftParen) => { - let ((name, pos), modules, _, _) = *x; + let (Ident { name, pos }, modules, _, _) = *x; settings.pos = pos; parse_fn_call(input, state, lib, name, false, modules, settings.level_up())? } @@ -2054,7 +2070,7 @@ fn parse_primary( // module access (Expr::Variable(x), Token::DoubleColon) => match input.next().unwrap() { (Token::Identifier(id2), pos2) => { - let ((name, pos), mut modules, _, index) = *x; + let (Ident { name, pos }, mut modules, _, index) = *x; if let Some(ref mut modules) = modules { modules.push((name, pos)); @@ -2064,7 +2080,7 @@ fn parse_primary( modules = Some(Box::new(m)); } - Expr::Variable(Box::new(((id2, pos2), modules, 0, index))) + Expr::Variable(Box::new((Ident::new(id2, pos2), modules, 0, index))) } (Token::Reserved(id2), pos2) if is_valid_identifier(id2.chars()) => { return Err(PERR::Reserved(id2).into_err(pos2)); @@ -2088,7 +2104,7 @@ fn parse_primary( match &mut root_expr { // Cache the hash key for module-qualified variables Expr::Variable(x) if x.1.is_some() => { - let ((name, _), modules, hash, _) = x.as_mut(); + let (Ident { name, .. }, modules, hash, _) = x.as_mut(); let modules = modules.as_mut().unwrap(); // Qualifiers + variable name @@ -2249,7 +2265,15 @@ fn make_assignment_stmt<'a>( } // var (indexed) = rhs Expr::Variable(x) => { - let ((name, name_pos), _, _, index) = x.as_ref(); + let ( + Ident { + name, + pos: name_pos, + }, + _, + _, + index, + ) = x.as_ref(); match state.stack[(state.stack.len() - index.unwrap().get())].1 { ScopeEntryType::Normal => { Ok(Stmt::Assignment(Box::new((lhs, fn_name.into(), rhs)), pos)) @@ -2268,7 +2292,15 @@ fn make_assignment_stmt<'a>( } // var[???] (indexed) = rhs, var.??? (indexed) = rhs Expr::Variable(x) => { - let ((name, name_pos), _, _, index) = x.as_ref(); + let ( + Ident { + name, + pos: name_pos, + }, + _, + _, + index, + ) = x.as_ref(); match state.stack[(state.stack.len() - index.unwrap().get())].1 { ScopeEntryType::Normal => { Ok(Stmt::Assignment(Box::new((lhs, fn_name.into(), rhs)), pos)) @@ -2344,7 +2376,7 @@ fn make_dot_expr(lhs: Expr, rhs: Expr, op_pos: Position) -> Result { - let (name, pos) = x.0; + let Ident { name, pos } = x.0; let getter = make_getter(&name); let setter = make_setter(&name); @@ -2758,7 +2790,12 @@ fn parse_custom_syntax( MARKER_IDENT => match input.next().unwrap() { (Token::Identifier(s), pos) => { segments.push(s.clone()); - exprs.push(Expr::Variable(Box::new(((s, pos), None, 0, None)))); + exprs.push(Expr::Variable(Box::new(( + Ident::new(s, pos), + None, + 0, + None, + )))); } (Token::Reserved(s), pos) if is_valid_identifier(s.chars()) => { return Err(PERR::Reserved(s).into_err(pos)); @@ -3051,12 +3088,20 @@ fn parse_let( // let name = expr ScopeEntryType::Normal => { state.stack.push((name.clone(), ScopeEntryType::Normal)); - Ok(Stmt::Let(Box::new((name, pos)), init_value, token_pos)) + Ok(Stmt::Let( + Box::new(Ident::new(name, pos)), + init_value, + token_pos, + )) } // const name = { expr:constant } ScopeEntryType::Constant => { state.stack.push((name.clone(), ScopeEntryType::Constant)); - Ok(Stmt::Const(Box::new((name, pos)), init_value, token_pos)) + Ok(Stmt::Const( + Box::new(Ident::new(name, pos)), + init_value, + token_pos, + )) } } } @@ -3098,7 +3143,7 @@ fn parse_import( Ok(Stmt::Import( expr, - Some(Box::new((name.into(), settings.pos))), + Some(Box::new(IdentX::new(name, settings.pos))), token_pos, )) } @@ -3131,7 +3176,7 @@ fn parse_export( let rename = if match_token(input, Token::As).0 { match input.next().unwrap() { - (Token::Identifier(s), pos) => Some((s.clone(), pos)), + (Token::Identifier(s), pos) => Some(Ident::new(s.clone(), pos)), (Token::Reserved(s), pos) if is_valid_identifier(s.chars()) => { return Err(PERR::Reserved(s).into_err(pos)); } @@ -3142,7 +3187,7 @@ fn parse_export( None }; - exports.push(((id, id_pos), rename)); + exports.push((Ident::new(id, id_pos), rename)); match input.peek().unwrap() { (Token::Comma, _) => { @@ -3163,12 +3208,12 @@ fn parse_export( exports .iter() .enumerate() - .try_for_each(|(i, ((id1, _), _))| { + .try_for_each(|(i, (Ident { name: id1, .. }, _))| { exports .iter() .skip(i + 1) - .find(|((id2, _), _)| id2 == id1) - .map_or_else(|| Ok(()), |((id2, pos), _)| Err((id2, *pos))) + .find(|(Ident { name: id2, .. }, _)| id2 == id1) + .map_or_else(|| Ok(()), |(Ident { name: id2, pos }, _)| Err((id2, *pos))) }) .map_err(|(id2, pos)| PERR::DuplicatedExport(id2.to_string()).into_err(pos))?; @@ -3451,7 +3496,7 @@ fn parse_try_catch( // try { body } catch ( let var_def = if match_token(input, Token::LeftParen).0 { let id = match input.next().unwrap() { - (Token::Identifier(s), pos) => (s, pos), + (Token::Identifier(s), pos) => Ident::new(s, pos), (_, pos) => return Err(PERR::VariableExpected.into_err(pos)), }; @@ -3474,9 +3519,10 @@ fn parse_try_catch( let catch_body = parse_block(input, state, lib, settings.level_up())?; Ok(Stmt::TryCatch(Box::new(( - (body, token_pos), + body, var_def, - (catch_body, catch_pos), + catch_body, + (token_pos, catch_pos), )))) } @@ -3588,11 +3634,7 @@ fn parse_fn( /// Creates a curried expression from a list of external variables #[cfg(not(feature = "no_function"))] -fn make_curry_from_externals( - fn_expr: Expr, - externals: StaticVec<(String, Position)>, - pos: Position, -) -> Expr { +fn make_curry_from_externals(fn_expr: Expr, externals: StaticVec, pos: Position) -> Expr { if externals.is_empty() { return fn_expr; } @@ -3603,13 +3645,8 @@ fn make_curry_from_externals( args.push(fn_expr); #[cfg(not(feature = "no_closure"))] - externals.iter().for_each(|(var_name, pos)| { - args.push(Expr::Variable(Box::new(( - (var_name.into(), *pos), - None, - 0, - None, - )))); + externals.iter().for_each(|x| { + args.push(Expr::Variable(Box::new((x.clone(), None, 0, None)))); }); #[cfg(feature = "no_closure")] @@ -3634,11 +3671,7 @@ fn make_curry_from_externals( // Statement block let mut statements: Vec<_> = Default::default(); // Insert `Share` statements - statements.extend( - externals - .into_iter() - .map(|(var_name, pos)| Stmt::Share(var_name, pos)), - ); + statements.extend(externals.into_iter().map(Stmt::Share)); // Final expression statements.push(Stmt::Expr(expr)); Expr::Stmt(Box::new((Stmt::Block(statements, pos), pos))) @@ -3723,7 +3756,7 @@ fn parse_anon_fn( state .externals .iter() - .map(|(k, &v)| (k.clone(), v)) + .map(|(k, &v)| Ident::new(k.clone(), v)) .collect() } #[cfg(feature = "no_closure")] @@ -3733,8 +3766,7 @@ fn parse_anon_fn( let params: StaticVec<_> = if cfg!(not(feature = "no_closure")) { externals .iter() - .map(|(k, _)| k) - .cloned() + .map(|k| k.name.clone()) .chain(params.into_iter().map(|(v, _)| v)) .collect() } else { @@ -3767,7 +3799,7 @@ fn parse_anon_fn( lib: None, }; - let expr = Expr::FnPointer(Box::new((fn_name, settings.pos))); + let expr = Expr::FnPointer(Box::new(IdentX::new(fn_name, settings.pos))); let expr = if cfg!(not(feature = "no_closure")) { make_curry_from_externals(expr, externals, settings.pos) @@ -3920,7 +3952,7 @@ pub fn map_dynamic_to_expr(value: Dynamic, pos: Position) -> Option { Union::Unit(_) => Some(Expr::Unit(pos)), Union::Int(value) => Some(Expr::IntegerConstant(Box::new((value, pos)))), Union::Char(value) => Some(Expr::CharConstant(Box::new((value, pos)))), - Union::Str(value) => Some(Expr::StringConstant(Box::new((value, pos)))), + Union::Str(value) => Some(Expr::StringConstant(Box::new(IdentX::new(value, pos)))), Union::Bool(true) => Some(Expr::True(pos)), Union::Bool(false) => Some(Expr::False(pos)), #[cfg(not(feature = "no_index"))] @@ -3943,14 +3975,14 @@ pub fn map_dynamic_to_expr(value: Dynamic, pos: Position) -> Option { Union::Map(map) => { let items: Vec<_> = map .into_iter() - .map(|(k, v)| ((k, pos), map_dynamic_to_expr(v, pos))) + .map(|(k, v)| (IdentX::new(k, pos), map_dynamic_to_expr(v, pos))) .collect(); if items.iter().all(|(_, expr)| expr.is_some()) { Some(Expr::Map(Box::new(( items .into_iter() - .map(|((k, pos), expr)| ((k, pos), expr.unwrap())) + .map(|(k, expr)| (k, expr.unwrap())) .collect(), pos, )))) diff --git a/src/unsafe.rs b/src/unsafe.rs index da766950..2442459f 100644 --- a/src/unsafe.rs +++ b/src/unsafe.rs @@ -1,14 +1,11 @@ //! A helper module containing unsafe utility functions. use crate::any::Variant; -use crate::engine::State; use crate::stdlib::{ any::{Any, TypeId}, - borrow::Cow, boxed::Box, mem, ptr, - string::ToString, }; /// Cast a type into another type. @@ -46,7 +43,7 @@ pub fn unsafe_cast_box(item: Box) -> Result, B /// # DANGEROUS!!! /// -/// A dangerous function that blindly casts a `&str` from one lifetime to a `Cow` of +/// A dangerous function that blindly casts a `&str` from one lifetime to a `&str` of /// another lifetime. This is mainly used to let us push a block-local variable into the /// current `Scope` without cloning the variable name. Doing this is safe because all local /// variables in the `Scope` are cleared out before existing the block. @@ -54,15 +51,8 @@ pub fn unsafe_cast_box(item: Box) -> Result, B /// Force-casting a local variable's lifetime to the current `Scope`'s larger lifetime saves /// on allocations and string cloning, thus avoids us having to maintain a chain of `Scope`'s. #[inline] -pub fn unsafe_cast_var_name_to_lifetime<'s>(name: &str, state: &State) -> Cow<'s, str> { - // If not at global level, we can force-cast - if state.scope_level > 0 { - // WARNING - force-cast the variable name into the scope's lifetime to avoid cloning it - // this is safe because all local variables are cleared at the end of the block - unsafe { mem::transmute::<_, &'s str>(name) }.into() - } else { - // The variable is introduced at global (top) level and may persist after the script run. - // Therefore, clone the variable name. - name.to_string().into() - } +pub fn unsafe_cast_var_name_to_lifetime<'s>(name: &str) -> &'s str { + // WARNING - force-cast the variable name into the scope's lifetime to avoid cloning it + // this is safe because all local variables are cleared at the end of the block + unsafe { mem::transmute(name) } }