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')
|
// (disabled with 'no_index')
|
||||||
record[4] = '\x58'; // 0x58 = 'X'
|
record[4] = '\x58'; // 0x58 = 'X'
|
||||||
record == "Bob X. Davis: age 42 ❤\n";
|
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:
|
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
|
let y = [1, 2, 3]; // array literal with 3 elements
|
||||||
y[1] = 42;
|
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
|
print(y[1]); // prints 42
|
||||||
|
|
||||||
ts.list = y; // arrays can be assigned completely (by value copy)
|
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(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)
|
ts.obj = y; // object maps can be assigned completely (by value copy)
|
||||||
let foo = ts.list.a;
|
let foo = ts.list.a;
|
||||||
foo == 42;
|
foo == 42;
|
||||||
|
104
src/engine.rs
104
src/engine.rs
@ -290,6 +290,7 @@ impl Default for Engine<'_> {
|
|||||||
(type_name::<Map>(), "map"),
|
(type_name::<Map>(), "map"),
|
||||||
(type_name::<String>(), "string"),
|
(type_name::<String>(), "string"),
|
||||||
(type_name::<Dynamic>(), "dynamic"),
|
(type_name::<Dynamic>(), "dynamic"),
|
||||||
|
(type_name::<Variant>(), "variant"),
|
||||||
]
|
]
|
||||||
.iter()
|
.iter()
|
||||||
.map(|(k, v)| (k.to_string(), v.to_string()))
|
.map(|(k, v)| (k.to_string(), v.to_string()))
|
||||||
@ -416,7 +417,7 @@ impl Engine<'_> {
|
|||||||
) -> Result<Option<Dynamic>, EvalAltResult> {
|
) -> Result<Option<Dynamic>, EvalAltResult> {
|
||||||
let spec = FnSpec {
|
let spec = FnSpec {
|
||||||
name: fn_name.into(),
|
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
|
// Search built-in's and external functions
|
||||||
@ -501,7 +502,7 @@ impl Engine<'_> {
|
|||||||
|
|
||||||
let spec = FnSpec {
|
let spec = FnSpec {
|
||||||
name: fn_name.into(),
|
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
|
// 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
|
/// Evaluate an expression
|
||||||
fn eval_expr(
|
fn eval_expr(
|
||||||
&mut self,
|
&mut self,
|
||||||
@ -1302,7 +1382,7 @@ impl Engine<'_> {
|
|||||||
self.eval_expr(scope, item, level).map(|val| arr.push(val))
|
self.eval_expr(scope, item, level).map(|val| arr.push(val))
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
Ok(Box::new(arr))
|
Ok((arr).into_dynamic())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(feature = "no_object"))]
|
#[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) => {
|
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
|
self
|
||||||
.eval_expr(scope, &*lhs, level)?
|
.eval_expr(scope, lhs.as_ref(), level)?
|
||||||
.try_cast::<bool>()
|
.try_cast::<bool>()
|
||||||
.map_err(|_| {
|
.map_err(|_| {
|
||||||
EvalAltResult::ErrorBooleanArgMismatch("AND".into(), lhs.position())
|
EvalAltResult::ErrorBooleanArgMismatch("AND".into(), lhs.position())
|
||||||
})?
|
})?
|
||||||
&& // Short-circuit using &&
|
&& // Short-circuit using &&
|
||||||
self
|
self
|
||||||
.eval_expr(scope, &*rhs, level)?
|
.eval_expr(scope, rhs.as_ref(), level)?
|
||||||
.try_cast::<bool>()
|
.try_cast::<bool>()
|
||||||
.map_err(|_| {
|
.map_err(|_| {
|
||||||
EvalAltResult::ErrorBooleanArgMismatch("AND".into(), rhs.position())
|
EvalAltResult::ErrorBooleanArgMismatch("AND".into(), rhs.position())
|
||||||
})?,
|
})?,
|
||||||
)),
|
)),
|
||||||
|
|
||||||
Expr::Or(lhs, rhs) => Ok(Box::new(
|
Expr::Or(lhs, rhs, _) => Ok(Box::new(
|
||||||
self
|
self
|
||||||
.eval_expr(scope, &*lhs, level)?
|
.eval_expr(scope, lhs.as_ref(), level)?
|
||||||
.try_cast::<bool>()
|
.try_cast::<bool>()
|
||||||
.map_err(|_| {
|
.map_err(|_| {
|
||||||
EvalAltResult::ErrorBooleanArgMismatch("OR".into(), lhs.position())
|
EvalAltResult::ErrorBooleanArgMismatch("OR".into(), lhs.position())
|
||||||
})?
|
})?
|
||||||
|| // Short-circuit using ||
|
|| // Short-circuit using ||
|
||||||
self
|
self
|
||||||
.eval_expr(scope, &*rhs, level)?
|
.eval_expr(scope, rhs.as_ref(), level)?
|
||||||
.try_cast::<bool>()
|
.try_cast::<bool>()
|
||||||
.map_err(|_| {
|
.map_err(|_| {
|
||||||
EvalAltResult::ErrorBooleanArgMismatch("OR".into(), rhs.position())
|
EvalAltResult::ErrorBooleanArgMismatch("OR".into(), rhs.position())
|
||||||
@ -1559,7 +1641,7 @@ impl Engine<'_> {
|
|||||||
// For loop
|
// For loop
|
||||||
Stmt::For(name, expr, body) => {
|
Stmt::For(name, expr, body) => {
|
||||||
let arr = self.eval_expr(scope, expr, level)?;
|
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(type_iterators) = &self.type_iterators {
|
||||||
if let Some(iter_fn) = type_iterators.get(&tid) {
|
if let Some(iter_fn) = type_iterators.get(&tid) {
|
||||||
|
@ -54,6 +54,8 @@ pub enum ParseErrorType {
|
|||||||
/// Not available under the `no_index` feature.
|
/// Not available under the `no_index` feature.
|
||||||
#[cfg(not(feature = "no_index"))]
|
#[cfg(not(feature = "no_index"))]
|
||||||
MalformedIndexExpr(String),
|
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.
|
/// A map definition has duplicated property names. Wrapped value is the property name.
|
||||||
///
|
///
|
||||||
/// Not available under the `no_object` feature.
|
/// Not available under the `no_object` feature.
|
||||||
@ -138,6 +140,7 @@ impl ParseError {
|
|||||||
ParseErrorType::MalformedCallExpr(_) => "Invalid expression in function call arguments",
|
ParseErrorType::MalformedCallExpr(_) => "Invalid expression in function call arguments",
|
||||||
#[cfg(not(feature = "no_index"))]
|
#[cfg(not(feature = "no_index"))]
|
||||||
ParseErrorType::MalformedIndexExpr(_) => "Invalid index in indexing expression",
|
ParseErrorType::MalformedIndexExpr(_) => "Invalid index in indexing expression",
|
||||||
|
ParseErrorType::MalformedInExpr(_) => "Invalid 'in' expression",
|
||||||
#[cfg(not(feature = "no_object"))]
|
#[cfg(not(feature = "no_object"))]
|
||||||
ParseErrorType::DuplicatedProperty(_) => "Duplicated property in object map literal",
|
ParseErrorType::DuplicatedProperty(_) => "Duplicated property in object map literal",
|
||||||
ParseErrorType::ForbiddenConstantExpr(_) => "Expecting a constant",
|
ParseErrorType::ForbiddenConstantExpr(_) => "Expecting a constant",
|
||||||
@ -180,6 +183,10 @@ impl fmt::Display for ParseError {
|
|||||||
write!(f, "{}", if s.is_empty() { self.desc() } else { s })?
|
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"))]
|
#[cfg(not(feature = "no_object"))]
|
||||||
ParseErrorType::DuplicatedProperty(s) => {
|
ParseErrorType::DuplicatedProperty(s) => {
|
||||||
write!(f, "Duplicated property '{}' for object map literal", 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()
|
.into_iter()
|
||||||
.map(|(key, expr, pos)| (key, optimize_expr(expr, state), pos))
|
.map(|(key, expr, pos)| (key, optimize_expr(expr, state), pos))
|
||||||
.collect(), 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
|
// lhs && rhs
|
||||||
Expr::And(lhs, rhs) => match (*lhs, *rhs) {
|
Expr::And(lhs, rhs, pos) => match (*lhs, *rhs) {
|
||||||
// true && rhs -> rhs
|
// true && rhs -> rhs
|
||||||
(Expr::True(_), rhs) => {
|
(Expr::True(_), rhs) => {
|
||||||
state.set_dirty();
|
state.set_dirty();
|
||||||
@ -413,10 +460,11 @@ fn optimize_expr<'a>(expr: Expr, state: &mut State<'a>) -> Expr {
|
|||||||
(lhs, rhs) => Expr::And(
|
(lhs, rhs) => Expr::And(
|
||||||
Box::new(optimize_expr(lhs, state)),
|
Box::new(optimize_expr(lhs, state)),
|
||||||
Box::new(optimize_expr(rhs, state)),
|
Box::new(optimize_expr(rhs, state)),
|
||||||
|
pos
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
// lhs || rhs
|
// lhs || rhs
|
||||||
Expr::Or(lhs, rhs) => match (*lhs, *rhs) {
|
Expr::Or(lhs, rhs, pos) => match (*lhs, *rhs) {
|
||||||
// false || rhs -> rhs
|
// false || rhs -> rhs
|
||||||
(Expr::False(_), rhs) => {
|
(Expr::False(_), rhs) => {
|
||||||
state.set_dirty();
|
state.set_dirty();
|
||||||
@ -436,6 +484,7 @@ fn optimize_expr<'a>(expr: Expr, state: &mut State<'a>) -> Expr {
|
|||||||
(lhs, rhs) => Expr::Or(
|
(lhs, rhs) => Expr::Or(
|
||||||
Box::new(optimize_expr(lhs, state)),
|
Box::new(optimize_expr(lhs, state)),
|
||||||
Box::new(optimize_expr(rhs, 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]
|
/// expr[expr]
|
||||||
#[cfg(not(feature = "no_index"))]
|
#[cfg(not(feature = "no_index"))]
|
||||||
Index(Box<Expr>, Box<Expr>, Position),
|
Index(Box<Expr>, Box<Expr>, Position),
|
||||||
#[cfg(not(feature = "no_index"))]
|
|
||||||
/// [ expr, ... ]
|
/// [ expr, ... ]
|
||||||
|
#[cfg(not(feature = "no_index"))]
|
||||||
Array(Vec<Expr>, Position),
|
Array(Vec<Expr>, Position),
|
||||||
#[cfg(not(feature = "no_object"))]
|
|
||||||
/// #{ name:expr, ... }
|
/// #{ name:expr, ... }
|
||||||
|
#[cfg(not(feature = "no_object"))]
|
||||||
Map(Vec<(String, Expr, Position)>, Position),
|
Map(Vec<(String, Expr, Position)>, Position),
|
||||||
|
/// lhs in rhs
|
||||||
|
In(Box<Expr>, Box<Expr>, Position),
|
||||||
/// lhs && rhs
|
/// lhs && rhs
|
||||||
And(Box<Expr>, Box<Expr>),
|
And(Box<Expr>, Box<Expr>, Position),
|
||||||
/// lhs || rhs
|
/// lhs || rhs
|
||||||
Or(Box<Expr>, Box<Expr>),
|
Or(Box<Expr>, Box<Expr>, Position),
|
||||||
/// true
|
/// true
|
||||||
True(Position),
|
True(Position),
|
||||||
/// false
|
/// false
|
||||||
@ -446,29 +448,29 @@ impl Expr {
|
|||||||
/// Panics when the expression is not constant.
|
/// Panics when the expression is not constant.
|
||||||
pub fn get_constant_value(&self) -> Dynamic {
|
pub fn get_constant_value(&self) -> Dynamic {
|
||||||
match self {
|
match self {
|
||||||
Expr::IntegerConstant(i, _) => i.into_dynamic(),
|
Self::IntegerConstant(i, _) => i.into_dynamic(),
|
||||||
Expr::CharConstant(c, _) => c.into_dynamic(),
|
Self::CharConstant(c, _) => c.into_dynamic(),
|
||||||
Expr::StringConstant(s, _) => s.clone().into_owned().into_dynamic(),
|
Self::StringConstant(s, _) => s.clone().into_owned().into_dynamic(),
|
||||||
Expr::True(_) => true.into_dynamic(),
|
Self::True(_) => true.into_dynamic(),
|
||||||
Expr::False(_) => false.into_dynamic(),
|
Self::False(_) => false.into_dynamic(),
|
||||||
Expr::Unit(_) => ().into_dynamic(),
|
Self::Unit(_) => ().into_dynamic(),
|
||||||
|
|
||||||
#[cfg(not(feature = "no_index"))]
|
#[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()
|
.iter()
|
||||||
.map(Expr::get_constant_value)
|
.map(Self::get_constant_value)
|
||||||
.collect::<Vec<_>>()
|
.collect::<Vec<_>>()
|
||||||
.into_dynamic(),
|
.into_dynamic(),
|
||||||
|
|
||||||
#[cfg(not(feature = "no_object"))]
|
#[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()
|
.iter()
|
||||||
.map(|(k, v, _)| (k.clone(), v.get_constant_value()))
|
.map(|(k, v, _)| (k.clone(), v.get_constant_value()))
|
||||||
.collect::<HashMap<_, _>>()
|
.collect::<HashMap<_, _>>()
|
||||||
.into_dynamic(),
|
.into_dynamic(),
|
||||||
|
|
||||||
#[cfg(not(feature = "no_float"))]
|
#[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"),
|
_ => panic!("cannot get value of non-constant expression"),
|
||||||
}
|
}
|
||||||
@ -481,18 +483,18 @@ impl Expr {
|
|||||||
/// Panics when the expression is not constant.
|
/// Panics when the expression is not constant.
|
||||||
pub fn get_constant_str(&self) -> String {
|
pub fn get_constant_str(&self) -> String {
|
||||||
match self {
|
match self {
|
||||||
Expr::IntegerConstant(i, _) => i.to_string(),
|
Self::IntegerConstant(i, _) => i.to_string(),
|
||||||
Expr::CharConstant(c, _) => c.to_string(),
|
Self::CharConstant(c, _) => c.to_string(),
|
||||||
Expr::StringConstant(_, _) => "string".to_string(),
|
Self::StringConstant(_, _) => "string".to_string(),
|
||||||
Expr::True(_) => "true".to_string(),
|
Self::True(_) => "true".to_string(),
|
||||||
Expr::False(_) => "false".to_string(),
|
Self::False(_) => "false".to_string(),
|
||||||
Expr::Unit(_) => "()".to_string(),
|
Self::Unit(_) => "()".to_string(),
|
||||||
|
|
||||||
#[cfg(not(feature = "no_index"))]
|
#[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"))]
|
#[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"),
|
_ => panic!("cannot get value of non-constant expression"),
|
||||||
}
|
}
|
||||||
@ -501,35 +503,36 @@ impl Expr {
|
|||||||
/// Get the `Position` of the expression.
|
/// Get the `Position` of the expression.
|
||||||
pub fn position(&self) -> Position {
|
pub fn position(&self) -> Position {
|
||||||
match self {
|
match self {
|
||||||
Expr::IntegerConstant(_, pos)
|
Self::IntegerConstant(_, pos)
|
||||||
| Expr::CharConstant(_, pos)
|
| Self::CharConstant(_, pos)
|
||||||
| Expr::StringConstant(_, pos)
|
| Self::StringConstant(_, pos)
|
||||||
| Expr::Variable(_, pos)
|
| Self::Variable(_, pos)
|
||||||
| Expr::Property(_, pos)
|
| Self::Property(_, pos)
|
||||||
| Expr::Stmt(_, pos)
|
| Self::Stmt(_, pos)
|
||||||
| Expr::FunctionCall(_, _, _, pos)
|
| Self::FunctionCall(_, _, _, pos)
|
||||||
| Expr::True(pos)
|
| Self::And(_, _, pos)
|
||||||
| Expr::False(pos)
|
| Self::Or(_, _, pos)
|
||||||
| Expr::Unit(pos) => *pos,
|
| Self::In(_, _, pos)
|
||||||
|
| Self::True(pos)
|
||||||
|
| Self::False(pos)
|
||||||
|
| Self::Unit(pos) => *pos,
|
||||||
|
|
||||||
Expr::Assignment(expr, _, _) | Expr::And(expr, _) | Expr::Or(expr, _) => {
|
Self::Assignment(expr, _, _) => expr.position(),
|
||||||
expr.position()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(not(feature = "no_object"))]
|
#[cfg(not(feature = "no_object"))]
|
||||||
Expr::Dot(expr, _, _) => expr.position(),
|
Self::Dot(expr, _, _) => expr.position(),
|
||||||
|
|
||||||
#[cfg(not(feature = "no_float"))]
|
#[cfg(not(feature = "no_float"))]
|
||||||
Expr::FloatConstant(_, pos) => *pos,
|
Self::FloatConstant(_, pos) => *pos,
|
||||||
|
|
||||||
#[cfg(not(feature = "no_index"))]
|
#[cfg(not(feature = "no_index"))]
|
||||||
Expr::Array(_, pos) => *pos,
|
Self::Array(_, pos) => *pos,
|
||||||
|
|
||||||
#[cfg(not(feature = "no_object"))]
|
#[cfg(not(feature = "no_object"))]
|
||||||
Expr::Map(_, pos) => *pos,
|
Self::Map(_, pos) => *pos,
|
||||||
|
|
||||||
#[cfg(not(feature = "no_index"))]
|
#[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 {
|
pub fn is_pure(&self) -> bool {
|
||||||
match self {
|
match self {
|
||||||
#[cfg(not(feature = "no_index"))]
|
#[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"))]
|
#[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?
|
/// Is the expression a constant?
|
||||||
pub fn is_constant(&self) -> bool {
|
pub fn is_constant(&self) -> bool {
|
||||||
match self {
|
match self {
|
||||||
Expr::IntegerConstant(_, _)
|
Self::IntegerConstant(_, _)
|
||||||
| Expr::CharConstant(_, _)
|
| Self::CharConstant(_, _)
|
||||||
| Expr::StringConstant(_, _)
|
| Self::StringConstant(_, _)
|
||||||
| Expr::True(_)
|
| Self::True(_)
|
||||||
| Expr::False(_)
|
| Self::False(_)
|
||||||
| Expr::Unit(_) => true,
|
| Self::Unit(_) => true,
|
||||||
|
|
||||||
#[cfg(not(feature = "no_float"))]
|
#[cfg(not(feature = "no_float"))]
|
||||||
Expr::FloatConstant(_, _) => true,
|
Self::FloatConstant(_, _) => true,
|
||||||
|
|
||||||
// An array literal is constant if all items are constant
|
// An array literal is constant if all items are constant
|
||||||
#[cfg(not(feature = "no_index"))]
|
#[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,
|
_ => false,
|
||||||
}
|
}
|
||||||
@ -653,7 +669,7 @@ pub enum Token {
|
|||||||
impl Token {
|
impl Token {
|
||||||
/// Get the syntax of the token.
|
/// Get the syntax of the token.
|
||||||
pub fn syntax(&self) -> Cow<str> {
|
pub fn syntax(&self) -> Cow<str> {
|
||||||
use self::Token::*;
|
use Token::*;
|
||||||
|
|
||||||
match self {
|
match self {
|
||||||
IntegerConstant(i) => i.to_string().into(),
|
IntegerConstant(i) => i.to_string().into(),
|
||||||
@ -738,7 +754,7 @@ impl Token {
|
|||||||
// If another operator is after these, it's probably an unary operator
|
// If another operator is after these, it's probably an unary operator
|
||||||
// (not sure about fn name).
|
// (not sure about fn name).
|
||||||
pub fn is_next_unary(&self) -> bool {
|
pub fn is_next_unary(&self) -> bool {
|
||||||
use self::Token::*;
|
use Token::*;
|
||||||
|
|
||||||
match self {
|
match self {
|
||||||
LexError(_) |
|
LexError(_) |
|
||||||
@ -799,40 +815,31 @@ impl Token {
|
|||||||
|
|
||||||
/// Get the precedence number of the token.
|
/// Get the precedence number of the token.
|
||||||
pub fn precedence(&self) -> u8 {
|
pub fn precedence(&self) -> u8 {
|
||||||
|
use Token::*;
|
||||||
|
|
||||||
match self {
|
match self {
|
||||||
Self::Equals
|
Equals | PlusAssign | MinusAssign | MultiplyAssign | DivideAssign | LeftShiftAssign
|
||||||
| Self::PlusAssign
|
| RightShiftAssign | AndAssign | OrAssign | XOrAssign | ModuloAssign
|
||||||
| Self::MinusAssign
|
| PowerOfAssign => 10,
|
||||||
| Self::MultiplyAssign
|
|
||||||
| Self::DivideAssign
|
|
||||||
| Self::LeftShiftAssign
|
|
||||||
| Self::RightShiftAssign
|
|
||||||
| Self::AndAssign
|
|
||||||
| Self::OrAssign
|
|
||||||
| Self::XOrAssign
|
|
||||||
| Self::ModuloAssign
|
|
||||||
| Self::PowerOfAssign => 10,
|
|
||||||
|
|
||||||
Self::Or | Self::XOr | Self::Pipe => 50,
|
Or | XOr | Pipe => 40,
|
||||||
|
|
||||||
Self::And | Self::Ampersand => 60,
|
And | Ampersand => 50,
|
||||||
|
|
||||||
Self::LessThan
|
LessThan | LessThanEqualsTo | GreaterThan | GreaterThanEqualsTo | EqualsTo
|
||||||
| Self::LessThanEqualsTo
|
| NotEqualsTo => 60,
|
||||||
| Self::GreaterThan
|
|
||||||
| Self::GreaterThanEqualsTo
|
|
||||||
| Self::EqualsTo
|
|
||||||
| Self::NotEqualsTo => 70,
|
|
||||||
|
|
||||||
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,
|
_ => 0,
|
||||||
}
|
}
|
||||||
@ -840,23 +847,16 @@ impl Token {
|
|||||||
|
|
||||||
/// Does an expression bind to the right (instead of left)?
|
/// Does an expression bind to the right (instead of left)?
|
||||||
pub fn is_bind_right(&self) -> bool {
|
pub fn is_bind_right(&self) -> bool {
|
||||||
|
use Token::*;
|
||||||
|
|
||||||
match self {
|
match self {
|
||||||
// Assignments bind to the right
|
// Assignments bind to the right
|
||||||
Self::Equals
|
Equals | PlusAssign | MinusAssign | MultiplyAssign | DivideAssign | LeftShiftAssign
|
||||||
| Self::PlusAssign
|
| RightShiftAssign | AndAssign | OrAssign | XOrAssign | ModuloAssign
|
||||||
| Self::MinusAssign
|
| PowerOfAssign => true,
|
||||||
| Self::MultiplyAssign
|
|
||||||
| Self::DivideAssign
|
|
||||||
| Self::LeftShiftAssign
|
|
||||||
| Self::RightShiftAssign
|
|
||||||
| Self::AndAssign
|
|
||||||
| Self::OrAssign
|
|
||||||
| Self::XOrAssign
|
|
||||||
| Self::ModuloAssign
|
|
||||||
| Self::PowerOfAssign => true,
|
|
||||||
|
|
||||||
// Property access binds to the right
|
// Property access binds to the right
|
||||||
Self::Period => true,
|
Period => true,
|
||||||
|
|
||||||
_ => false,
|
_ => false,
|
||||||
}
|
}
|
||||||
@ -1539,22 +1539,18 @@ fn parse_index_expr<'a>(
|
|||||||
Expr::FloatConstant(_, pos)
|
Expr::FloatConstant(_, pos)
|
||||||
| Expr::CharConstant(_, pos)
|
| Expr::CharConstant(_, pos)
|
||||||
| Expr::Assignment(_, _, pos)
|
| Expr::Assignment(_, _, pos)
|
||||||
| Expr::Unit(pos)
|
| Expr::And(_, _, pos)
|
||||||
|
| Expr::Or(_, _, pos)
|
||||||
|
| Expr::In(_, _, pos)
|
||||||
| Expr::True(pos)
|
| Expr::True(pos)
|
||||||
| Expr::False(pos) => {
|
| Expr::False(pos)
|
||||||
|
| Expr::Unit(pos) => {
|
||||||
return Err(PERR::MalformedIndexExpr(
|
return Err(PERR::MalformedIndexExpr(
|
||||||
"Only arrays, object maps and strings can be indexed".into(),
|
"Only arrays, object maps and strings can be indexed".into(),
|
||||||
)
|
)
|
||||||
.into_err(pos))
|
.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::FloatConstant(_, pos)
|
||||||
| Expr::CharConstant(_, pos)
|
| Expr::CharConstant(_, pos)
|
||||||
| Expr::Assignment(_, _, pos)
|
| Expr::Assignment(_, _, pos)
|
||||||
| Expr::Unit(pos)
|
| Expr::And(_, _, pos)
|
||||||
|
| Expr::Or(_, _, pos)
|
||||||
|
| Expr::In(_, _, pos)
|
||||||
| Expr::True(pos)
|
| Expr::True(pos)
|
||||||
| Expr::False(pos) => {
|
| Expr::False(pos)
|
||||||
|
| Expr::Unit(pos) => {
|
||||||
return Err(PERR::MalformedIndexExpr(
|
return Err(PERR::MalformedIndexExpr(
|
||||||
"Only arrays, object maps and strings can be indexed".into(),
|
"Only arrays, object maps and strings can be indexed".into(),
|
||||||
)
|
)
|
||||||
.into_err(pos))
|
.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))
|
.into_err(*pos))
|
||||||
}
|
}
|
||||||
// lhs[??? && ???], lhs[??? || ???]
|
// lhs[??? && ???], lhs[??? || ???], lhs[??? in ???], lhs[true], lhs[false]
|
||||||
Expr::And(lhs, _) | Expr::Or(lhs, _) => {
|
Expr::And(_, _, pos)
|
||||||
return Err(PERR::MalformedIndexExpr(
|
| Expr::Or(_, _, pos)
|
||||||
"Array access expects integer index, not a boolean".into(),
|
| Expr::In(_, _, pos)
|
||||||
)
|
| Expr::True(pos)
|
||||||
.into_err(lhs.position()))
|
| Expr::False(pos) => {
|
||||||
}
|
|
||||||
// lhs[true], lhs[false]
|
|
||||||
Expr::True(pos) | Expr::False(pos) => {
|
|
||||||
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(),
|
||||||
)
|
)
|
||||||
@ -1699,9 +1688,7 @@ fn parse_array_literal<'a>(
|
|||||||
match input.peek().ok_or_else(|| {
|
match input.peek().ok_or_else(|| {
|
||||||
PERR::MissingToken("]".into(), "to end this array literal".into()).into_err_eof()
|
PERR::MissingToken("]".into(), "to end this array literal".into()).into_err_eof()
|
||||||
})? {
|
})? {
|
||||||
(Token::Comma, _) => {
|
(Token::Comma, _) => input.next(),
|
||||||
input.next();
|
|
||||||
}
|
|
||||||
(Token::RightBracket, _) => break,
|
(Token::RightBracket, _) => break,
|
||||||
(_, pos) => {
|
(_, pos) => {
|
||||||
return Err(PERR::MissingToken(
|
return Err(PERR::MissingToken(
|
||||||
@ -1710,7 +1697,7 @@ fn parse_array_literal<'a>(
|
|||||||
)
|
)
|
||||||
.into_err(*pos))
|
.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.
|
/// Parse a binary expression.
|
||||||
fn parse_binary_op<'a>(
|
fn parse_binary_op<'a>(
|
||||||
input: &mut Peekable<TokenIterator<'a>>,
|
input: &mut Peekable<TokenIterator<'a>>,
|
||||||
@ -2189,8 +2324,11 @@ fn parse_binary_op<'a>(
|
|||||||
pos,
|
pos,
|
||||||
),
|
),
|
||||||
|
|
||||||
Token::Or => Expr::Or(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)),
|
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::XOr => Expr::FunctionCall("^".into(), vec![current_lhs, rhs], None, pos),
|
||||||
Token::OrAssign => parse_op_assignment("|", current_lhs, rhs, pos)?,
|
Token::OrAssign => parse_op_assignment("|", current_lhs, rhs, pos)?,
|
||||||
Token::AndAssign => 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()
|
.peek()
|
||||||
.ok_or_else(|| PERR::FnMissingParams(name.clone()).into_err_eof())?
|
.ok_or_else(|| PERR::FnMissingParams(name.clone()).into_err_eof())?
|
||||||
{
|
{
|
||||||
(Token::LeftParen, _) => {
|
(Token::LeftParen, _) => input.next(),
|
||||||
input.next();
|
|
||||||
}
|
|
||||||
(_, pos) => return Err(PERR::FnMissingParams(name).into_err(*pos)),
|
(_, pos) => return Err(PERR::FnMissingParams(name).into_err(*pos)),
|
||||||
}
|
};
|
||||||
|
|
||||||
let mut params = Vec::new();
|
let mut params = Vec::new();
|
||||||
|
|
||||||
@ -2580,9 +2716,7 @@ fn parse_fn<'a>(
|
|||||||
.next()
|
.next()
|
||||||
.ok_or_else(|| PERR::MissingToken(")".into(), end_err.to_string()).into_err_eof())?
|
.ok_or_else(|| PERR::MissingToken(")".into(), end_err.to_string()).into_err_eof())?
|
||||||
{
|
{
|
||||||
(Token::Identifier(s), pos) => {
|
(Token::Identifier(s), pos) => params.push((s, pos)),
|
||||||
params.push((s, pos));
|
|
||||||
}
|
|
||||||
(_, pos) => return Err(PERR::MissingToken(")".into(), end_err).into_err(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()),
|
None => return Err(PERR::FnMissingBody(name).into_err_eof()),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let params = params.into_iter().map(|(p, _)| p).collect();
|
||||||
|
|
||||||
Ok(FnDef {
|
Ok(FnDef {
|
||||||
name,
|
name,
|
||||||
params: params.into_iter().map(|(p, _)| p).collect(),
|
params,
|
||||||
body,
|
body,
|
||||||
pos,
|
pos,
|
||||||
})
|
})
|
||||||
|
@ -49,6 +49,8 @@ pub enum EvalAltResult {
|
|||||||
ErrorNumericIndexExpr(Position),
|
ErrorNumericIndexExpr(Position),
|
||||||
/// Trying to index into a map with an index that is not `String`.
|
/// Trying to index into a map with an index that is not `String`.
|
||||||
ErrorStringIndexExpr(Position),
|
ErrorStringIndexExpr(Position),
|
||||||
|
/// Invalid arguments for `in` operator.
|
||||||
|
ErrorInExpr(Position),
|
||||||
/// The guard expression in an `if` or `while` statement does not return a boolean value.
|
/// The guard expression in an `if` or `while` statement does not return a boolean value.
|
||||||
ErrorLogicGuard(Position),
|
ErrorLogicGuard(Position),
|
||||||
/// The `for` statement encounters a type that is not an iterator.
|
/// 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::ErrorAssignmentToConstant(_, _) => "Assignment to a constant variable",
|
||||||
Self::ErrorMismatchOutputType(_, _) => "Output type is incorrect",
|
Self::ErrorMismatchOutputType(_, _) => "Output type is incorrect",
|
||||||
|
Self::ErrorInExpr(_) => "Malformed 'in' expression",
|
||||||
Self::ErrorDotExpr(_, _) => "Malformed dot expression",
|
Self::ErrorDotExpr(_, _) => "Malformed dot expression",
|
||||||
Self::ErrorArithmetic(_, _) => "Arithmetic error",
|
Self::ErrorArithmetic(_, _) => "Arithmetic error",
|
||||||
Self::ErrorStackOverflow(_) => "Stack overflow",
|
Self::ErrorStackOverflow(_) => "Stack overflow",
|
||||||
@ -154,6 +157,7 @@ impl fmt::Display for EvalAltResult {
|
|||||||
| Self::ErrorLogicGuard(pos)
|
| Self::ErrorLogicGuard(pos)
|
||||||
| Self::ErrorFor(pos)
|
| Self::ErrorFor(pos)
|
||||||
| Self::ErrorAssignmentToUnknownLHS(pos)
|
| Self::ErrorAssignmentToUnknownLHS(pos)
|
||||||
|
| Self::ErrorInExpr(pos)
|
||||||
| Self::ErrorDotExpr(_, pos)
|
| Self::ErrorDotExpr(_, pos)
|
||||||
| Self::ErrorStackOverflow(pos) => write!(f, "{} ({})", desc, pos),
|
| Self::ErrorStackOverflow(pos) => write!(f, "{} ({})", desc, pos),
|
||||||
|
|
||||||
@ -256,6 +260,7 @@ impl EvalAltResult {
|
|||||||
| Self::ErrorAssignmentToUnknownLHS(pos)
|
| Self::ErrorAssignmentToUnknownLHS(pos)
|
||||||
| Self::ErrorAssignmentToConstant(_, pos)
|
| Self::ErrorAssignmentToConstant(_, pos)
|
||||||
| Self::ErrorMismatchOutputType(_, pos)
|
| Self::ErrorMismatchOutputType(_, pos)
|
||||||
|
| Self::ErrorInExpr(pos)
|
||||||
| Self::ErrorDotExpr(_, pos)
|
| Self::ErrorDotExpr(_, pos)
|
||||||
| Self::ErrorArithmetic(_, pos)
|
| Self::ErrorArithmetic(_, pos)
|
||||||
| Self::ErrorStackOverflow(pos)
|
| Self::ErrorStackOverflow(pos)
|
||||||
@ -288,6 +293,7 @@ impl EvalAltResult {
|
|||||||
| Self::ErrorAssignmentToUnknownLHS(pos)
|
| Self::ErrorAssignmentToUnknownLHS(pos)
|
||||||
| Self::ErrorAssignmentToConstant(_, pos)
|
| Self::ErrorAssignmentToConstant(_, pos)
|
||||||
| Self::ErrorMismatchOutputType(_, pos)
|
| Self::ErrorMismatchOutputType(_, pos)
|
||||||
|
| Self::ErrorInExpr(pos)
|
||||||
| Self::ErrorDotExpr(_, pos)
|
| Self::ErrorDotExpr(_, pos)
|
||||||
| Self::ErrorArithmetic(_, pos)
|
| Self::ErrorArithmetic(_, pos)
|
||||||
| Self::ErrorStackOverflow(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]"#)?,
|
engine.eval::<char>(r#"let y = [1, [ 42, 88, "93" ], 3]; y[1][2][1]"#)?,
|
||||||
'3'
|
'3'
|
||||||
);
|
);
|
||||||
|
assert!(engine.eval::<bool>("let y = [1, 2, 3]; 2 in y")?);
|
||||||
|
|
||||||
#[cfg(not(feature = "no_stdlib"))]
|
#[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")?;
|
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"))]
|
#[cfg(not(feature = "no_stdlib"))]
|
||||||
{
|
{
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
|
@ -15,6 +15,10 @@ fn test_string() -> Result<(), EvalAltResult> {
|
|||||||
|
|
||||||
assert_eq!(engine.eval::<String>(r#""foo" + "bar""#)?, "foobar");
|
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"))]
|
#[cfg(not(feature = "no_stdlib"))]
|
||||||
assert_eq!(engine.eval::<String>(r#""foo" + 123"#)?, "foo123");
|
assert_eq!(engine.eval::<String>(r#""foo" + 123"#)?, "foo123");
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user