Fix unary operators binding.
This commit is contained in:
parent
730a7320d6
commit
2955a4ab64
@ -16,6 +16,8 @@ exports the full list of functions metadata (including those in an `AST`) as a J
|
|||||||
Bug fixes
|
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 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.
|
* 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`.
|
* Generic type parameter of `Engine::register_iterator` is `IntoIterator` instead of `Iterator`.
|
||||||
|
@ -1191,12 +1191,16 @@ impl Expr {
|
|||||||
| Self::Map(_, _) => match token {
|
| Self::Map(_, _) => match token {
|
||||||
#[cfg(not(feature = "no_index"))]
|
#[cfg(not(feature = "no_index"))]
|
||||||
Token::LeftBracket => true,
|
Token::LeftBracket => true,
|
||||||
|
#[cfg(not(feature = "no_object"))]
|
||||||
|
Token::Period => true,
|
||||||
_ => false,
|
_ => false,
|
||||||
},
|
},
|
||||||
|
|
||||||
Self::Variable(_) => match token {
|
Self::Variable(_) => match token {
|
||||||
#[cfg(not(feature = "no_index"))]
|
#[cfg(not(feature = "no_index"))]
|
||||||
Token::LeftBracket => true,
|
Token::LeftBracket => true,
|
||||||
|
#[cfg(not(feature = "no_object"))]
|
||||||
|
Token::Period => true,
|
||||||
Token::LeftParen => true,
|
Token::LeftParen => true,
|
||||||
Token::Bang => true,
|
Token::Bang => true,
|
||||||
Token::DoubleColon => true,
|
Token::DoubleColon => true,
|
||||||
@ -1206,6 +1210,8 @@ impl Expr {
|
|||||||
Self::Property(_) => match token {
|
Self::Property(_) => match token {
|
||||||
#[cfg(not(feature = "no_index"))]
|
#[cfg(not(feature = "no_index"))]
|
||||||
Token::LeftBracket => true,
|
Token::LeftBracket => true,
|
||||||
|
#[cfg(not(feature = "no_object"))]
|
||||||
|
Token::Period => true,
|
||||||
Token::LeftParen => true,
|
Token::LeftParen => true,
|
||||||
_ => false,
|
_ => false,
|
||||||
},
|
},
|
||||||
|
@ -454,19 +454,18 @@ impl Engine {
|
|||||||
hash_script: u64,
|
hash_script: u64,
|
||||||
pub_only: bool,
|
pub_only: bool,
|
||||||
) -> bool {
|
) -> bool {
|
||||||
// NOTE: We skip script functions for global_namespace and packages, and native functions for lib
|
|
||||||
|
|
||||||
// First check script-defined functions
|
// First check script-defined functions
|
||||||
(hash_script != 0 && lib.iter().any(|&m| m.contains_fn(hash_script, pub_only)))
|
(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
|
// 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)
|
|| self.global_namespace.contains_fn(hash_fn, false)
|
||||||
// Then check packages
|
// Then check packages
|
||||||
|| self.packages.contains_fn(hash_script)
|
|| (hash_script != 0 && self.packages.contains_fn(hash_script))
|
||||||
|| self.packages.contains_fn(hash_fn)
|
|| self.packages.contains_fn(hash_fn)
|
||||||
// Then check imported modules
|
// 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.
|
/// Perform an actual function call, native Rust or scripted, taking care of special functions.
|
||||||
@ -678,14 +677,15 @@ impl Engine {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Evaluate the AST
|
// Evaluate the AST
|
||||||
let mut new_state: State = Default::default();
|
let mut new_state = State {
|
||||||
new_state.operations = state.operations;
|
operations: state.operations,
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
|
||||||
let result =
|
let result = self.eval_statements_raw(scope, mods, &mut new_state, ast.statements(), lib);
|
||||||
self.eval_statements_raw(scope, mods, &mut new_state, ast.statements(), lib)?;
|
|
||||||
|
|
||||||
state.operations = new_state.operations;
|
state.operations = new_state.operations;
|
||||||
return Ok(result);
|
result
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Call a dot method.
|
/// Call a dot method.
|
||||||
|
@ -143,6 +143,10 @@ macro_rules! gen_signed_functions {
|
|||||||
Ok(Dynamic::from(-x))
|
Ok(Dynamic::from(-x))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#[rhai_fn(name = "+")]
|
||||||
|
pub fn plus(x: $arg_type) -> $arg_type {
|
||||||
|
x
|
||||||
|
}
|
||||||
#[rhai_fn(return_raw)]
|
#[rhai_fn(return_raw)]
|
||||||
pub fn abs(x: $arg_type) -> Result<Dynamic, Box<EvalAltResult>> {
|
pub fn abs(x: $arg_type) -> Result<Dynamic, Box<EvalAltResult>> {
|
||||||
if cfg!(not(feature = "unchecked")) {
|
if cfg!(not(feature = "unchecked")) {
|
||||||
@ -251,6 +255,10 @@ mod f32_functions {
|
|||||||
pub fn neg(x: f32) -> f32 {
|
pub fn neg(x: f32) -> f32 {
|
||||||
-x
|
-x
|
||||||
}
|
}
|
||||||
|
#[rhai_fn(name = "+")]
|
||||||
|
pub fn plus(x: f32) -> f32 {
|
||||||
|
-x
|
||||||
|
}
|
||||||
pub fn abs(x: f32) -> f32 {
|
pub fn abs(x: f32) -> f32 {
|
||||||
x.abs()
|
x.abs()
|
||||||
}
|
}
|
||||||
@ -310,6 +318,10 @@ mod f64_functions {
|
|||||||
pub fn neg(x: f64) -> f64 {
|
pub fn neg(x: f64) -> f64 {
|
||||||
-x
|
-x
|
||||||
}
|
}
|
||||||
|
#[rhai_fn(name = "+")]
|
||||||
|
pub fn plus(x: f64) -> f64 {
|
||||||
|
-x
|
||||||
|
}
|
||||||
pub fn abs(x: f64) -> f64 {
|
pub fn abs(x: f64) -> f64 {
|
||||||
x.abs()
|
x.abs()
|
||||||
}
|
}
|
||||||
|
@ -1105,6 +1105,12 @@ fn parse_primary(
|
|||||||
(expr, Token::LeftBracket) => {
|
(expr, Token::LeftBracket) => {
|
||||||
parse_index_chain(input, state, lib, expr, settings.level_up())?
|
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
|
// Unknown postfix operator
|
||||||
(expr, token) => unreachable!(
|
(expr, token) => unreachable!(
|
||||||
"unknown postfix operator '{}' for {:?}",
|
"unknown postfix operator '{}' for {:?}",
|
||||||
@ -1114,20 +1120,25 @@ fn parse_primary(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Cache the hash key for namespace-qualified variables
|
||||||
match &mut root_expr {
|
match &mut root_expr {
|
||||||
// Cache the hash key for namespace-qualified variables
|
Expr::Variable(x) if x.1.is_some() => Some(x),
|
||||||
Expr::Variable(x) if x.1.is_some() => {
|
Expr::Index(x, _) | Expr::Dot(x, _) => match &mut x.lhs {
|
||||||
let (_, modules, hash, IdentX { name, .. }) = x.as_mut();
|
Expr::Variable(x) if x.1.is_some() => Some(x),
|
||||||
let namespace = modules.as_mut().unwrap();
|
_ => None,
|
||||||
|
},
|
||||||
// Qualifiers + variable name
|
_ => None,
|
||||||
*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));
|
|
||||||
}
|
|
||||||
_ => (),
|
|
||||||
}
|
}
|
||||||
|
.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
|
// Make sure identifiers are valid
|
||||||
Ok(root_expr)
|
Ok(root_expr)
|
||||||
@ -1201,8 +1212,33 @@ fn parse_unary(
|
|||||||
}
|
}
|
||||||
// +expr
|
// +expr
|
||||||
Token::UnaryPlus => {
|
Token::UnaryPlus => {
|
||||||
eat_token(input, Token::UnaryPlus);
|
let pos = eat_token(input, Token::UnaryPlus);
|
||||||
parse_unary(input, state, lib, settings.level_up())
|
|
||||||
|
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
|
// !expr
|
||||||
Token::Bang => {
|
Token::Bang => {
|
||||||
@ -1770,6 +1806,7 @@ fn parse_binary_op(
|
|||||||
make_in_expr(current_lhs, rhs, pos)?
|
make_in_expr(current_lhs, rhs, pos)?
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// This is needed to parse closure followed by a dot.
|
||||||
#[cfg(not(feature = "no_object"))]
|
#[cfg(not(feature = "no_object"))]
|
||||||
Token::Period => {
|
Token::Period => {
|
||||||
let rhs = args.pop().unwrap();
|
let rhs = args.pop().unwrap();
|
||||||
|
@ -48,6 +48,7 @@ fn test_module_sub_module() -> Result<(), Box<EvalAltResult>> {
|
|||||||
|
|
||||||
assert_eq!(engine.eval::<INT>("question::MYSTIC_NUMBER")?, 42);
|
assert_eq!(engine.eval::<INT>("question::MYSTIC_NUMBER")?, 42);
|
||||||
assert!(engine.eval::<INT>("MYSTIC_NUMBER").is_err());
|
assert!(engine.eval::<INT>("MYSTIC_NUMBER").is_err());
|
||||||
|
assert_eq!(engine.eval::<INT>("question::life::universe::answer")?, 41);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
engine.eval::<INT>("question::life::universe::answer + 1")?,
|
engine.eval::<INT>("question::life::universe::answer + 1")?,
|
||||||
42
|
42
|
||||||
@ -60,6 +61,8 @@ fn test_module_sub_module() -> Result<(), Box<EvalAltResult>> {
|
|||||||
.eval::<INT>("inc(question::life::universe::answer)")
|
.eval::<INT>("inc(question::life::universe::answer)")
|
||||||
.is_err());
|
.is_err());
|
||||||
#[cfg(not(feature = "no_object"))]
|
#[cfg(not(feature = "no_object"))]
|
||||||
|
assert_eq!(engine.eval::<INT>("question::MYSTIC_NUMBER.doubled")?, 84);
|
||||||
|
#[cfg(not(feature = "no_object"))]
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
engine.eval::<INT>("question::life::universe::answer.doubled")?,
|
engine.eval::<INT>("question::life::universe::answer.doubled")?,
|
||||||
82
|
82
|
||||||
|
Loading…
Reference in New Issue
Block a user