@@ -25,33 +25,28 @@ public protocol IncrementalParseReusedNodeDelegate {
25
25
/// - range: The source region of the currently parsed source.
26
26
/// - previousNode: The node from the previous tree that is associated with
27
27
/// the skipped source region.
28
- func parserReusedNode( range: ByteSourceRange , previousNode: Syntax )
28
+ func parserReusedNode( range: ByteSourceRange , previousNode: SyntaxNode )
29
29
}
30
30
31
31
/// An implementation of `IncrementalParseReusedNodeDelegate` that just collects
32
32
/// the range and re-used node into an array.
33
33
public final class IncrementalParseReusedNodeCollector :
34
34
IncrementalParseReusedNodeDelegate {
35
- public var rangeAndNodes : [ ( ByteSourceRange , Syntax ) ] = [ ]
35
+ public var rangeAndNodes : [ ( ByteSourceRange , SyntaxNode ) ] = [ ]
36
36
37
37
public init ( ) { }
38
38
39
- public func parserReusedNode( range: ByteSourceRange , previousNode: Syntax ) {
39
+ public func parserReusedNode( range: ByteSourceRange , previousNode: SyntaxNode ) {
40
40
rangeAndNodes. append ( ( range, previousNode) )
41
41
}
42
42
}
43
43
44
44
/// Keeps track of a previously parsed syntax tree and the source edits that
45
- /// occurred since it was created and provides a mechanism for the parser to
46
- /// skip regions of an incrementally updated source that was already parsed
47
- /// during a previous parse invocation.
45
+ /// occurred since it was created.
48
46
public final class IncrementalParseTransition {
49
- // The implementation is based on `SyntaxParsingCache` from the swift
50
- // repository.
51
-
52
- private let previousTree : SourceFileSyntax
53
- private let edits : [ SourceEdit ]
54
- private let reusedDelegate : IncrementalParseReusedNodeDelegate ?
47
+ fileprivate let previousTree : SourceFileSyntax
48
+ fileprivate let edits : [ SourceEdit ]
49
+ fileprivate let reusedDelegate : IncrementalParseReusedNodeDelegate ?
55
50
56
51
/// - Parameters:
57
52
/// - previousTree: The previous tree to do lookups on.
@@ -92,6 +87,26 @@ public final class IncrementalParseTransition {
92
87
}
93
88
return true
94
89
}
90
+ }
91
+
92
+ /// Provides a mechanism for the parser to skip regions of an incrementally
93
+ /// updated source that was already parsed during a previous parse invocation.
94
+ internal struct IncrementalParseLookup {
95
+ fileprivate let transition : IncrementalParseTransition
96
+ fileprivate var cursor : SyntaxCursor
97
+
98
+ init ( transition: IncrementalParseTransition ) {
99
+ self . transition = transition
100
+ self . cursor = . init( root: transition. previousTree. data. absoluteRaw)
101
+ }
102
+
103
+ fileprivate var edits : [ SourceEdit ] {
104
+ return transition. edits
105
+ }
106
+
107
+ fileprivate var reusedDelegate : IncrementalParseReusedNodeDelegate ? {
108
+ return transition. reusedDelegate
109
+ }
95
110
96
111
/// Does a lookup to see if the current source `offset` should be associated
97
112
/// with a known `Syntax` node and its region skipped during parsing.
@@ -102,15 +117,15 @@ public final class IncrementalParseTransition {
102
117
/// - Parameters:
103
118
/// - offset: The byte offset of the source string that is currently parsed.
104
119
/// - kind: The `SyntaxKind` that the parser expects at this position.
105
- /// - Returns: A `Syntax ` node from the previous parse invocation,
120
+ /// - Returns: A `SyntaxNode ` node from the previous parse invocation,
106
121
/// representing the contents of this region, if it is still valid
107
122
/// to re-use. `nil` otherwise.
108
- func lookUp( _ newOffset: Int , kind: SyntaxKind ) -> _SyntaxBase ? {
123
+ mutating func lookUp( _ newOffset: Int , kind: SyntaxKind ) -> SyntaxNode ? {
109
124
guard let prevOffset = translateToPreEditOffset ( newOffset) else {
110
125
return nil
111
126
}
112
127
let prevPosition = AbsolutePosition ( utf8Offset: prevOffset)
113
- let node = lookUpFrom ( previousTree , prevPosition: prevPosition, kind: kind)
128
+ let node = cursorLookup ( prevPosition: prevPosition, kind: kind)
114
129
if let delegate = reusedDelegate, let node = node {
115
130
delegate. parserReusedNode (
116
131
range: ByteSourceRange ( offset: newOffset, length: node. byteSize) ,
@@ -119,46 +134,57 @@ public final class IncrementalParseTransition {
119
134
return node
120
135
}
121
136
122
- fileprivate func lookUpFrom(
123
- _ node: _SyntaxBase , prevPosition: AbsolutePosition , kind: SyntaxKind
124
- ) -> _SyntaxBase ? {
125
- if nodeCanBeReused ( node, prevPosition: prevPosition, kind: kind) {
126
- return node
127
- }
137
+ mutating fileprivate func cursorLookup(
138
+ prevPosition: AbsolutePosition , kind: SyntaxKind
139
+ ) -> SyntaxNode ? {
140
+ guard !cursor. finished else { return nil }
128
141
129
- for child in node . children {
130
- if child . position <= prevPosition && prevPosition < child . endPosition {
131
- return lookUpFrom ( child , prevPosition : prevPosition , kind : kind )
142
+ while true {
143
+ if nodeAtCursorCanBeReused ( prevPosition: prevPosition, kind : kind ) {
144
+ return cursor . asSyntaxNode
132
145
}
146
+ guard cursor. advanceToNextNode ( at: prevPosition) else { return nil }
133
147
}
134
- return nil
135
148
}
136
149
137
- fileprivate func nodeCanBeReused (
138
- _ node : _SyntaxBase , prevPosition: AbsolutePosition , kind: SyntaxKind
150
+ fileprivate func nodeAtCursorCanBeReused (
151
+ prevPosition: AbsolutePosition , kind: SyntaxKind
139
152
) -> Bool {
153
+ let node = cursor. node
140
154
if node. position != prevPosition {
141
155
return false
142
156
}
143
157
if node. raw. kind != kind {
144
158
return false
145
159
}
146
160
161
+ // Fast path check: if parser is past all the edits then any matching node
162
+ // can be re-used.
163
+ if !edits. isEmpty && edits. last!. range. endOffset < node. position. utf8Offset {
164
+ return true ;
165
+ }
166
+
147
167
// Node can also not be reused if an edit has been made in the next token's
148
168
// text, e.g. because `private struct Foo {}` parses as a CodeBlockItem with
149
169
// a StructDecl inside and `private struc Foo {}` parses as two
150
170
// CodeBlockItems one for `private` and one for `struc Foo {}`
151
- var nextLeafNodeLength = 0
152
- if let nextToken = node. nextToken {
153
- assert ( nextToken. isPresent)
154
- nextLeafNodeLength = nextToken. byteSize - nextToken. trailingTriviaLength. utf8Length
171
+ var nextLeafNodeLength : SourceLength = . zero
172
+ if let nextSibling = cursor. nextSibling {
173
+ // Fast path check: if next sibling is before all the edits then we can
174
+ // re-use the node.
175
+ if !edits. isEmpty && edits. first!. range. offset > nextSibling. endPosition. utf8Offset {
176
+ return true ;
177
+ }
178
+ if let nextToken = nextSibling. raw. firstPresentToken {
179
+ nextLeafNodeLength = nextToken. totalLength - nextToken. trailingTriviaLength
180
+ }
155
181
}
182
+ let nodeAffectRange = ByteSourceRange ( offset: node. position. utf8Offset,
183
+ length: ( node. raw. totalLength + nextLeafNodeLength) . utf8Length)
156
184
157
185
for edit in edits {
158
186
// Check if this node or the trivia of the next node has been edited. If
159
187
// it has, we cannot reuse it.
160
- let nodeAffectRange = ByteSourceRange ( offset: node. position. utf8Offset,
161
- length: node. byteSize + nextLeafNodeLength)
162
188
if edit. range. offset > nodeAffectRange. endOffset {
163
189
// Remaining edits don't affect the node. (Edits are sorted)
164
190
break
@@ -188,3 +214,79 @@ public final class IncrementalParseTransition {
188
214
return offset
189
215
}
190
216
}
217
+
218
+ /// Functions as an iterator that walks the tree looking for nodes with a
219
+ /// certain position.
220
+ fileprivate struct SyntaxCursor {
221
+ var parents : [ AbsoluteRawSyntax ]
222
+ var node : AbsoluteRawSyntax
223
+ var finished : Bool
224
+
225
+ init ( root: AbsoluteRawSyntax ) {
226
+ self . node = root
227
+ self . parents = [ ]
228
+ self . finished = false
229
+ }
230
+
231
+ var asSyntaxNode : SyntaxNode {
232
+ return SyntaxNode ( node: node, parents: ArraySlice ( parents) )
233
+ }
234
+
235
+ /// Returns the next sibling node or the parent's sibling node if this is
236
+ /// the last child. The cursor state is unmodified.
237
+ /// - Returns: False if it run out of nodes to walk to.
238
+ var nextSibling : AbsoluteRawSyntax ? {
239
+ var parents = ArraySlice ( self . parents)
240
+ var node = self . node
241
+ while !parents. isEmpty {
242
+ if let sibling = node. nextSibling ( parent: parents. last!) {
243
+ return sibling
244
+ }
245
+ node = parents. removeLast ( )
246
+ }
247
+
248
+ return nil
249
+ }
250
+
251
+ /// Moves to the first child of the current node.
252
+ /// - Returns: False if the node has no children.
253
+ mutating func advanceToFirstChild( ) -> Bool {
254
+ guard let child = node. firstChild else { return false }
255
+ parents. append ( node)
256
+ node = child
257
+ return true
258
+ }
259
+
260
+ /// Moves to the next sibling node or the parent's sibling node if this is
261
+ /// the last child.
262
+ /// - Returns: False if it run out of nodes to walk to.
263
+ mutating func advanceToNextSibling( ) -> Bool {
264
+ while !parents. isEmpty {
265
+ if let sibling = node. nextSibling ( parent: parents. last!) {
266
+ node = sibling
267
+ return true
268
+ }
269
+ node = parents. removeLast ( )
270
+ }
271
+
272
+ finished = true
273
+ return false
274
+ }
275
+
276
+ /// Moves to the next node in the tree with the provided `position`.
277
+ /// The caller should be calling this with `position`s in ascending order, not
278
+ /// random ones.
279
+ /// - Returns: True if it moved to a new node at the provided position,
280
+ /// false if it moved to a node past the position or there are no more nodes.
281
+ mutating func advanceToNextNode( at position: AbsolutePosition ) -> Bool {
282
+ repeat {
283
+ // if the node is fully before the requested position we can skip its children.
284
+ if node. endPosition > position {
285
+ if advanceToFirstChild ( ) { continue }
286
+ }
287
+ if !advanceToNextSibling( ) { return false }
288
+ } while node. position < position
289
+
290
+ return node. position == position
291
+ }
292
+ }
0 commit comments