use crate::core::Expression;
#[derive(Debug, Clone)]
pub struct WildcardConstraints {
pub exclude: Vec<Expression>,
pub properties: Vec<fn(&Expression) -> bool>,
}
impl PartialEq for WildcardConstraints {
fn eq(&self, other: &Self) -> bool {
self.exclude == other.exclude && self.properties.len() == other.properties.len()
}
}
impl WildcardConstraints {
pub fn with_exclude(exclude: Vec<Expression>) -> Self {
Self {
exclude,
properties: Vec::new(),
}
}
pub fn with_properties(properties: Vec<fn(&Expression) -> bool>) -> Self {
Self {
exclude: Vec::new(),
properties,
}
}
pub fn is_satisfied_by(&self, expr: &Expression) -> bool {
for excluded in &self.exclude {
if expr == excluded {
return false;
}
}
for excluded in &self.exclude {
if contains_subexpression(expr, excluded) {
return false;
}
}
for property in &self.properties {
if !property(expr) {
return false;
}
}
true
}
}
fn contains_subexpression(expr: &Expression, sub: &Expression) -> bool {
if expr == sub {
return true;
}
match expr {
Expression::Add(terms) | Expression::Mul(terms) | Expression::Set(terms) => {
terms.iter().any(|t| contains_subexpression(t, sub))
}
Expression::Pow(base, exp) => {
contains_subexpression(base, sub) || contains_subexpression(exp, sub)
}
Expression::Function { args, .. } => args.iter().any(|a| contains_subexpression(a, sub)),
Expression::Complex(data) => {
contains_subexpression(&data.real, sub) || contains_subexpression(&data.imag, sub)
}
_ => false,
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum Pattern {
Wildcard {
name: String,
constraints: Option<WildcardConstraints>,
},
Exact(Expression),
Add(Vec<Pattern>),
Mul(Vec<Pattern>),
Pow(Box<Pattern>, Box<Pattern>),
Function { name: String, args: Vec<Pattern> },
}
impl Pattern {
pub fn wildcard(name: impl Into<String>) -> Self {
Pattern::Wildcard {
name: name.into(),
constraints: None,
}
}
pub fn wildcard_excluding(name: impl Into<String>, exclude: Vec<Expression>) -> Self {
Pattern::Wildcard {
name: name.into(),
constraints: Some(WildcardConstraints::with_exclude(exclude)),
}
}
pub fn wildcard_with_properties(
name: impl Into<String>,
properties: Vec<fn(&Expression) -> bool>,
) -> Self {
Pattern::Wildcard {
name: name.into(),
constraints: Some(WildcardConstraints::with_properties(properties)),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::prelude::*;
#[test]
fn test_wildcard_constraints_exclude() {
let x = symbol!(x);
let constraints = WildcardConstraints::with_exclude(vec![Expression::symbol(x.clone())]);
assert!(!constraints.is_satisfied_by(&Expression::symbol(x.clone())));
assert!(constraints.is_satisfied_by(&Expression::integer(42)));
}
#[test]
fn test_wildcard_constraints_exclude_subexpression() {
let x = symbol!(x);
let constraints = WildcardConstraints::with_exclude(vec![Expression::symbol(x.clone())]);
let expr_with_x =
Expression::add(vec![Expression::symbol(x.clone()), Expression::integer(1)]);
assert!(!constraints.is_satisfied_by(&expr_with_x));
}
#[test]
fn test_wildcard_constraints_properties() {
fn is_integer(expr: &Expression) -> bool {
matches!(expr, Expression::Number(_))
}
let constraints = WildcardConstraints::with_properties(vec![is_integer]);
assert!(constraints.is_satisfied_by(&Expression::integer(42)));
let x = symbol!(x);
assert!(!constraints.is_satisfied_by(&Expression::symbol(x)));
}
#[test]
fn test_pattern_wildcard_construction() {
let pattern = Pattern::wildcard("x");
match pattern {
Pattern::Wildcard { name, constraints } => {
assert_eq!(name.as_str(), "x");
assert!(constraints.is_none());
}
_ => panic!("Expected Wildcard pattern"),
}
}
#[test]
fn test_pattern_wildcard_excluding_construction() {
let x = symbol!(x);
let pattern = Pattern::wildcard_excluding("a", vec![Expression::symbol(x.clone())]);
match pattern {
Pattern::Wildcard { name, constraints } => {
assert_eq!(name.as_str(), "a");
assert!(constraints.is_some());
}
_ => panic!("Expected Wildcard pattern"),
}
}
#[test]
fn test_pattern_wildcard_with_properties_construction() {
fn is_integer(expr: &Expression) -> bool {
matches!(expr, Expression::Number(_))
}
let pattern = Pattern::wildcard_with_properties("n", vec![is_integer]);
match pattern {
Pattern::Wildcard { name, constraints } => {
assert_eq!(name.as_str(), "n");
assert!(constraints.is_some());
}
_ => panic!("Expected Wildcard pattern"),
}
}
#[test]
fn test_contains_subexpression_direct() {
let x = symbol!(x);
let expr = Expression::symbol(x.clone());
assert!(contains_subexpression(&expr, &expr));
}
#[test]
fn test_contains_subexpression_in_add() {
let x = symbol!(x);
let expr = Expression::add(vec![Expression::symbol(x.clone()), Expression::integer(1)]);
assert!(contains_subexpression(
&expr,
&Expression::symbol(x.clone())
));
assert!(contains_subexpression(&expr, &Expression::integer(1)));
}
#[test]
fn test_contains_subexpression_in_mul() {
let x = symbol!(x);
let expr = Expression::mul(vec![Expression::integer(2), Expression::symbol(x.clone())]);
assert!(contains_subexpression(
&expr,
&Expression::symbol(x.clone())
));
}
#[test]
fn test_contains_subexpression_in_pow() {
let x = symbol!(x);
let expr = Expression::pow(Expression::symbol(x.clone()), Expression::integer(2));
assert!(contains_subexpression(
&expr,
&Expression::symbol(x.clone())
));
assert!(contains_subexpression(&expr, &Expression::integer(2)));
}
#[test]
fn test_contains_subexpression_in_function() {
let x = symbol!(x);
let expr = Expression::function("sin", vec![Expression::symbol(x.clone())]);
assert!(contains_subexpression(
&expr,
&Expression::symbol(x.clone())
));
}
#[test]
fn test_contains_subexpression_not_found() {
let x = symbol!(x);
let y = symbol!(y);
let expr = Expression::symbol(x);
assert!(!contains_subexpression(&expr, &Expression::symbol(y)));
}
}