Allow scope constants in strict variables mode.
This commit is contained in:
parent
c3d013bddc
commit
6b8ddd925b
@ -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
|
||||
------------
|
||||
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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")]
|
||||
|
@ -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")]
|
||||
|
@ -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,
|
||||
)?;
|
||||
|
||||
|
@ -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,
|
||||
|
@ -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(())
|
||||
|
Loading…
Reference in New Issue
Block a user