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.
Script-breaking changes
-----------------------
* _Strict Variables Mode_ no longer returns an error when an undeclared variable matches a constant in the provided external `Scope`.
Enhancements
------------

View File

@ -225,13 +225,8 @@ impl Engine {
scripts.as_ref(),
self.token_mapper.as_ref().map(Box::as_ref),
);
let mut state = ParseState::new(self, tokenizer_control);
self.parse(
&mut stream.peekable(),
&mut state,
scope,
optimization_level,
)
let mut state = ParseState::new(self, scope, tokenizer_control);
self.parse(&mut stream.peekable(), &mut state, optimization_level)
}
/// Compile a string containing an expression into an [`AST`],
/// 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));
let mut peekable = stream.peekable();
let mut state = ParseState::new(self, tokenizer_control);
self.parse_global_expr(
&mut peekable,
&mut state,
scope,
self.options.optimization_level,
)
let mut state = ParseState::new(self, scope, tokenizer_control);
self.parse_global_expr(&mut peekable, &mut state, self.options.optimization_level)
}
}

View File

@ -116,13 +116,12 @@ impl Engine {
let scripts = [script];
let (stream, tokenizer_control) =
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
let ast = self.parse_global_expr(
&mut stream.peekable(),
&mut state,
scope,
#[cfg(not(feature = "no_optimize"))]
OptimizationLevel::None,
#[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(
&mut stream.peekable(),
&mut state,
&Scope::new(),
#[cfg(not(feature = "no_optimize"))]
OptimizationLevel::None,
#[cfg(feature = "no_optimize")]

View File

@ -24,12 +24,11 @@ impl Engine {
let scripts = [script];
let (stream, tokenizer_control) =
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(
&mut stream.peekable(),
&mut state,
scope,
self.options.optimization_level,
)?;

View File

@ -47,6 +47,8 @@ pub struct ParseState<'e> {
pub tokenizer_control: TokenizerControl,
/// Interned strings.
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.
pub stack: Scope<'e>,
/// 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`].
#[inline(always)]
#[must_use]
pub fn new(engine: &Engine, tokenizer_control: TokenizerControl) -> Self {
pub fn new(engine: &Engine, scope: &'e Scope, tokenizer_control: TokenizerControl) -> Self {
Self {
tokenizer_control,
#[cfg(not(feature = "no_closure"))]
@ -80,6 +82,7 @@ impl<'e> ParseState<'e> {
#[cfg(not(feature = "no_closure"))]
allow_capture: true,
interned_strings: StringsInterner::new(),
scope,
stack: Scope::new(),
block_stack_len: 0,
#[cfg(not(feature = "no_module"))]
@ -1261,7 +1264,8 @@ impl Engine {
// | ...
#[cfg(not(feature = "no_function"))]
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"))]
{
@ -1297,6 +1301,10 @@ impl Engine {
if settings.options.strict_var
&& !settings.is_closure_scope
&& index.is_none()
&& !matches!(
state.scope.get_index(name),
Some((_, AccessMode::ReadOnly))
)
{
// If the parent scope is not inside another capturing closure
// 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);
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(
PERR::VariableUndefined(s.to_string()).into_err(settings.pos)
);
@ -3061,7 +3072,8 @@ impl Engine {
match input.next().expect(NEVER_ENDS) {
(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"))]
{
@ -3533,7 +3545,6 @@ impl Engine {
&self,
input: &mut TokenStream,
state: &mut ParseState,
_scope: &Scope,
_optimization_level: OptimizationLevel,
) -> ParseResult<AST> {
let mut functions = BTreeMap::new();
@ -3575,7 +3586,7 @@ impl Engine {
#[cfg(not(feature = "no_optimize"))]
return Ok(crate::optimizer::optimize_into_ast(
self,
_scope,
state.scope,
statements,
#[cfg(not(feature = "no_function"))]
StaticVec::new_const(),
@ -3657,7 +3668,6 @@ impl Engine {
&self,
input: &mut TokenStream,
state: &mut ParseState,
_scope: &Scope,
_optimization_level: OptimizationLevel,
) -> ParseResult<AST> {
let (statements, _lib) = self.parse_global_level(input, state)?;
@ -3665,7 +3675,7 @@ impl Engine {
#[cfg(not(feature = "no_optimize"))]
return Ok(crate::optimizer::optimize_into_ast(
self,
_scope,
state.scope,
statements,
#[cfg(not(feature = "no_function"))]
_lib,

View File

@ -56,6 +56,10 @@ fn test_options_allow() -> Result<(), Box<EvalAltResult>> {
fn test_options_strict_var() -> Result<(), Box<EvalAltResult>> {
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 };")?;
#[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());
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"))]
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 };")?;
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(())