I'm thinking about something like this.
A generic form for an expression. Which could be a single variable, an association between expressions, operations or even associative operations over expressions.
Implementations of Symbols, Associations and Operations would compose the expressions. They could assume addition, subtraction, division, power, trigonometric operations, differential operations, and else after the implementation.
Well known expression manipulation like substitution, expansion, simplification should be implemented in the form of traits, so that it could analyze the expression tree through recursion.
I wonder how the existing std::collections could take place. I'd keep the expression structure separated from anything else, in order to keep it clean. A pre-processing step of the whole structure could generate related collections of auxiliary data, as long as it is necessary for manipulation algorithms.
use std::cell::RefCell;
pub trait Association/* : Evaluable + Replaceable + Expandable + Simplifiable + Sortable */ {
fn rhs(&self) -> &RefCell<Expression>;
fn lhs(&self) -> &RefCell<Expression>;
}
pub trait Operation/* : Evaluable + Replaceable + Expandable + Simplifiable + Sortable */ {
fn argument(&self) -> &RefCell<Expression>;
}
pub trait AssociativeOperation/* : Evaluable + Replaceable + Expandable + Simplifiable + Sortable */ {
fn argument(&self) -> &RefCell<Expression>;
fn modifier(&self) -> &RefCell<Expression>;
}
pub enum Expression {
AssociativeOperation(Box<dyn AssociativeOperation>),
Operation(Box<dyn Operation>),
Association(Box<dyn Association>),
Symbol(Symbol),
}
pub trait Replaceable {
fn substitute(&self, old: &Symbol, new: &Symbol) -> Result<Rc<Expression>, Rc<Expression>>;
}
pub trait Evaluable {
fn evaluate(&self) -> Result<Rc<Expression>, Rc<Expression>>;
}
pub trait Simplifiable {
fn simplify(&self) -> Result<Rc<Expression>, Rc<Expression>>;
}
pub trait Expandable {
fn expand(&self) -> Result<Rc<Expression>, Rc<Expression>>;
}
pub trait Sortable {
fn sort(&self) -> Result<Rc<Expression>, Rc<Expression>>;
}
pub struct Addition {
rhs: RefCell<Expression>,
lhs: RefCell<Expression>,
}
impl Association for Addition {
fn rhs(&self) -> &RefCell<Expression> {
&self.rhs
}
fn lhs(&self) -> &RefCell<Expression> {
&self.lhs
}
}
impl std::ops::Add for Expression {
type Output = Expression;
fn add(self, other: Expression) -> Expression {
Expression::Association(Box::new(Addition {
rhs: RefCell::new(self),
lhs: RefCell::new(other),
}))
}
}
pub struct Symbol {
pub label: String,
pub value: Option<f64>,
}
impl Symbol {
pub fn variable(label: String) -> Expression {
Expression::Symbol(Self {
label: label,
value: None,
})
}
pub fn constant(label: String, value: f64) -> Expression {
Expression::Symbol(Self {
label: label,
value: Some(value),
})
}
}
impl Eq for Symbol {}
impl PartialEq for Symbol {
fn eq(&self, other: &Self) -> bool {
self.label.eq(&other.label) && self.value == other.value
}
}
Question:
- Would it be a good design?
- How to improve it?