Map in operator to contains function call.
This commit is contained in:
parent
ff7844893d
commit
975bb3d6bf
25
src/ast.rs
25
src/ast.rs
@ -1300,7 +1300,7 @@ pub enum Expr {
|
||||
Variable(Box<(Option<NonZeroUsize>, Option<(u64, NamespaceRef)>, Ident)>),
|
||||
/// Property access - (getter, hash, setter, hash, prop)
|
||||
Property(Box<(ImmutableString, u64, ImmutableString, u64, Ident)>),
|
||||
/// { [statement][Stmt] }
|
||||
/// { [statement][Stmt] ... }
|
||||
Stmt(Box<StaticVec<Stmt>>, Position),
|
||||
/// func `(` expr `,` ... `)`
|
||||
FnCall(Box<FnCallExpr>, Position),
|
||||
@ -1308,8 +1308,6 @@ pub enum Expr {
|
||||
Dot(Box<BinaryExpr>, Position),
|
||||
/// expr `[` expr `]`
|
||||
Index(Box<BinaryExpr>, Position),
|
||||
/// lhs `in` rhs
|
||||
In(Box<BinaryExpr>, Position),
|
||||
/// lhs `&&` rhs
|
||||
And(Box<BinaryExpr>, Position),
|
||||
/// lhs `||` rhs
|
||||
@ -1397,7 +1395,7 @@ impl Expr {
|
||||
Self::Variable(x) => (x.2).pos,
|
||||
Self::FnCall(_, pos) => *pos,
|
||||
|
||||
Self::And(x, _) | Self::Or(x, _) | Self::In(x, _) => x.lhs.position(),
|
||||
Self::And(x, _) | Self::Or(x, _) => x.lhs.position(),
|
||||
|
||||
Self::Unit(pos) => *pos,
|
||||
|
||||
@ -1424,7 +1422,7 @@ impl Expr {
|
||||
Self::Property(x) => (x.4).pos = new_pos,
|
||||
Self::Stmt(_, pos) => *pos = new_pos,
|
||||
Self::FnCall(_, pos) => *pos = new_pos,
|
||||
Self::And(_, pos) | Self::Or(_, pos) | Self::In(_, pos) => *pos = new_pos,
|
||||
Self::And(_, pos) | Self::Or(_, pos) => *pos = new_pos,
|
||||
Self::Unit(pos) => *pos = new_pos,
|
||||
Self::Dot(_, pos) | Self::Index(_, pos) => *pos = new_pos,
|
||||
Self::Custom(_, pos) => *pos = new_pos,
|
||||
@ -1441,7 +1439,7 @@ impl Expr {
|
||||
|
||||
Self::Map(x, _) => x.iter().map(|(_, v)| v).all(Self::is_pure),
|
||||
|
||||
Self::Index(x, _) | Self::And(x, _) | Self::Or(x, _) | Self::In(x, _) => {
|
||||
Self::Index(x, _) | Self::And(x, _) | Self::Or(x, _) => {
|
||||
x.lhs.is_pure() && x.rhs.is_pure()
|
||||
}
|
||||
|
||||
@ -1480,13 +1478,6 @@ impl Expr {
|
||||
// An map literal is constant if all items are constant
|
||||
Self::Map(x, _) => x.iter().map(|(_, expr)| expr).all(Self::is_constant),
|
||||
|
||||
// Check in expression
|
||||
Self::In(x, _) => match (&x.lhs, &x.rhs) {
|
||||
(Self::StringConstant(_, _), Self::StringConstant(_, _))
|
||||
| (Self::CharConstant(_, _), Self::StringConstant(_, _)) => true,
|
||||
_ => false,
|
||||
},
|
||||
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
@ -1507,7 +1498,6 @@ impl Expr {
|
||||
| Self::IntegerConstant(_, _)
|
||||
| Self::CharConstant(_, _)
|
||||
| Self::FnPointer(_, _)
|
||||
| Self::In(_, _)
|
||||
| Self::And(_, _)
|
||||
| Self::Or(_, _)
|
||||
| Self::Unit(_) => false,
|
||||
@ -1553,11 +1543,7 @@ impl Expr {
|
||||
Self::Stmt(x, _) => x.iter().for_each(|s| s.walk(path, on_node)),
|
||||
Self::Array(x, _) => x.iter().for_each(|e| e.walk(path, on_node)),
|
||||
Self::Map(x, _) => x.iter().for_each(|(_, e)| e.walk(path, on_node)),
|
||||
Self::Index(x, _)
|
||||
| Self::Dot(x, _)
|
||||
| Expr::In(x, _)
|
||||
| Expr::And(x, _)
|
||||
| Expr::Or(x, _) => {
|
||||
Self::Index(x, _) | Self::Dot(x, _) | Expr::And(x, _) | Expr::Or(x, _) => {
|
||||
x.lhs.walk(path, on_node);
|
||||
x.rhs.walk(path, on_node);
|
||||
}
|
||||
@ -1582,6 +1568,7 @@ mod tests {
|
||||
assert_eq!(size_of::<Option<Dynamic>>(), 16);
|
||||
assert_eq!(size_of::<Position>(), 4);
|
||||
assert_eq!(size_of::<ast::Expr>(), 16);
|
||||
assert_eq!(size_of::<crate::ast_packed::ExprPacked>(), 32);
|
||||
assert_eq!(size_of::<Option<ast::Expr>>(), 16);
|
||||
assert_eq!(size_of::<ast::Stmt>(), 32);
|
||||
assert_eq!(size_of::<Option<ast::Stmt>>(), 32);
|
||||
|
@ -202,9 +202,15 @@ pub const FN_IDX_GET: &str = "index$get$";
|
||||
pub const FN_IDX_SET: &str = "index$set$";
|
||||
#[cfg(not(feature = "no_function"))]
|
||||
pub const FN_ANONYMOUS: &str = "anon$";
|
||||
#[cfg(any(not(feature = "no_index"), not(feature = "no_object")))]
|
||||
|
||||
/// Standard equality comparison operator.
|
||||
pub const OP_EQUALS: &str = "==";
|
||||
|
||||
/// Standard method function for containment testing.
|
||||
///
|
||||
/// The `in` operator is implemented as a call to this method.
|
||||
pub const OP_CONTAINS: &str = "contains";
|
||||
|
||||
/// Method of chaining.
|
||||
#[cfg(any(not(feature = "no_index"), not(feature = "no_object")))]
|
||||
#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
|
||||
@ -1609,78 +1615,6 @@ impl Engine {
|
||||
}
|
||||
}
|
||||
|
||||
// Evaluate an 'in' expression.
|
||||
#[inline(always)]
|
||||
fn eval_in_expr(
|
||||
&self,
|
||||
scope: &mut Scope,
|
||||
mods: &mut Imports,
|
||||
state: &mut State,
|
||||
lib: &[&Module],
|
||||
this_ptr: &mut Option<&mut Dynamic>,
|
||||
lhs: &Expr,
|
||||
rhs: &Expr,
|
||||
level: usize,
|
||||
) -> RhaiResult {
|
||||
self.inc_operations(state, rhs.position())?;
|
||||
|
||||
let lhs_value = self.eval_expr(scope, mods, state, lib, this_ptr, lhs, level)?;
|
||||
|
||||
let mut rhs_target = if rhs.get_variable_access(false).is_some() {
|
||||
let (rhs_ptr, pos) = self.search_namespace(scope, mods, state, lib, this_ptr, rhs)?;
|
||||
self.inc_operations(state, pos)?;
|
||||
rhs_ptr
|
||||
} else {
|
||||
self.eval_expr(scope, mods, state, lib, this_ptr, rhs, level)?
|
||||
.into()
|
||||
};
|
||||
|
||||
match rhs_target.as_mut() {
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
Dynamic(Union::Array(rhs_value, _)) => {
|
||||
// Call the `==` operator to compare each value
|
||||
let hash = calc_fn_hash(empty(), OP_EQUALS, 2);
|
||||
for value in rhs_value.iter_mut() {
|
||||
let args = &mut [&mut lhs_value.clone(), &mut value.clone()];
|
||||
let pos = rhs.position();
|
||||
|
||||
if self
|
||||
.call_native_fn(mods, state, lib, OP_EQUALS, hash, args, false, false, pos)?
|
||||
.0
|
||||
.as_bool()
|
||||
.unwrap_or(false)
|
||||
{
|
||||
return Ok(true.into());
|
||||
}
|
||||
}
|
||||
|
||||
Ok(false.into())
|
||||
}
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
Dynamic(Union::Map(rhs_value, _)) => {
|
||||
// Only allows string or char
|
||||
if let Ok(c) = lhs_value.as_char() {
|
||||
Ok(rhs_value.contains_key(&c.to_string()).into())
|
||||
} else if let Some(s) = lhs_value.read_lock::<ImmutableString>() {
|
||||
Ok(rhs_value.contains_key(&*s).into())
|
||||
} else {
|
||||
EvalAltResult::ErrorInExpr(lhs.position()).into()
|
||||
}
|
||||
}
|
||||
Dynamic(Union::Str(rhs_value, _)) => {
|
||||
// Only allows string or char
|
||||
if let Ok(c) = lhs_value.as_char() {
|
||||
Ok(rhs_value.contains(c).into())
|
||||
} else if let Some(s) = lhs_value.read_lock::<ImmutableString>() {
|
||||
Ok(rhs_value.contains(s.as_str()).into())
|
||||
} else {
|
||||
EvalAltResult::ErrorInExpr(lhs.position()).into()
|
||||
}
|
||||
}
|
||||
_ => EvalAltResult::ErrorInExpr(rhs.position()).into(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Evaluate an expression.
|
||||
pub(crate) fn eval_expr(
|
||||
&self,
|
||||
@ -1792,10 +1726,6 @@ impl Engine {
|
||||
)
|
||||
}
|
||||
|
||||
Expr::In(x, _) => {
|
||||
self.eval_in_expr(scope, mods, state, lib, this_ptr, &x.lhs, &x.rhs, level)
|
||||
}
|
||||
|
||||
Expr::And(x, _) => {
|
||||
Ok((self
|
||||
.eval_expr(scope, mods, state, lib, this_ptr, &x.lhs, level)?
|
||||
|
@ -1,5 +1,6 @@
|
||||
//! Built-in implementations for common operators.
|
||||
|
||||
use crate::engine::OP_CONTAINS;
|
||||
use crate::fn_native::{FnCallArgs, NativeCallContext};
|
||||
use crate::stdlib::{any::TypeId, format, string::ToString};
|
||||
use crate::{Dynamic, ImmutableString, RhaiResult, INT};
|
||||
@ -77,6 +78,13 @@ pub fn get_builtin_binary_op_fn(
|
||||
Ok((x $op y).into())
|
||||
})
|
||||
};
|
||||
($xx:ident . $func:ident ( $yy:ty )) => {
|
||||
return Some(|_, args| {
|
||||
let x = &*args[0].read_lock::<$xx>().unwrap();
|
||||
let y = &*args[1].read_lock::<$yy>().unwrap();
|
||||
Ok(x.$func(y).into())
|
||||
})
|
||||
};
|
||||
($func:ident ( $op:tt )) => {
|
||||
return Some(|_, args| {
|
||||
let (x, y) = $func(args);
|
||||
@ -259,6 +267,24 @@ pub fn get_builtin_binary_op_fn(
|
||||
">=" => impl_op!(get_s1s2(>=)),
|
||||
"<" => impl_op!(get_s1s2(<)),
|
||||
"<=" => impl_op!(get_s1s2(<=)),
|
||||
OP_CONTAINS => {
|
||||
return Some(|_, args| {
|
||||
let s = &*args[0].read_lock::<ImmutableString>().unwrap();
|
||||
let c = args[1].as_char().unwrap();
|
||||
Ok((s.contains(c)).into())
|
||||
})
|
||||
}
|
||||
_ => return None,
|
||||
}
|
||||
}
|
||||
|
||||
// map op string
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
if types_pair == (TypeId::of::<crate::Map>(), TypeId::of::<ImmutableString>()) {
|
||||
use crate::Map;
|
||||
|
||||
match op {
|
||||
OP_CONTAINS => impl_op!(Map.contains_key(ImmutableString)),
|
||||
_ => return None,
|
||||
}
|
||||
}
|
||||
@ -342,6 +368,13 @@ pub fn get_builtin_binary_op_fn(
|
||||
">=" => impl_op!(ImmutableString >= ImmutableString),
|
||||
"<" => impl_op!(ImmutableString < ImmutableString),
|
||||
"<=" => impl_op!(ImmutableString <= ImmutableString),
|
||||
OP_CONTAINS => {
|
||||
return Some(|_, args| {
|
||||
let s1 = &*args[0].read_lock::<ImmutableString>().unwrap();
|
||||
let s2 = &*args[1].read_lock::<ImmutableString>().unwrap();
|
||||
Ok((s1.contains(s2.as_str())).into())
|
||||
})
|
||||
}
|
||||
_ => return None,
|
||||
}
|
||||
}
|
||||
|
@ -831,6 +831,7 @@ impl Engine {
|
||||
}
|
||||
|
||||
/// Evaluate a text script in place - used primarily for 'eval'.
|
||||
#[inline]
|
||||
fn eval_script_expr_in_place(
|
||||
&self,
|
||||
scope: &mut Scope,
|
||||
|
@ -124,7 +124,7 @@ pub type FLOAT = f32;
|
||||
|
||||
pub use ast::{FnAccess, AST};
|
||||
pub use dynamic::Dynamic;
|
||||
pub use engine::{Engine, EvalContext};
|
||||
pub use engine::{Engine, EvalContext, OP_CONTAINS, OP_EQUALS};
|
||||
pub use fn_native::{FnPtr, NativeCallContext};
|
||||
pub use fn_register::{RegisterFn, RegisterResultFn};
|
||||
pub use module::{FnNamespace, Module};
|
||||
|
@ -15,7 +15,6 @@ use crate::stdlib::{
|
||||
vec,
|
||||
vec::Vec,
|
||||
};
|
||||
use crate::token::is_valid_identifier;
|
||||
use crate::utils::get_hasher;
|
||||
use crate::{
|
||||
calc_fn_hash, calc_fn_params_hash, combine_hashes, Dynamic, Engine, Module, Position, Scope,
|
||||
@ -598,32 +597,6 @@ fn optimize_expr(expr: &mut Expr, state: &mut State) {
|
||||
// #{ key:value, .. }
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
Expr::Map(x, _) => x.iter_mut().for_each(|(_, expr)| optimize_expr(expr, state)),
|
||||
// lhs in rhs
|
||||
Expr::In(x, _) => match (&mut x.lhs, &mut x.rhs) {
|
||||
// "xxx" in "xxxxx"
|
||||
(Expr::StringConstant(a, pos), Expr::StringConstant(b, _)) => {
|
||||
state.set_dirty();
|
||||
*expr = Expr::BoolConstant( b.contains(a.as_str()), *pos);
|
||||
}
|
||||
// 'x' in "xxxxx"
|
||||
(Expr::CharConstant(a, pos), Expr::StringConstant(b, _)) => {
|
||||
state.set_dirty();
|
||||
*expr = Expr::BoolConstant(b.contains(*a), *pos);
|
||||
}
|
||||
// "xxx" in #{...}
|
||||
(Expr::StringConstant(a, pos), Expr::Map(b, _)) => {
|
||||
state.set_dirty();
|
||||
*expr = Expr::BoolConstant(b.iter().find(|(x, _)| x.name == *a).is_some(), *pos);
|
||||
}
|
||||
// 'x' in #{...}
|
||||
(Expr::CharConstant(a, pos), Expr::Map(b, _)) => {
|
||||
state.set_dirty();
|
||||
let ch = a.to_string();
|
||||
*expr = Expr::BoolConstant(b.iter().find(|(x, _)| x.name == &ch).is_some(), *pos);
|
||||
}
|
||||
// lhs in rhs
|
||||
(lhs, rhs) => { optimize_expr(lhs, state); optimize_expr(rhs, state); }
|
||||
},
|
||||
// lhs && rhs
|
||||
Expr::And(x, _) => match (&mut x.lhs, &mut x.rhs) {
|
||||
// true && rhs -> rhs
|
||||
@ -684,7 +657,7 @@ fn optimize_expr(expr: &mut Expr, state: &mut State) {
|
||||
&& state.optimization_level == OptimizationLevel::Simple // simple optimizations
|
||||
&& x.args.len() == 2 // binary call
|
||||
&& x.args.iter().all(Expr::is_constant) // all arguments are constants
|
||||
&& !is_valid_identifier(x.name.chars()) // cannot be scripted
|
||||
//&& !is_valid_identifier(x.name.chars()) // cannot be scripted
|
||||
=> {
|
||||
let mut arg_values: StaticVec<_> = x.args.iter().map(|e| e.get_constant_value().unwrap()).collect();
|
||||
let arg_types: StaticVec<_> = arg_values.iter().map(Dynamic::type_id).collect();
|
||||
|
@ -237,11 +237,11 @@ mod array_functions {
|
||||
pub fn contains(
|
||||
ctx: NativeCallContext,
|
||||
array: &mut Array,
|
||||
mut value: Dynamic,
|
||||
value: Dynamic,
|
||||
) -> Result<Dynamic, Box<EvalAltResult>> {
|
||||
for item in array.iter() {
|
||||
for item in array.iter_mut() {
|
||||
if ctx
|
||||
.call_fn_dynamic_raw(OP_EQUALS, true, &mut [&mut value, &mut item.clone()])
|
||||
.call_fn_dynamic_raw(OP_EQUALS, true, &mut [item, &mut value.clone()])
|
||||
.or_else(|err| match *err {
|
||||
EvalAltResult::ErrorFunctionNotFound(ref fn_sig, _)
|
||||
if fn_sig.starts_with(OP_EQUALS) =>
|
||||
@ -268,11 +268,11 @@ mod array_functions {
|
||||
pub fn index_of(
|
||||
ctx: NativeCallContext,
|
||||
array: &mut Array,
|
||||
mut value: Dynamic,
|
||||
value: Dynamic,
|
||||
) -> Result<Dynamic, Box<EvalAltResult>> {
|
||||
for (i, item) in array.iter().enumerate() {
|
||||
for (i, item) in array.iter_mut().enumerate() {
|
||||
if ctx
|
||||
.call_fn_dynamic_raw(OP_EQUALS, true, &mut [&mut value, &mut item.clone()])
|
||||
.call_fn_dynamic_raw(OP_EQUALS, true, &mut [item, &mut value.clone()])
|
||||
.or_else(|err| match *err {
|
||||
EvalAltResult::ErrorFunctionNotFound(ref fn_sig, _)
|
||||
if fn_sig.starts_with(OP_EQUALS) =>
|
||||
|
@ -13,8 +13,8 @@ def_package!(crate:BasicMapPackage:"Basic object map utilities.", lib, {
|
||||
|
||||
#[export_module]
|
||||
mod map_functions {
|
||||
#[rhai_fn(pure)]
|
||||
pub fn has(map: &mut Map, prop: ImmutableString) -> bool {
|
||||
#[rhai_fn(name = "has", pure)]
|
||||
pub fn contains(map: &mut Map, prop: ImmutableString) -> bool {
|
||||
map.contains_key(&prop)
|
||||
}
|
||||
#[rhai_fn(pure)]
|
||||
|
@ -74,14 +74,6 @@ mod string_functions {
|
||||
}
|
||||
}
|
||||
|
||||
#[rhai_fn(name = "contains")]
|
||||
pub fn contains_char(string: &str, character: char) -> bool {
|
||||
string.contains(character)
|
||||
}
|
||||
pub fn contains(string: &str, find_string: &str) -> bool {
|
||||
string.contains(find_string)
|
||||
}
|
||||
|
||||
#[rhai_fn(name = "index_of")]
|
||||
pub fn index_of_char_starting_from(string: &str, character: char, start: INT) -> INT {
|
||||
let start = if start < 0 {
|
||||
|
159
src/parser.rs
159
src/parser.rs
@ -5,7 +5,7 @@ use crate::ast::{
|
||||
Stmt,
|
||||
};
|
||||
use crate::dynamic::{AccessMode, Union};
|
||||
use crate::engine::KEYWORD_THIS;
|
||||
use crate::engine::{KEYWORD_THIS, OP_CONTAINS};
|
||||
use crate::module::NamespaceRef;
|
||||
use crate::optimize::optimize_into_ast;
|
||||
use crate::optimize::OptimizationLevel;
|
||||
@ -480,7 +480,6 @@ fn parse_index_chain(
|
||||
Expr::CharConstant(_, _)
|
||||
| Expr::And(_, _)
|
||||
| Expr::Or(_, _)
|
||||
| Expr::In(_, _)
|
||||
| Expr::BoolConstant(_, _)
|
||||
| Expr::Unit(_) => {
|
||||
return Err(PERR::MalformedIndexExpr(
|
||||
@ -514,7 +513,6 @@ fn parse_index_chain(
|
||||
Expr::CharConstant(_, _)
|
||||
| Expr::And(_, _)
|
||||
| Expr::Or(_, _)
|
||||
| Expr::In(_, _)
|
||||
| Expr::BoolConstant(_, _)
|
||||
| Expr::Unit(_) => {
|
||||
return Err(PERR::MalformedIndexExpr(
|
||||
@ -548,8 +546,8 @@ fn parse_index_chain(
|
||||
)
|
||||
.into_err(x.position()))
|
||||
}
|
||||
// lhs[??? && ???], lhs[??? || ???], lhs[??? in ???]
|
||||
x @ Expr::And(_, _) | x @ Expr::Or(_, _) | x @ Expr::In(_, _) => {
|
||||
// lhs[??? && ???], lhs[??? || ???]
|
||||
x @ Expr::And(_, _) | x @ Expr::Or(_, _) => {
|
||||
return Err(PERR::MalformedIndexExpr(
|
||||
"Array access expects integer index, not a boolean".into(),
|
||||
)
|
||||
@ -1602,139 +1600,6 @@ fn make_dot_expr(
|
||||
})
|
||||
}
|
||||
|
||||
/// Make an 'in' expression.
|
||||
fn make_in_expr(lhs: Expr, rhs: Expr, op_pos: Position) -> Result<Expr, ParseError> {
|
||||
match (&lhs, &rhs) {
|
||||
(_, x @ Expr::IntegerConstant(_, _))
|
||||
| (_, x @ Expr::And(_, _))
|
||||
| (_, x @ Expr::Or(_, _))
|
||||
| (_, x @ Expr::In(_, _))
|
||||
| (_, x @ Expr::BoolConstant(_, _))
|
||||
| (_, x @ Expr::Unit(_)) => {
|
||||
return Err(PERR::MalformedInExpr(
|
||||
"'in' expression expects a string, array or object map".into(),
|
||||
)
|
||||
.into_err(x.position()))
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "no_float"))]
|
||||
(_, x @ Expr::FloatConstant(_, _)) => {
|
||||
return Err(PERR::MalformedInExpr(
|
||||
"'in' expression expects a string, array or object map".into(),
|
||||
)
|
||||
.into_err(x.position()))
|
||||
}
|
||||
|
||||
// "xxx" in "xxxx", 'x' in "xxxx" - OK!
|
||||
(Expr::StringConstant(_, _), Expr::StringConstant(_, _))
|
||||
| (Expr::CharConstant(_, _), Expr::StringConstant(_, _)) => (),
|
||||
|
||||
// 123.456 in "xxxx"
|
||||
#[cfg(not(feature = "no_float"))]
|
||||
(x @ Expr::FloatConstant(_, _), Expr::StringConstant(_, _)) => {
|
||||
return Err(PERR::MalformedInExpr(
|
||||
"'in' expression for a string expects a string, not a float".into(),
|
||||
)
|
||||
.into_err(x.position()))
|
||||
}
|
||||
// 123 in "xxxx"
|
||||
(x @ Expr::IntegerConstant(_, _), Expr::StringConstant(_, _)) => {
|
||||
return Err(PERR::MalformedInExpr(
|
||||
"'in' expression for a string expects a string, not a number".into(),
|
||||
)
|
||||
.into_err(x.position()))
|
||||
}
|
||||
// (??? && ???) in "xxxx", (??? || ???) in "xxxx", (??? in ???) in "xxxx",
|
||||
// true in "xxxx", false in "xxxx"
|
||||
(x @ Expr::And(_, _), Expr::StringConstant(_, _))
|
||||
| (x @ Expr::Or(_, _), Expr::StringConstant(_, _))
|
||||
| (x @ Expr::In(_, _), Expr::StringConstant(_, _))
|
||||
| (x @ Expr::BoolConstant(_, _), Expr::StringConstant(_, _)) => {
|
||||
return Err(PERR::MalformedInExpr(
|
||||
"'in' expression for a string expects a string, not a boolean".into(),
|
||||
)
|
||||
.into_err(x.position()))
|
||||
}
|
||||
// [???, ???, ???] in "xxxx"
|
||||
(x @ Expr::Array(_, _), Expr::StringConstant(_, _)) => {
|
||||
return Err(PERR::MalformedInExpr(
|
||||
"'in' expression for a string expects a string, not an array".into(),
|
||||
)
|
||||
.into_err(x.position()))
|
||||
}
|
||||
// #{...} in "xxxx"
|
||||
(x @ Expr::Map(_, _), Expr::StringConstant(_, _)) => {
|
||||
return Err(PERR::MalformedInExpr(
|
||||
"'in' expression for a string expects a string, not an object map".into(),
|
||||
)
|
||||
.into_err(x.position()))
|
||||
}
|
||||
// () in "xxxx"
|
||||
(x @ Expr::Unit(_), Expr::StringConstant(_, _)) => {
|
||||
return Err(PERR::MalformedInExpr(
|
||||
"'in' expression for a string expects a string, not ()".into(),
|
||||
)
|
||||
.into_err(x.position()))
|
||||
}
|
||||
|
||||
// "xxx" in #{...}, 'x' in #{...} - OK!
|
||||
(Expr::StringConstant(_, _), Expr::Map(_, _))
|
||||
| (Expr::CharConstant(_, _), Expr::Map(_, _)) => (),
|
||||
|
||||
// 123.456 in #{...}
|
||||
#[cfg(not(feature = "no_float"))]
|
||||
(x @ Expr::FloatConstant(_, _), Expr::Map(_, _)) => {
|
||||
return Err(PERR::MalformedInExpr(
|
||||
"'in' expression for an object map expects a string, not a float".into(),
|
||||
)
|
||||
.into_err(x.position()))
|
||||
}
|
||||
// 123 in #{...}
|
||||
(x @ Expr::IntegerConstant(_, _), Expr::Map(_, _)) => {
|
||||
return Err(PERR::MalformedInExpr(
|
||||
"'in' expression for an object map expects a string, not a number".into(),
|
||||
)
|
||||
.into_err(x.position()))
|
||||
}
|
||||
// (??? && ???) in #{...}, (??? || ???) in #{...}, (??? in ???) in #{...},
|
||||
// true in #{...}, false in #{...}
|
||||
(x @ Expr::And(_, _), Expr::Map(_, _))
|
||||
| (x @ Expr::Or(_, _), Expr::Map(_, _))
|
||||
| (x @ Expr::In(_, _), Expr::Map(_, _))
|
||||
| (x @ Expr::BoolConstant(_, _), Expr::Map(_, _)) => {
|
||||
return Err(PERR::MalformedInExpr(
|
||||
"'in' expression for an object map expects a string, not a boolean".into(),
|
||||
)
|
||||
.into_err(x.position()))
|
||||
}
|
||||
// [???, ???, ???] in #{..}
|
||||
(x @ Expr::Array(_, _), Expr::Map(_, _)) => {
|
||||
return Err(PERR::MalformedInExpr(
|
||||
"'in' expression for an object map expects a string, not an array".into(),
|
||||
)
|
||||
.into_err(x.position()))
|
||||
}
|
||||
// #{...} in #{..}
|
||||
(x @ Expr::Map(_, _), Expr::Map(_, _)) => {
|
||||
return Err(PERR::MalformedInExpr(
|
||||
"'in' expression for an object map expects a string, not an object map".into(),
|
||||
)
|
||||
.into_err(x.position()))
|
||||
}
|
||||
// () in #{...}
|
||||
(x @ Expr::Unit(_), Expr::Map(_, _)) => {
|
||||
return Err(PERR::MalformedInExpr(
|
||||
"'in' expression for an object map expects a string, not ()".into(),
|
||||
)
|
||||
.into_err(x.position()))
|
||||
}
|
||||
|
||||
_ => (),
|
||||
}
|
||||
|
||||
Ok(Expr::In(Box::new(BinaryExpr { lhs, rhs }), op_pos))
|
||||
}
|
||||
|
||||
/// Parse a binary expression.
|
||||
fn parse_binary_op(
|
||||
input: &mut TokenStream,
|
||||
@ -1880,9 +1745,21 @@ fn parse_binary_op(
|
||||
)
|
||||
}
|
||||
Token::In => {
|
||||
let rhs = args.pop().unwrap();
|
||||
let current_lhs = args.pop().unwrap();
|
||||
make_in_expr(current_lhs, rhs, pos)?
|
||||
// Swap the arguments
|
||||
let current_lhs = args.remove(0);
|
||||
args.push(current_lhs);
|
||||
|
||||
// Convert into a call to `contains`
|
||||
let hash = calc_fn_hash(empty(), OP_CONTAINS, 2);
|
||||
Expr::FnCall(
|
||||
Box::new(FnCallExpr {
|
||||
hash: FnHash::from_script(hash),
|
||||
args,
|
||||
name: OP_CONTAINS.into(),
|
||||
..op_base
|
||||
}),
|
||||
pos,
|
||||
)
|
||||
}
|
||||
|
||||
Token::Custom(s)
|
||||
|
@ -38,7 +38,7 @@ fn test_map_indexing() -> Result<(), Box<EvalAltResult>> {
|
||||
engine.eval::<()>("let y = #{a: 1, b: 2, c: 3}; y.z")?;
|
||||
|
||||
assert!(engine.eval::<bool>(r#"let y = #{a: 1, b: 2, c: 3}; "c" in y"#)?);
|
||||
assert!(engine.eval::<bool>("let y = #{a: 1, b: 2, c: 3}; 'b' in y")?);
|
||||
assert!(engine.eval::<bool>(r#"let y = #{a: 1, b: 2, c: 3}; "b" in y"#)?);
|
||||
assert!(!engine.eval::<bool>(r#"let y = #{a: 1, b: 2, c: 3}; "z" in y"#)?);
|
||||
|
||||
assert_eq!(
|
||||
|
Loading…
Reference in New Issue
Block a user