Add 'in' expression.

This commit is contained in:
Stephen Chung 2020-04-06 17:47:34 +08:00
parent 32672b184b
commit e204ae1a2c
9 changed files with 447 additions and 147 deletions

View File

@ -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;

View File

@ -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) {

View File

@ -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)?

View File

@ -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
),
},

View File

@ -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,
})

View File

@ -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)

View File

@ -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"))]
{

View File

@ -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!(

View File

@ -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");