use std::collections::BTreeSet;
use pest_consume::{match_nodes, Parser};
use crate::sql::expression::function::Function;
use crate::sql::expression::operator::comparison::ComparisonOperator;
use crate::sql::expression::todo_field::TodoField;
use crate::sql::expression::value::Value;
use crate::sql::expression::Expression;
#[derive(Parser)]
#[grammar = "sql/todosql.pest"]
pub struct ExpressionParser;
pub type Result<T> = std::result::Result<T, pest_consume::Error<Rule>>;
type Node<'i> = pest_consume::Node<'i, Rule, ()>;
#[pest_consume::parser]
impl ExpressionParser {
fn null(_n: Node) -> Result<Value> {
Ok(Value::Null)
}
fn boolean(n: Node) -> Result<Value> {
let v = match n.as_str() {
"true" => Value::Boolean(true),
"false" => Value::Boolean(false),
_ => unreachable!(),
};
Ok(v)
}
fn text(n: Node) -> Result<Value> {
let v = Value::Text(n.as_str().to_string().replace("'", ""));
Ok(v)
}
fn set_of_texts(n: Node) -> Result<Value> {
let mut set = BTreeSet::new();
match_nodes!(n.into_children();
[text(ts)..] => {
for t in ts {
match t {
Value::Text(s) => set.insert(s),
_ => unreachable!(),
};
};
}
);
Ok(Value::Set(set))
}
pub fn value(n: Node) -> Result<Value> {
Ok(match_nodes!(n.into_children();
[null(v)] => v,
[boolean(v)] => v,
[text(v)] => v,
[set_of_texts(v)] => v,
))
}
fn not(n: Node) -> Result<()> {
Ok(())
}
pub fn negated(n: Node) -> Result<Expression> {
Ok(match_nodes!(n.into_children();
[not(op), operand_4(e)] => {
Expression::Negated(Box::new(e))
}
))
}
fn gt(_n: Node) -> Result<ComparisonOperator> {
Ok(ComparisonOperator::Gt)
}
fn lt(_n: Node) -> Result<ComparisonOperator> {
Ok(ComparisonOperator::Lt)
}
fn gt_eq(_n: Node) -> Result<ComparisonOperator> {
Ok(ComparisonOperator::GtEq)
}
fn lt_eq(_n: Node) -> Result<ComparisonOperator> {
Ok(ComparisonOperator::LtEq)
}
fn eq(_n: Node) -> Result<ComparisonOperator> {
Ok(ComparisonOperator::Eq)
}
fn not_eq(_n: Node) -> Result<ComparisonOperator> {
Ok(ComparisonOperator::NotEq)
}
fn contains(_n: Node) -> Result<ComparisonOperator> {
Ok(ComparisonOperator::Contains)
}
fn is_contained_by(_n: Node) -> Result<ComparisonOperator> {
Ok(ComparisonOperator::IsContainedBy)
}
fn overlap(_n: Node) -> Result<ComparisonOperator> {
Ok(ComparisonOperator::Overlap)
}
pub fn comparison_operator(n: Node) -> Result<ComparisonOperator> {
Ok(match_nodes!(n.into_children();
[gt(bo)] => bo,
[lt(bo)] => bo,
[gt_eq(bo)] => bo,
[lt_eq(bo)] => bo,
[eq(bo)] => bo,
[not_eq(bo)] => bo,
[contains(bo)] => bo,
[is_contained_by(bo)] => bo,
[overlap(bo)] => bo,
))
}
fn function_name(n: Node) -> Result<String> {
Ok(n.as_str().to_string())
}
fn function_argument_list_empty(_n: Node) -> Result<()> {
Ok(())
}
fn function_argument_list_nonempty(n: Node) -> Result<Vec<Value>> {
Ok(match_nodes!(n.into_children();
[value(vs)..] => vs.collect()
))
}
fn function_argument_list(n: Node) -> Result<Vec<Value>> {
Ok(match_nodes!(n.into_children();
[function_argument_list_empty(_)] => {
Vec::new()
},
[function_argument_list_nonempty(args)] => {
args
}
))
}
pub fn function(n: Node) -> Result<Function> {
Ok(match_nodes!(n.into_children();
[function_name(name), function_argument_list(args)] => {
Function { name, args }
},
))
}
pub fn todo_field(n: Node) -> Result<TodoField> {
Ok(match n.as_str() {
"full_text" => TodoField::FullText,
"is_completed" => TodoField::IsCompleted,
"id" => TodoField::Id,
"is_blocking_for" => TodoField::IsBlockingFor,
"is_blocked" => TodoField::IsBlocked,
"blocked_by" => TodoField::BlockedBy,
"creation_date" => TodoField::CreationDate,
"completion_date" => TodoField::CompletionDate,
"priority" => TodoField::Priority,
"ccontext" => TodoField::CanonicalContext,
"contexts" => TodoField::Contexts,
"cproject" => TodoField::CanonicalProject,
"projects" => TodoField::Projects,
"due_date" => TodoField::DueDate,
"threshold_date" => TodoField::ThresholdDate,
"is_hidden" => TodoField::IsHidden,
_ => unreachable!(),
})
}
fn and(n: Node) -> Result<Expression> {
Ok(match_nodes!(n.into_children();
[operand_5(left)] => {
left
},
[operand_5(left), operand_5(rights)..] => {
let mut expr = left;
for right in rights {
expr = Expression::And {
left: Box::from(expr),
right: Box::from(right),
};
}
expr
},
))
}
fn or(n: Node) -> Result<Expression> {
Ok(match_nodes!(n.into_children();
[operand_6(left)] => {
left
},
[operand_6(left), operand_6(rights)..] => {
let mut expr = left;
for right in rights {
expr = Expression::Or {
left: Box::from(expr),
right: Box::from(right),
};
}
expr
},
))
}
fn identifier(n: Node) -> Result<Expression> {
let tf = ExpressionParser::todo_field(n.into_children().single()?)?;
Ok(Expression::Identifier(tf))
}
fn nested(n: Node) -> Result<Expression> {
let inner = ExpressionParser::expression(n.into_children().single()?)?;
Ok(Expression::Nested(Box::new(inner)))
}
fn between(n: Node) -> Result<Expression> {
Ok(match_nodes!(n.into_children();
[operand_1(expr), not(_), operand_1(low), operand_1(high)] => {
Expression::Between {
expr: Box::new(expr),
negated: true,
low: Box::new(low),
high: Box::new(high),
}
},
[operand_1(expr), operand_1(low), operand_1(high)] => {
Expression::Between {
expr: Box::new(expr),
negated: false,
low: Box::new(low),
high: Box::new(high),
}
}
))
}
fn in_set(n: Node) -> Result<Expression> {
Ok(match_nodes!(n.into_children();
[operand_1(expr), not(_), set_of_texts(set)] => {
Expression::InSet {
expr: Box::new(expr),
negated: true,
set
}
},
[operand_1(expr), set_of_texts(set)] => {
Expression::InSet {
expr: Box::new(expr),
negated: false,
set
}
}
))
}
fn ntf(n: Node) -> Result<Value> {
Ok(match n.as_str() {
"null" => Value::Null,
"true" => Value::Boolean(true),
"false" => Value::Boolean(false),
_ => unreachable!(),
})
}
fn is(n: Node) -> Result<Expression> {
Ok(match_nodes!(n.into_children();
[operand_3(left), not(_), ntf(right)] => {
Expression::Is {
negated: true,
left: Box::from(left),
right: right,
}
},
[operand_3(left), ntf(right)] => {
Expression::Is {
negated: false,
left: Box::from(left),
right: right,
}
}
))
}
fn _comparison_operator_and_operand(n: Node) -> Result<(ComparisonOperator, Expression)> {
Ok(match_nodes!(n.into_children();
[comparison_operator(operator), operand_2(operand)] => (operator, operand)
))
}
pub fn comparison_op_(n: Node) -> Result<Expression> {
Ok(match_nodes!(n.into_children();
[operand_2(left), comparison_operator(op), operand_2(right)] => {
Expression::ComparisonOp {
left: Box::new(left),
op,
right: Box::new(right)
}
}
))
}
fn operand_0(n: Node) -> Result<Expression> {
Ok(match_nodes!(n.into_children();
[identifier(e)] => e,
[nested(e)] => e,
[value(v)] => Expression::Value(v),
))
}
fn operand_1(n: Node) -> Result<Expression> {
Ok(match_nodes!(n.into_children();
[function(f)] => Expression::Function(f),
[operand_0(e)] => e,
))
}
fn operand_2(n: Node) -> Result<Expression> {
Ok(match_nodes!(n.into_children();
[between(e)] => e,
[in_set(e)] => e,
[operand_1(e)] => e,
))
}
fn operand_3(n: Node) -> Result<Expression> {
Ok(match_nodes!(n.into_children();
[comparison_op_(e)] => e,
[operand_2(e)] => e,
))
}
fn operand_4(n: Node) -> Result<Expression> {
Ok(match_nodes!(n.into_children();
[is(e)] => e,
[operand_3(e)] => e,
))
}
fn operand_5(n: Node) -> Result<Expression> {
Ok(match_nodes!(n.into_children();
[negated(e)] => e,
[operand_4(e)] => e,
))
}
fn operand_6(n: Node) -> Result<Expression> {
Ok(match_nodes!(n.into_children();
[and(e)] => e,
[operand_5(e)] => e,
))
}
fn operand_7(n: Node) -> Result<Expression> {
Ok(match_nodes!(n.into_children();
[or(e)] => e,
[operand_6(e)] => e,
))
}
pub fn expression(n: Node) -> Result<Expression> {
ExpressionParser::operand_7(n.into_children().single()?)
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn parsing_todo_fields() {
assert!(TodoField::parse("full_text").is_ok());
assert!(TodoField::parse("is_completed").is_ok());
assert!(TodoField::parse("is_blocked").is_ok());
assert!(TodoField::parse("viscosity").is_err());
}
#[test]
fn parsing_values() {
assert_eq!(Value::parse("null"), Ok(Value::Null));
assert_eq!(Value::parse("true"), Ok(Value::Boolean(true)));
assert_eq!(Value::parse("false"), Ok(Value::Boolean(false)));
assert_eq!(Value::parse("'foo'"), Ok(Value::Text("foo".to_string())));
let mut exp_set = BTreeSet::new();
exp_set.insert("z".to_string());
exp_set.insert("a".to_string());
exp_set.insert("b".to_string());
let exp_setv = Value::Set(exp_set);
assert_eq!(Value::parse("('a', 'b', 'z')"), Ok(exp_setv.clone()));
assert_eq!(
Value::parse("( 'a' , 'b' , 'z' )"),
Ok(exp_setv.clone())
);
assert_eq!(Value::parse("('a', 'b', 'z',)"), Ok(exp_setv)); }
#[test]
fn parsing_functions() {
assert_eq!(
Function::parse("noargs()"),
Ok(Function {
name: "noargs".to_string(),
args: vec![],
})
);
assert_eq!(
Function::parse("day('2020-10-10')"),
Ok(Function {
name: "day".to_string(),
args: vec![Value::Text("2020-10-10".to_string())],
})
);
assert_eq!(
Function::parse("foo(true, null, 'bar')"),
Ok(Function {
name: "foo".to_string(),
args: vec![
Value::Boolean(true),
Value::Null,
Value::Text("bar".to_string())
],
})
);
}
#[test]
fn parsing_operand_0() {
assert_eq!(
Expression::parse("threshold_date").unwrap(),
Expression::Identifier(TodoField::ThresholdDate)
);
assert_eq!(
Expression::parse("(threshold_date)").unwrap(),
Expression::Nested(Box::new(Expression::Identifier(TodoField::ThresholdDate))),
);
assert_eq!(
Expression::parse("'abc'").unwrap(),
Expression::Value(Value::Text("abc".to_string()))
);
assert_eq!(
Expression::parse("null").unwrap(),
Expression::Value(Value::Null)
);
}
#[test]
fn parsing_other_expressions() {
assert_eq!(
Expression::parse("not true").unwrap(),
Expression::Negated(Box::from(Expression::Value(Value::Boolean(true))),)
);
assert_eq!(
Expression::parse("'somewhere' between 'immensity' and 'eternity'").unwrap(),
Expression::Between {
expr: Box::new(Expression::Value(Value::Text("somewhere".to_string()))),
negated: false,
low: Box::new(Expression::Value(Value::Text("immensity".to_string()))),
high: Box::new(Expression::Value(Value::Text("eternity".to_string()))),
}
);
let mut exp_set = BTreeSet::new();
exp_set.insert("@home".to_string());
exp_set.insert("@pc".to_string());
assert_eq!(
Expression::parse("ccontext not in ('@home', '@pc')").unwrap(),
Expression::InSet {
expr: Box::new(Expression::Identifier(TodoField::CanonicalContext)),
negated: true,
set: Value::Set(exp_set.clone()),
}
);
assert_eq!(
Expression::parse("ccontext in ('@home', '@pc')").unwrap(),
Expression::InSet {
expr: Box::new(Expression::Identifier(TodoField::CanonicalContext)),
negated: false,
set: Value::Set(exp_set),
}
);
assert_eq!(
Expression::parse("true is not null").unwrap(),
Expression::Is {
left: Box::new(Expression::Value(Value::Boolean(true))),
negated: true,
right: Value::Null,
}
);
assert_eq!(
Expression::parse("null is null").unwrap(),
Expression::Is {
negated: false,
left: Box::new(Expression::Value(Value::Null)),
right: Value::Null
}
);
let got = Expression::parse(
"(true == true) and (false != true) and ('sucka' <> 'blyat') or true",
);
assert!(got.is_ok());
}
}