Allow non-custom reserved symbols in custom syntax.

This commit is contained in:
Stephen Chung 2021-12-16 18:01:49 +08:00
parent f92cbe1f6d
commit fbc2b1f13d
8 changed files with 77 additions and 60 deletions

View File

@ -12,6 +12,11 @@ New features
* Added support for integer _ranges_ via the `..` and `..=` operators. * Added support for integer _ranges_ via the `..` and `..=` operators.
Deprecated API's
----------------
* `Expression::get_variable_name` is deprecated in favor of the new `Expression::get_string_value`.
Version 1.3.1 Version 1.3.1
============= =============

View File

@ -1,8 +1,8 @@
//! Module containing all deprecated API that will be removed in the next major version. //! Module containing all deprecated API that will be removed in the next major version.
use crate::{ use crate::{
Dynamic, Engine, EvalAltResult, FnPtr, ImmutableString, NativeCallContext, RhaiResult, Scope, Dynamic, Engine, EvalAltResult, Expression, FnPtr, ImmutableString, NativeCallContext,
AST, RhaiResult, Scope, AST,
}; };
#[cfg(feature = "no_std")] #[cfg(feature = "no_std")]
use std::prelude::v1::*; use std::prelude::v1::*;
@ -304,3 +304,19 @@ impl FnPtr {
self.call_raw(context, this_ptr, arg_values) self.call_raw(context, this_ptr, arg_values)
} }
} }
impl Expression<'_> {
/// If this expression is a variable name, return it. Otherwise [`None`].
///
/// # Deprecated
///
/// This method is deprecated. Use [`get_string_value`][Expression::get_string_value] instead.
///
/// This method will be removed in the next major version.
#[deprecated(since = "1.4.0", note = "use `get_string_value` instead")]
#[inline(always)]
#[must_use]
pub fn get_variable_name(&self) -> Option<&str> {
self.get_string_value()
}
}

View File

@ -65,11 +65,17 @@ impl<'a> From<&'a Expr> for Expression<'a> {
} }
impl Expression<'_> { impl Expression<'_> {
/// If this expression is a variable name, return it. Otherwise [`None`]. /// Get the value of this expression if it is a variable name or a string constant.
///
/// Returns [`None`] also if the constant is not of the specified type.
#[inline(always)] #[inline(always)]
#[must_use] #[must_use]
pub fn get_variable_name(&self) -> Option<&str> { pub fn get_string_value(&self) -> Option<&str> {
self.0.get_variable_name(true) match self.0 {
Expr::Variable(_, _, x) if x.1.is_none() => Some(x.2.as_str()),
Expr::StringConstant(x, _) => Some(x.as_str()),
_ => None,
}
} }
/// Get the position of this expression. /// Get the position of this expression.
#[inline(always)] #[inline(always)]
@ -109,6 +115,9 @@ impl Expression<'_> {
if TypeId::of::<T>() == TypeId::of::<ImmutableString>() { if TypeId::of::<T>() == TypeId::of::<ImmutableString>() {
return match self.0 { return match self.0 {
Expr::StringConstant(x, _) => unsafe_try_cast(x.clone()).ok(), Expr::StringConstant(x, _) => unsafe_try_cast(x.clone()).ok(),
Expr::Variable(_, _, x) => {
unsafe_try_cast(Into::<ImmutableString>::into(&x.2)).ok()
}
_ => None, _ => None,
}; };
} }

View File

@ -1401,10 +1401,7 @@ fn parse_primary(
let msg = format!("'{}' can only be used in functions", s); let msg = format!("'{}' can only be used in functions", s);
return Err(LexError::ImproperSymbol(s.to_string(), msg).into_err(settings.pos)); return Err(LexError::ImproperSymbol(s.to_string(), msg).into_err(settings.pos));
} }
_ if is_valid_identifier(s.chars()) => { _ => return Err(PERR::Reserved(s.to_string()).into_err(settings.pos)),
return Err(PERR::Reserved(s.to_string()).into_err(settings.pos))
}
_ => return Err(LexError::UnexpectedInput(s.to_string()).into_err(settings.pos)),
} }
} }
@ -1698,12 +1695,12 @@ fn make_assignment_stmt(
ref e => Some(e.position()), ref e => Some(e.position()),
}, },
Expr::Index(x, term, _) | Expr::Dot(x, term, _) => match x.lhs { Expr::Index(x, term, _) | Expr::Dot(x, term, _) => match x.lhs {
Expr::Property(_) => unreachable!("unexpected Expr::Property in indexing"), Expr::Property(_) => unreachable!("unexpected `Expr::Property` in indexing"),
_ if !term => check_lvalue(&x.rhs, matches!(expr, Expr::Dot(_, _, _))), _ if !term => check_lvalue(&x.rhs, matches!(expr, Expr::Dot(_, _, _))),
_ => None, _ => None,
}, },
Expr::Property(_) if parent_is_dot => None, Expr::Property(_) if parent_is_dot => None,
Expr::Property(_) => unreachable!("unexpected Expr::Property in indexing"), Expr::Property(_) => unreachable!("unexpected `Expr::Property` in indexing"),
e if parent_is_dot => Some(e.position()), e if parent_is_dot => Some(e.position()),
_ => None, _ => None,
} }
@ -2277,15 +2274,9 @@ fn parse_expr(
settings.pos = input.peek().expect(NEVER_ENDS).1; settings.pos = input.peek().expect(NEVER_ENDS).1;
// Parse expression normally. // Parse expression normally.
let precedence = Precedence::new(1);
let lhs = parse_unary(input, state, lib, settings.level_up())?; let lhs = parse_unary(input, state, lib, settings.level_up())?;
parse_binary_op( parse_binary_op(input, state, lib, precedence, lhs, settings.level_up())
input,
state,
lib,
Precedence::new(1),
lhs,
settings.level_up(),
)
} }
/// Parse an if statement. /// Parse an if statement.

