Add allow_shadowing.
This commit is contained in:
parent
6c1c8bc538
commit
3be27746e0
@ -26,6 +26,7 @@ New features
|
|||||||
* A debugging interface is added.
|
* A debugging interface is added.
|
||||||
* A new bin tool, `rhai-dbg` (aka _The Rhai Debugger_), is added to showcase the debugging interface.
|
* A new bin tool, `rhai-dbg` (aka _The Rhai Debugger_), is added to showcase the debugging interface.
|
||||||
* A new package, `DebuggingPackage`, is added which contains the `back_trace` function to get the current call stack anywhere in a script.
|
* A new package, `DebuggingPackage`, is added which contains the `back_trace` function to get the current call stack anywhere in a script.
|
||||||
|
* `Engine::set_allow_shadowing` is added to allow/disallow variables _shadowing_, with new errors `EvalAltResult::ErrorVariableExists` and `ParseErrorType::VariableExists`.
|
||||||
|
|
||||||
Enhancements
|
Enhancements
|
||||||
------------
|
------------
|
||||||
|
@ -17,9 +17,11 @@ pub struct LanguageOptions {
|
|||||||
#[cfg(not(feature = "no_function"))]
|
#[cfg(not(feature = "no_function"))]
|
||||||
pub allow_anonymous_fn: bool,
|
pub allow_anonymous_fn: bool,
|
||||||
/// Is looping allowed?
|
/// Is looping allowed?
|
||||||
pub allow_loop: bool,
|
pub allow_looping: bool,
|
||||||
/// Strict variables mode?
|
/// Strict variables mode?
|
||||||
pub strict_var: bool,
|
pub strict_var: bool,
|
||||||
|
/// Is variables shadowing allowed?
|
||||||
|
pub allow_shadowing: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LanguageOptions {
|
impl LanguageOptions {
|
||||||
@ -32,8 +34,9 @@ impl LanguageOptions {
|
|||||||
allow_stmt_expr: true,
|
allow_stmt_expr: true,
|
||||||
#[cfg(not(feature = "no_function"))]
|
#[cfg(not(feature = "no_function"))]
|
||||||
allow_anonymous_fn: true,
|
allow_anonymous_fn: true,
|
||||||
allow_loop: true,
|
allow_looping: true,
|
||||||
strict_var: false,
|
strict_var: false,
|
||||||
|
allow_shadowing: true,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -94,12 +97,12 @@ impl Engine {
|
|||||||
/// Is looping allowed?
|
/// Is looping allowed?
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn allow_looping(&self) -> bool {
|
pub fn allow_looping(&self) -> bool {
|
||||||
self.options.allow_loop
|
self.options.allow_looping
|
||||||
}
|
}
|
||||||
/// Set whether looping is allowed.
|
/// Set whether looping is allowed.
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
pub fn set_allow_looping(&mut self, enable: bool) {
|
pub fn set_allow_looping(&mut self, enable: bool) {
|
||||||
self.options.allow_loop = enable;
|
self.options.allow_looping = enable;
|
||||||
}
|
}
|
||||||
/// Is strict variables mode enabled?
|
/// Is strict variables mode enabled?
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
@ -111,4 +114,14 @@ impl Engine {
|
|||||||
pub fn set_strict_variables(&mut self, enable: bool) {
|
pub fn set_strict_variables(&mut self, enable: bool) {
|
||||||
self.options.strict_var = enable;
|
self.options.strict_var = enable;
|
||||||
}
|
}
|
||||||
|
/// Is variables shadowing allowed?
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn allow_shadowing(&self) -> bool {
|
||||||
|
self.options.allow_shadowing
|
||||||
|
}
|
||||||
|
/// Set whether variables shadowing is allowed.
|
||||||
|
#[inline(always)]
|
||||||
|
pub fn set_allow_shadowing(&mut self, enable: bool) {
|
||||||
|
self.options.allow_shadowing = enable;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -802,9 +802,14 @@ impl Engine {
|
|||||||
// Empty return
|
// Empty return
|
||||||
Stmt::Return(_, None, pos) => Err(ERR::Return(Dynamic::UNIT, *pos).into()),
|
Stmt::Return(_, None, pos) => Err(ERR::Return(Dynamic::UNIT, *pos).into()),
|
||||||
|
|
||||||
|
// Let/const statement - shadowing disallowed
|
||||||
|
Stmt::Var(_, x, _, pos) if !self.allow_shadowing() && scope.contains(&x.name) => {
|
||||||
|
Err(ERR::ErrorVariableExists(x.name.to_string(), *pos).into())
|
||||||
|
}
|
||||||
// Let/const statement
|
// Let/const statement
|
||||||
Stmt::Var(expr, x, options, _) => {
|
Stmt::Var(expr, x, options, _) => {
|
||||||
let var_name = &x.name;
|
let var_name = &x.name;
|
||||||
|
|
||||||
let entry_type = if options.contains(AST_OPTION_CONSTANT) {
|
let entry_type = if options.contains(AST_OPTION_CONSTANT) {
|
||||||
AccessMode::ReadOnly
|
AccessMode::ReadOnly
|
||||||
} else {
|
} else {
|
||||||
|
@ -16,7 +16,7 @@ use crate::types::dynamic::AccessMode;
|
|||||||
use crate::types::StringsInterner;
|
use crate::types::StringsInterner;
|
||||||
use crate::{
|
use crate::{
|
||||||
calc_fn_hash, Dynamic, Engine, ExclusiveRange, Identifier, ImmutableString, InclusiveRange,
|
calc_fn_hash, Dynamic, Engine, ExclusiveRange, Identifier, ImmutableString, InclusiveRange,
|
||||||
LexError, ParseError, Position, Scope, Shared, StaticVec, AST, INT, PERR,
|
LexError, ParseError, Position, Scope, Shared, StaticVec, Variant, AST, INT, PERR,
|
||||||
};
|
};
|
||||||
#[cfg(feature = "no_std")]
|
#[cfg(feature = "no_std")]
|
||||||
use std::prelude::v1::*;
|
use std::prelude::v1::*;
|
||||||
@ -2591,6 +2591,12 @@ fn parse_let(
|
|||||||
// let name ...
|
// let name ...
|
||||||
let (name, pos) = parse_var_name(input)?;
|
let (name, pos) = parse_var_name(input)?;
|
||||||
|
|
||||||
|
if !settings.default_options.allow_shadowing
|
||||||
|
&& state.stack.iter().any(|(v, _)| v == name.as_ref())
|
||||||
|
{
|
||||||
|
return Err(PERR::VariableExists(name.to_string()).into_err(pos));
|
||||||
|
}
|
||||||
|
|
||||||
let name = state.get_identifier("", name);
|
let name = state.get_identifier("", name);
|
||||||
let var_def = Ident {
|
let var_def = Ident {
|
||||||
name: name.clone(),
|
name: name.clone(),
|
||||||
@ -2973,25 +2979,25 @@ fn parse_stmt(
|
|||||||
|
|
||||||
Token::If => parse_if(input, state, lib, settings.level_up()),
|
Token::If => parse_if(input, state, lib, settings.level_up()),
|
||||||
Token::Switch => parse_switch(input, state, lib, settings.level_up()),
|
Token::Switch => parse_switch(input, state, lib, settings.level_up()),
|
||||||
Token::While | Token::Loop if settings.default_options.allow_loop => {
|
Token::While | Token::Loop if settings.default_options.allow_looping => {
|
||||||
parse_while_loop(input, state, lib, settings.level_up())
|
parse_while_loop(input, state, lib, settings.level_up())
|
||||||
}
|
}
|
||||||
Token::Do if settings.default_options.allow_loop => {
|
Token::Do if settings.default_options.allow_looping => {
|
||||||
parse_do(input, state, lib, settings.level_up())
|
parse_do(input, state, lib, settings.level_up())
|
||||||
}
|
}
|
||||||
Token::For if settings.default_options.allow_loop => {
|
Token::For if settings.default_options.allow_looping => {
|
||||||
parse_for(input, state, lib, settings.level_up())
|
parse_for(input, state, lib, settings.level_up())
|
||||||
}
|
}
|
||||||
|
|
||||||
Token::Continue if settings.default_options.allow_loop && settings.is_breakable => {
|
Token::Continue if settings.default_options.allow_looping && settings.is_breakable => {
|
||||||
let pos = eat_token(input, Token::Continue);
|
let pos = eat_token(input, Token::Continue);
|
||||||
Ok(Stmt::BreakLoop(AST_OPTION_NONE, pos))
|
Ok(Stmt::BreakLoop(AST_OPTION_NONE, pos))
|
||||||
}
|
}
|
||||||
Token::Break if settings.default_options.allow_loop && settings.is_breakable => {
|
Token::Break if settings.default_options.allow_looping && settings.is_breakable => {
|
||||||
let pos = eat_token(input, Token::Break);
|
let pos = eat_token(input, Token::Break);
|
||||||
Ok(Stmt::BreakLoop(AST_OPTION_BREAK, pos))
|
Ok(Stmt::BreakLoop(AST_OPTION_BREAK, pos))
|
||||||
}
|
}
|
||||||
Token::Continue | Token::Break if settings.default_options.allow_loop => {
|
Token::Continue | Token::Break if settings.default_options.allow_looping => {
|
||||||
Err(PERR::LoopBreak.into_err(token_pos))
|
Err(PERR::LoopBreak.into_err(token_pos))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -30,6 +30,8 @@ pub enum EvalAltResult {
|
|||||||
/// Syntax error.
|
/// Syntax error.
|
||||||
ErrorParsing(ParseErrorType, Position),
|
ErrorParsing(ParseErrorType, Position),
|
||||||
|
|
||||||
|
/// Shadowing of an existing variable disallowed. Wrapped value is the variable name.
|
||||||
|
ErrorVariableExists(String, Position),
|
||||||
/// Usage of an unknown variable. Wrapped value is the variable name.
|
/// Usage of an unknown variable. Wrapped value is the variable name.
|
||||||
ErrorVariableNotFound(String, Position),
|
ErrorVariableNotFound(String, Position),
|
||||||
/// Call to an unknown function. Wrapped value is the function signature.
|
/// Call to an unknown function. Wrapped value is the function signature.
|
||||||
@ -139,8 +141,9 @@ impl fmt::Display for EvalAltResult {
|
|||||||
}
|
}
|
||||||
Self::ErrorInModule(s, err, _) => write!(f, "Error in module {}: {}", s, err)?,
|
Self::ErrorInModule(s, err, _) => write!(f, "Error in module {}: {}", s, err)?,
|
||||||
|
|
||||||
Self::ErrorFunctionNotFound(s, _) => write!(f, "Function not found: {}", s)?,
|
Self::ErrorVariableExists(s, _) => write!(f, "Variable is already defined: {}", s)?,
|
||||||
Self::ErrorVariableNotFound(s, _) => write!(f, "Variable not found: {}", s)?,
|
Self::ErrorVariableNotFound(s, _) => write!(f, "Variable not found: {}", s)?,
|
||||||
|
Self::ErrorFunctionNotFound(s, _) => write!(f, "Function not found: {}", s)?,
|
||||||
Self::ErrorModuleNotFound(s, _) => write!(f, "Module not found: {}", s)?,
|
Self::ErrorModuleNotFound(s, _) => write!(f, "Module not found: {}", s)?,
|
||||||
Self::ErrorDataRace(s, _) => {
|
Self::ErrorDataRace(s, _) => {
|
||||||
write!(f, "Data race detected when accessing variable: {}", s)?
|
write!(f, "Data race detected when accessing variable: {}", s)?
|
||||||
@ -274,6 +277,7 @@ impl EvalAltResult {
|
|||||||
| Self::ErrorBitFieldBounds(_, _, _)
|
| Self::ErrorBitFieldBounds(_, _, _)
|
||||||
| Self::ErrorIndexingType(_, _)
|
| Self::ErrorIndexingType(_, _)
|
||||||
| Self::ErrorFor(_)
|
| Self::ErrorFor(_)
|
||||||
|
| Self::ErrorVariableExists(_, _)
|
||||||
| Self::ErrorVariableNotFound(_, _)
|
| Self::ErrorVariableNotFound(_, _)
|
||||||
| Self::ErrorModuleNotFound(_, _)
|
| Self::ErrorModuleNotFound(_, _)
|
||||||
| Self::ErrorDataRace(_, _)
|
| Self::ErrorDataRace(_, _)
|
||||||
@ -364,7 +368,8 @@ impl EvalAltResult {
|
|||||||
Self::ErrorIndexingType(t, _) => {
|
Self::ErrorIndexingType(t, _) => {
|
||||||
map.insert("type".into(), t.into());
|
map.insert("type".into(), t.into());
|
||||||
}
|
}
|
||||||
Self::ErrorVariableNotFound(v, _)
|
Self::ErrorVariableExists(v, _)
|
||||||
|
| Self::ErrorVariableNotFound(v, _)
|
||||||
| Self::ErrorDataRace(v, _)
|
| Self::ErrorDataRace(v, _)
|
||||||
| Self::ErrorAssignmentToConstant(v, _) => {
|
| Self::ErrorAssignmentToConstant(v, _) => {
|
||||||
map.insert("variable".into(), v.into());
|
map.insert("variable".into(), v.into());
|
||||||
@ -415,6 +420,7 @@ impl EvalAltResult {
|
|||||||
| Self::ErrorBitFieldBounds(_, _, pos)
|
| Self::ErrorBitFieldBounds(_, _, pos)
|
||||||
| Self::ErrorIndexingType(_, pos)
|
| Self::ErrorIndexingType(_, pos)
|
||||||
| Self::ErrorFor(pos)
|
| Self::ErrorFor(pos)
|
||||||
|
| Self::ErrorVariableExists(_, pos)
|
||||||
| Self::ErrorVariableNotFound(_, pos)
|
| Self::ErrorVariableNotFound(_, pos)
|
||||||
| Self::ErrorModuleNotFound(_, pos)
|
| Self::ErrorModuleNotFound(_, pos)
|
||||||
| Self::ErrorDataRace(_, pos)
|
| Self::ErrorDataRace(_, pos)
|
||||||
@ -463,6 +469,7 @@ impl EvalAltResult {
|
|||||||
| Self::ErrorBitFieldBounds(_, _, pos)
|
| Self::ErrorBitFieldBounds(_, _, pos)
|
||||||
| Self::ErrorIndexingType(_, pos)
|
| Self::ErrorIndexingType(_, pos)
|
||||||
| Self::ErrorFor(pos)
|
| Self::ErrorFor(pos)
|
||||||
|
| Self::ErrorVariableExists(_, pos)
|
||||||
| Self::ErrorVariableNotFound(_, pos)
|
| Self::ErrorVariableNotFound(_, pos)
|
||||||
| Self::ErrorModuleNotFound(_, pos)
|
| Self::ErrorModuleNotFound(_, pos)
|
||||||
| Self::ErrorDataRace(_, pos)
|
| Self::ErrorDataRace(_, pos)
|
||||||
|
@ -167,6 +167,10 @@ pub enum ParseErrorType {
|
|||||||
/// Assignment to an inappropriate LHS (left-hand-side) expression.
|
/// Assignment to an inappropriate LHS (left-hand-side) expression.
|
||||||
/// Wrapped value is the error message (if any).
|
/// Wrapped value is the error message (if any).
|
||||||
AssignmentToInvalidLHS(String),
|
AssignmentToInvalidLHS(String),
|
||||||
|
/// A variable is already defined.
|
||||||
|
///
|
||||||
|
/// Only appears when variables shadowing is disabled.
|
||||||
|
VariableExists(String),
|
||||||
/// A variable is not found.
|
/// A variable is not found.
|
||||||
///
|
///
|
||||||
/// Only appears when strict variables mode is enabled.
|
/// Only appears when strict variables mode is enabled.
|
||||||
@ -241,6 +245,7 @@ impl fmt::Display for ParseErrorType {
|
|||||||
Self::DuplicatedSwitchCase => f.write_str("Duplicated switch case"),
|
Self::DuplicatedSwitchCase => f.write_str("Duplicated switch case"),
|
||||||
Self::DuplicatedVariable(s) => write!(f, "Duplicated variable name: {}", s),
|
Self::DuplicatedVariable(s) => write!(f, "Duplicated variable name: {}", s),
|
||||||
|
|
||||||
|
Self::VariableExists(s) => write!(f, "Variable already defined: {}", s),
|
||||||
Self::VariableUndefined(s) => write!(f, "Undefined variable: {}", s),
|
Self::VariableUndefined(s) => write!(f, "Undefined variable: {}", s),
|
||||||
Self::ModuleUndefined(s) => write!(f, "Undefined module: {}", s),
|
Self::ModuleUndefined(s) => write!(f, "Undefined module: {}", s),
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
use rhai::{Engine, EvalAltResult};
|
use rhai::{Engine, EvalAltResult, Scope, INT};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_options_allow() -> Result<(), Box<EvalAltResult>> {
|
fn test_options_allow() -> Result<(), Box<EvalAltResult>> {
|
||||||
@ -31,6 +31,20 @@ fn test_options_allow() -> Result<(), Box<EvalAltResult>> {
|
|||||||
|
|
||||||
assert!(engine.compile("while x > y { foo(z); }").is_err());
|
assert!(engine.compile("while x > y { foo(z); }").is_err());
|
||||||
|
|
||||||
|
engine.compile("let x = 42; let x = 123;")?;
|
||||||
|
|
||||||
|
engine.set_allow_shadowing(false);
|
||||||
|
|
||||||
|
assert!(engine.compile("let x = 42; let x = 123;").is_err());
|
||||||
|
assert!(engine.compile("const x = 42; let x = 123;").is_err());
|
||||||
|
assert!(engine.compile("let x = 42; const x = 123;").is_err());
|
||||||
|
assert!(engine.compile("const x = 42; const x = 123;").is_err());
|
||||||
|
|
||||||
|
let mut scope = Scope::new();
|
||||||
|
scope.push("x", 42 as INT);
|
||||||
|
|
||||||
|
assert!(engine.run_with_scope(&mut scope, "let x = 42;").is_err());
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user