1
+ {-# LANGUAGE DeriveGeneric #-}
2
+ {-# LANGUAGE DataKinds #-}
3
+ {-# LANGUAGE TypeApplications #-}
1
4
{-# LANGUAGE NamedFieldPuns #-}
2
5
{-# LANGUAGE OverloadedLists #-}
6
+ {-# LANGUAGE OverloadedStrings #-}
3
7
module Stack.Path
4
8
( Path (.. )
9
+ , Compatibility (.. )
10
+ , compatibility
11
+ , concatenate
5
12
, Edge (.. )
6
13
, StartingSize (.. )
7
14
, PathInvariantError (.. )
@@ -12,15 +19,20 @@ module Stack.Path
12
19
, Completion (.. )
13
20
, completion
14
21
, isIncremental
22
+
15
23
) where
16
24
17
25
18
26
import Data.Functor.Tagged
19
27
import Data.Monoid
20
28
import Data.Semigroup (sconcat )
21
- import Data.Sequence (Seq (.. ))
29
+ import Data.Sequence (Seq (.. ), (|>) )
22
30
import Data.Text (Text )
23
31
import Stack.Graph (Node (.. ), Symbol )
32
+ import Data.Generics.Product
33
+ import Control.Lens.Getter ((^.) )
34
+ import GHC.Generics
35
+ import Data.List (isPrefixOf )
24
36
25
37
-- | A partial path through a stack graph. These will be generated
26
38
-- from walks through the stack graph, and can be thought of as
@@ -34,7 +46,7 @@ data Path = Path
34
46
, endingSymbolStack :: [Symbol ]
35
47
, startingScopeStackSize :: StartingSize
36
48
, endingScopeStack :: [Tag ]
37
- } deriving (Eq , Show )
49
+ } deriving (Eq , Show , Generic )
38
50
39
51
data Edge = Edge
40
52
{ sourceNode :: Tagged Node
@@ -45,7 +57,7 @@ data Edge = Edge
45
57
data StartingSize
46
58
= Zero
47
59
| One
48
- deriving (Eq , Show )
60
+ deriving (Eq , Show , Ord , Enum )
49
61
50
62
data PathInvariantError
51
63
= ExpectedEqual (Tagged Node ) (Tagged Node )
@@ -120,7 +132,10 @@ validity p = sconcat [vStart, vEnd, vSize]
120
132
(One , _) -> Invalid
121
133
_otherwise -> Valid
122
134
123
- data Completion = Partial | Complete
135
+ data Completion
136
+ = Partial
137
+ | Complete
138
+ deriving (Eq , Show )
124
139
125
140
-- | A path is complete if its starting node is a reference node and its ending node is a definition node. Otherwise it is partial.
126
141
completion :: Path -> Completion
@@ -130,3 +145,90 @@ completion _
130
145
-- | A path is incremental if the source node and sink node of every edge in the path belongs to the same file.
131
146
isIncremental :: Path -> Bool
132
147
isIncremental = error " TODO: need file support to implement this"
148
+
149
+ data Compatibility
150
+ = Compatible
151
+ | Incompatible
152
+ deriving (Eq , Show )
153
+
154
+ compatibility :: Path -> Path -> Compatibility
155
+ compatibility left right
156
+ -- Two paths 'left' and 'right' are compatible with each other if all the following are true:
157
+ | and @ [] [nodesCompatible, stackPrefix, hasElements] = Compatible
158
+ | otherwise = Incompatible
159
+ where
160
+ -- Any of the following are true:
161
+ nodesCompatible =
162
+ -- The ending node of 'left' and the starting node of 'right' are both the root node.
163
+ let bothRootNode = left ^. field @ " endingNode" . contents == Root && right ^. field @ " startingNode" . contents == Root
164
+ -- The ending node of 'left' and the starting node of 'right' are both scope references, and both refer to the same scope
165
+ bothSameScope = case (endingNode left, startingNode right) of
166
+ -- TODO: determining "same scope" by symbol comparison is rough
167
+ -- should we be comparing tags as well?
168
+ (Scope s1 :# _, Scope s2 :# _) -> s1 == s2
169
+ _ -> False
170
+ in bothRootNode || bothSameScope
171
+ -- The starting symbol stack of 'right' is a prefix of the ending symbol stack of 'left'.
172
+ stackPrefix = startingSymbolStack right `isPrefixOf` endingSymbolStack left
173
+ -- The ending scope stack of 'left' has at least as many elements as the starting scope stack size of 'right'.
174
+ hasElements = length (endingScopeStack left) >= fromEnum (startingScopeStackSize right)
175
+
176
+ concatenate :: Path -> Path -> Maybe Path
177
+ concatenate left right
178
+ -- Incompatible paths cannot be concatenated.
179
+ | compatibility left right == Incompatible = Nothing
180
+ -- If left and right are compatible with each other, you can concatenate them together, yielding a new path:
181
+ | otherwise =
182
+ -- The new path's starting node, starting symbol stack, and starting scope stack size are the same as left.
183
+ let (newStartingNode, newStartingSymbolStack, newStartingScopeStackSize) = (startingNode left, startingSymbolStack left, startingScopeStackSize left)
184
+ -- The new path's edge list is the concatenation of left's and right's edge lists.
185
+ allEdges = edges left <> edges right
186
+ -- The new path's ending symbol stack is the value of new symbol stack after doing the following:
187
+ newEndingSymbolStack =
188
+ -- Let new symbol stack be a copy of left's ending symbol stack.
189
+ let newSymbolStack = endingSymbolStack left
190
+ -- Remove right's starting symbol stack from the beginning of new symbol stack. (This must succeed because the two input paths are compatible.)
191
+ withoutRight = drop (length (startingSymbolStack right)) newSymbolStack
192
+ in -- Prepend a copy of right's ending symbol stack to the beginning of new symbol stack.
193
+ endingSymbolStack right <> withoutRight
194
+ -- The new path's ending scope stack is the value of new scope stack after doing the following:
195
+ newEndingScopeStack =
196
+ -- Let new scope stack be a copy of left's ending scope stack.
197
+ let newScopeStack = endingScopeStack left
198
+ -- If right's starting scope stack size is 1, pop resolved scope identifier from the beginning of new scope stack.
199
+ popped = if startingScopeStackSize right == One then drop 1 newScopeStack else newScopeStack
200
+ in -- Prepend a copy of right's ending scope stack to the beginning of new scope stack.
201
+ endingScopeStack right <> popped
202
+ -- The new path's ending node is the same as right's.
203
+ newEndingNode = endingNode right
204
+ newEdges = case newEndingNode of
205
+ -- If right's ending node is a jump to scope node node, then:
206
+ JumpToScope :# _ ->
207
+ -- Let jump edge be a new edge whose:
208
+ let jumpEdge =
209
+ Edge
210
+ { sourceNode = newEndingNode, -- source node is node
211
+
212
+ -- PT TODO: we don't appear to have scope identifiers attached to exported scope nodes
213
+ -- Also: calling unsafeTagged here is a sin, but this function should be pure, so whatever.
214
+ -- If anyone haa a better idea, hit me up.
215
+
216
+ -- sink node is an exported scope node whose scope identifier is resolved scope identifier
217
+ sinkNode = unsafeTagged ExportedScope ,
218
+ -- label is `jump``
219
+ label = " jump"
220
+ }
221
+ in -- Append jump edge to the new path.
222
+ (allEdges |> jumpEdge)
223
+ -- Otherwise, do nothing
224
+ _ -> allEdges
225
+ in Just
226
+ Path
227
+ { startingNode = newStartingNode,
228
+ endingNode = newEndingNode,
229
+ edges = newEdges,
230
+ startingSymbolStack = newStartingSymbolStack,
231
+ endingSymbolStack = newEndingSymbolStack,
232
+ startingScopeStackSize = newStartingScopeStackSize,
233
+ endingScopeStack = newEndingScopeStack
234
+ }
0 commit comments