open_hypergraphs/lax/var/
operators.rs

1use super::var::*;
2use std::ops::{Add, BitAnd, BitOr, BitXor};
3
4// helper function to create binary ops
5fn binop<O: Clone, A: HasVar>(lhs: Var<O, A>, rhs: Var<O, A>, result_label: O, op: A) -> Var<O, A> {
6    // Get nodes for lhs and rhs vars
7    let lhs_node = lhs.new_target();
8    let rhs_node = rhs.new_target();
9
10    // Get source/target types
11    let source_type = vec![lhs.label.clone(), rhs.label.clone()];
12    let target_type = vec![result_label.clone()];
13
14    let target = {
15        let mut state = lhs.state.borrow_mut();
16
17        // Create a new Add operation
18        let (_, (s, t)) = state.new_operation(op, source_type, target_type);
19
20        // op is a binary operation by construction
21        assert_eq!(s.len(), 2);
22        assert_eq!(t.len(), 1);
23
24        // Connect the input vars to the new operation
25        state.unify(s[0], lhs_node);
26        state.unify(s[1], rhs_node);
27
28        t[0]
29    };
30
31    // Create a Var for the result, unify op target (t[0]) with its input, and return the Var.
32    // TODO: helper method to create a var from a node?
33    let v = Var::new(rhs.state.clone(), result_label);
34    let v_source = v.new_source(); // new_source calls borrow_mut, so must be done first.
35    v.state.borrow_mut().unify(target, v_source); // unify target from above with source of v.
36    v
37}
38
39/// Vars can be added when the underlying signature has an operation for 'addition'.
40pub trait HasAdd<O, A> {
41    fn add(lhs_type: O, rhs_type: O) -> (O, A);
42}
43
44impl<O: Clone, A: HasVar + HasAdd<O, A>> Add for Var<O, A> {
45    type Output = Var<O, A>;
46
47    fn add(self, rhs: Self) -> Self::Output {
48        // only difference between impls
49        let (result_label, op) = A::add(self.label.clone(), rhs.label.clone());
50        binop(self, rhs, result_label, op)
51    }
52}
53
54/// Vars can be added when the underlying signature has an operation for 'addition'.
55pub trait HasBitXor<O, A> {
56    fn bitxor(lhs_type: O, rhs_type: O) -> (O, A);
57}
58
59impl<O: Clone, A: HasVar + HasBitXor<O, A>> BitXor for Var<O, A> {
60    type Output = Var<O, A>;
61
62    fn bitxor(self, rhs: Self) -> Self::Output {
63        // only difference between impls
64        let (result_label, op) = A::bitxor(self.label.clone(), rhs.label.clone());
65        binop(self, rhs, result_label, op)
66    }
67}
68
69// Macro to reduce boilerplate for binary operators
70macro_rules! define_binary_op {
71    ($trait_name:ident, $fn_name:ident, $has_trait_name:ident) => {
72        #[doc = r" Vars support this operator when the underlying signature has the appropriate operation."]
73        pub trait $has_trait_name<O, A> {
74            fn $fn_name(lhs_type: O, rhs_type: O) -> (O, A);
75        }
76
77        impl<O: Clone, A: HasVar + $has_trait_name<O, A>> $trait_name for Var<O, A> {
78            type Output = Var<O, A>;
79
80            fn $fn_name(self, rhs: Self) -> Self::Output {
81                let (result_label, op) = A::$fn_name(self.label.clone(), rhs.label.clone());
82                binop(self, rhs, result_label, op)
83            }
84        }
85    };
86}
87
88define_binary_op!(BitAnd, bitand, HasBitAnd);
89define_binary_op!(BitOr, bitor, HasBitOr);