Fix unary operators binding.

This commit is contained in:
Stephen Chung 2020-12-21 17:39:37 +08:00
parent 730a7320d6
commit 2955a4ab64
6 changed files with 85 additions and 25 deletions

View File

@ -16,6 +16,8 @@ exports the full list of functions metadata (including those in an `AST`) as a J
Bug fixes
---------
* Unary prefix operators `-`, `+` and `!` now bind correctly when applied to an expression. Previously, `-x.len` is parsed as `(-x).len` which is obviously counter-intuitive.
* Indexing of namespace-qualified variables now work properly, such as `path::to::var[x]`.
* Constants are no longer propagated by the optimizer if shadowed by a non-constant variable.
* Constants passed as the `this` parameter to Rhai functions now throws an error if assigned to.
* Generic type parameter of `Engine::register_iterator` is `IntoIterator` instead of `Iterator`.

View File

@ -1191,12 +1191,16 @@ impl Expr {
| Self::Map(_, _) => match token {
#[cfg(not(feature = "no_index"))]
Token::LeftBracket => true,
#[cfg(not(feature = "no_object"))]
Token::Period => true,
_ => false,
},
Self::Variable(_) => match token {
#[cfg(not(feature = "no_index"))]
Token::LeftBracket => true,
#[cfg(not(feature = "no_object"))]
Token::Period => true,
Token::LeftParen => true,
Token::Bang => true,
Token::DoubleColon => true,
@ -1206,6 +1210,8 @@ impl Expr {
Self::Property(_) => match token {
#[cfg(not(feature = "no_index"))]
Token::LeftBracket => true,
#[cfg(not(feature = "no_object"))]
Token::Period => true,
Token::LeftParen => true,
_ => false,
},

View File

@ -454,19 +454,18 @@ impl Engine {
hash_script: u64,
pub_only: bool,
) -> bool {
// NOTE: We skip script functions for global_namespace and packages, and native functions for lib
// First check script-defined functions
(hash_script != 0 && lib.iter().any(|&m| m.contains_fn(hash_script, pub_only)))
//|| (hash_fn != 0 && lib.iter().any(|&m| m.contains_fn(hash_fn, pub_only)))
//|| lib.iter().any(|&m| m.contains_fn(hash_fn, pub_only))
// Then check registered functions
//|| self.global_namespace.contains_fn(hash_script, pub_only)
//|| (hash_script != 0 && self.global_namespace.contains_fn(hash_script, pub_only))
|| self.global_namespace.contains_fn(hash_fn, false)
// Then check packages
|| self.packages.contains_fn(hash_script)
|| (hash_script != 0 && self.packages.contains_fn(hash_script))
|| self.packages.contains_fn(hash_fn)
// Then check imported modules
|| mods.map(|m| m.contains_fn(hash_script) || m.contains_fn(hash_fn)).unwrap_or(false)
|| (hash_script != 0 && mods.map(|m| m.contains_fn(hash_script)).unwrap_or(false))
|| mods.map(|m| m.contains_fn(hash_fn)).unwrap_or(false)
}
/// Perform an actual function call, native Rust or scripted, taking care of special functions.
@ -678,14 +677,15 @@ impl Engine {
}
// Evaluate the AST
let mut new_state: State = Default::default();
new_state.operations = state.operations;
let mut new_state = State {
operations: state.operations,
..Default::default()
};
let result =
self.eval_statements_raw(scope, mods, &mut new_state, ast.statements(), lib)?;
let result = self.eval_statements_raw(scope, mods, &mut new_state, ast.statements(), lib);
state.operations = new_state.operations;
return Ok(result);
result
}
/// Call a dot method.

View File

@ -143,6 +143,10 @@ macro_rules! gen_signed_functions {
Ok(Dynamic::from(-x))
}
}
#[rhai_fn(name = "+")]
pub fn plus(x: $arg_type) -> $arg_type {
x
}
#[rhai_fn(return_raw)]
pub fn abs(x: $arg_type) -> Result<Dynamic, Box<EvalAltResult>> {
if cfg!(not(feature = "unchecked")) {
@ -251,6 +255,10 @@ mod f32_functions {
pub fn neg(x: f32) -> f32 {
-x
}
#[rhai_fn(name = "+")]
pub fn plus(x: f32) -> f32 {
-x
}
pub fn abs(x: f32) -> f32 {
x.abs()
}
@ -310,6 +318,10 @@ mod f64_functions {
pub fn neg(x: f64) -> f64 {
-x
}
#[rhai_fn(name = "+")]
pub fn plus(x: f64) -> f64 {
-x
}
pub fn abs(x: f64) -> f64 {
x.abs()
}

View File

@ -1105,6 +1105,12 @@ fn parse_primary(
(expr, Token::LeftBracket) => {
parse_index_chain(input, state, lib, expr, settings.level_up())?
}
// Method access
#[cfg(not(feature = "no_object"))]
(expr, Token::Period) => {
let rhs = parse_unary(input, state, lib, settings.level_up())?;
make_dot_expr(state, expr, rhs, token_pos)?
}
// Unknown postfix operator
(expr, token) => unreachable!(
"unknown postfix operator '{}' for {:?}",
@ -1114,20 +1120,25 @@ fn parse_primary(
}
}
// Cache the hash key for namespace-qualified variables
match &mut root_expr {
// Cache the hash key for namespace-qualified variables
Expr::Variable(x) if x.1.is_some() => {
let (_, modules, hash, IdentX { name, .. }) = x.as_mut();
let namespace = modules.as_mut().unwrap();
// Qualifiers + variable name
*hash = calc_script_fn_hash(namespace.iter().map(|v| v.name.as_str()), name, 0);
#[cfg(not(feature = "no_module"))]
namespace.set_index(state.find_module(&namespace[0].name));
}
_ => (),
Expr::Variable(x) if x.1.is_some() => Some(x),
Expr::Index(x, _) | Expr::Dot(x, _) => match &mut x.lhs {
Expr::Variable(x) if x.1.is_some() => Some(x),
_ => None,
},
_ => None,
}
.map(|x| {
let (_, modules, hash, IdentX { name, .. }) = x.as_mut();
let namespace = modules.as_mut().unwrap();
// Qualifiers + variable name
*hash = calc_script_fn_hash(namespace.iter().map(|v| v.name.as_str()), name, 0);
#[cfg(not(feature = "no_module"))]
namespace.set_index(state.find_module(&namespace[0].name));
});
// Make sure identifiers are valid
Ok(root_expr)
@ -1201,8 +1212,33 @@ fn parse_unary(
}
// +expr
Token::UnaryPlus => {
eat_token(input, Token::UnaryPlus);
parse_unary(input, state, lib, settings.level_up())
let pos = eat_token(input, Token::UnaryPlus);
match parse_unary(input, state, lib, settings.level_up())? {
expr @ Expr::IntegerConstant(_, _) => Ok(expr),
#[cfg(not(feature = "no_float"))]
expr @ Expr::FloatConstant(_, _) => Ok(expr),
// Call plus function
expr => {
let op = "+";
let hash = calc_script_fn_hash(empty(), op, 1);
let mut args = StaticVec::new();
args.push(expr);
Ok(Expr::FnCall(
Box::new(FnCallExpr {
name: op.into(),
native_only: true,
namespace: None,
hash,
args,
..Default::default()
}),
pos,
))
}
}
}
// !expr
Token::Bang => {
@ -1770,6 +1806,7 @@ fn parse_binary_op(
make_in_expr(current_lhs, rhs, pos)?
}
// This is needed to parse closure followed by a dot.
#[cfg(not(feature = "no_object"))]
Token::Period => {
let rhs = args.pop().unwrap();

View File

@ -48,6 +48,7 @@ fn test_module_sub_module() -> Result<(), Box<EvalAltResult>> {
assert_eq!(engine.eval::<INT>("question::MYSTIC_NUMBER")?, 42);
assert!(engine.eval::<INT>("MYSTIC_NUMBER").is_err());
assert_eq!(engine.eval::<INT>("question::life::universe::answer")?, 41);
assert_eq!(
engine.eval::<INT>("question::life::universe::answer + 1")?,
42
@ -60,6 +61,8 @@ fn test_module_sub_module() -> Result<(), Box<EvalAltResult>> {
.eval::<INT>("inc(question::life::universe::answer)")
.is_err());
#[cfg(not(feature = "no_object"))]
assert_eq!(engine.eval::<INT>("question::MYSTIC_NUMBER.doubled")?, 84);
#[cfg(not(feature = "no_object"))]
assert_eq!(
engine.eval::<INT>("question::life::universe::answer.doubled")?,
82