Make shadowing variables in custom syntax work.

This commit is contained in:
Stephen Chung 2020-11-21 13:05:57 +08:00
parent 17cd305af7
commit 2be757fda0
3 changed files with 55 additions and 8 deletions

View File

@ -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 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. and iterators registered on the custom type work normally.
Bug fixes
---------
* Custom syntax that introduces a shadowing variable now works properly.
Breaking changes Breaking changes
---------------- ----------------

View File

@ -46,6 +46,8 @@ struct ParseState<'e> {
strings: HashMap<String, ImmutableString>, strings: HashMap<String, ImmutableString>,
/// Encapsulates a local stack with variable names to simulate an actual runtime scope. /// Encapsulates a local stack with variable names to simulate an actual runtime scope.
stack: Vec<(ImmutableString, ScopeEntryType)>, 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). /// Tracks a list of external variables (variables that are not explicitly declared in the scope).
#[cfg(not(feature = "no_closure"))] #[cfg(not(feature = "no_closure"))]
externals: HashMap<ImmutableString, Position>, externals: HashMap<ImmutableString, Position>,
@ -92,6 +94,7 @@ impl<'e> ParseState<'e> {
allow_capture: true, allow_capture: true,
strings: HashMap::with_capacity(64), strings: HashMap::with_capacity(64),
stack: Vec::with_capacity(16), stack: Vec::with_capacity(16),
entry_stack_len: 0,
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
modules: Default::default(), modules: Default::default(),
} }
@ -103,15 +106,26 @@ impl<'e> ParseState<'e> {
/// ///
/// The return value is the offset to be deducted from `Stack::len`, /// The return value is the offset to be deducted from `Stack::len`,
/// i.e. the top element of the `ParseState` is offset 1. /// i.e. the top element of the `ParseState` is offset 1.
///
/// Return `None` when the variable name is not found in the `stack`. /// Return `None` when the variable name is not found in the `stack`.
#[inline] #[inline]
fn access_var(&mut self, name: &str, _pos: Position) -> Option<NonZeroUsize> { fn access_var(&mut self, name: &str, _pos: Position) -> Option<NonZeroUsize> {
let mut barrier = false;
let index = self let index = self
.stack .stack
.iter() .iter()
.rev() .rev()
.enumerate() .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)); .and_then(|(i, _)| NonZeroUsize::new(i + 1));
#[cfg(not(feature = "no_closure"))] #[cfg(not(feature = "no_closure"))]
@ -123,7 +137,11 @@ impl<'e> ParseState<'e> {
self.allow_capture = true self.allow_capture = true
} }
index if barrier {
None
} else {
index
}
} }
/// Find a module by name in the `ParseState`, searching in reverse. /// Find a module by name in the `ParseState`, searching in reverse.
@ -1781,6 +1799,9 @@ fn parse_custom_syntax(
// Adjust the variables stack // Adjust the variables stack
match syntax.scope_delta { match syntax.scope_delta {
delta if delta > 0 => { 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.resize(
state.stack.len() + delta as usize, state.stack.len() + delta as usize,
("".into(), ScopeEntryType::Normal), ("".into(), ScopeEntryType::Normal),
@ -2284,7 +2305,9 @@ fn parse_block(
settings.ensure_level_within_max_limit(state.max_expr_depth)?; settings.ensure_level_within_max_limit(state.max_expr_depth)?;
let mut statements = Vec::with_capacity(8); 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"))] #[cfg(not(feature = "no_module"))]
let prev_mods_len = state.modules.len(); 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"))] #[cfg(not(feature = "no_module"))]
state.modules.truncate(prev_mods_len); state.modules.truncate(prev_mods_len);
@ -2372,10 +2396,11 @@ fn parse_stmt(
settings.ensure_level_within_max_limit(state.max_expr_depth)?; settings.ensure_level_within_max_limit(state.max_expr_depth)?;
match token { match token {
// Semicolon - empty statement // ; - empty statement
Token::SemiColon => Ok(Some(Stmt::Noop(settings.pos))), 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 ... // fn ...
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]

View File

@ -52,6 +52,16 @@ fn test_custom_syntax() -> Result<(), Box<EvalAltResult>> {
}, },
)?; )?;
assert_eq!(
engine.eval::<INT>(
r"
let x = 0;
exec |x| -> { x += 1 } while x < 42;
x
"
)?,
42
);
assert_eq!( assert_eq!(
engine.eval::<INT>( engine.eval::<INT>(
r" r"
@ -96,8 +106,10 @@ fn test_custom_syntax_raw() -> Result<(), Box<EvalAltResult>> {
}, },
_ => unreachable!(), _ => unreachable!(),
}, },
0, 1,
|_, inputs| { |context, inputs| {
context.scope.push("foo", 999 as INT);
Ok(match inputs[0].get_variable_name().unwrap() { Ok(match inputs[0].get_variable_name().unwrap() {
"world" => 123 as INT, "world" => 123 as INT,
"kitty" => 42 as INT, "kitty" => 42 as INT,
@ -109,6 +121,11 @@ fn test_custom_syntax_raw() -> Result<(), Box<EvalAltResult>> {
assert_eq!(engine.eval::<INT>("hello world")?, 123); assert_eq!(engine.eval::<INT>("hello world")?, 123);
assert_eq!(engine.eval::<INT>("hello kitty")?, 42); assert_eq!(engine.eval::<INT>("hello kitty")?, 42);
assert_eq!(
engine.eval::<INT>("let foo = 0; (hello kitty) + foo")?,
1041
);
assert_eq!(engine.eval::<INT>("(hello kitty) + foo")?, 1041);
assert_eq!( assert_eq!(
*engine.compile("hello hey").expect_err("should error").0, *engine.compile("hello hey").expect_err("should error").0,
ParseErrorType::BadInput(LexError::ImproperSymbol("hey".to_string())) ParseErrorType::BadInput(LexError::ImproperSymbol("hey".to_string()))