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}