grabapl/
semantics.rs

1use crate::Graph;
2use crate::graph::NodeAttribute;
3use crate::operation::BuiltinOperation;
4use crate::operation::query::BuiltinQuery;
5use petgraph::data::Build;
6// /// Returns the corresponding abstract value/type for a given concrete value.
7// pub trait ToAbstract {
8//     type Abstract;
9//
10//     fn to_abstract(&self) -> Self::Abstract;
11// }
12
13pub mod example;
14pub mod example_with_ref;
15
16/// This matcher always returns true.
17#[derive(Default)]
18pub struct AnyMatcher<A> {
19    phantom_data: std::marker::PhantomData<A>,
20}
21
22impl<A> AbstractMatcher for AnyMatcher<A> {
23    type Abstract = A;
24
25    fn matches(_argument: &Self::Abstract, _parameter: &Self::Abstract) -> bool {
26        true
27    }
28}
29
30pub trait AbstractMatcher {
31    /// The type this matcher operates on.
32    type Abstract;
33
34    /// Decides if the argument type can be assigned to the parameter type.
35    /// In other words, it checks if `argument` is a subtype of `parameter`.
36    // TODO: rename "arg_matches_param"?
37    fn matches(argument: &Self::Abstract, parameter: &Self::Abstract) -> bool;
38}
39
40/// A basic AbstractJoin that can join a type and a supertype into the supertype.
41///
42/// For basic cases this is enough, but as soon as you have a more complex type system (i.e.,
43/// one where you have incomparable types), this Join is too simplistic and will not give you the
44/// best performance.
45///
46/// # Example
47/// If you have a type system with two types, `a` and `b`, where `a <: b`, then this Join will
48/// return `a` when joining `a` and `a`, and `b` when joining `a` and `b`.
49///
50/// However, if you have a third type `c` with `c <: b`, i.e., `c` is not comparable to `a`, then
51/// the join will not be able to join `a` and `c`, even though `b` would be a valid join.
52#[derive(Default)]
53pub struct MatchJoiner<M: AbstractMatcher<Abstract: Clone>> {
54    phantom: std::marker::PhantomData<M>,
55}
56
57impl<M: AbstractMatcher<Abstract: Clone>> AbstractJoin for MatchJoiner<M> {
58    type Abstract = M::Abstract;
59
60    fn join(a: &Self::Abstract, b: &Self::Abstract) -> Option<Self::Abstract> {
61        if M::matches(a, b) {
62            // a <: b, so we return b
63            Some(b.clone())
64        } else {
65            if M::matches(b, a) {
66                // b <: a, so we return a
67                Some(a.clone())
68            } else {
69                None
70            }
71        }
72    }
73}
74
75pub trait AbstractJoin {
76    /// The type this join operates on.
77    type Abstract;
78
79    /// Returns the abstract type that is the join of the two abstract types, i.e.,
80    /// the most specific type that is a supertype of both, if it exists.
81    ///
82    /// The default implementation assumes no join exists, returning `None`.
83    /// This is generally a bad idea, since at the very least equivalent types should be joined to themselves.
84    // TODO: remove default implementation?
85    fn join(a: &Self::Abstract, b: &Self::Abstract) -> Option<Self::Abstract> {
86        // Default implementation returns None, meaning no join exists.
87        // Note that this is probably a bit absurd, as in the very least if two nodes are equal
88        // (either via Eq or via mathes(a,b) and matches(b,a)), then the join is the same node.
89        // But this would induce a Clone requirement which I don't want to have just yet.
90        // TODO: revisit if Abstract: Clone is useful.
91        // ==> we have MatchJoiner now. If we had Clone, we could add a type default to the Semantics trait for type NodeJoin = MatchJoiner<Self::NodeMatcher>;
92        // aaand we have the clone requirement now. Oh. no type defaults, so can't add MatchJoiner as default.
93        None
94    }
95}
96
97/// Defines the semantics of a client implementation.
98pub trait Semantics {
99    /// A data graph's nodes contain values of this type.
100    /// PL analogy: values.
101    type NodeConcrete: Clone;
102    /// An operation can define patterns for nodes using this type.
103    /// PL analogy: types.
104    type NodeAbstract: Clone + PartialEq;
105    /// A data graph's edges contain values of this type.
106    /// PL analogy: values.
107    type EdgeConcrete: Clone;
108    /// An operation can define patterns for edges using this type.
109    /// PL analogy: types.
110    type EdgeAbstract: Clone + PartialEq;
111    /// The specific matching process for nodes.
112    type NodeMatcher: AbstractMatcher<Abstract = Self::NodeAbstract>;
113    /// The specific matching process for edges.
114    type EdgeMatcher: AbstractMatcher<Abstract = Self::EdgeAbstract>;
115    /// The specific join process for nodes.
116    type NodeJoin: AbstractJoin<Abstract = Self::NodeAbstract>;
117    /// The specific join process for edges.
118    type EdgeJoin: AbstractJoin<Abstract = Self::EdgeAbstract>;
119
120    type NodeConcreteToAbstract: ConcreteToAbstract<Concrete = Self::NodeConcrete, Abstract = Self::NodeAbstract>;
121    type EdgeConcreteToAbstract: ConcreteToAbstract<Concrete = Self::EdgeConcrete, Abstract = Self::EdgeAbstract>;
122
123    /// Builtin operations are of this type.
124    type BuiltinOperation: BuiltinOperation<S = Self>;
125    /// Queries are of this type
126    type BuiltinQuery: BuiltinQuery<S = Self>;
127
128    /// Returns the top node of the abstract graph, if the semantics defines one.
129    /// This is mainly used for added ergonomics on LibBuiltinOperations, since they require explicit parameters.
130    /// If a semantics defines a top abstract node value, some of the LibBuiltinOperations can default to that abstract value.
131    fn top_node_abstract() -> Option<Self::NodeAbstract> {
132        None
133    }
134
135    /// Returns the top edge of the abstract graph, if the semantics defines one.
136    /// This is mainly used for added ergonomics on LibBuiltinOperations, since they require explicit parameters.
137    /// If a semantics defines a top abstract edge value, some of the LibBuiltinOperations can default to that abstract value.
138    fn top_edge_abstract() -> Option<Self::EdgeAbstract> {
139        None
140    }
141
142    fn new_concrete_graph() -> ConcreteGraph<Self> {
143        Graph::new()
144    }
145
146    fn new_abstract_graph() -> AbstractGraph<Self> {
147        Graph::new()
148    }
149
150    fn join_edges(a: &Self::EdgeAbstract, b: &Self::EdgeAbstract) -> Option<Self::EdgeAbstract> {
151        Self::EdgeJoin::join(a, b)
152    }
153
154    fn join_nodes(a: &Self::NodeAbstract, b: &Self::NodeAbstract) -> Option<Self::NodeAbstract> {
155        Self::NodeJoin::join(a, b)
156    }
157
158    // TODO: Assert that the node keys are the same
159    fn concrete_to_abstract(c: &ConcreteGraph<Self>) -> AbstractGraph<Self> {
160        let mut abstract_graph = Graph::new();
161        for (node_key, node_concrete) in c.nodes() {
162            let node_abstract = Self::NodeConcreteToAbstract::concrete_to_abstract(&node_concrete);
163            // TODO: make this better (don't depend on Graph internals)
164            abstract_graph.graph.add_node(node_key);
165            abstract_graph
166                .node_attr_map
167                .insert(node_key, NodeAttribute::new(node_abstract));
168        }
169        abstract_graph.max_node_key = c.max_node_key;
170
171        for (src, dst, weight) in c.graph.all_edges() {
172            let edge_abstract = Self::EdgeConcreteToAbstract::concrete_to_abstract(weight.attr());
173            // TODO: make this better (don't depend on Graph internals)
174            let new_edge_attr = weight.with(edge_abstract);
175            abstract_graph.graph.add_edge(src, dst, new_edge_attr);
176        }
177
178        abstract_graph
179    }
180}
181
182pub type ConcreteGraph<S> = Graph<<S as Semantics>::NodeConcrete, <S as Semantics>::EdgeConcrete>;
183
184pub type AbstractGraph<S> = Graph<<S as Semantics>::NodeAbstract, <S as Semantics>::EdgeAbstract>;
185
186pub trait ConcreteToAbstract {
187    type Concrete;
188    type Abstract;
189    fn concrete_to_abstract(c: &Self::Concrete) -> Self::Abstract;
190}