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 ((^.) , Lens' )
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,15 @@ data Path = Path
34
46
, endingSymbolStack :: [Symbol ]
35
47
, startingScopeStackSize :: StartingSize
36
48
, endingScopeStack :: [Tag ]
37
- } deriving (Eq , Show )
49
+ } deriving (Eq , Show , Generic )
50
+
51
+ startingNode_ , endingNode_ :: Lens' Path (Tagged Node )
52
+ startingNode_ = field @ " startingNode"
53
+ endingNode_ = field @ " endingNode"
54
+
55
+ startingSymbolStack_ , endingSymbolStack_ :: Lens' Path [Symbol ]
56
+ startingSymbolStack_ = field @ " startingSymbolStack"
57
+ endingSymbolStack_ = field @ " endingSymbolStack"
38
58
39
59
data Edge = Edge
40
60
{ sourceNode :: Tagged Node
@@ -45,7 +65,7 @@ data Edge = Edge
45
65
data StartingSize
46
66
= Zero
47
67
| One
48
- deriving (Eq , Show )
68
+ deriving (Eq , Show , Ord , Enum )
49
69
50
70
data PathInvariantError
51
71
= ExpectedEqual (Tagged Node ) (Tagged Node )
@@ -120,7 +140,10 @@ validity p = sconcat [vStart, vEnd, vSize]
120
140
(One , _) -> Invalid
121
141
_otherwise -> Valid
122
142
123
- data Completion = Partial | Complete
143
+ data Completion
144
+ = Partial
145
+ | Complete
146
+ deriving (Eq , Show )
124
147
125
148
-- | 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
149
completion :: Path -> Completion
@@ -130,3 +153,85 @@ completion _
130
153
-- | A path is incremental if the source node and sink node of every edge in the path belongs to the same file.
131
154
isIncremental :: Path -> Bool
132
155
isIncremental = error " TODO: need file support to implement this"
156
+
157
+ data Compatibility
158
+ = Compatible
159
+ | Incompatible
160
+ deriving (Eq , Show )
161
+
162
+ compatibility :: Path -> Path -> Compatibility
163
+ compatibility left right
164
+ -- Two paths 'left' and 'right' are compatible with each other if all the following are true:
165
+ | and @ [] [nodesCompatible, stackPrefix, hasElements] = Compatible
166
+ | otherwise = Incompatible
167
+ where
168
+ -- Any of the following are true:
169
+ nodesCompatible =
170
+ -- The ending node of 'left' and the starting node of 'right' are both the root node.
171
+ let bothRootNode = left ^. endingNode_. contents == Root && right ^. startingNode_. contents == Root
172
+ -- The ending node of 'left' and the starting node of 'right' are both scope references, and both refer to the same scope
173
+ bothSameScope = case (left ^. endingNode_, right ^. startingNode_) of
174
+ -- TODO: determining "same scope" by symbol comparison is rough
175
+ (Scope s1 :# _, Scope s2 :# _) -> s1 == s2
176
+ _ -> False
177
+ in bothRootNode || bothSameScope
178
+ -- The starting symbol stack of 'right' is a prefix of the ending symbol stack of 'left'.
179
+ stackPrefix = (right ^. startingSymbolStack_) `isPrefixOf` (left ^. endingSymbolStack_)
180
+ -- The ending scope stack of 'left' has at least as many elements as the starting scope stack size of 'right'.
181
+ hasElements = (length (endingScopeStack left)) >= fromEnum (startingScopeStackSize right)
182
+
183
+ concatenate :: Path -> Path -> Maybe Path
184
+ concatenate left right
185
+ -- Incompatible paths cannot be concatenated.
186
+ | compatibility left right == Incompatible = Nothing
187
+ -- If left and right are compatible with each other, you can concatenate them together, yielding a new path:
188
+ | otherwise =
189
+ -- The new path's starting node, starting symbol stack, and starting scope stack size are the same as left.
190
+ let (newStartingNode, newStartingSymbolStack, newStartingScopeStackSize) = (startingNode left, startingSymbolStack left, startingScopeStackSize left)
191
+ -- The new path's edge list is the concatenation of left's and right's edge lists.
192
+ allEdges = edges left <> edges right
193
+ -- The new path's ending symbol stack is the value of new symbol stack after doing the following:
194
+ newEndingSymbolStack =
195
+ -- Let new symbol stack be a copy of left's ending symbol stack.
196
+ let newSymbolStack = endingSymbolStack left
197
+ -- Remove right's starting symbol stack from the beginning of new symbol stack. (This must succeed because the two input paths are compatible.)
198
+ withoutRight = drop (length (startingSymbolStack right)) newSymbolStack
199
+ in -- Prepend a copy of right's ending symbol stack to the beginning of new symbol stack.
200
+ endingSymbolStack right <> withoutRight
201
+ -- The new path's ending scope stack is the value of new scope stack after doing the following:
202
+ newEndingScopeStack =
203
+ -- Let new scope stack be a copy of left's ending scope stack.
204
+ let newScopeStack = endingScopeStack left
205
+ -- If right's starting scope stack size is 1, pop resolved scope identifier from the beginning of new scope stack.
206
+ popped = if startingScopeStackSize right == One then drop 1 newScopeStack else newScopeStack
207
+ in -- Prepend a copy of right's ending scope stack to the beginning of new scope stack.
208
+ endingScopeStack right <> popped
209
+ -- The new path's ending node is the same as right's.
210
+ newEndingNode = endingNode right
211
+ newEdges = case newEndingNode of
212
+ -- If right's ending node is a jump to scope node node, then:
213
+ JumpToScope :# _ ->
214
+ -- Let jump edge be a new edge whose:
215
+ let jumpEdge =
216
+ Edge
217
+ { sourceNode = newEndingNode, -- source node is node
218
+ -- sink node is an exported scope node whose scope identifier is resolved scope identifier
219
+ -- PT TODO: we don't appear to have scope identifiers attached to exported scope nodes
220
+ sinkNode = unsafeTagged ExportedScope ,
221
+ -- label is `jump``
222
+ label = " jump"
223
+ }
224
+ in -- Append jump edge to the new path.
225
+ (allEdges |> jumpEdge)
226
+ -- Otherwise, do nothing
227
+ _ -> allEdges
228
+ in pure
229
+ Path
230
+ { startingNode = newStartingNode,
231
+ endingNode = newEndingNode,
232
+ edges = newEdges,
233
+ startingSymbolStack = newStartingSymbolStack,
234
+ endingSymbolStack = newEndingSymbolStack,
235
+ startingScopeStackSize = newStartingScopeStackSize,
236
+ endingScopeStack = newEndingScopeStack
237
+ }
0 commit comments