diff --git a/src/ast.rs b/src/ast.rs index 6335ba31..b3eda699 100644 --- a/src/ast.rs +++ b/src/ast.rs @@ -9,7 +9,7 @@ use crate::stdlib::{ fmt, hash::Hash, iter::empty, - num::NonZeroUsize, + num::{NonZeroU8, NonZeroUsize}, ops::{Add, AddAssign}, string::String, vec, @@ -1574,8 +1574,15 @@ pub enum Expr { ), /// () Unit(Position), - /// Variable access - (optional index, optional (hash, modules), variable name) - Variable(Box<(Option, Option<(u64, NamespaceRef)>, Ident)>), + /// Variable access - optional short index, (optional index, optional (hash, modules), variable name) + /// + /// The short index is [`u8`] which is used when the index is <= 255, which should be the vast + /// majority of cases (unless there are more than 255 variables defined!). + /// This is to avoid reading a pointer redirection during each variable access. + Variable( + Option, + Box<(Option, Option<(u64, NamespaceRef)>, Ident)>, + ), /// Property access - ((getter, hash), (setter, hash), prop) Property(Box<((Identifier, u64), (Identifier, u64), Ident)>), /// { [statement][Stmt] ... } @@ -1644,7 +1651,7 @@ impl Expr { #[inline(always)] 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.2).name.as_str()), + Self::Variable(_, x) if !non_qualified || x.1.is_none() => Some((x.2).name.as_str()), _ => None, } } @@ -1666,7 +1673,7 @@ impl Expr { Self::Map(_, pos) => *pos, Self::Property(x) => (x.2).pos, Self::Stmt(x) => x.pos, - Self::Variable(x) => (x.2).pos, + Self::Variable(_, x) => (x.2).pos, Self::FnCall(_, pos) => *pos, Self::And(x, _) | Self::Or(x, _) => x.lhs.position(), @@ -1696,7 +1703,7 @@ impl Expr { Self::FnPointer(_, pos) => *pos = new_pos, Self::Array(_, pos) => *pos = new_pos, Self::Map(_, pos) => *pos = new_pos, - Self::Variable(x) => (x.2).pos = new_pos, + Self::Variable(_, x) => (x.2).pos = new_pos, Self::Property(x) => (x.2).pos = new_pos, Self::Stmt(x) => x.pos = new_pos, Self::FnCall(_, pos) => *pos = new_pos, @@ -1724,7 +1731,7 @@ impl Expr { Self::Stmt(x) => x.statements.iter().all(Stmt::is_pure), - Self::Variable(_) => true, + Self::Variable(_, _) => true, _ => self.is_constant(), } @@ -1794,7 +1801,7 @@ impl Expr { _ => false, }, - Self::Variable(_) => match token { + Self::Variable(_, _) => match token { #[cfg(not(feature = "no_index"))] Token::LeftBracket => true, Token::LeftParen => true, diff --git a/src/engine.rs b/src/engine.rs index 21400e02..aeb0c16b 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -959,7 +959,12 @@ impl Engine { expr: &Expr, ) -> Result<(Target<'s>, Position), Box> { match expr { - Expr::Variable(v) => match v.as_ref() { + Expr::Variable(Some(_), _) => { + self.search_scope_only(scope, mods, state, lib, this_ptr, expr) + } + Expr::Variable(None, v) => match v.as_ref() { + // Normal variable access + (_, None, _) => self.search_scope_only(scope, mods, state, lib, this_ptr, expr), // Qualified variable (_, Some((hash_var, modules)), Ident { name, pos, .. }) => { let module = self.search_imports(mods, state, modules).ok_or_else(|| { @@ -983,8 +988,6 @@ impl Engine { target.set_access_mode(AccessMode::ReadOnly); Ok((target.into(), *pos)) } - // Normal variable access - _ => self.search_scope_only(scope, mods, state, lib, this_ptr, expr), }, _ => unreachable!("Expr::Variable expected, but gets {:?}", expr), } @@ -1000,8 +1003,8 @@ impl Engine { this_ptr: &'s mut Option<&mut Dynamic>, expr: &Expr, ) -> Result<(Target<'s>, Position), Box> { - let (index, _, Ident { name, pos, .. }) = match expr { - Expr::Variable(v) => v.as_ref(), + let (short_index, (index, _, Ident { name, pos, .. })) = match expr { + Expr::Variable(i, v) => (i, v.as_ref()), _ => unreachable!("Expr::Variable expected, but gets {:?}", expr), }; @@ -1015,11 +1018,17 @@ impl Engine { } // Check if it is directly indexed - let index = if state.always_search { &None } else { index }; + let index = if state.always_search { + 0 + } else { + short_index.map_or_else( + || index.map(NonZeroUsize::get).unwrap_or(0), + |x| x.get() as usize, + ) + }; // Check the variable resolver, if any if let Some(ref resolve_var) = self.resolve_var { - let index = index.map(NonZeroUsize::get).unwrap_or(0); let context = EvalContext { engine: self, scope, @@ -1037,8 +1046,8 @@ impl Engine { } } - let index = if let Some(index) = index { - scope.len() - index.get() + let index = if index > 0 { + scope.len() - index } else { // Find the variable in the scope scope @@ -1401,7 +1410,7 @@ impl Engine { match lhs { // id.??? or id[???] - Expr::Variable(x) => { + Expr::Variable(_, x) => { let Ident { name: var_name, pos: var_pos, @@ -1679,11 +1688,11 @@ impl Engine { Expr::CharConstant(x, _) => Ok((*x).into()), Expr::FnPointer(x, _) => Ok(FnPtr::new_unchecked(x.clone(), Default::default()).into()), - Expr::Variable(x) if (x.2).name == KEYWORD_THIS => this_ptr + Expr::Variable(None, x) if x.0.is_none() && (x.2).name == KEYWORD_THIS => this_ptr .as_deref() .cloned() .ok_or_else(|| EvalAltResult::ErrorUnboundThis((x.2).pos).into()), - Expr::Variable(_) => self + Expr::Variable(_, _) => self .search_namespace(scope, mods, state, lib, this_ptr, expr) .map(|(val, _)| val.take_or_clone()), @@ -2061,7 +2070,7 @@ impl Engine { // Must be either `var[index] op= val` or `var.prop op= val` match lhs_expr { // name op= rhs (handled above) - Expr::Variable(_) => { + Expr::Variable(_, _) => { unreachable!("Expr::Variable case should already been handled") } // idx_lhs[idx_expr] op= rhs diff --git a/src/fn_call.rs b/src/fn_call.rs index f323632b..ccf67349 100644 --- a/src/fn_call.rs +++ b/src/fn_call.rs @@ -750,7 +750,7 @@ impl Engine { // Method call of script function - map first argument to `this` let (first, rest) = args.split_first_mut().unwrap(); - let orig_source = mem::take(&mut state.source); + let orig_source = state.source.take(); state.source = source; let level = _level + 1; @@ -780,7 +780,7 @@ impl Engine { backup.as_mut().unwrap().change_first_arg_to_copy(args); } - let orig_source = mem::take(&mut state.source); + let orig_source = state.source.take(); state.source = source; let level = _level + 1; diff --git a/src/optimize.rs b/src/optimize.rs index 6d8edc6a..796af57f 100644 --- a/src/optimize.rs +++ b/src/optimize.rs @@ -385,7 +385,7 @@ fn optimize_stmt(stmt: &mut Stmt, state: &mut State, preserve_result: bool) { match stmt { // expr op= expr Stmt::Assignment(x, _) => match x.0 { - Expr::Variable(_) => optimize_expr(&mut x.2, state), + Expr::Variable(_, _) => optimize_expr(&mut x.2, state), _ => { optimize_expr(&mut x.0, state); optimize_expr(&mut x.2, state); @@ -635,7 +635,7 @@ fn optimize_expr(expr: &mut Expr, state: &mut State) { .unwrap_or_else(|| Expr::Unit(*pos)); } // var.rhs - (Expr::Variable(_), rhs) => optimize_expr(rhs, state), + (Expr::Variable(_, _), rhs) => optimize_expr(rhs, state), // lhs.rhs (lhs, rhs) => { optimize_expr(lhs, state); optimize_expr(rhs, state); } } @@ -670,7 +670,7 @@ fn optimize_expr(expr: &mut Expr, state: &mut State) { *expr = Expr::CharConstant(s.chars().nth(*i as usize).unwrap(), *pos); } // var[rhs] - (Expr::Variable(_), rhs) => optimize_expr(rhs, state), + (Expr::Variable(_, _), rhs) => optimize_expr(rhs, state), // lhs[rhs] (lhs, rhs) => { optimize_expr(lhs, state); optimize_expr(rhs, state); } }, @@ -901,7 +901,7 @@ fn optimize_expr(expr: &mut Expr, state: &mut State) { } // constant-name - Expr::Variable(x) if x.1.is_none() && state.find_constant(&x.2.name).is_some() => { + Expr::Variable(_, x) if x.1.is_none() && state.find_constant(&x.2.name).is_some() => { state.set_dirty(); // Replace constant with value diff --git a/src/parser.rs b/src/parser.rs index 1ba1b130..f1db5fd8 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -15,7 +15,7 @@ use crate::stdlib::{ format, hash::{Hash, Hasher}, iter::empty, - num::NonZeroUsize, + num::{NonZeroU8, NonZeroUsize}, string::{String, ToString}, vec, vec::Vec, @@ -225,7 +225,7 @@ impl Expr { #[inline(always)] fn into_property(self, state: &mut ParseState) -> Self { match self { - Self::Variable(x) if x.1.is_none() => { + Self::Variable(_, x) if x.1.is_none() => { let ident = x.2; let getter = state.get_identifier(crate::engine::make_getter(&ident.name)); let hash_get = calc_fn_hash(empty(), &getter, 1); @@ -1081,7 +1081,7 @@ fn parse_primary( name: state.get_identifier(s), pos: settings.pos, }; - Expr::Variable(Box::new((None, None, var_name_def))) + Expr::Variable(None, Box::new((None, None, var_name_def))) } // Namespace qualification #[cfg(not(feature = "no_module"))] @@ -1095,7 +1095,7 @@ fn parse_primary( name: state.get_identifier(s), pos: settings.pos, }; - Expr::Variable(Box::new((None, None, var_name_def))) + Expr::Variable(None, Box::new((None, None, var_name_def))) } // Normal variable access _ => { @@ -1104,7 +1104,14 @@ fn parse_primary( name: state.get_identifier(s), pos: settings.pos, }; - Expr::Variable(Box::new((index, None, var_name_def))) + let short_index = index.and_then(|x| { + if x.get() <= u8::MAX as usize { + NonZeroU8::new(x.get() as u8) + } else { + None + } + }); + Expr::Variable(short_index, Box::new((index, None, var_name_def))) } } } @@ -1123,7 +1130,7 @@ fn parse_primary( name: state.get_identifier(s), pos: settings.pos, }; - Expr::Variable(Box::new((None, None, var_name_def))) + Expr::Variable(None, Box::new((None, None, var_name_def))) } // Access to `this` as a variable is OK within a function scope _ if s == KEYWORD_THIS && settings.is_function_scope => { @@ -1131,7 +1138,7 @@ fn parse_primary( name: state.get_identifier(s), pos: settings.pos, }; - Expr::Variable(Box::new((None, None, var_name_def))) + Expr::Variable(None, Box::new((None, None, var_name_def))) } // Cannot access to `this` as a variable not in a function scope _ if s == KEYWORD_THIS => { @@ -1168,7 +1175,7 @@ fn parse_primary( root_expr = match (root_expr, tail_token) { // Qualified function call with ! - (Expr::Variable(x), Token::Bang) if x.1.is_some() => { + (Expr::Variable(_, x), Token::Bang) if x.1.is_some() => { return Err(if !match_token(input, Token::LeftParen).0 { LexError::UnexpectedInput(Token::Bang.syntax().to_string()).into_err(tail_pos) } else { @@ -1180,7 +1187,7 @@ fn parse_primary( }); } // Function call with ! - (Expr::Variable(x), Token::Bang) => { + (Expr::Variable(_, x), Token::Bang) => { let (matched, pos) = match_token(input, Token::LeftParen); if !matched { return Err(PERR::MissingToken( @@ -1196,31 +1203,30 @@ fn parse_primary( parse_fn_call(input, state, lib, name, true, ns, settings.level_up())? } // Function call - (Expr::Variable(x), Token::LeftParen) => { + (Expr::Variable(_, x), Token::LeftParen) => { let (_, namespace, Ident { name, pos, .. }) = *x; settings.pos = pos; let ns = namespace.map(|(_, ns)| ns); parse_fn_call(input, state, lib, name, false, ns, settings.level_up())? } // module access - (Expr::Variable(x), Token::DoubleColon) => match input.next().unwrap() { + (Expr::Variable(_, x), Token::DoubleColon) => match input.next().unwrap() { (Token::Identifier(id2), pos2) => { - let (index, mut namespace, var_name_def) = *x; + let (_, mut namespace, var_name_def) = *x; if let Some((_, ref mut namespace)) = namespace { namespace.push(var_name_def); } else { let mut ns: NamespaceRef = Default::default(); ns.push(var_name_def); - let index = 42; // Dummy - namespace = Some((index, ns)); + namespace = Some((42, ns)); } let var_name_def = Ident { name: state.get_identifier(id2), pos: pos2, }; - Expr::Variable(Box::new((index, namespace, var_name_def))) + Expr::Variable(None, Box::new((None, namespace, var_name_def))) } (Token::Reserved(id2), pos2) if is_valid_identifier(id2.chars()) => { return Err(PERR::Reserved(id2).into_err(pos2)); @@ -1263,9 +1269,9 @@ fn parse_primary( // Cache the hash key for namespace-qualified variables match &mut root_expr { - Expr::Variable(x) if x.1.is_some() => Some(x), + Expr::Variable(_, x) if x.1.is_some() => Some(x), Expr::Index(x, _) | Expr::Dot(x, _) => match &mut x.lhs { - Expr::Variable(x) if x.1.is_some() => Some(x), + Expr::Variable(_, x) if x.1.is_some() => Some(x), _ => None, }, _ => None, @@ -1423,13 +1429,14 @@ fn make_assignment_stmt<'a>( Err(PERR::AssignmentToConstant("".into()).into_err(lhs.position())) } // var (non-indexed) = rhs - Expr::Variable(x) if x.0.is_none() => { + Expr::Variable(None, x) if x.0.is_none() => { Ok(Stmt::Assignment(Box::new((lhs, op_info, rhs)), op_pos)) } // var (indexed) = rhs - Expr::Variable(x) => { + Expr::Variable(i, x) => { let (index, _, Ident { name, pos, .. }) = x.as_ref(); - match state.stack[(state.stack.len() - index.unwrap().get())].1 { + let index = i.map_or_else(|| index.unwrap().get(), |n| n.get() as usize); + match state.stack[state.stack.len() - index].1 { AccessMode::ReadWrite => { Ok(Stmt::Assignment(Box::new((lhs, op_info, rhs)), op_pos)) } @@ -1444,13 +1451,14 @@ fn make_assignment_stmt<'a>( match check_lvalue(&x.rhs, matches!(lhs, Expr::Dot(_, _))) { Position::NONE => match &x.lhs { // var[???] (non-indexed) = rhs, var.??? (non-indexed) = rhs - Expr::Variable(x) if x.0.is_none() => { + Expr::Variable(None, x) if x.0.is_none() => { Ok(Stmt::Assignment(Box::new((lhs, op_info, rhs)), op_pos)) } // var[???] (indexed) = rhs, var.??? (indexed) = rhs - Expr::Variable(x) => { + Expr::Variable(i, x) => { let (index, _, Ident { name, pos, .. }) = x.as_ref(); - match state.stack[(state.stack.len() - index.unwrap().get())].1 { + let index = i.map_or_else(|| index.unwrap().get(), |n| n.get() as usize); + match state.stack[state.stack.len() - index].1 { AccessMode::ReadWrite => { Ok(Stmt::Assignment(Box::new((lhs, op_info, rhs)), op_pos)) } @@ -1532,7 +1540,7 @@ fn make_dot_expr( Expr::Index(x, pos) } // lhs.id - (lhs, Expr::Variable(x)) if x.1.is_none() => { + (lhs, Expr::Variable(_, x)) if x.1.is_none() => { let ident = x.2; let getter = state.get_identifier(crate::engine::make_getter(&ident.name)); let hash_get = calc_fn_hash(empty(), &getter, 1); @@ -1544,7 +1552,7 @@ fn make_dot_expr( Expr::Dot(Box::new(BinaryExpr { lhs, rhs }), op_pos) } // lhs.module::id - syntax error - (_, Expr::Variable(x)) if x.1.is_some() => { + (_, Expr::Variable(_, x)) if x.1.is_some() => { return Err(PERR::PropertyExpected.into_err(x.1.unwrap().1[0].pos)) } // lhs.prop @@ -1553,7 +1561,7 @@ fn make_dot_expr( } // lhs.dot_lhs.dot_rhs (lhs, Expr::Dot(x, pos)) => match x.lhs { - Expr::Variable(_) | Expr::Property(_) => { + Expr::Variable(_, _) | Expr::Property(_) => { let rhs = Expr::Dot( Box::new(BinaryExpr { lhs: x.lhs.into_property(state), @@ -1869,7 +1877,7 @@ fn parse_custom_syntax( segments.push(name.clone().into()); tokens.push(state.get_identifier(MARKER_IDENT)); let var_name_def = Ident { name, pos }; - keywords.push(Expr::Variable(Box::new((None, None, var_name_def)))); + keywords.push(Expr::Variable(None, Box::new((None, None, var_name_def)))); } (Token::Reserved(s), pos) if is_valid_identifier(s.chars()) => { return Err(PERR::Reserved(s).into_err(pos)); @@ -2825,7 +2833,7 @@ fn make_curry_from_externals( name: x.clone(), pos: Position::NONE, }; - args.push(Expr::Variable(Box::new((None, None, var_def)))); + args.push(Expr::Variable(None, Box::new((None, None, var_def)))); }); let expr = Expr::FnCall( diff --git a/src/token.rs b/src/token.rs index 389ad8f7..9d0884c0 100644 --- a/src/token.rs +++ b/src/token.rs @@ -9,7 +9,6 @@ use crate::stdlib::{ cell::Cell, char, fmt, format, iter::{FusedIterator, Peekable}, - mem, num::NonZeroUsize, ops::{Add, AddAssign}, rc::Rc, @@ -828,7 +827,7 @@ impl From for String { /// This type is volatile and may change. #[derive(Debug, Clone, Eq, PartialEq, Default)] pub struct TokenizeState { - /// Maximum length of a string (0 = unlimited). + /// Maximum length of a string. pub max_string_size: Option, /// Can the next token be a unary operator? pub non_unary: bool, @@ -1187,7 +1186,7 @@ fn get_next_token_inner( } // Within text? - if let Some(ch) = mem::take(&mut state.is_within_text_terminated_by) { + if let Some(ch) = state.is_within_text_terminated_by.take() { let start_pos = *pos; return parse_string_literal(stream, state, pos, ch, false, true, true, true).map_or_else( @@ -1316,42 +1315,42 @@ fn get_next_token_inner( } // Parse number - if let Some(radix) = radix_base { - let out: String = result.iter().skip(2).filter(|&&c| c != NUM_SEP).collect(); + return Some(( + if let Some(radix) = radix_base { + let out: String = + result.iter().skip(2).filter(|&&c| c != NUM_SEP).collect(); - return Some(( INT::from_str_radix(&out, radix) .map(Token::IntegerConstant) .unwrap_or_else(|_| { Token::LexError(LERR::MalformedNumber(result.into_iter().collect())) - }), - start_pos, - )); - } else { - let out: String = result.iter().filter(|&&c| c != NUM_SEP).collect(); - let num = INT::from_str(&out).map(Token::IntegerConstant); + }) + } else { + let out: String = result.iter().filter(|&&c| c != NUM_SEP).collect(); + let num = INT::from_str(&out).map(Token::IntegerConstant); - // If integer parsing is unnecessary, try float instead - #[cfg(not(feature = "no_float"))] - let num = - num.or_else(|_| FloatWrapper::from_str(&out).map(Token::FloatConstant)); + // If integer parsing is unnecessary, try float instead + #[cfg(not(feature = "no_float"))] + let num = + num.or_else(|_| FloatWrapper::from_str(&out).map(Token::FloatConstant)); - // Then try decimal - #[cfg(feature = "decimal")] - let num = num.or_else(|_| Decimal::from_str(&out).map(Token::DecimalConstant)); + // Then try decimal + #[cfg(feature = "decimal")] + let num = + num.or_else(|_| Decimal::from_str(&out).map(Token::DecimalConstant)); - // Then try decimal in scientific notation - #[cfg(feature = "decimal")] - let num = - num.or_else(|_| Decimal::from_scientific(&out).map(Token::DecimalConstant)); + // Then try decimal in scientific notation + #[cfg(feature = "decimal")] + let num = num.or_else(|_| { + Decimal::from_scientific(&out).map(Token::DecimalConstant) + }); - return Some(( num.unwrap_or_else(|_| { Token::LexError(LERR::MalformedNumber(result.into_iter().collect())) - }), - start_pos, - )); - } + }) + }, + start_pos, + )); } // letter or underscore ...