View File

@ -2213,14 +2213,9 @@ impl<'a> Iterator for TokenIterator<'a> {
)), )),
// Reserved keyword/operator that is custom. // Reserved keyword/operator that is custom.
(_, true) => Token::Custom(s), (_, true) => Token::Custom(s),
// Reserved operator that is not custom.
(token, false) if !is_valid_identifier(token.chars()) => {
let msg = format!("'{}' is a reserved symbol", token);
Token::LexError(LERR::ImproperSymbol(s.to_string(), msg))
},
// Reserved keyword that is not custom and disabled. // Reserved keyword that is not custom and disabled.
(token, false) if self.engine.disabled_symbols.contains(token) => { (token, false) if self.engine.disabled_symbols.contains(token) => {
let msg = format!("reserved symbol '{}' is disabled", token); let msg = format!("reserved {} '{}' is disabled", if is_valid_identifier(token.chars()) { "keyword"} else {"symbol"}, token);
Token::LexError(LERR::ImproperSymbol(s.to_string(), msg)) Token::LexError(LERR::ImproperSymbol(s.to_string(), msg))
}, },
// Reserved keyword/operator that is not custom. // Reserved keyword/operator that is not custom.
@ -2230,14 +2225,14 @@ impl<'a> Iterator for TokenIterator<'a> {
Some((Token::Identifier(s), pos)) if self.engine.custom_keywords.contains_key(&*s) => { Some((Token::Identifier(s), pos)) if self.engine.custom_keywords.contains_key(&*s) => {
(Token::Custom(s), pos) (Token::Custom(s), pos)
} }
// Custom standard keyword/symbol - must be disabled // Custom keyword/symbol - must be disabled
Some((token, pos)) if self.engine.custom_keywords.contains_key(&*token.syntax()) => { Some((token, pos)) if self.engine.custom_keywords.contains_key(&*token.syntax()) => {
if self.engine.disabled_symbols.contains(&*token.syntax()) { if self.engine.disabled_symbols.contains(&*token.syntax()) {
// Disabled standard keyword/symbol // Disabled standard keyword/symbol
(Token::Custom(token.syntax().into()), pos) (Token::Custom(token.syntax().into()), pos)
} else { } else {
// Active standard keyword - should never be a custom keyword! // Active standard keyword - should never be a custom keyword!
unreachable!("{:?} is an active keyword", token) unreachable!("`{:?}` is an active keyword", token)
} }
} }
// Disabled symbol // Disabled symbol

View File

@ -1,5 +1,6 @@
//! Module containing error definitions for the parsing process. //! Module containing error definitions for the parsing process.
use crate::tokenizer::is_valid_identifier;
use crate::{EvalAltResult, Position}; use crate::{EvalAltResult, Position};
#[cfg(feature = "no_std")] #[cfg(feature = "no_std")]
use core_error::Error; use core_error::Error;
@ -120,7 +121,7 @@ pub enum ParseErrorType {
PropertyExpected, PropertyExpected,
/// Missing a variable name after the `let`, `const`, `for` or `catch` keywords. /// Missing a variable name after the `let`, `const`, `for` or `catch` keywords.
VariableExpected, VariableExpected,
/// An identifier is a reserved keyword. /// An identifier is a reserved symbol.
Reserved(String), Reserved(String),
/// An expression is of the wrong type. /// An expression is of the wrong type.
/// Wrapped values are the type requested and type of the actual result. /// Wrapped values are the type requested and type of the actual result.
@ -260,7 +261,8 @@ impl fmt::Display for ParseErrorType {
}, },
Self::LiteralTooLarge(typ, max) => write!(f, "{} exceeds the maximum limit ({})", typ, max), Self::LiteralTooLarge(typ, max) => write!(f, "{} exceeds the maximum limit ({})", typ, max),
Self::Reserved(s) => write!(f, "'{}' is a reserved keyword", s), Self::Reserved(s) if is_valid_identifier(s.chars()) => write!(f, "'{}' is a reserved keyword", s),
Self::Reserved(s) => write!(f, "'{}' is a reserved symbol", s),
Self::UnexpectedEOF => f.write_str("Script is incomplete"), Self::UnexpectedEOF => f.write_str("Script is incomplete"),
Self::WrongSwitchIntegerCase => f.write_str("Integer switch case cannot follow a range case"), Self::WrongSwitchIntegerCase => f.write_str("Integer switch case cannot follow a range case"),
Self::WrongSwitchDefaultCase => f.write_str("Default switch case must be the last"), Self::WrongSwitchDefaultCase => f.write_str("Default switch case must be the last"),

View File

@ -25,13 +25,13 @@ fn test_custom_syntax() -> Result<(), Box<EvalAltResult>> {
], ],
true, true,
|context, inputs| { |context, inputs| {
let var_name = inputs[0].get_variable_name().unwrap().to_string(); let var_name = inputs[0].get_string_value().unwrap();
let op = inputs[1].get_literal_value::<ImmutableString>().unwrap(); let op = inputs[1].get_literal_value::<ImmutableString>().unwrap();
let max = inputs[2].get_literal_value::<INT>().unwrap(); let max = inputs[2].get_literal_value::<INT>().unwrap();
let stmt = &inputs[3]; let stmt = &inputs[3];
let condition = &inputs[4]; let condition = &inputs[4];
context.scope_mut().push(var_name.clone(), 0 as INT); context.scope_mut().push(var_name.to_string(), 0 as INT);
let mut count: INT = 0; let mut count: INT = 0;
@ -151,14 +151,14 @@ fn test_custom_syntax() -> Result<(), Box<EvalAltResult>> {
&["var", "$ident$", "=", "$expr$"], &["var", "$ident$", "=", "$expr$"],
true, true,
|context, inputs| { |context, inputs| {
let var_name = inputs[0].get_variable_name().unwrap().to_string(); let var_name = inputs[0].get_string_value().unwrap();
let expr = &inputs[1]; let expr = &inputs[1];
// Evaluate the expression // Evaluate the expression
let value = context.eval_expression_tree(expr)?; let value = context.eval_expression_tree(expr)?;
if !context.scope().is_constant(&var_name).unwrap_or(false) { if !context.scope().is_constant(var_name).unwrap_or(false) {
context.scope_mut().set_value(var_name, value); context.scope_mut().set_value(var_name.to_string(), value);
Ok(Dynamic::UNIT) Ok(Dynamic::UNIT)
} else { } else {
Err(format!("variable {} is constant", var_name).into()) Err(format!("variable {} is constant", var_name).into())
@ -206,7 +206,7 @@ fn test_custom_syntax_raw() -> Result<(), Box<EvalAltResult>> {
|context, inputs| { |context, inputs| {
context.scope_mut().push("foo", 999 as INT); context.scope_mut().push("foo", 999 as INT);
Ok(match inputs[0].get_variable_name().unwrap() { Ok(match inputs[0].get_string_value().unwrap() {
"world" "world"
if inputs if inputs
.last() .last()
@ -244,32 +244,31 @@ fn test_custom_syntax_raw() -> Result<(), Box<EvalAltResult>> {
fn test_custom_syntax_raw2() -> Result<(), Box<EvalAltResult>> { fn test_custom_syntax_raw2() -> Result<(), Box<EvalAltResult>> {
let mut engine = Engine::new(); let mut engine = Engine::new();
engine engine.register_custom_syntax_raw(
.register_custom_operator("#", 255)? "#",
.register_custom_syntax_raw( |symbols, lookahead| match symbols.len() {
"#", 1 if lookahead == "-" => Ok(Some("$symbol$".into())),
|symbols, lookahead| match symbols.len() { 1 => Ok(Some("$int$".into())),
1 if lookahead == "-" => Ok(Some("$symbol$".into())), 2 if symbols[1] == "-" => Ok(Some("$int$".into())),
1 => Ok(Some("$int$".into())), 2 => Ok(None),
2 if symbols[1] == "-" => Ok(Some("$int$".into())), 3 => Ok(None),
2 => Ok(None), _ => unreachable!(),
3 => Ok(None), },
_ => unreachable!(), false,
}, move |_, inputs| {
false, let id = if inputs.len() == 2 {
move |_, inputs| { -inputs[1].get_literal_value::<INT>().unwrap()
let id = if inputs.len() == 2 { } else {
-inputs[1].get_literal_value::<INT>().unwrap() inputs[0].get_literal_value::<INT>().unwrap()
} else { };
inputs[0].get_literal_value::<INT>().unwrap() Ok(id.into())
}; },
Ok(id.into()) );
},
);
assert_eq!(engine.eval::<INT>("#-1")?, -1);
assert_eq!(engine.eval::<INT>("let x = 41; x + #1")?, 42); assert_eq!(engine.eval::<INT>("let x = 41; x + #1")?, 42);
assert_eq!(engine.eval::<INT>("#42/2")?, 21); assert_eq!(engine.eval::<INT>("#42/2")?, 21);
assert_eq!(engine.eval::<INT>("#-1")?, -1); assert_eq!(engine.eval::<INT>("sign(#1)")?, 1);
Ok(()) Ok(())
} }

View File

@ -1,4 +1,4 @@
use rhai::{Engine, EvalAltResult, LexError, ParseErrorType, INT}; use rhai::{Engine, EvalAltResult, ParseErrorType, INT};
#[test] #[test]
fn test_tokens_disabled() { fn test_tokens_disabled() {
@ -26,7 +26,7 @@ fn test_tokens_disabled() {
assert!(matches!( assert!(matches!(
*engine.compile("let x = += 0;").expect_err("should error").0, *engine.compile("let x = += 0;").expect_err("should error").0,
ParseErrorType::BadInput(LexError::UnexpectedInput(err)) if err == "+=" ParseErrorType::Reserved(err) if err == "+="
)); ));
} }