From 2be757fda02e0243202b14db58a949089f7178bf Mon Sep 17 00:00:00 2001 From: Stephen Chung Date: Sat, 21 Nov 2020 13:05:57 +0800 Subject: [PATCH] Make shadowing variables in custom syntax work. --- RELEASES.md | 5 +++++ src/parser.rs | 37 +++++++++++++++++++++++++++++++------ tests/syntax.rs | 21 +++++++++++++++++++-- 3 files changed, 55 insertions(+), 8 deletions(-) diff --git a/RELEASES.md b/RELEASES.md index 4d5f3028..1f0a1fed 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -10,6 +10,11 @@ It also allows exposing selected module functions (usually methods) to the globa This is very convenient when encapsulating the API of a custom Rust type into a module while having methods and iterators registered on the custom type work normally. +Bug fixes +--------- + +* Custom syntax that introduces a shadowing variable now works properly. + Breaking changes ---------------- diff --git a/src/parser.rs b/src/parser.rs index aea543c8..2a7a4684 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -46,6 +46,8 @@ struct ParseState<'e> { strings: HashMap, /// Encapsulates a local stack with variable names to simulate an actual runtime scope. stack: Vec<(ImmutableString, ScopeEntryType)>, + /// Size of the local variables stack upon entry of the current block scope. + entry_stack_len: usize, /// Tracks a list of external variables (variables that are not explicitly declared in the scope). #[cfg(not(feature = "no_closure"))] externals: HashMap, @@ -92,6 +94,7 @@ impl<'e> ParseState<'e> { allow_capture: true, strings: HashMap::with_capacity(64), stack: Vec::with_capacity(16), + entry_stack_len: 0, #[cfg(not(feature = "no_module"))] modules: Default::default(), } @@ -103,15 +106,26 @@ impl<'e> ParseState<'e> { /// /// The return value is the offset to be deducted from `Stack::len`, /// i.e. the top element of the `ParseState` is offset 1. + /// /// Return `None` when the variable name is not found in the `stack`. #[inline] fn access_var(&mut self, name: &str, _pos: Position) -> Option { + let mut barrier = false; + let index = self .stack .iter() .rev() .enumerate() - .find(|(_, (n, _))| *n == name) + .find(|(_, (n, _))| { + if n.is_empty() { + // Do not go beyond empty variable names + barrier = true; + false + } else { + *n == name + } + }) .and_then(|(i, _)| NonZeroUsize::new(i + 1)); #[cfg(not(feature = "no_closure"))] @@ -123,7 +137,11 @@ impl<'e> ParseState<'e> { self.allow_capture = true } - index + if barrier { + None + } else { + index + } } /// Find a module by name in the `ParseState`, searching in reverse. @@ -1781,6 +1799,9 @@ fn parse_custom_syntax( // Adjust the variables stack match syntax.scope_delta { delta if delta > 0 => { + // Add enough empty variable names to the stack. + // Empty variable names act as a barrier so earlier variables will not be matched. + // Variable searches stop at the first empty variable name. state.stack.resize( state.stack.len() + delta as usize, ("".into(), ScopeEntryType::Normal), @@ -2284,7 +2305,9 @@ fn parse_block( settings.ensure_level_within_max_limit(state.max_expr_depth)?; let mut statements = Vec::with_capacity(8); - let prev_stack_len = state.stack.len(); + + let prev_entry_stack_len = state.entry_stack_len; + state.entry_stack_len = state.stack.len(); #[cfg(not(feature = "no_module"))] let prev_mods_len = state.modules.len(); @@ -2328,7 +2351,8 @@ fn parse_block( } } - state.stack.truncate(prev_stack_len); + state.stack.truncate(state.entry_stack_len); + state.entry_stack_len = prev_entry_stack_len; #[cfg(not(feature = "no_module"))] state.modules.truncate(prev_mods_len); @@ -2372,10 +2396,11 @@ fn parse_stmt( settings.ensure_level_within_max_limit(state.max_expr_depth)?; match token { - // Semicolon - empty statement + // ; - empty statement Token::SemiColon => Ok(Some(Stmt::Noop(settings.pos))), - Token::LeftBrace => parse_block(input, state, lib, settings.level_up()).map(Some), + // { - statements block + Token::LeftBrace => Ok(Some(parse_block(input, state, lib, settings.level_up())?)), // fn ... #[cfg(not(feature = "no_function"))] diff --git a/tests/syntax.rs b/tests/syntax.rs index 225feec4..819931e6 100644 --- a/tests/syntax.rs +++ b/tests/syntax.rs @@ -52,6 +52,16 @@ fn test_custom_syntax() -> Result<(), Box> { }, )?; + assert_eq!( + engine.eval::( + r" + let x = 0; + exec |x| -> { x += 1 } while x < 42; + x + " + )?, + 42 + ); assert_eq!( engine.eval::( r" @@ -96,8 +106,10 @@ fn test_custom_syntax_raw() -> Result<(), Box> { }, _ => unreachable!(), }, - 0, - |_, inputs| { + 1, + |context, inputs| { + context.scope.push("foo", 999 as INT); + Ok(match inputs[0].get_variable_name().unwrap() { "world" => 123 as INT, "kitty" => 42 as INT, @@ -109,6 +121,11 @@ fn test_custom_syntax_raw() -> Result<(), Box> { assert_eq!(engine.eval::("hello world")?, 123); assert_eq!(engine.eval::("hello kitty")?, 42); + assert_eq!( + engine.eval::("let foo = 0; (hello kitty) + foo")?, + 1041 + ); + assert_eq!(engine.eval::("(hello kitty) + foo")?, 1041); assert_eq!( *engine.compile("hello hey").expect_err("should error").0, ParseErrorType::BadInput(LexError::ImproperSymbol("hey".to_string()))