Allow scope constants in strict variables mode.

This commit is contained in:
Stephen Chung 2022-04-21 13:21:53 +08:00
parent c3d013bddc
commit 6b8ddd925b
7 changed files with 44 additions and 28 deletions

View File

@ -9,6 +9,11 @@ Bug fixes
* Compound assignments now work properly with indexers. * Compound assignments now work properly with indexers.
Script-breaking changes
-----------------------
* _Strict Variables Mode_ no longer returns an error when an undeclared variable matches a constant in the provided external `Scope`.
Enhancements Enhancements
------------ ------------

View File

@ -225,13 +225,8 @@ impl Engine {
scripts.as_ref(), scripts.as_ref(),
self.token_mapper.as_ref().map(Box::as_ref), self.token_mapper.as_ref().map(Box::as_ref),
); );
let mut state = ParseState::new(self, tokenizer_control); let mut state = ParseState::new(self, scope, tokenizer_control);
self.parse( self.parse(&mut stream.peekable(), &mut state, optimization_level)
&mut stream.peekable(),
&mut state,
scope,
optimization_level,
)
} }
/// Compile a string containing an expression into an [`AST`], /// Compile a string containing an expression into an [`AST`],
/// which can be used later for evaluation. /// which can be used later for evaluation.
@ -300,12 +295,7 @@ impl Engine {
self.lex_raw(&scripts, self.token_mapper.as_ref().map(Box::as_ref)); self.lex_raw(&scripts, self.token_mapper.as_ref().map(Box::as_ref));
let mut peekable = stream.peekable(); let mut peekable = stream.peekable();
let mut state = ParseState::new(self, tokenizer_control); let mut state = ParseState::new(self, scope, tokenizer_control);
self.parse_global_expr( self.parse_global_expr(&mut peekable, &mut state, self.options.optimization_level)
&mut peekable,
&mut state,
scope,
self.options.optimization_level,
)
} }
} }

View File

