use std::collections::HashSet;
use std::sync::Arc;
#[derive(Debug, Eq, PartialEq, Clone)]
pub enum RelationOp {
LessThan,
LessThanEq,
GreaterThan,
GreaterThanEq,
Equals,
NotEquals,
In,
}
#[derive(Debug, Eq, PartialEq, Clone)]
pub enum ArithmeticOp {
Add,
Subtract,
Divide,
Multiply,
Modulus,
}
#[derive(Debug, Eq, PartialEq, Clone)]
pub enum UnaryOp {
Not,
DoubleNot,
Minus,
DoubleMinus,
}
#[derive(Debug, PartialEq, Clone)]
pub enum Expression {
Arithmetic(Box<Expression>, ArithmeticOp, Box<Expression>),
Relation(Box<Expression>, RelationOp, Box<Expression>),
Ternary(Box<Expression>, Box<Expression>, Box<Expression>),
Or(Box<Expression>, Box<Expression>),
And(Box<Expression>, Box<Expression>),
Unary(UnaryOp, Box<Expression>),
Member(Box<Expression>, Box<Member>),
FunctionCall(Box<Expression>, Option<Box<Expression>>, Vec<Expression>),
List(Vec<Expression>),
Map(Vec<(Expression, Expression)>),
Atom(Atom),
Ident(Arc<String>),
}
#[derive(Debug, PartialEq, Clone)]
pub enum Member {
Attribute(Arc<String>),
Index(Box<Expression>),
Fields(Vec<(Arc<String>, Expression)>),
}
#[derive(Debug, PartialEq, Clone)]
pub enum Atom {
Int(i64),
UInt(u64),
Float(f64),
String(Arc<String>),
Bytes(Arc<Vec<u8>>),
Bool(bool),
Null,
}
pub struct ExpressionReferences<'expr> {
variables: HashSet<&'expr str>,
functions: HashSet<&'expr str>,
}
impl ExpressionReferences<'_> {
pub fn has_variable(&self, name: impl AsRef<str>) -> bool {
self.variables.contains(name.as_ref())
}
pub fn has_function(&self, name: impl AsRef<str>) -> bool {
self.functions.contains(name.as_ref())
}
pub fn variables(&self) -> Vec<&str> {
self.variables.iter().copied().collect()
}
pub fn functions(&self) -> Vec<&str> {
self.functions.iter().copied().collect()
}
}
impl Expression {
pub fn references(&self) -> ExpressionReferences {
let mut variables = HashSet::new();
let mut functions = HashSet::new();
self._references(&mut variables, &mut functions);
ExpressionReferences {
variables,
functions,
}
}
fn _references<'expr>(
&'expr self,
variables: &mut HashSet<&'expr str>,
functions: &mut HashSet<&'expr str>,
) {
match self {
Expression::Arithmetic(e1, _, e2)
| Expression::Relation(e1, _, e2)
| Expression::Ternary(e1, _, e2)
| Expression::Or(e1, e2)
| Expression::And(e1, e2) => {
e1._references(variables, functions);
e2._references(variables, functions);
}
Expression::Unary(_, e) => {
e._references(variables, functions);
}
Expression::Member(e, _) => {
e._references(variables, functions);
}
Expression::FunctionCall(name, target, args) => {
if let Expression::Ident(v) = &**name {
functions.insert(v.as_str());
}
if let Some(target) = target {
target._references(variables, functions);
}
for e in args {
e._references(variables, functions);
}
}
Expression::List(e) => {
for e in e {
e._references(variables, functions);
}
}
Expression::Map(v) => {
for (e1, e2) in v {
e1._references(variables, functions);
e2._references(variables, functions);
}
}
Expression::Atom(_) => {}
Expression::Ident(v) => {
variables.insert(v.as_str());
}
}
}
}
#[cfg(test)]
mod tests {
use crate::parse;
#[test]
fn test_references() {
let expr =
parse("foo.bar.baz == true && size(foo) > 0 && foo[0] == 1 && bar.startsWith('a')")
.unwrap();
let refs = expr.references();
assert!(refs.has_variable("foo"));
assert!(refs.has_variable("bar"));
assert_eq!(refs.variables.len(), 2);
assert!(refs.has_function("size"));
assert!(refs.has_function("startsWith"));
assert_eq!(refs.functions.len(), 2);
}
}