Add 'in' expression.
This commit is contained in:
parent
32672b184b
commit
e204ae1a2c
11
README.md
11
README.md
@ -1039,6 +1039,11 @@ record == "Bob C. Davis: age 42 ❤\n"; // '\n' = new-line
|
||||
// (disabled with 'no_index')
|
||||
record[4] = '\x58'; // 0x58 = 'X'
|
||||
record == "Bob X. Davis: age 42 ❤\n";
|
||||
|
||||
// Use 'in' to test if a substring (or character) exists in a string
|
||||
"Davis" in record == true;
|
||||
'X' in record == true;
|
||||
'C' in record == false;
|
||||
```
|
||||
|
||||
The following standard functions (defined in the standard library but excluded if [`no_stdlib`]) operate on strings:
|
||||
@ -1114,6 +1119,9 @@ Examples:
|
||||
let y = [1, 2, 3]; // array literal with 3 elements
|
||||
y[1] = 42;
|
||||
|
||||
print(1 in y); // use 'in' to test if an item exists in the array, prints true
|
||||
print(9 in y); // ... prints false
|
||||
|
||||
print(y[1]); // prints 42
|
||||
|
||||
ts.list = y; // arrays can be assigned completely (by value copy)
|
||||
@ -1219,6 +1227,9 @@ print(y.a); // prints 42
|
||||
|
||||
print(y["baz!$@"]); // prints 123.456 - access via index notation
|
||||
|
||||
print("baz!$@" in y); // use 'in' to test if a property exists in the object map, prints true
|
||||
print("z" in y); // ... prints false
|
||||
|
||||
ts.obj = y; // object maps can be assigned completely (by value copy)
|
||||
let foo = ts.list.a;
|
||||
foo == 42;
|
||||
|
104
src/engine.rs
104
src/engine.rs
@ -290,6 +290,7 @@ impl Default for Engine<'_> {
|
||||
(type_name::<Map>(), "map"),
|
||||
(type_name::<String>(), "string"),
|
||||
(type_name::<Dynamic>(), "dynamic"),
|
||||
(type_name::<Variant>(), "variant"),
|
||||
]
|
||||
.iter()
|
||||
.map(|(k, v)| (k.to_string(), v.to_string()))
|
||||
@ -416,7 +417,7 @@ impl Engine<'_> {
|
||||
) -> Result<Option<Dynamic>, EvalAltResult> {
|
||||
let spec = FnSpec {
|
||||
name: fn_name.into(),
|
||||
args: args.iter().map(|a| Any::type_id(&**a)).collect(),
|
||||
args: args.iter().map(|a| Any::type_id(*a)).collect(),
|
||||
};
|
||||
|
||||
// Search built-in's and external functions
|
||||
@ -501,7 +502,7 @@ impl Engine<'_> {
|
||||
|
||||
let spec = FnSpec {
|
||||
name: fn_name.into(),
|
||||
args: args.iter().map(|a| Any::type_id(&**a)).collect(),
|
||||
args: args.iter().map(|a| Any::type_id(*a)).collect(),
|
||||
};
|
||||
|
||||
// Argument must be a string
|
||||
@ -1179,6 +1180,85 @@ impl Engine<'_> {
|
||||
}
|
||||
}
|
||||
|
||||
// Evaluate an 'in' expression
|
||||
fn eval_in_expr(
|
||||
&mut self,
|
||||
scope: &mut Scope,
|
||||
lhs: &Expr,
|
||||
rhs: &Expr,
|
||||
level: usize,
|
||||
) -> Result<Dynamic, EvalAltResult> {
|
||||
let mut lhs_value = self.eval_expr(scope, lhs, level)?;
|
||||
let rhs_value = self.eval_expr(scope, rhs, level)?;
|
||||
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
{
|
||||
if rhs_value.is::<Array>() {
|
||||
let mut rhs_value = rhs_value.cast::<Array>();
|
||||
let def_value = false.into_dynamic();
|
||||
let mut result = false;
|
||||
|
||||
// Call the '==' operator to compare each value
|
||||
for value in rhs_value.iter_mut() {
|
||||
if self
|
||||
.call_fn_raw(
|
||||
None,
|
||||
"==",
|
||||
&mut [lhs_value.as_mut(), value.as_mut()],
|
||||
Some(&def_value),
|
||||
rhs.position(),
|
||||
level,
|
||||
)?
|
||||
.try_cast::<bool>()
|
||||
.unwrap_or(false)
|
||||
{
|
||||
result = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return Ok(result.into_dynamic());
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
{
|
||||
if rhs_value.is::<Map>() {
|
||||
let rhs_value = rhs_value.cast::<Map>();
|
||||
|
||||
// Only allows String or char
|
||||
return if lhs_value.is::<String>() {
|
||||
Ok(rhs_value
|
||||
.contains_key(&lhs_value.cast::<String>())
|
||||
.into_dynamic())
|
||||
} else if lhs_value.is::<char>() {
|
||||
Ok(rhs_value
|
||||
.contains_key(&lhs_value.cast::<char>().to_string())
|
||||
.into_dynamic())
|
||||
} else {
|
||||
Err(EvalAltResult::ErrorInExpr(lhs.position()))
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
if rhs_value.is::<String>() {
|
||||
let rhs_value = rhs_value.cast::<String>();
|
||||
|
||||
// Only allows String or char
|
||||
return if lhs_value.is::<String>() {
|
||||
Ok(rhs_value
|
||||
.contains(&lhs_value.cast::<String>())
|
||||
.into_dynamic())
|
||||
} else if lhs_value.is::<char>() {
|
||||
Ok(rhs_value.contains(lhs_value.cast::<char>()).into_dynamic())
|
||||
} else {
|
||||
Err(EvalAltResult::ErrorInExpr(lhs.position()))
|
||||
};
|
||||
}
|
||||
|
||||
return Err(EvalAltResult::ErrorInExpr(rhs.position()));
|
||||
}
|
||||
|
||||
/// Evaluate an expression
|
||||
fn eval_expr(
|
||||
&mut self,
|
||||
@ -1302,7 +1382,7 @@ impl Engine<'_> {
|
||||
self.eval_expr(scope, item, level).map(|val| arr.push(val))
|
||||
})?;
|
||||
|
||||
Ok(Box::new(arr))
|
||||
Ok((arr).into_dynamic())
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
@ -1315,7 +1395,7 @@ impl Engine<'_> {
|
||||
})
|
||||
})?;
|
||||
|
||||
Ok(Box::new(map))
|
||||
Ok((map).into_dynamic())
|
||||
}
|
||||
|
||||
Expr::FunctionCall(fn_name, args_expr_list, def_val, pos) => {
|
||||
@ -1445,32 +1525,34 @@ impl Engine<'_> {
|
||||
}
|
||||
}
|
||||
|
||||
Expr::And(lhs, rhs) => Ok(Box::new(
|
||||
Expr::In(lhs, rhs, _) => self.eval_in_expr(scope, lhs.as_ref(), rhs.as_ref(), level),
|
||||
|
||||
Expr::And(lhs, rhs, _) => Ok(Box::new(
|
||||
self
|
||||
.eval_expr(scope, &*lhs, level)?
|
||||
.eval_expr(scope, lhs.as_ref(), level)?
|
||||
.try_cast::<bool>()
|
||||
.map_err(|_| {
|
||||
EvalAltResult::ErrorBooleanArgMismatch("AND".into(), lhs.position())
|
||||
})?
|
||||
&& // Short-circuit using &&
|
||||
self
|
||||
.eval_expr(scope, &*rhs, level)?
|
||||
.eval_expr(scope, rhs.as_ref(), level)?
|
||||
.try_cast::<bool>()
|
||||
.map_err(|_| {
|
||||
EvalAltResult::ErrorBooleanArgMismatch("AND".into(), rhs.position())
|
||||
})?,
|
||||
)),
|
||||
|
||||
Expr::Or(lhs, rhs) => Ok(Box::new(
|
||||
Expr::Or(lhs, rhs, _) => Ok(Box::new(
|
||||
self
|
||||
.eval_expr(scope, &*lhs, level)?
|
||||
.eval_expr(scope, lhs.as_ref(), level)?
|
||||
.try_cast::<bool>()
|
||||
.map_err(|_| {
|
||||
EvalAltResult::ErrorBooleanArgMismatch("OR".into(), lhs.position())
|
||||
})?
|
||||
|| // Short-circuit using ||
|
||||
self
|
||||
.eval_expr(scope, &*rhs, level)?
|
||||
.eval_expr(scope, rhs.as_ref(), level)?
|
||||
.try_cast::<bool>()
|
||||
.map_err(|_| {
|
||||
EvalAltResult::ErrorBooleanArgMismatch("OR".into(), rhs.position())
|
||||
@ -1559,7 +1641,7 @@ impl Engine<'_> {
|
||||
// For loop
|
||||
Stmt::For(name, expr, body) => {
|
||||
let arr = self.eval_expr(scope, expr, level)?;
|
||||
let tid = Any::type_id(&*arr);
|
||||
let tid = Any::type_id(arr.as_ref());
|
||||
|
||||
if let Some(type_iterators) = &self.type_iterators {
|
||||
if let Some(iter_fn) = type_iterators.get(&tid) {
|
||||
|
@ -54,6 +54,8 @@ pub enum ParseErrorType {
|
||||
/// Not available under the `no_index` feature.
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
MalformedIndexExpr(String),
|
||||
/// An expression in an `in` expression has syntax error. Wrapped value is the error description (if any).
|
||||
MalformedInExpr(String),
|
||||
/// A map definition has duplicated property names. Wrapped value is the property name.
|
||||
///
|
||||
/// Not available under the `no_object` feature.
|
||||
@ -138,6 +140,7 @@ impl ParseError {
|
||||
ParseErrorType::MalformedCallExpr(_) => "Invalid expression in function call arguments",
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
ParseErrorType::MalformedIndexExpr(_) => "Invalid index in indexing expression",
|
||||
ParseErrorType::MalformedInExpr(_) => "Invalid 'in' expression",
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
ParseErrorType::DuplicatedProperty(_) => "Duplicated property in object map literal",
|
||||
ParseErrorType::ForbiddenConstantExpr(_) => "Expecting a constant",
|
||||
@ -180,6 +183,10 @@ impl fmt::Display for ParseError {
|
||||
write!(f, "{}", if s.is_empty() { self.desc() } else { s })?
|
||||
}
|
||||
|
||||
ParseErrorType::MalformedInExpr(s) => {
|
||||
write!(f, "{}", if s.is_empty() { self.desc() } else { s })?
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
ParseErrorType::DuplicatedProperty(s) => {
|
||||
write!(f, "Duplicated property '{}' for object map literal", s)?
|
||||
|
@ -392,8 +392,55 @@ fn optimize_expr<'a>(expr: Expr, state: &mut State<'a>) -> Expr {
|
||||
.into_iter()
|
||||
.map(|(key, expr, pos)| (key, optimize_expr(expr, state), pos))
|
||||
.collect(), pos),
|
||||
// lhs in rhs
|
||||
Expr::In(lhs, rhs, pos) => match (*lhs, *rhs) {
|
||||
// "xxx" in "xxxxx"
|
||||
(Expr::StringConstant(lhs, pos), Expr::StringConstant(rhs, _)) => {
|
||||
state.set_dirty();
|
||||
if rhs.contains(lhs.as_ref()) {
|
||||
Expr::True(pos)
|
||||
} else {
|
||||
Expr::False(pos)
|
||||
}
|
||||
}
|
||||
// 'x' in "xxxxx"
|
||||
(Expr::CharConstant(lhs, pos), Expr::StringConstant(rhs, _)) => {
|
||||
state.set_dirty();
|
||||
if rhs.contains(&lhs.to_string()) {
|
||||
Expr::True(pos)
|
||||
} else {
|
||||
Expr::False(pos)
|
||||
}
|
||||
}
|
||||
// "xxx" in #{...}
|
||||
(Expr::StringConstant(lhs, pos), Expr::Map(items, _)) => {
|
||||
state.set_dirty();
|
||||
if items.iter().find(|(name, _, _)| name == &lhs).is_some() {
|
||||
Expr::True(pos)
|
||||
} else {
|
||||
Expr::False(pos)
|
||||
}
|
||||
}
|
||||
// 'x' in #{...}
|
||||
(Expr::CharConstant(lhs, pos), Expr::Map(items, _)) => {
|
||||
state.set_dirty();
|
||||
let lhs = lhs.to_string();
|
||||
|
||||
if items.iter().find(|(name, _, _)| name == &lhs).is_some() {
|
||||
Expr::True(pos)
|
||||
} else {
|
||||
Expr::False(pos)
|
||||
}
|
||||
}
|
||||
// lhs in rhs
|
||||
(lhs, rhs) => Expr::In(
|
||||
Box::new(optimize_expr(lhs, state)),
|
||||
Box::new(optimize_expr(rhs, state)),
|
||||
pos
|
||||
),
|
||||
},
|
||||
// lhs && rhs
|
||||
Expr::And(lhs, rhs) => match (*lhs, *rhs) {
|
||||
Expr::And(lhs, rhs, pos) => match (*lhs, *rhs) {
|
||||
// true && rhs -> rhs
|
||||
(Expr::True(_), rhs) => {
|
||||
state.set_dirty();
|
||||
@ -413,10 +460,11 @@ fn optimize_expr<'a>(expr: Expr, state: &mut State<'a>) -> Expr {
|
||||
(lhs, rhs) => Expr::And(
|
||||
Box::new(optimize_expr(lhs, state)),
|
||||
Box::new(optimize_expr(rhs, state)),
|
||||
pos
|
||||
),
|
||||
},
|
||||
// lhs || rhs
|
||||
Expr::Or(lhs, rhs) => match (*lhs, *rhs) {
|
||||
Expr::Or(lhs, rhs, pos) => match (*lhs, *rhs) {
|
||||
// false || rhs -> rhs
|
||||
(Expr::False(_), rhs) => {
|
||||
state.set_dirty();
|
||||
@ -436,6 +484,7 @@ fn optimize_expr<'a>(expr: Expr, state: &mut State<'a>) -> Expr {
|
||||
(lhs, rhs) => Expr::Or(
|
||||
Box::new(optimize_expr(lhs, state)),
|
||||
Box::new(optimize_expr(rhs, state)),
|
||||
pos
|
||||
),
|
||||
},
|
||||
|
||||
|
404
src/parser.rs
404
src/parser.rs
@ -420,16 +420,18 @@ pub enum Expr {
|
||||
/// expr[expr]
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
Index(Box<Expr>, Box<Expr>, Position),
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
/// [ expr, ... ]
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
Array(Vec<Expr>, Position),
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
/// #{ name:expr, ... }
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
Map(Vec<(String, Expr, Position)>, Position),
|
||||
/// lhs in rhs
|
||||
In(Box<Expr>, Box<Expr>, Position),
|
||||
/// lhs && rhs
|
||||
And(Box<Expr>, Box<Expr>),
|
||||
And(Box<Expr>, Box<Expr>, Position),
|
||||
/// lhs || rhs
|
||||
Or(Box<Expr>, Box<Expr>),
|
||||
Or(Box<Expr>, Box<Expr>, Position),
|
||||
/// true
|
||||
True(Position),
|
||||
/// false
|
||||
@ -446,29 +448,29 @@ impl Expr {
|
||||
/// Panics when the expression is not constant.
|
||||
pub fn get_constant_value(&self) -> Dynamic {
|
||||
match self {
|
||||
Expr::IntegerConstant(i, _) => i.into_dynamic(),
|
||||
Expr::CharConstant(c, _) => c.into_dynamic(),
|
||||
Expr::StringConstant(s, _) => s.clone().into_owned().into_dynamic(),
|
||||
Expr::True(_) => true.into_dynamic(),
|
||||
Expr::False(_) => false.into_dynamic(),
|
||||
Expr::Unit(_) => ().into_dynamic(),
|
||||
Self::IntegerConstant(i, _) => i.into_dynamic(),
|
||||
Self::CharConstant(c, _) => c.into_dynamic(),
|
||||
Self::StringConstant(s, _) => s.clone().into_owned().into_dynamic(),
|
||||
Self::True(_) => true.into_dynamic(),
|
||||
Self::False(_) => false.into_dynamic(),
|
||||
Self::Unit(_) => ().into_dynamic(),
|
||||
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
Expr::Array(items, _) if items.iter().all(Expr::is_constant) => items
|
||||
Self::Array(items, _) if items.iter().all(Self::is_constant) => items
|
||||
.iter()
|
||||
.map(Expr::get_constant_value)
|
||||
.map(Self::get_constant_value)
|
||||
.collect::<Vec<_>>()
|
||||
.into_dynamic(),
|
||||
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
Expr::Map(items, _) if items.iter().all(|(_, v, _)| v.is_constant()) => items
|
||||
Self::Map(items, _) if items.iter().all(|(_, v, _)| v.is_constant()) => items
|
||||
.iter()
|
||||
.map(|(k, v, _)| (k.clone(), v.get_constant_value()))
|
||||
.collect::<HashMap<_, _>>()
|
||||
.into_dynamic(),
|
||||
|
||||
#[cfg(not(feature = "no_float"))]
|
||||
Expr::FloatConstant(f, _) => f.into_dynamic(),
|
||||
Self::FloatConstant(f, _) => f.into_dynamic(),
|
||||
|
||||
_ => panic!("cannot get value of non-constant expression"),
|
||||
}
|
||||
@ -481,18 +483,18 @@ impl Expr {
|
||||
/// Panics when the expression is not constant.
|
||||
pub fn get_constant_str(&self) -> String {
|
||||
match self {
|
||||
Expr::IntegerConstant(i, _) => i.to_string(),
|
||||
Expr::CharConstant(c, _) => c.to_string(),
|
||||
Expr::StringConstant(_, _) => "string".to_string(),
|
||||
Expr::True(_) => "true".to_string(),
|
||||
Expr::False(_) => "false".to_string(),
|
||||
Expr::Unit(_) => "()".to_string(),
|
||||
Self::IntegerConstant(i, _) => i.to_string(),
|
||||
Self::CharConstant(c, _) => c.to_string(),
|
||||
Self::StringConstant(_, _) => "string".to_string(),
|
||||
Self::True(_) => "true".to_string(),
|
||||
Self::False(_) => "false".to_string(),
|
||||
Self::Unit(_) => "()".to_string(),
|
||||
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
Expr::Array(items, _) if items.iter().all(Expr::is_constant) => "array".to_string(),
|
||||
Self::Array(items, _) if items.iter().all(Self::is_constant) => "array".to_string(),
|
||||
|
||||
#[cfg(not(feature = "no_float"))]
|
||||
Expr::FloatConstant(f, _) => f.to_string(),
|
||||
Self::FloatConstant(f, _) => f.to_string(),
|
||||
|
||||
_ => panic!("cannot get value of non-constant expression"),
|
||||
}
|
||||
@ -501,35 +503,36 @@ impl Expr {
|
||||
/// Get the `Position` of the expression.
|
||||
pub fn position(&self) -> Position {
|
||||
match self {
|
||||
Expr::IntegerConstant(_, pos)
|
||||
| Expr::CharConstant(_, pos)
|
||||
| Expr::StringConstant(_, pos)
|
||||
| Expr::Variable(_, pos)
|
||||
| Expr::Property(_, pos)
|
||||
| Expr::Stmt(_, pos)
|
||||
| Expr::FunctionCall(_, _, _, pos)
|
||||
| Expr::True(pos)
|
||||
| Expr::False(pos)
|
||||
| Expr::Unit(pos) => *pos,
|
||||
Self::IntegerConstant(_, pos)
|
||||
| Self::CharConstant(_, pos)
|
||||
| Self::StringConstant(_, pos)
|
||||
| Self::Variable(_, pos)
|
||||
| Self::Property(_, pos)
|
||||
| Self::Stmt(_, pos)
|
||||
| Self::FunctionCall(_, _, _, pos)
|
||||
| Self::And(_, _, pos)
|
||||
| Self::Or(_, _, pos)
|
||||
| Self::In(_, _, pos)
|
||||
| Self::True(pos)
|
||||
| Self::False(pos)
|
||||
| Self::Unit(pos) => *pos,
|
||||
|
||||
Expr::Assignment(expr, _, _) | Expr::And(expr, _) | Expr::Or(expr, _) => {
|
||||
expr.position()
|
||||
}
|
||||
Self::Assignment(expr, _, _) => expr.position(),
|
||||
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
Expr::Dot(expr, _, _) => expr.position(),
|
||||
Self::Dot(expr, _, _) => expr.position(),
|
||||
|
||||
#[cfg(not(feature = "no_float"))]
|
||||
Expr::FloatConstant(_, pos) => *pos,
|
||||
Self::FloatConstant(_, pos) => *pos,
|
||||
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
Expr::Array(_, pos) => *pos,
|
||||
Self::Array(_, pos) => *pos,
|
||||
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
Expr::Map(_, pos) => *pos,
|
||||
Self::Map(_, pos) => *pos,
|
||||
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
Expr::Index(expr, _, _) => expr.position(),
|
||||
Self::Index(expr, _, _) => expr.position(),
|
||||
}
|
||||
}
|
||||
|
||||
@ -539,35 +542,48 @@ impl Expr {
|
||||
pub fn is_pure(&self) -> bool {
|
||||
match self {
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
Expr::Array(expressions, _) => expressions.iter().all(Expr::is_pure),
|
||||
Self::Array(expressions, _) => expressions.iter().all(Self::is_pure),
|
||||
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
Expr::Index(x, y, _) => x.is_pure() && y.is_pure(),
|
||||
Self::Index(x, y, _) => x.is_pure() && y.is_pure(),
|
||||
|
||||
Expr::And(x, y) | Expr::Or(x, y) => x.is_pure() && y.is_pure(),
|
||||
Self::And(x, y, _) | Self::Or(x, y, _) | Self::In(x, y, _) => {
|
||||
x.is_pure() && y.is_pure()
|
||||
}
|
||||
|
||||
Expr::Stmt(stmt, _) => stmt.is_pure(),
|
||||
Self::Stmt(stmt, _) => stmt.is_pure(),
|
||||
|
||||
expr => expr.is_constant() || matches!(expr, Expr::Variable(_, _)),
|
||||
expr => expr.is_constant() || matches!(expr, Self::Variable(_, _)),
|
||||
}
|
||||
}
|
||||
|
||||
/// Is the expression a constant?
|
||||
pub fn is_constant(&self) -> bool {
|
||||
match self {
|
||||
Expr::IntegerConstant(_, _)
|
||||
| Expr::CharConstant(_, _)
|
||||
| Expr::StringConstant(_, _)
|
||||
| Expr::True(_)
|
||||
| Expr::False(_)
|
||||
| Expr::Unit(_) => true,
|
||||
Self::IntegerConstant(_, _)
|
||||
| Self::CharConstant(_, _)
|
||||
| Self::StringConstant(_, _)
|
||||
| Self::True(_)
|
||||
| Self::False(_)
|
||||
| Self::Unit(_) => true,
|
||||
|
||||
#[cfg(not(feature = "no_float"))]
|
||||
Expr::FloatConstant(_, _) => true,
|
||||
Self::FloatConstant(_, _) => true,
|
||||
|
||||
// An array literal is constant if all items are constant
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
Expr::Array(expressions, _) => expressions.iter().all(Expr::is_constant),
|
||||
Self::Array(expressions, _) => expressions.iter().all(Self::is_constant),
|
||||
|
||||
// An map literal is constant if all items are constant
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
Self::Map(items, _) => items.iter().map(|(_, expr, _)| expr).all(Self::is_constant),
|
||||
|
||||
// Check in expression
|
||||
Self::In(lhs, rhs, _) => match (lhs.as_ref(), rhs.as_ref()) {
|
||||
(Self::StringConstant(_, _), Self::StringConstant(_, _))
|
||||
| (Self::CharConstant(_, _), Self::StringConstant(_, _)) => true,
|
||||
_ => false,
|
||||
},
|
||||
|
||||
_ => false,
|
||||
}
|
||||
@ -653,7 +669,7 @@ pub enum Token {
|
||||
impl Token {
|
||||
/// Get the syntax of the token.
|
||||
pub fn syntax(&self) -> Cow<str> {
|
||||
use self::Token::*;
|
||||
use Token::*;
|
||||
|
||||
match self {
|
||||
IntegerConstant(i) => i.to_string().into(),
|
||||
@ -738,7 +754,7 @@ impl Token {
|
||||
// If another operator is after these, it's probably an unary operator
|
||||
// (not sure about fn name).
|
||||
pub fn is_next_unary(&self) -> bool {
|
||||
use self::Token::*;
|
||||
use Token::*;
|
||||
|
||||
match self {
|
||||
LexError(_) |
|
||||
@ -799,40 +815,31 @@ impl Token {
|
||||
|
||||
/// Get the precedence number of the token.
|
||||
pub fn precedence(&self) -> u8 {
|
||||
use Token::*;
|
||||
|
||||
match self {
|
||||
Self::Equals
|
||||
| Self::PlusAssign
|
||||
| Self::MinusAssign
|
||||
| Self::MultiplyAssign
|
||||
| Self::DivideAssign
|
||||
| Self::LeftShiftAssign
|
||||
| Self::RightShiftAssign
|
||||
| Self::AndAssign
|
||||
| Self::OrAssign
|
||||
| Self::XOrAssign
|
||||
| Self::ModuloAssign
|
||||
| Self::PowerOfAssign => 10,
|
||||
Equals | PlusAssign | MinusAssign | MultiplyAssign | DivideAssign | LeftShiftAssign
|
||||
| RightShiftAssign | AndAssign | OrAssign | XOrAssign | ModuloAssign
|
||||
| PowerOfAssign => 10,
|
||||
|
||||
Self::Or | Self::XOr | Self::Pipe => 50,
|
||||
Or | XOr | Pipe => 40,
|
||||
|
||||
Self::And | Self::Ampersand => 60,
|
||||
And | Ampersand => 50,
|
||||
|
||||
Self::LessThan
|
||||
| Self::LessThanEqualsTo
|
||||
| Self::GreaterThan
|
||||
| Self::GreaterThanEqualsTo
|
||||
| Self::EqualsTo
|
||||
| Self::NotEqualsTo => 70,
|
||||
LessThan | LessThanEqualsTo | GreaterThan | GreaterThanEqualsTo | EqualsTo
|
||||
| NotEqualsTo => 60,
|
||||
|
||||
Self::Plus | Self::Minus => 80,
|
||||
In => 70,
|
||||
|
||||
Self::Divide | Self::Multiply | Self::PowerOf => 90,
|
||||
Plus | Minus => 80,
|
||||
|
||||
Self::LeftShift | Self::RightShift => 100,
|
||||
Divide | Multiply | PowerOf => 90,
|
||||
|
||||
Self::Modulo => 110,
|
||||
LeftShift | RightShift => 100,
|
||||
|
||||
Self::Period => 120,
|
||||
Modulo => 110,
|
||||
|
||||
Period => 120,
|
||||
|
||||
_ => 0,
|
||||
}
|
||||
@ -840,23 +847,16 @@ impl Token {
|
||||
|
||||
/// Does an expression bind to the right (instead of left)?
|
||||
pub fn is_bind_right(&self) -> bool {
|
||||
use Token::*;
|
||||
|
||||
match self {
|
||||
// Assignments bind to the right
|
||||
Self::Equals
|
||||
| Self::PlusAssign
|
||||
| Self::MinusAssign
|
||||
| Self::MultiplyAssign
|
||||
| Self::DivideAssign
|
||||
| Self::LeftShiftAssign
|
||||
| Self::RightShiftAssign
|
||||
| Self::AndAssign
|
||||
| Self::OrAssign
|
||||
| Self::XOrAssign
|
||||
| Self::ModuloAssign
|
||||
| Self::PowerOfAssign => true,
|
||||
Equals | PlusAssign | MinusAssign | MultiplyAssign | DivideAssign | LeftShiftAssign
|
||||
| RightShiftAssign | AndAssign | OrAssign | XOrAssign | ModuloAssign
|
||||
| PowerOfAssign => true,
|
||||
|
||||
// Property access binds to the right
|
||||
Self::Period => true,
|
||||
Period => true,
|
||||
|
||||
_ => false,
|
||||
}
|
||||
@ -1539,22 +1539,18 @@ fn parse_index_expr<'a>(
|
||||
Expr::FloatConstant(_, pos)
|
||||
| Expr::CharConstant(_, pos)
|
||||
| Expr::Assignment(_, _, pos)
|
||||
| Expr::Unit(pos)
|
||||
| Expr::And(_, _, pos)
|
||||
| Expr::Or(_, _, pos)
|
||||
| Expr::In(_, _, pos)
|
||||
| Expr::True(pos)
|
||||
| Expr::False(pos) => {
|
||||
| Expr::False(pos)
|
||||
| Expr::Unit(pos) => {
|
||||
return Err(PERR::MalformedIndexExpr(
|
||||
"Only arrays, object maps and strings can be indexed".into(),
|
||||
)
|
||||
.into_err(pos))
|
||||
}
|
||||
|
||||
Expr::And(lhs, _) | Expr::Or(lhs, _) => {
|
||||
return Err(PERR::MalformedIndexExpr(
|
||||
"Only arrays, object maps and strings can be indexed".into(),
|
||||
)
|
||||
.into_err(lhs.position()))
|
||||
}
|
||||
|
||||
_ => (),
|
||||
},
|
||||
|
||||
@ -1572,22 +1568,18 @@ fn parse_index_expr<'a>(
|
||||
Expr::FloatConstant(_, pos)
|
||||
| Expr::CharConstant(_, pos)
|
||||
| Expr::Assignment(_, _, pos)
|
||||
| Expr::Unit(pos)
|
||||
| Expr::And(_, _, pos)
|
||||
| Expr::Or(_, _, pos)
|
||||
| Expr::In(_, _, pos)
|
||||
| Expr::True(pos)
|
||||
| Expr::False(pos) => {
|
||||
| Expr::False(pos)
|
||||
| Expr::Unit(pos) => {
|
||||
return Err(PERR::MalformedIndexExpr(
|
||||
"Only arrays, object maps and strings can be indexed".into(),
|
||||
)
|
||||
.into_err(pos))
|
||||
}
|
||||
|
||||
Expr::And(lhs, _) | Expr::Or(lhs, _) => {
|
||||
return Err(PERR::MalformedIndexExpr(
|
||||
"Only arrays, object maps and strings can be indexed".into(),
|
||||
)
|
||||
.into_err(lhs.position()))
|
||||
}
|
||||
|
||||
_ => (),
|
||||
},
|
||||
|
||||
@ -1613,15 +1605,12 @@ fn parse_index_expr<'a>(
|
||||
)
|
||||
.into_err(*pos))
|
||||
}
|
||||
// lhs[??? && ???], lhs[??? || ???]
|
||||
Expr::And(lhs, _) | Expr::Or(lhs, _) => {
|
||||
return Err(PERR::MalformedIndexExpr(
|
||||
"Array access expects integer index, not a boolean".into(),
|
||||
)
|
||||
.into_err(lhs.position()))
|
||||
}
|
||||
// lhs[true], lhs[false]
|
||||
Expr::True(pos) | Expr::False(pos) => {
|
||||
// lhs[??? && ???], lhs[??? || ???], lhs[??? in ???], lhs[true], lhs[false]
|
||||
Expr::And(_, _, pos)
|
||||
| Expr::Or(_, _, pos)
|
||||
| Expr::In(_, _, pos)
|
||||
| Expr::True(pos)
|
||||
| Expr::False(pos) => {
|
||||
return Err(PERR::MalformedIndexExpr(
|
||||
"Array access expects integer index, not a boolean".into(),
|
||||
)
|
||||
@ -1699,9 +1688,7 @@ fn parse_array_literal<'a>(
|
||||
match input.peek().ok_or_else(|| {
|
||||
PERR::MissingToken("]".into(), "to end this array literal".into()).into_err_eof()
|
||||
})? {
|
||||
(Token::Comma, _) => {
|
||||
input.next();
|
||||
}
|
||||
(Token::Comma, _) => input.next(),
|
||||
(Token::RightBracket, _) => break,
|
||||
(_, pos) => {
|
||||
return Err(PERR::MissingToken(
|
||||
@ -1710,7 +1697,7 @@ fn parse_array_literal<'a>(
|
||||
)
|
||||
.into_err(*pos))
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@ -2065,6 +2052,154 @@ fn parse_op_assignment<S: Into<Cow<'static, str>>>(
|
||||
)
|
||||
}
|
||||
|
||||
/// Parse an 'in' expression.
|
||||
fn parse_in_expr(lhs: Expr, rhs: Expr, op_pos: Position) -> Result<Expr, ParseError> {
|
||||
match (&lhs, &rhs) {
|
||||
#[cfg(not(feature = "no_float"))]
|
||||
(_, Expr::FloatConstant(_, pos)) => {
|
||||
return Err(PERR::MalformedInExpr(
|
||||
"'in' expression expects a string, array or object map".into(),
|
||||
)
|
||||
.into_err(*pos))
|
||||
}
|
||||
|
||||
(_, Expr::IntegerConstant(_, pos))
|
||||
| (_, Expr::And(_, _, pos))
|
||||
| (_, Expr::Or(_, _, pos))
|
||||
| (_, Expr::In(_, _, pos))
|
||||
| (_, Expr::True(pos))
|
||||
| (_, Expr::False(pos))
|
||||
| (_, Expr::Assignment(_, _, pos))
|
||||
| (_, Expr::Unit(pos)) => {
|
||||
return Err(PERR::MalformedInExpr(
|
||||
"'in' expression expects a string, array or object map".into(),
|
||||
)
|
||||
.into_err(*pos))
|
||||
}
|
||||
|
||||
// "xxx" in "xxxx", 'x' in "xxxx" - OK!
|
||||
(Expr::StringConstant(_, _), Expr::StringConstant(_, _))
|
||||
| (Expr::CharConstant(_, _), Expr::StringConstant(_, _)) => (),
|
||||
|
||||
// 123.456 in "xxxx"
|
||||
#[cfg(not(feature = "no_float"))]
|
||||
(Expr::FloatConstant(_, pos), Expr::StringConstant(_, _)) => {
|
||||
return Err(PERR::MalformedInExpr(
|
||||
"'in' expression for a string expects a string, not a float".into(),
|
||||
)
|
||||
.into_err(*pos))
|
||||
}
|
||||
// 123 in "xxxx"
|
||||
(Expr::IntegerConstant(_, pos), Expr::StringConstant(_, _)) => {
|
||||
return Err(PERR::MalformedInExpr(
|
||||
"'in' expression for a string expects a string, not a number".into(),
|
||||
)
|
||||
.into_err(*pos))
|
||||
}
|
||||
// (??? && ???) in "xxxx", (??? || ???) in "xxxx", (??? in ???) in "xxxx",
|
||||
// true in "xxxx", false in "xxxx"
|
||||
(Expr::And(_, _, pos), Expr::StringConstant(_, _))
|
||||
| (Expr::Or(_, _, pos), Expr::StringConstant(_, _))
|
||||
| (Expr::In(_, _, pos), Expr::StringConstant(_, _))
|
||||
| (Expr::True(pos), Expr::StringConstant(_, _))
|
||||
| (Expr::False(pos), Expr::StringConstant(_, _)) => {
|
||||
return Err(PERR::MalformedInExpr(
|
||||
"'in' expression for a string expects a string, not a boolean".into(),
|
||||
)
|
||||
.into_err(*pos))
|
||||
}
|
||||
// [???, ???, ???] in "xxxx"
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
(Expr::Array(_, pos), Expr::StringConstant(_, _)) => {
|
||||
return Err(PERR::MalformedInExpr(
|
||||
"'in' expression for a string expects a string, not an array".into(),
|
||||
)
|
||||
.into_err(*pos))
|
||||
}
|
||||
// #{...} in "xxxx"
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
(Expr::Map(_, pos), Expr::StringConstant(_, _)) => {
|
||||
return Err(PERR::MalformedInExpr(
|
||||
"'in' expression for a string expects a string, not an object map".into(),
|
||||
)
|
||||
.into_err(*pos))
|
||||
}
|
||||
// (??? = ???) in "xxxx", () in "xxxx"
|
||||
(Expr::Assignment(_, _, pos), Expr::StringConstant(_, _))
|
||||
| (Expr::Unit(pos), Expr::StringConstant(_, _)) => {
|
||||
return Err(PERR::MalformedInExpr(
|
||||
"'in' expression for a string expects a string, not ()".into(),
|
||||
)
|
||||
.into_err(*pos))
|
||||
}
|
||||
|
||||
// "xxx" in #{...}, 'x' in #{...} - OK!
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
(Expr::StringConstant(_, _), Expr::Map(_, _))
|
||||
| (Expr::CharConstant(_, _), Expr::Map(_, _)) => (),
|
||||
|
||||
// 123.456 in #{...}
|
||||
#[cfg(not(feature = "no_float"))]
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
(Expr::FloatConstant(_, pos), Expr::Map(_, _)) => {
|
||||
return Err(PERR::MalformedInExpr(
|
||||
"'in' expression for an object map expects a string, not a float".into(),
|
||||
)
|
||||
.into_err(*pos))
|
||||
}
|
||||
// 123 in #{...}
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
(Expr::IntegerConstant(_, pos), Expr::Map(_, _)) => {
|
||||
return Err(PERR::MalformedInExpr(
|
||||
"'in' expression for an object map expects a string, not a number".into(),
|
||||
)
|
||||
.into_err(*pos))
|
||||
}
|
||||
// (??? && ???) in #{...}, (??? || ???) in #{...}, (??? in ???) in #{...},
|
||||
// true in #{...}, false in #{...}
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
(Expr::And(_, _, pos), Expr::Map(_, _))
|
||||
| (Expr::Or(_, _, pos), Expr::Map(_, _))
|
||||
| (Expr::In(_, _, pos), Expr::Map(_, _))
|
||||
| (Expr::True(pos), Expr::Map(_, _))
|
||||
| (Expr::False(pos), Expr::Map(_, _)) => {
|
||||
return Err(PERR::MalformedInExpr(
|
||||
"'in' expression for an object map expects a string, not a boolean".into(),
|
||||
)
|
||||
.into_err(*pos))
|
||||
}
|
||||
// [???, ???, ???] in #{..}
|
||||
#[cfg(not(feature = "no_index"))]
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
(Expr::Array(_, pos), Expr::Map(_, _)) => {
|
||||
return Err(PERR::MalformedInExpr(
|
||||
"'in' expression for an object map expects a string, not an array".into(),
|
||||
)
|
||||
.into_err(*pos))
|
||||
}
|
||||
// #{...} in #{..}
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
(Expr::Map(_, pos), Expr::Map(_, _)) => {
|
||||
return Err(PERR::MalformedInExpr(
|
||||
"'in' expression for an object map expects a string, not an object map".into(),
|
||||
)
|
||||
.into_err(*pos))
|
||||
}
|
||||
// (??? = ???) in #{...}, () in #{...}
|
||||
#[cfg(not(feature = "no_object"))]
|
||||
(Expr::Assignment(_, _, pos), Expr::Map(_, _)) | (Expr::Unit(pos), Expr::Map(_, _)) => {
|
||||
return Err(PERR::MalformedInExpr(
|
||||
"'in' expression for an object map expects a string, not ()".into(),
|
||||
)
|
||||
.into_err(*pos))
|
||||
}
|
||||
|
||||
_ => (),
|
||||
}
|
||||
|
||||
Ok(Expr::In(Box::new(lhs), Box::new(rhs), op_pos))
|
||||
}
|
||||
|
||||
/// Parse a binary expression.
|
||||
fn parse_binary_op<'a>(
|
||||
input: &mut Peekable<TokenIterator<'a>>,
|
||||
@ -2189,8 +2324,11 @@ fn parse_binary_op<'a>(
|
||||
pos,
|
||||
),
|
||||
|
||||
Token::Or => Expr::Or(Box::new(current_lhs), Box::new(rhs)),
|
||||
Token::And => Expr::And(Box::new(current_lhs), Box::new(rhs)),
|
||||
Token::Or => Expr::Or(Box::new(current_lhs), Box::new(rhs), pos),
|
||||
Token::And => Expr::And(Box::new(current_lhs), Box::new(rhs), pos),
|
||||
|
||||
Token::In => parse_in_expr(current_lhs, rhs, pos)?,
|
||||
|
||||
Token::XOr => Expr::FunctionCall("^".into(), vec![current_lhs, rhs], None, pos),
|
||||
Token::OrAssign => parse_op_assignment("|", current_lhs, rhs, pos)?,
|
||||
Token::AndAssign => parse_op_assignment("&", current_lhs, rhs, pos)?,
|
||||
@ -2561,11 +2699,9 @@ fn parse_fn<'a>(
|
||||
.peek()
|
||||
.ok_or_else(|| PERR::FnMissingParams(name.clone()).into_err_eof())?
|
||||
{
|
||||
(Token::LeftParen, _) => {
|
||||
input.next();
|
||||
}
|
||||
(Token::LeftParen, _) => input.next(),
|
||||
(_, pos) => return Err(PERR::FnMissingParams(name).into_err(*pos)),
|
||||
}
|
||||
};
|
||||
|
||||
let mut params = Vec::new();
|
||||
|
||||
@ -2580,9 +2716,7 @@ fn parse_fn<'a>(
|
||||
.next()
|
||||
.ok_or_else(|| PERR::MissingToken(")".into(), end_err.to_string()).into_err_eof())?
|
||||
{
|
||||
(Token::Identifier(s), pos) => {
|
||||
params.push((s, pos));
|
||||
}
|
||||
(Token::Identifier(s), pos) => params.push((s, pos)),
|
||||
(_, pos) => return Err(PERR::MissingToken(")".into(), end_err).into_err(pos)),
|
||||
}
|
||||
|
||||
@ -2622,9 +2756,11 @@ fn parse_fn<'a>(
|
||||
None => return Err(PERR::FnMissingBody(name).into_err_eof()),
|
||||
};
|
||||
|
||||
let params = params.into_iter().map(|(p, _)| p).collect();
|
||||
|
||||
Ok(FnDef {
|
||||
name,
|
||||
params: params.into_iter().map(|(p, _)| p).collect(),
|
||||
params,
|
||||
body,
|
||||
pos,
|
||||
})
|
||||
|
@ -49,6 +49,8 @@ pub enum EvalAltResult {
|
||||
ErrorNumericIndexExpr(Position),
|
||||
/// Trying to index into a map with an index that is not `String`.
|
||||
ErrorStringIndexExpr(Position),
|
||||
/// Invalid arguments for `in` operator.
|
||||
ErrorInExpr(Position),
|
||||
/// The guard expression in an `if` or `while` statement does not return a boolean value.
|
||||
ErrorLogicGuard(Position),
|
||||
/// The `for` statement encounters a type that is not an iterator.
|
||||
@ -118,6 +120,7 @@ impl EvalAltResult {
|
||||
}
|
||||
Self::ErrorAssignmentToConstant(_, _) => "Assignment to a constant variable",
|
||||
Self::ErrorMismatchOutputType(_, _) => "Output type is incorrect",
|
||||
Self::ErrorInExpr(_) => "Malformed 'in' expression",
|
||||
Self::ErrorDotExpr(_, _) => "Malformed dot expression",
|
||||
Self::ErrorArithmetic(_, _) => "Arithmetic error",
|
||||
Self::ErrorStackOverflow(_) => "Stack overflow",
|
||||
@ -154,6 +157,7 @@ impl fmt::Display for EvalAltResult {
|
||||
| Self::ErrorLogicGuard(pos)
|
||||
| Self::ErrorFor(pos)
|
||||
| Self::ErrorAssignmentToUnknownLHS(pos)
|
||||
| Self::ErrorInExpr(pos)
|
||||
| Self::ErrorDotExpr(_, pos)
|
||||
| Self::ErrorStackOverflow(pos) => write!(f, "{} ({})", desc, pos),
|
||||
|
||||
@ -256,6 +260,7 @@ impl EvalAltResult {
|
||||
| Self::ErrorAssignmentToUnknownLHS(pos)
|
||||
| Self::ErrorAssignmentToConstant(_, pos)
|
||||
| Self::ErrorMismatchOutputType(_, pos)
|
||||
| Self::ErrorInExpr(pos)
|
||||
| Self::ErrorDotExpr(_, pos)
|
||||
| Self::ErrorArithmetic(_, pos)
|
||||
| Self::ErrorStackOverflow(pos)
|
||||
@ -288,6 +293,7 @@ impl EvalAltResult {
|
||||
| Self::ErrorAssignmentToUnknownLHS(pos)
|
||||
| Self::ErrorAssignmentToConstant(_, pos)
|
||||
| Self::ErrorMismatchOutputType(_, pos)
|
||||
| Self::ErrorInExpr(pos)
|
||||
| Self::ErrorDotExpr(_, pos)
|
||||
| Self::ErrorArithmetic(_, pos)
|
||||
| Self::ErrorStackOverflow(pos)
|
||||
|
@ -11,6 +11,7 @@ fn test_arrays() -> Result<(), EvalAltResult> {
|
||||
engine.eval::<char>(r#"let y = [1, [ 42, 88, "93" ], 3]; y[1][2][1]"#)?,
|
||||
'3'
|
||||
);
|
||||
assert!(engine.eval::<bool>("let y = [1, 2, 3]; 2 in y")?);
|
||||
|
||||
#[cfg(not(feature = "no_stdlib"))]
|
||||
{
|
||||
|
@ -29,6 +29,10 @@ fn test_map_indexing() -> Result<(), 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}; "z" in y"#)?);
|
||||
|
||||
#[cfg(not(feature = "no_stdlib"))]
|
||||
{
|
||||
assert_eq!(
|
||||
|
@ -15,6 +15,10 @@ fn test_string() -> Result<(), EvalAltResult> {
|
||||
|
||||
assert_eq!(engine.eval::<String>(r#""foo" + "bar""#)?, "foobar");
|
||||
|
||||
assert!(engine.eval::<bool>(r#"let y = "hello, world!"; "world" in y"#)?);
|
||||
assert!(engine.eval::<bool>(r#"let y = "hello, world!"; 'w' in y"#)?);
|
||||
assert!(!engine.eval::<bool>(r#"let y = "hello, world!"; "hey" in y"#)?);
|
||||
|
||||
#[cfg(not(feature = "no_stdlib"))]
|
||||
assert_eq!(engine.eval::<String>(r#""foo" + 123"#)?, "foo123");
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user