use std::ops::ControlFlow;
use ddx_core::sqlparser::ast::{Expr, Ident, VisitMut, VisitorMut};
use ddx_core::sqlparser::dialect::GenericDialect;
use ddx_core::sqlparser::parser::Parser;
use ddx_core::{ColRef, Ddx};
struct StripNested;
impl VisitorMut for StripNested {
type Break = ();
fn post_visit_expr(&mut self, e: &mut Expr) -> ControlFlow<()> {
while matches!(e, Expr::Nested(_)) {
let owned = std::mem::replace(e, Expr::Identifier(Ident::new("")));
if let Expr::Nested(inner) = owned {
*e = *inner;
}
}
ControlFlow::Continue(())
}
}
fn strip(mut e: Expr) -> Expr {
let _ = VisitMut::visit(&mut e, &mut StripNested);
e
}
fn parse(text: &str) -> Expr {
Parser::new(&GenericDialect {})
.try_with_sql(text)
.and_then(|mut p| p.parse_expr())
.unwrap_or_else(|e| panic!("reparse of `{text}` failed: {e}"))
}
fn assert_roundtrips(expr: &str, wrt: &str) {
let ddx = Ddx::new();
let d = ddx
.differentiate(&parse(expr), &ColRef::bare(wrt))
.unwrap_or_else(|e| panic!("differentiate d/d{wrt} ({expr}): {e}"));
let rendered = d.to_string();
let reparsed = parse(&rendered);
assert_eq!(
strip(reparsed),
strip(d),
"reparse(render(d)) != d modulo Nested for d/d{wrt} ({expr}); rendered = {rendered}"
);
}
#[test]
fn precedence_sensitive_derivatives_round_trip() {
assert_roundtrips("(a + b) * c", "a"); assert_roundtrips("x / y", "x"); assert_roundtrips("x / y", "y"); assert_roundtrips("sin(x) * x", "x"); assert_roundtrips("power(x, 3)", "x"); assert_roundtrips("sin(x * y + x)", "x"); assert_roundtrips("1 / (x + y)", "x"); assert_roundtrips("sqrt(x * x + y * y)", "x"); assert_roundtrips("a * b * c * d", "a"); assert_roundtrips("exp(x) / (x - 1)", "x"); }
#[test]
fn negative_literal_derivatives_round_trip() {
assert_roundtrips("abs(x)", "x"); assert_roundtrips("power(x, -2)", "x"); assert_roundtrips("power(x, 0.5)", "x"); assert_roundtrips("x / y", "y"); assert_roundtrips("cos(x) * x", "x"); }
#[test]
fn same_precedence_right_operands_keep_their_parentheses() {
assert_roundtrips("sin(x * y * x)", "x");
assert_roundtrips("sin(x) * cos(x) * tan(x)", "x");
assert_roundtrips("x * y / x", "x");
assert_roundtrips("x / y / x", "x");
assert_roundtrips("1.5 / power(((y / (y / x)) - x), 1.5)", "y");
assert_roundtrips(
"((y / x) / ((-(y / x)) / 1.5)) / log2(tanh(power(3, 2.5)))",
"y",
);
assert_roundtrips("x - y - x", "x");
assert_roundtrips("x + y - x", "x");
}
#[test]
fn strip_nested_actually_normalizes() {
assert_eq!(strip(parse("a + b * c")), strip(parse("a + (b * c)")));
assert_ne!(strip(parse("(a + b) * c")), strip(parse("a + b * c")));
}