Map in operator to contains function call.

This commit is contained in:
Stephen Chung 2021-03-09 13:44:54 +08:00
parent ff7844893d
commit 975bb3d6bf
11 changed files with 76 additions and 283 deletions

View File

@ -1300,7 +1300,7 @@ pub enum Expr {
Variable(Box<(Option<NonZeroUsize>, Option<(u64, NamespaceRef)>, Ident)>), Variable(Box<(Option<NonZeroUsize>, Option<(u64, NamespaceRef)>, Ident)>),
/// Property access - (getter, hash, setter, hash, prop) /// Property access - (getter, hash, setter, hash, prop)
Property(Box<(ImmutableString, u64, ImmutableString, u64, Ident)>), Property(Box<(ImmutableString, u64, ImmutableString, u64, Ident)>),
/// { [statement][Stmt] } /// { [statement][Stmt] ... }
Stmt(Box<StaticVec<Stmt>>, Position), Stmt(Box<StaticVec<Stmt>>, Position),
/// func `(` expr `,` ... `)` /// func `(` expr `,` ... `)`
FnCall(Box<FnCallExpr>, Position), FnCall(Box<FnCallExpr>, Position),
@ -1308,8 +1308,6 @@ pub enum Expr {
Dot(Box<BinaryExpr>, Position), Dot(Box<BinaryExpr>, Position),
/// expr `[` expr `]` /// expr `[` expr `]`
Index(Box<BinaryExpr>, Position), Index(Box<BinaryExpr>, Position),
/// lhs `in` rhs
In(Box<BinaryExpr>, Position),
/// lhs `&&` rhs /// lhs `&&` rhs
And(Box<BinaryExpr>, Position), And(Box<BinaryExpr>, Position),
/// lhs `||` rhs /// lhs `||` rhs
@ -1397,7 +1395,7 @@ impl Expr {
Self::Variable(x) => (x.2).pos, Self::Variable(x) => (x.2).pos,
Self::FnCall(_, pos) => *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, Self::Unit(pos) => *pos,
@ -1424,7 +1422,7 @@ impl Expr {
Self::Property(x) => (x.4).pos = new_pos, Self::Property(x) => (x.4).pos = new_pos,
Self::Stmt(_, pos) => *pos = new_pos, Self::Stmt(_, pos) => *pos = new_pos,
Self::FnCall(_, 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::Unit(pos) => *pos = new_pos,
Self::Dot(_, pos) | Self::Index(_, pos) => *pos = new_pos, Self::Dot(_, pos) | Self::Index(_, pos) => *pos = new_pos,
Self::Custom(_, 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::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() x.lhs.is_pure() && x.rhs.is_pure()
} }
@ -1480,13 +1478,6 @@ impl Expr {
// An map literal is constant if all items are constant // An map literal is constant if all items are constant
Self::Map(x, _) => x.iter().map(|(_, expr)| expr).all(Self::is_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, _ => false,
} }
} }
@ -1507,7 +1498,6 @@ impl Expr {
| Self::IntegerConstant(_, _) | Self::IntegerConstant(_, _)
| Self::CharConstant(_, _) | Self::CharConstant(_, _)
| Self::FnPointer(_, _) | Self::FnPointer(_, _)
| Self::In(_, _)
| Self::And(_, _) | Self::And(_, _)
| Self::Or(_, _) | Self::Or(_, _)
| Self::Unit(_) => false, | Self::Unit(_) => false,
@ -1553,11 +1543,7 @@ impl Expr {
Self::Stmt(x, _) => x.iter().for_each(|s| s.walk(path, on_node)), 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::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::Map(x, _) => x.iter().for_each(|(_, e)| e.walk(path, on_node)),
Self::Index(x, _) Self::Index(x, _) | Self::Dot(x, _) | Expr::And(x, _) | Expr::Or(x, _) => {
| Self::Dot(x, _)
| Expr::In(x, _)
| Expr::And(x, _)
| Expr::Or(x, _) => {
x.lhs.walk(path, on_node); x.lhs.walk(path, on_node);
x.rhs.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::<Option<Dynamic>>(), 16);
assert_eq!(size_of::<Position>(), 4); assert_eq!(size_of::<Position>(), 4);
assert_eq!(size_of::<ast::Expr>(), 16); 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::<Option<ast::Expr>>(), 16);
assert_eq!(size_of::<ast::Stmt>(), 32); assert_eq!(size_of::<ast::Stmt>(), 32);
assert_eq!(size_of::<Option<ast::Stmt>>(), 32); assert_eq!(size_of::<Option<ast::Stmt>>(), 32);

View File

@ -202,9 +202,15 @@ pub const FN_IDX_GET: &str = "index$get$";
pub const FN_IDX_SET: &str = "index$set$"; pub const FN_IDX_SET: &str = "index$set$";
#[cfg(not(feature = "no_function"))] #[cfg(not(feature = "no_function"))]
pub const FN_ANONYMOUS: &str = "anon$"; 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 = "=="; 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. /// Method of chaining.
#[cfg(any(not(feature = "no_index"), not(feature = "no_object")))] #[cfg(any(not(feature = "no_index"), not(feature = "no_object")))]
#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)] #[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. /// Evaluate an expression.
pub(crate) fn eval_expr( pub(crate) fn eval_expr(
&self, &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, _) => { Expr::And(x, _) => {
Ok((self Ok((self
.eval_expr(scope, mods, state, lib, this_ptr, &x.lhs, level)? .eval_expr(scope, mods, state, lib, this_ptr, &x.lhs, level)?

View File

@ -1,5 +1,6 @@
//! Built-in implementations for common operators. //! Built-in implementations for common operators.
use crate::engine::OP_CONTAINS;
use crate::fn_native::{FnCallArgs, NativeCallContext}; use crate::fn_native::{FnCallArgs, NativeCallContext};
use crate::stdlib::{any::TypeId, format, string::ToString}; use crate::stdlib::{any::TypeId, format, string::ToString};
use crate::{Dynamic, ImmutableString, RhaiResult, INT}; use crate::{Dynamic, ImmutableString, RhaiResult, INT};
@ -77,6 +78,13 @@ pub fn get_builtin_binary_op_fn(
Ok((x $op y).into()) 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 )) => { ($func:ident ( $op:tt )) => {
return Some(|_, args| { return Some(|_, args| {
let (x, y) = $func(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(<)), "<" => 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, _ => 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), "<" => 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, _ => return None,
} }
} }

View File

@ -831,6 +831,7 @@ impl Engine {
} }
/// Evaluate a text script in place - used primarily for 'eval'. /// Evaluate a text script in place - used primarily for 'eval'.
#[inline]
fn eval_script_expr_in_place( fn eval_script_expr_in_place(
&self, &self,
scope: &mut Scope, scope: &mut Scope,

View File

@ -124,7 +124,7 @@ pub type FLOAT = f32;
pub use ast::{FnAccess, AST}; pub use ast::{FnAccess, AST};
pub use dynamic::Dynamic; 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_native::{FnPtr, NativeCallContext};
pub use fn_register::{RegisterFn, RegisterResultFn}; pub use fn_register::{RegisterFn, RegisterResultFn};
pub use module::{FnNamespace, Module}; pub use module::{FnNamespace, Module};

View File

@ -15,7 +15,6 @@ use crate::stdlib::{
vec, vec,
vec::Vec, vec::Vec,
}; };
use crate::token::is_valid_identifier;
use crate::utils::get_hasher; use crate::utils::get_hasher;
use crate::{ use crate::{
calc_fn_hash, calc_fn_params_hash, combine_hashes, Dynamic, Engine, Module, Position, Scope, 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, .. } // #{ key:value, .. }
#[cfg(not(feature = "no_object"))] #[cfg(not(feature = "no_object"))]
Expr::Map(x, _) => x.iter_mut().for_each(|(_, expr)| optimize_expr(expr, state)), 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 // lhs && rhs
Expr::And(x, _) => match (&mut x.lhs, &mut x.rhs) { Expr::And(x, _) => match (&mut x.lhs, &mut x.rhs) {
// true && rhs -> rhs // true && rhs -> rhs
@ -684,7 +657,7 @@ fn optimize_expr(expr: &mut Expr, state: &mut State) {
&& state.optimization_level == OptimizationLevel::Simple // simple optimizations && state.optimization_level == OptimizationLevel::Simple // simple optimizations
&& x.args.len() == 2 // binary call && x.args.len() == 2 // binary call
&& x.args.iter().all(Expr::is_constant) // all arguments are constants && 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 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(); let arg_types: StaticVec<_> = arg_values.iter().map(Dynamic::type_id).collect();

View File

@ -237,11 +237,11 @@ mod array_functions {
pub fn contains( pub fn contains(
ctx: NativeCallContext, ctx: NativeCallContext,
array: &mut Array, array: &mut Array,
mut value: Dynamic, value: Dynamic,
) -> Result<Dynamic, Box<EvalAltResult>> { ) -> Result<Dynamic, Box<EvalAltResult>> {
for item in array.iter() { for item in array.iter_mut() {
if ctx 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 { .or_else(|err| match *err {
EvalAltResult::ErrorFunctionNotFound(ref fn_sig, _) EvalAltResult::ErrorFunctionNotFound(ref fn_sig, _)
if fn_sig.starts_with(OP_EQUALS) => if fn_sig.starts_with(OP_EQUALS) =>
@ -268,11 +268,11 @@ mod array_functions {
pub fn index_of( pub fn index_of(
ctx: NativeCallContext, ctx: NativeCallContext,
array: &mut Array, array: &mut Array,
mut value: Dynamic, value: Dynamic,
) -> Result<Dynamic, Box<EvalAltResult>> { ) -> Result<Dynamic, Box<EvalAltResult>> {
for (i, item) in array.iter().enumerate() { for (i, item) in array.iter_mut().enumerate() {
if ctx 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 { .or_else(|err| match *err {
EvalAltResult::ErrorFunctionNotFound(ref fn_sig, _) EvalAltResult::ErrorFunctionNotFound(ref fn_sig, _)
if fn_sig.starts_with(OP_EQUALS) => if fn_sig.starts_with(OP_EQUALS) =>

View File

@ -13,8 +13,8 @@ def_package!(crate:BasicMapPackage:"Basic object map utilities.", lib, {
#[export_module] #[export_module]
mod map_functions { mod map_functions {
#[rhai_fn(pure)] #[rhai_fn(name = "has", pure)]
pub fn has(map: &mut Map, prop: ImmutableString) -> bool { pub fn contains(map: &mut Map, prop: ImmutableString) -> bool {
map.contains_key(&prop) map.contains_key(&prop)
} }
#[rhai_fn(pure)] #[rhai_fn(pure)]

View File

@ -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")] #[rhai_fn(name = "index_of")]
pub fn index_of_char_starting_from(string: &str, character: char, start: INT) -> INT { pub fn index_of_char_starting_from(string: &str, character: char, start: INT) -> INT {
let start = if start < 0 { let start = if start < 0 {

View File

@ -5,7 +5,7 @@ use crate::ast::{
Stmt, Stmt,
}; };
use crate::dynamic::{AccessMode, Union}; use crate::dynamic::{AccessMode, Union};
use crate::engine::KEYWORD_THIS; use crate::engine::{KEYWORD_THIS, OP_CONTAINS};
use crate::module::NamespaceRef; use crate::module::NamespaceRef;
use crate::optimize::optimize_into_ast; use crate::optimize::optimize_into_ast;
use crate::optimize::OptimizationLevel; use crate::optimize::OptimizationLevel;
@ -480,7 +480,6 @@ fn parse_index_chain(
Expr::CharConstant(_, _) Expr::CharConstant(_, _)
| Expr::And(_, _) | Expr::And(_, _)
| Expr::Or(_, _) | Expr::Or(_, _)
| Expr::In(_, _)
| Expr::BoolConstant(_, _) | Expr::BoolConstant(_, _)
| Expr::Unit(_) => { | Expr::Unit(_) => {
return Err(PERR::MalformedIndexExpr( return Err(PERR::MalformedIndexExpr(
@ -514,7 +513,6 @@ fn parse_index_chain(
Expr::CharConstant(_, _) Expr::CharConstant(_, _)
| Expr::And(_, _) | Expr::And(_, _)
| Expr::Or(_, _) | Expr::Or(_, _)
| Expr::In(_, _)
| Expr::BoolConstant(_, _) | Expr::BoolConstant(_, _)
| Expr::Unit(_) => { | Expr::Unit(_) => {
return Err(PERR::MalformedIndexExpr( return Err(PERR::MalformedIndexExpr(
@ -548,8 +546,8 @@ fn parse_index_chain(
) )
.into_err(x.position())) .into_err(x.position()))
} }
// lhs[??? && ???], lhs[??? || ???], lhs[??? in ???] // lhs[??? && ???], lhs[??? || ???]
x @ Expr::And(_, _) | x @ Expr::Or(_, _) | x @ Expr::In(_, _) => { x @ Expr::And(_, _) | x @ Expr::Or(_, _) => {
return Err(PERR::MalformedIndexExpr( return Err(PERR::MalformedIndexExpr(
"Array access expects integer index, not a boolean".into(), "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. /// Parse a binary expression.
fn parse_binary_op( fn parse_binary_op(
input: &mut TokenStream, input: &mut TokenStream,
@ -1880,9 +1745,21 @@ fn parse_binary_op(
) )
} }
Token::In => { Token::In => {
let rhs = args.pop().unwrap(); // Swap the arguments
let current_lhs = args.pop().unwrap(); let current_lhs = args.remove(0);
make_in_expr(current_lhs, rhs, pos)? 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) Token::Custom(s)

View File

@ -38,7 +38,7 @@ fn test_map_indexing() -> Result<(), Box<EvalAltResult>> {
engine.eval::<()>("let y = #{a: 1, b: 2, c: 3}; y.z")?; 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>(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!(!engine.eval::<bool>(r#"let y = #{a: 1, b: 2, c: 3}; "z" in y"#)?);
assert_eq!( assert_eq!(