@ -116,13 +116,12 @@ impl Engine {
let scripts = [script]; let scripts = [script];
let (stream, tokenizer_control) = let (stream, tokenizer_control) =
self.lex_raw(&scripts, self.token_mapper.as_ref().map(Box::as_ref)); self.lex_raw(&scripts, self.token_mapper.as_ref().map(Box::as_ref));
let mut state = ParseState::new(self, tokenizer_control); let mut state = ParseState::new(self, scope, tokenizer_control);
// No need to optimize a lone expression // No need to optimize a lone expression
let ast = self.parse_global_expr( let ast = self.parse_global_expr(
&mut stream.peekable(), &mut stream.peekable(),
&mut state, &mut state,
scope,
#[cfg(not(feature = "no_optimize"))] #[cfg(not(feature = "no_optimize"))]
OptimizationLevel::None, OptimizationLevel::None,
#[cfg(feature = "no_optimize")] #[cfg(feature = "no_optimize")]

View File

@ -116,12 +116,12 @@ impl Engine {
}, },
); );
let mut state = ParseState::new(self, tokenizer_control); let scope = &Scope::new();
let mut state = ParseState::new(self, scope, tokenizer_control);
let ast = self.parse_global_expr( let ast = self.parse_global_expr(
&mut stream.peekable(), &mut stream.peekable(),
&mut state, &mut state,
&Scope::new(),
#[cfg(not(feature = "no_optimize"))] #[cfg(not(feature = "no_optimize"))]
OptimizationLevel::None, OptimizationLevel::None,
#[cfg(feature = "no_optimize")] #[cfg(feature = "no_optimize")]

View File

@ -24,12 +24,11 @@ impl Engine {
let scripts = [script]; let scripts = [script];
let (stream, tokenizer_control) = let (stream, tokenizer_control) =
self.lex_raw(&scripts, self.token_mapper.as_ref().map(Box::as_ref)); self.lex_raw(&scripts, self.token_mapper.as_ref().map(Box::as_ref));
let mut state = ParseState::new(self, tokenizer_control); let mut state = ParseState::new(self, scope, tokenizer_control);
let ast = self.parse( let ast = self.parse(
&mut stream.peekable(), &mut stream.peekable(),
&mut state, &mut state,
scope,
self.options.optimization_level, self.options.optimization_level,
)?; )?;

View File

@ -47,6 +47,8 @@ pub struct ParseState<'e> {
pub tokenizer_control: TokenizerControl, pub tokenizer_control: TokenizerControl,
/// Interned strings. /// Interned strings.
interned_strings: StringsInterner, interned_strings: StringsInterner,
/// External [scope][Scope] with constants.
pub scope: &'e Scope<'e>,
/// 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.
pub stack: Scope<'e>, pub stack: Scope<'e>,
/// Size of the local variables stack upon entry of the current block scope. /// Size of the local variables stack upon entry of the current block scope.
@ -72,7 +74,7 @@ impl<'e> ParseState<'e> {
/// Create a new [`ParseState`]. /// Create a new [`ParseState`].
#[inline(always)] #[inline(always)]
#[must_use] #[must_use]
pub fn new(engine: &Engine, tokenizer_control: TokenizerControl) -> Self { pub fn new(engine: &Engine, scope: &'e Scope, tokenizer_control: TokenizerControl) -> Self {
Self { Self {
tokenizer_control, tokenizer_control,
#[cfg(not(feature = "no_closure"))] #[cfg(not(feature = "no_closure"))]
@ -80,6 +82,7 @@ impl<'e> ParseState<'e> {
#[cfg(not(feature = "no_closure"))] #[cfg(not(feature = "no_closure"))]
allow_capture: true, allow_capture: true,
interned_strings: StringsInterner::new(), interned_strings: StringsInterner::new(),
scope,
stack: Scope::new(), stack: Scope::new(),
block_stack_len: 0, block_stack_len: 0,
#[cfg(not(feature = "no_module"))] #[cfg(not(feature = "no_module"))]
@ -1261,7 +1264,8 @@ impl Engine {
// | ... // | ...
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
Token::Pipe | Token::Or if settings.options.allow_anonymous_fn => { Token::Pipe | Token::Or if settings.options.allow_anonymous_fn => {
let mut new_state = ParseState::new(self, state.tokenizer_control.clone()); let mut new_state =
ParseState::new(self, state.scope, state.tokenizer_control.clone());
#[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "unchecked"))]
{ {
@ -1297,6 +1301,10 @@ impl Engine {
if settings.options.strict_var if settings.options.strict_var
&& !settings.is_closure_scope && !settings.is_closure_scope
&& index.is_none() && index.is_none()
&& !matches!(
state.scope.get_index(name),
Some((_, AccessMode::ReadOnly))
)
{ {
// If the parent scope is not inside another capturing closure // If the parent scope is not inside another capturing closure
// then we can conclude that the captured variable doesn't exist. // then we can conclude that the captured variable doesn't exist.
@ -1440,7 +1448,10 @@ impl Engine {
_ => { _ => {
let index = state.access_var(&s, settings.pos); let index = state.access_var(&s, settings.pos);
if settings.options.strict_var && index.is_none() { if settings.options.strict_var
&& index.is_none()
&& !matches!(state.scope.get_index(&s), Some((_, AccessMode::ReadOnly)))
{
return Err( return Err(
PERR::VariableUndefined(s.to_string()).into_err(settings.pos) PERR::VariableUndefined(s.to_string()).into_err(settings.pos)
); );
@ -3061,7 +3072,8 @@ impl Engine {
match input.next().expect(NEVER_ENDS) { match input.next().expect(NEVER_ENDS) {
(Token::Fn, pos) => { (Token::Fn, pos) => {
let mut new_state = ParseState::new(self, state.tokenizer_control.clone()); let mut new_state =
ParseState::new(self, state.scope, state.tokenizer_control.clone());
#[cfg(not(feature = "unchecked"))] #[cfg(not(feature = "unchecked"))]
{ {
@ -3533,7 +3545,6 @@ impl Engine {
&self, &self,
input: &mut TokenStream, input: &mut TokenStream,
state: &mut ParseState, state: &mut ParseState,
_scope: &Scope,
_optimization_level: OptimizationLevel, _optimization_level: OptimizationLevel,
) -> ParseResult<AST> { ) -> ParseResult<AST> {
let mut functions = BTreeMap::new(); let mut functions = BTreeMap::new();
@ -3575,7 +3586,7 @@ impl Engine {
#[cfg(not(feature = "no_optimize"))] #[cfg(not(feature = "no_optimize"))]
return Ok(crate::optimizer::optimize_into_ast( return Ok(crate::optimizer::optimize_into_ast(
self, self,
_scope, state.scope,
statements, statements,
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
StaticVec::new_const(), StaticVec::new_const(),
@ -3657,7 +3668,6 @@ impl Engine {
&self, &self,
input: &mut TokenStream, input: &mut TokenStream,
state: &mut ParseState, state: &mut ParseState,
_scope: &Scope,
_optimization_level: OptimizationLevel, _optimization_level: OptimizationLevel,
) -> ParseResult<AST> { ) -> ParseResult<AST> {
let (statements, _lib) = self.parse_global_level(input, state)?; let (statements, _lib) = self.parse_global_level(input, state)?;
@ -3665,7 +3675,7 @@ impl Engine {
#[cfg(not(feature = "no_optimize"))] #[cfg(not(feature = "no_optimize"))]
return Ok(crate::optimizer::optimize_into_ast( return Ok(crate::optimizer::optimize_into_ast(
self, self,
_scope, state.scope,
statements, statements,
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
_lib, _lib,

View File

@ -56,6 +56,10 @@ fn test_options_allow() -> Result<(), Box<EvalAltResult>> {
fn test_options_strict_var() -> Result<(), Box<EvalAltResult>> { fn test_options_strict_var() -> Result<(), Box<EvalAltResult>> {
let mut engine = Engine::new(); let mut engine = Engine::new();
let mut scope = Scope::new();
scope.push_constant("x", 42 as INT);
scope.push_constant("y", 0 as INT);
engine.compile("let x = if y { z } else { w };")?; engine.compile("let x = if y { z } else { w };")?;
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
@ -79,6 +83,11 @@ fn test_options_strict_var() -> Result<(), Box<EvalAltResult>> {
assert!(engine.compile("let x = if y { z } else { w };").is_err()); assert!(engine.compile("let x = if y { z } else { w };").is_err());
engine.compile("let y = 42; let x = y;")?; engine.compile("let y = 42; let x = y;")?;
assert_eq!(
engine.eval_with_scope::<INT>(&mut scope, "{ let y = 42; x * y }")?,
42 * 42
);
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
assert!(engine.compile("fn foo(x) { x + y }").is_err()); assert!(engine.compile("fn foo(x) { x + y }").is_err());
@ -103,6 +112,10 @@ fn test_options_strict_var() -> Result<(), Box<EvalAltResult>> {
engine.compile("let x = 42; let f = |y| { || x + y };")?; engine.compile("let x = 42; let f = |y| { || x + y };")?;
assert!(engine.compile("fn foo() { |y| { || x + y } }").is_err()); assert!(engine.compile("fn foo() { |y| { || x + y } }").is_err());
} }
assert_eq!(
engine.eval_with_scope::<INT>(&mut scope, "fn foo(z) { x * y + z } foo(1)")?,
1
);
} }
Ok(()) Ok(())