diff --git a/src/engine.rs b/src/engine.rs index 85d5a031..9c0ed804 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -189,7 +189,7 @@ impl StaticVec { } /// A type that holds all the current states of the Engine. -#[derive(Debug, Clone, Hash, Copy)] +#[derive(Debug, Clone, Copy)] pub struct State { /// Normally, access to variables are parsed with a relative offset into the scope to avoid a lookup. /// In some situation, e.g. after running an `eval` statement, subsequent offsets may become mis-aligned. @@ -198,6 +198,7 @@ pub struct State { } impl State { + /// Create a new `State`. pub fn new() -> Self { Self { always_search: false, @@ -914,10 +915,9 @@ impl Engine { match dot_lhs { // id.??? or id[???] Expr::Variable(id, index, pos) => { - let (target, typ) = if !state.always_search && *index > 0 { - scope.get_mut(scope.len() - *index) - } else { - search_scope(scope, id, *pos)? + let (target, typ) = match index { + Some(i) if !state.always_search => scope.get_mut(scope.len() - i.get()), + _ => search_scope(scope, id, *pos)?, }; // Constants cannot be modified @@ -1156,8 +1156,8 @@ impl Engine { Expr::FloatConstant(f, _) => Ok((*f).into()), Expr::StringConstant(s, _) => Ok(s.to_string().into()), Expr::CharConstant(c, _) => Ok((*c).into()), - Expr::Variable(_, index, _) if !state.always_search && *index > 0 => { - Ok(scope.get_mut(scope.len() - *index).0.clone()) + Expr::Variable(_, Some(index), _) if !state.always_search => { + Ok(scope.get_mut(scope.len() - index.get()).0.clone()) } Expr::Variable(id, _, pos) => search_scope(scope, id, *pos).map(|(v, _)| v.clone()), Expr::Property(_, _) => panic!("unexpected property."), @@ -1272,13 +1272,17 @@ impl Engine { && args.len() == 1 && !self.has_override(fn_lib, KEYWORD_EVAL) { + let prev_len = scope.len(); + // Evaluate the text string as a script let result = self.eval_script_expr(scope, fn_lib, args[0], arg_exprs[0].position()); - // IMPORTANT! The eval may define new variables in the current scope! - // We can no longer trust the variable offsets. - state.always_search = true; + if scope.len() != prev_len { + // IMPORTANT! If the eval defines new variables in the current scope, + // all variable offsets from this point on will be mis-aligned. + state.always_search = true; + } return result; } diff --git a/src/optimize.rs b/src/optimize.rs index 1514b36c..a63075a3 100644 --- a/src/optimize.rs +++ b/src/optimize.rs @@ -729,10 +729,10 @@ pub fn optimize_into_ast( // Optimize the function body let mut body = - optimize(vec![fn_def.body], engine, &Scope::new(), &fn_lib, level); + optimize(vec![*fn_def.body], engine, &Scope::new(), &fn_lib, level); // {} -> Noop - fn_def.body = match body.pop().unwrap_or_else(|| Stmt::Noop(pos)) { + fn_def.body = Box::new(match body.pop().unwrap_or_else(|| Stmt::Noop(pos)) { // { return val; } -> val Stmt::ReturnWithVal(Some(val), ReturnType::Return, _) => Stmt::Expr(val), // { return; } -> () @@ -741,7 +741,7 @@ pub fn optimize_into_ast( } // All others stmt => stmt, - }; + }); } fn_def }) diff --git a/src/parser.rs b/src/parser.rs index 5fe913b3..dccad46e 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -14,6 +14,7 @@ use crate::stdlib::{ collections::HashMap, format, iter::Peekable, + num::NonZeroUsize, ops::Add, rc::Rc, string::{String, ToString}, @@ -169,7 +170,7 @@ pub struct FnDef { /// Names of function parameters. pub params: Vec, /// Function body. - pub body: Stmt, + pub body: Box, /// Position of the function definition. pub pos: Position, } @@ -184,29 +185,37 @@ pub enum ReturnType { } /// A type that encapsulates a local stack with variable names to simulate an actual runtime scope. +#[derive(Debug, Clone)] struct Stack(Vec); impl Stack { + /// Create a new `Stack`. pub fn new() -> Self { Self(Vec::new()) } + /// Get the number of variables in the `Stack`. pub fn len(&self) -> usize { self.0.len() } + /// Push (add) a new variable onto the `Stack`. pub fn push(&mut self, name: String) { self.0.push(name); } + /// Rewind the stack to a previous size. pub fn rewind(&mut self, len: usize) { self.0.truncate(len); } - pub fn find(&self, name: &str) -> usize { + /// Find a variable by name in the `Stack`, searching in reverse. + /// The return value is the offset to be deducted from `Stack::len`, + /// i.e. the top element of the `Stack` is offset 1. + /// Return zero when the variable name is not found in the `Stack`. + pub fn find(&self, name: &str) -> Option { self.0 .iter() .rev() .enumerate() .find(|(_, n)| *n == name) - .map(|(i, _)| i + 1) - .unwrap_or(0) + .and_then(|(i, _)| NonZeroUsize::new(i + 1)) } } @@ -308,7 +317,7 @@ pub enum Expr { /// String constant. StringConstant(String, Position), /// Variable access. - Variable(String, usize, Position), + Variable(String, Option, Position), /// Property access. Property(String, Position), /// { stmt } @@ -1836,10 +1845,10 @@ fn parse_fn<'a>( })?; // Parse function body - let body = match input.peek().unwrap() { + let body = Box::new(match input.peek().unwrap() { (Token::LeftBrace, _) => parse_block(input, stack, false, allow_stmt_expr)?, (_, pos) => return Err(PERR::FnMissingBody(name).into_err(*pos)), - }; + }); let params = params.into_iter().map(|(p, _)| p).collect();