use crate::{Arity, Eval, Factory, NodeValue, TreeNode, ops::Param};
use std::{
fmt::{Debug, Display},
hash::Hash,
};
pub enum Op<T> {
Fn(&'static str, Arity, fn(&[T]) -> T),
Var(&'static str, usize, Option<usize>),
Const(&'static str, T),
Value(&'static str, Arity, Param<T>, fn(&[T], &T) -> T),
Pair(&'static str, Arity, Param<(T, T)>, fn(&[T], &(T, T)) -> T),
}
impl<T> Op<T> {
pub fn name(&self) -> &str {
match self {
Op::Fn(name, _, _) => name,
Op::Var(name, _, _) => name,
Op::Const(name, _) => name,
Op::Value(name, _, _, _) => name,
Op::Pair(name, _, _, _) => name,
}
}
pub fn arity(&self) -> Arity {
match self {
Op::Fn(_, arity, _) => *arity,
Op::Var(_, _, _) => Arity::Zero,
Op::Const(_, _) => Arity::Zero,
Op::Value(_, arity, _, _) => *arity,
Op::Pair(_, arity, _, _) => *arity,
}
}
pub fn is_fn(&self) -> bool {
matches!(self, Op::Fn(_, _, _))
}
pub fn is_var(&self) -> bool {
matches!(self, Op::Var(_, _, _))
}
pub fn is_const(&self) -> bool {
matches!(self, Op::Const(_, _))
}
pub fn is_value(&self) -> bool {
matches!(self, Op::Value(_, _, _, _))
}
}
impl<T> Eval<[T], T> for Op<T>
where
T: Clone,
{
fn eval(&self, inputs: &[T]) -> T {
match self {
Op::Fn(_, _, op) => op(inputs),
Op::Var(_, index, _) => inputs[*index].clone(),
Op::Const(_, value) => value.clone(),
Op::Value(_, _, value, operation) => operation(inputs, value.data()),
Op::Pair(_, _, value, operation) => operation(inputs, value.data()),
}
}
}
impl<T> Factory<(), Op<T>> for Op<T>
where
T: Clone,
{
fn new_instance(&self, _: ()) -> Op<T> {
match self {
Op::Fn(name, arity, op) => Op::Fn(name, *arity, *op),
Op::Var(name, index, domain) => Op::Var(name, *index, *domain),
Op::Const(name, value) => Op::Const(name, value.clone()),
Op::Value(name, arity, value, operation) => {
Op::Value(name, *arity, value.new_instance(()), *operation)
}
Op::Pair(name, arity, value, operation) => {
Op::Pair(name, *arity, value.new_instance(()), *operation)
}
}
}
}
impl<T> Clone for Op<T>
where
T: Clone,
{
fn clone(&self) -> Self {
match self {
Op::Fn(name, arity, op) => Op::Fn(name, *arity, *op),
Op::Var(name, index, domain) => Op::Var(name, *index, *domain),
Op::Const(name, value) => Op::Const(name, value.clone()),
Op::Value(name, arity, value, operation) => {
Op::Value(name, *arity, value.clone(), *operation)
}
Op::Pair(name, arity, value, operation) => {
Op::Pair(name, *arity, value.clone(), *operation)
}
}
}
}
impl<T> PartialEq for Op<T>
where
T: PartialEq,
{
fn eq(&self, other: &Self) -> bool {
self.name() == other.name()
&& self.arity() == other.arity()
&& match (self, other) {
(Op::Fn(_, _, _), Op::Fn(_, _, _)) => true,
(Op::Var(_, idx_a, card_a), Op::Var(_, idx_b, card_b)) => {
idx_a == idx_b && card_a == card_b
}
(Op::Const(_, val_a), Op::Const(_, val_b)) => val_a == val_b,
(Op::Value(_, _, val_a, _), Op::Value(_, _, val_b, _)) => val_a == val_b,
(Op::Pair(_, _, val_a, _), Op::Pair(_, _, val_b, _)) => val_a == val_b,
_ => false,
}
}
}
impl Hash for Op<f32> {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.name().hash(state);
self.arity().hash(state);
match self {
Op::Fn(_, _, op) => {
let op_ptr = *op as usize;
op_ptr.hash(state);
}
Op::Var(_, index, domain) => {
index.hash(state);
domain.hash(state);
}
Op::Const(_, value) => {
value.to_bits().hash(state);
}
Op::Value(_, _, value, operation) => {
(*value).data().to_bits().hash(state);
let op_ptr = *operation as usize;
op_ptr.hash(state);
}
Op::Pair(_, _, value, operation) => {
let data = (*value).data();
let b_one = data.0.to_bits();
let b_two = data.1.to_bits();
b_one.hash(state);
b_two.hash(state);
let op_ptr = *operation as usize;
op_ptr.hash(state);
}
}
}
}
impl<T> Display for Op<T> {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "{}", self.name())
}
}
impl<T> Default for Op<T>
where
T: Default,
{
fn default() -> Self {
Op::Fn("default", Arity::Zero, |_: &[T]| T::default())
}
}
impl<T> Debug for Op<T>
where
T: Debug,
{
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Op::Fn(name, _, _) => write!(f, "Fn: {}", name),
Op::Var(name, index, card) => match card {
Some(k) => write!(f, "Var: {}({},{})", name, index, k),
None => write!(f, "Var: {}({})", name, index),
},
Op::Const(name, value) => match f.precision() {
Some(p) => write!(f, "Con: {}({:.*?})", name, p, value),
None => write!(f, "Con: {}({:?})", name, value),
},
Op::Value(name, _, value, _) => match f.precision() {
Some(p) => write!(f, "Val: {}({:.*?})", name, p, value),
None => write!(f, "Val: {}({:?})", name, value),
},
Op::Pair(name, _, value, _) => match f.precision() {
Some(p) => write!(f, "Pair: {}{:.*?}", name, p, value),
None => write!(f, "Pair: {}{:?}", name, value),
},
}
}
}
impl<T: Clone> From<Op<T>> for NodeValue<Op<T>> {
fn from(value: Op<T>) -> Self {
let arity = value.arity();
NodeValue::Bounded(value, arity)
}
}
impl<T> From<Op<T>> for TreeNode<Op<T>> {
fn from(value: Op<T>) -> Self {
let arity = value.arity();
TreeNode::with_arity(value, arity)
}
}
impl<T> From<Op<T>> for Vec<TreeNode<Op<T>>> {
fn from(value: Op<T>) -> Self {
vec![TreeNode::from(value)]
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_ops() {
let op = Op::add();
assert_eq!(op.name(), "add");
assert_eq!(op.arity(), Arity::Exact(2));
assert_eq!(op.eval(&[1_f32, 2_f32]), 3_f32);
assert_eq!(op.new_instance(()), op);
}
#[test]
fn test_op_clone() {
let op = Op::add();
let op2 = op.clone();
let result = op.eval(&[1_f32, 2_f32]);
let result2 = op2.eval(&[1_f32, 2_f32]);
assert_eq!(op, op2);
assert_eq!(result, result2);
}
}