1010//
1111//===----------------------------------------------------------------------===//
1212
13+ @_spi ( RawSyntax) import SwiftSyntax
1314/// Accepts the re-used ``Syntax`` nodes that `IncrementalParseTransition`
1415/// determined they should be re-used for a parse invocation.
1516///
@@ -66,140 +67,6 @@ public final class IncrementalParseTransition {
6667 }
6768}
6869
69- fileprivate extension Sequence where Element: Comparable {
70- var isSorted : Bool {
71- return zip ( self , self . dropFirst ( ) ) . allSatisfy ( { $0. 0 < $0. 1 } )
72- }
73- }
74-
75- /// Edits that are applied **simultaneously**. That is, the offsets of all edits
76- /// refer to the original string and are not shifted by previous edits. For
77- /// example applying
78- /// - insert 'x' at offset 0
79- /// - insert 'y' at offset 1
80- /// - insert 'z' at offset 2
81- /// to '012345' results in 'x0y1z2345'.
82- ///
83- /// The raw `edits` of this struct are guaranteed to
84- /// 1. not be overlapping.
85- /// 2. be in increasing source offset order.
86- public struct ConcurrentEdits {
87- enum ConcurrentEditsError : Error , CustomStringConvertible {
88- case editsNotConcurrent
89-
90- var description : String {
91- switch self {
92- case . editsNotConcurrent:
93- return " Edits passed to ConcurrentEdits(concurrent:) does not satisfy the requirements posed by ConcurrentEdits "
94- }
95- }
96- }
97-
98- /// The raw concurrent edits. Are guaranteed to satisfy the requirements
99- /// stated above.
100- public let edits : [ IncrementalEdit ]
101-
102- /// Initialize this struct from edits that are already in a concurrent form
103- /// and are guaranteed to satisfy the requirements posed above.
104- public init ( concurrent: [ IncrementalEdit ] ) throws {
105- if !Self. isValidConcurrentEditArray ( concurrent) {
106- throw ConcurrentEditsError . editsNotConcurrent
107- }
108- self . edits = concurrent
109- }
110-
111- /// Create concurrent from a set of sequential edits. Sequential edits are
112- /// applied one after the other. Applying the first edit results in an
113- /// intermediate string to which the second edit is applied etc. For example
114- /// applying
115- /// - insert 'x' at offset 0
116- /// - insert 'y' at offset 1
117- /// - insert 'z' at offset 2
118- /// to '012345' results in 'xyz012345'.
119-
120- public init ( fromSequential sequentialEdits: [ IncrementalEdit ] ) {
121- do {
122- try self . init ( concurrent: Self . translateSequentialEditsToConcurrentEdits ( sequentialEdits) )
123- } catch {
124- fatalError ( " ConcurrentEdits created by translateSequentialEditsToConcurrentEdits do not satisfy ConcurrentEdits requirements " )
125- }
126- }
127-
128- /// Construct a concurrent edits struct from a single edit. For a single edit,
129- /// there is no differentiation between being it being applied concurrently
130- /// or sequentially.
131- public init ( _ single: IncrementalEdit ) {
132- do {
133- try self . init ( concurrent: [ single] )
134- } catch {
135- fatalError ( " A single edit doesn't satisfy the ConcurrentEdits requirements? " )
136- }
137- }
138-
139- private static func translateSequentialEditsToConcurrentEdits(
140- _ edits: [ IncrementalEdit ]
141- ) -> [ IncrementalEdit ] {
142- var concurrentEdits : [ IncrementalEdit ] = [ ]
143- for editToAdd in edits {
144- var editToAdd = editToAdd
145- var editIndiciesMergedWithNewEdit : [ Int ] = [ ]
146- for (index, existingEdit) in concurrentEdits. enumerated ( ) {
147- if existingEdit. replacementRange. intersectsOrTouches ( editToAdd. range) {
148- let intersectionLength =
149- existingEdit. replacementRange. intersected ( editToAdd. range) . length
150- editToAdd = IncrementalEdit (
151- offset: Swift . min ( existingEdit. offset, editToAdd. offset) ,
152- length: existingEdit. length + editToAdd. length - intersectionLength,
153- replacementLength: existingEdit. replacementLength + editToAdd. replacementLength - intersectionLength
154- )
155- editIndiciesMergedWithNewEdit. append ( index)
156- } else if existingEdit. offset < editToAdd. endOffset {
157- editToAdd = IncrementalEdit (
158- offset: editToAdd. offset - existingEdit. replacementLength + existingEdit. length,
159- length: editToAdd. length,
160- replacementLength: editToAdd. replacementLength
161- )
162- }
163- }
164- precondition ( editIndiciesMergedWithNewEdit. isSorted)
165- for indexToRemove in editIndiciesMergedWithNewEdit. reversed ( ) {
166- concurrentEdits. remove ( at: indexToRemove)
167- }
168- let insertPos =
169- concurrentEdits. firstIndex ( where: { edit in
170- editToAdd. endOffset <= edit. offset
171- } ) ?? concurrentEdits. count
172- concurrentEdits. insert ( editToAdd, at: insertPos)
173- precondition ( ConcurrentEdits . isValidConcurrentEditArray ( concurrentEdits) )
174- }
175- return concurrentEdits
176- }
177-
178- private static func isValidConcurrentEditArray( _ edits: [ IncrementalEdit ] ) -> Bool {
179- // Not quite sure if we should disallow creating an `IncrementalParseTransition`
180- // object without edits but there doesn't seem to be much benefit if we do,
181- // and there are 'lit' tests that want to test incremental re-parsing without edits.
182- guard !edits. isEmpty else { return true }
183-
184- for i in 1 ..< edits. count {
185- let prevEdit = edits [ i - 1 ]
186- let curEdit = edits [ i]
187- if curEdit. range. offset < prevEdit. range. endOffset {
188- return false
189- }
190- if curEdit. intersectsRange ( prevEdit. range) {
191- return false
192- }
193- }
194- return true
195- }
196-
197- /// **Public for testing purposes only**
198- public static func _isValidConcurrentEditArray( _ edits: [ IncrementalEdit ] ) -> Bool {
199- return isValidConcurrentEditArray ( edits)
200- }
201- }
202-
20370/// Provides a mechanism for the parser to skip regions of an incrementally
20471/// updated source that was already parsed during a previous parse invocation.
20572public struct IncrementalParseLookup {
@@ -210,7 +77,7 @@ public struct IncrementalParseLookup {
21077 /// given ``IncrementalParseTransition``.
21178 public init ( transition: IncrementalParseTransition ) {
21279 self . transition = transition
213- self . cursor = . init( root: transition. previousTree. data )
80+ self . cursor = . init( root: Syntax ( transition. previousTree) )
21481 }
21582
21683 fileprivate var edits : ConcurrentEdits {
@@ -292,13 +159,13 @@ public struct IncrementalParseLookup {
292159 if !edits. edits. isEmpty && edits. edits. first!. range. offset > nextSibling. endPosition. utf8Offset {
293160 return true
294161 }
295- if let nextToken = nextSibling. raw . firstToken ( viewMode: . sourceAccurate) {
296- nextLeafNodeLength = nextToken. raw . totalLength - nextToken. trailingTriviaLength
162+ if let nextToken = nextSibling. firstToken ( viewMode: . sourceAccurate) {
163+ nextLeafNodeLength = nextToken. leadingTriviaLength + nextToken. contentLength
297164 }
298165 }
299166 let nodeAffectRange = ByteSourceRange (
300167 offset: node. position. utf8Offset,
301- length: ( node. raw . totalLength + nextLeafNodeLength) . utf8Length
168+ length: ( node. totalLength + nextLeafNodeLength) . utf8Length
302169 )
303170
304171 for edit in edits. edits {
@@ -337,11 +204,11 @@ public struct IncrementalParseLookup {
337204/// Functions as an iterator that walks the tree looking for nodes with a
338205/// certain position.
339206fileprivate struct SyntaxCursor {
340- var node : SyntaxData
207+ var node : Syntax
341208 var finished : Bool
342209 let viewMode = SyntaxTreeViewMode . sourceAccurate
343210
344- init ( root: SyntaxData ) {
211+ init ( root: Syntax ) {
345212 self . node = root
346213 self . finished = false
347214 }
@@ -353,11 +220,12 @@ fileprivate struct SyntaxCursor {
353220 /// Returns the next sibling node or the parent's sibling node if this is
354221 /// the last child. The cursor state is unmodified.
355222 /// - Returns: `nil` if it run out of nodes to walk to.
356- var nextSibling : SyntaxData ? {
223+ var nextSibling : Syntax ? {
357224 var node = self . node
358225 while let parent = node. parent {
359- if let sibling = node. absoluteRaw. nextSibling ( parent: parent. absoluteRaw, viewMode: viewMode) {
360- return SyntaxData ( sibling, parent: Syntax ( parent) )
226+ let children = parent. children ( viewMode: viewMode)
227+ if children. index ( after: node. index) != children. endIndex {
228+ return children [ children. index ( after: node. index) ]
361229 }
362230 node = parent
363231 }
@@ -367,8 +235,8 @@ fileprivate struct SyntaxCursor {
367235 /// Moves to the first child of the current node.
368236 /// - Returns: False if the node has no children.
369237 mutating func advanceToFirstChild( ) -> Bool {
370- guard let child = node. absoluteRaw . firstChild ( viewMode: viewMode) else { return false }
371- node = SyntaxData ( child, parent : Syntax ( node ) )
238+ guard let child = node. children ( viewMode: viewMode) . first else { return false }
239+ node = child
372240 return true
373241 }
374242
@@ -402,3 +270,137 @@ fileprivate struct SyntaxCursor {
402270 return node. position == position
403271 }
404272}
273+
274+ /// Edits that are applied **simultaneously**. That is, the offsets of all edits
275+ /// refer to the original string and are not shifted by previous edits. For
276+ /// example applying
277+ /// - insert 'x' at offset 0
278+ /// - insert 'y' at offset 1
279+ /// - insert 'z' at offset 2
280+ /// to '012345' results in 'x0y1z2345'.
281+ ///
282+ /// The raw `edits` of this struct are guaranteed to
283+ /// 1. not be overlapping.
284+ /// 2. be in increasing source offset order.
285+ public struct ConcurrentEdits {
286+ enum ConcurrentEditsError : Error , CustomStringConvertible {
287+ case editsNotConcurrent
288+
289+ var description : String {
290+ switch self {
291+ case . editsNotConcurrent:
292+ return " Edits passed to ConcurrentEdits(concurrent:) does not satisfy the requirements posed by ConcurrentEdits "
293+ }
294+ }
295+ }
296+
297+ /// The raw concurrent edits. Are guaranteed to satisfy the requirements
298+ /// stated above.
299+ public let edits : [ IncrementalEdit ]
300+
301+ /// Initialize this struct from edits that are already in a concurrent form
302+ /// and are guaranteed to satisfy the requirements posed above.
303+ public init ( concurrent: [ IncrementalEdit ] ) throws {
304+ if !Self. isValidConcurrentEditArray ( concurrent) {
305+ throw ConcurrentEditsError . editsNotConcurrent
306+ }
307+ self . edits = concurrent
308+ }
309+
310+ /// Create concurrent from a set of sequential edits. Sequential edits are
311+ /// applied one after the other. Applying the first edit results in an
312+ /// intermediate string to which the second edit is applied etc. For example
313+ /// applying
314+ /// - insert 'x' at offset 0
315+ /// - insert 'y' at offset 1
316+ /// - insert 'z' at offset 2
317+ /// to '012345' results in 'xyz012345'.
318+
319+ public init ( fromSequential sequentialEdits: [ IncrementalEdit ] ) {
320+ do {
321+ try self . init ( concurrent: Self . translateSequentialEditsToConcurrentEdits ( sequentialEdits) )
322+ } catch {
323+ fatalError ( " ConcurrentEdits created by translateSequentialEditsToConcurrentEdits do not satisfy ConcurrentEdits requirements " )
324+ }
325+ }
326+
327+ /// Construct a concurrent edits struct from a single edit. For a single edit,
328+ /// there is no differentiation between being it being applied concurrently
329+ /// or sequentially.
330+ public init ( _ single: IncrementalEdit ) {
331+ do {
332+ try self . init ( concurrent: [ single] )
333+ } catch {
334+ fatalError ( " A single edit doesn't satisfy the ConcurrentEdits requirements? " )
335+ }
336+ }
337+
338+ private static func translateSequentialEditsToConcurrentEdits(
339+ _ edits: [ IncrementalEdit ]
340+ ) -> [ IncrementalEdit ] {
341+ var concurrentEdits : [ IncrementalEdit ] = [ ]
342+ for editToAdd in edits {
343+ var editToAdd = editToAdd
344+ var editIndiciesMergedWithNewEdit : [ Int ] = [ ]
345+ for (index, existingEdit) in concurrentEdits. enumerated ( ) {
346+ if existingEdit. replacementRange. intersectsOrTouches ( editToAdd. range) {
347+ let intersectionLength =
348+ existingEdit. replacementRange. intersected ( editToAdd. range) . length
349+ editToAdd = IncrementalEdit (
350+ offset: Swift . min ( existingEdit. offset, editToAdd. offset) ,
351+ length: existingEdit. length + editToAdd. length - intersectionLength,
352+ replacementLength: existingEdit. replacementLength + editToAdd. replacementLength - intersectionLength
353+ )
354+ editIndiciesMergedWithNewEdit. append ( index)
355+ } else if existingEdit. offset < editToAdd. endOffset {
356+ editToAdd = IncrementalEdit (
357+ offset: editToAdd. offset - existingEdit. replacementLength + existingEdit. length,
358+ length: editToAdd. length,
359+ replacementLength: editToAdd. replacementLength
360+ )
361+ }
362+ }
363+ precondition ( editIndiciesMergedWithNewEdit. isSorted)
364+ for indexToRemove in editIndiciesMergedWithNewEdit. reversed ( ) {
365+ concurrentEdits. remove ( at: indexToRemove)
366+ }
367+ let insertPos =
368+ concurrentEdits. firstIndex ( where: { edit in
369+ editToAdd. endOffset <= edit. offset
370+ } ) ?? concurrentEdits. count
371+ concurrentEdits. insert ( editToAdd, at: insertPos)
372+ precondition ( ConcurrentEdits . isValidConcurrentEditArray ( concurrentEdits) )
373+ }
374+ return concurrentEdits
375+ }
376+
377+ private static func isValidConcurrentEditArray( _ edits: [ IncrementalEdit ] ) -> Bool {
378+ // Not quite sure if we should disallow creating an `IncrementalParseTransition`
379+ // object without edits but there doesn't seem to be much benefit if we do,
380+ // and there are 'lit' tests that want to test incremental re-parsing without edits.
381+ guard !edits. isEmpty else { return true }
382+
383+ for i in 1 ..< edits. count {
384+ let prevEdit = edits [ i - 1 ]
385+ let curEdit = edits [ i]
386+ if curEdit. range. offset < prevEdit. range. endOffset {
387+ return false
388+ }
389+ if curEdit. intersectsRange ( prevEdit. range) {
390+ return false
391+ }
392+ }
393+ return true
394+ }
395+
396+ /// **Public for testing purposes only**
397+ public static func _isValidConcurrentEditArray( _ edits: [ IncrementalEdit ] ) -> Bool {
398+ return isValidConcurrentEditArray ( edits)
399+ }
400+ }
401+
402+ fileprivate extension Sequence where Element: Comparable {
403+ var isSorted : Bool {
404+ return zip ( self , self . dropFirst ( ) ) . allSatisfy ( { $0. 0 < $0. 1 } )
405+ }
406+ }
0 commit comments