Skip to content

Commit e319339

Browse files
committed
Move IncrementalParseTransition.swift to SwiftParser
Also move `SequentialToConcurrentEditTranslationTests.swift` to `SwiftParserTest`
1 parent ca81f39 commit e319339

File tree

4 files changed

+149
-146
lines changed

4 files changed

+149
-146
lines changed

Sources/SwiftParser/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ add_swift_host_library(SwiftParser
1313
Declarations.swift
1414
Directives.swift
1515
Expressions.swift
16+
IncrementalParseTransition.swift
1617
Lookahead.swift
1718
LoopProgressCondition.swift
1819
Modifiers.swift

Sources/SwiftSyntax/IncrementalParseTransition.swift renamed to Sources/SwiftParser/IncrementalParseTransition.swift

Lines changed: 147 additions & 145 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
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.
20572
public 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.
339206
fileprivate 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+
}

Sources/SwiftSyntax/CMakeLists.txt

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ add_swift_host_library(SwiftSyntax
1111
Assert.swift
1212
BumpPtrAllocator.swift
1313
CommonAncestor.swift
14-
IncrementalParseTransition.swift
1514
MemoryLayout.swift
1615
MissingNodeInitializers.swift
1716
Trivia.swift

Tests/SwiftSyntaxTest/SequentialToConcurrentEditTranslationTests.swift renamed to Tests/SwiftParserTest/SequentialToConcurrentEditTranslationTests.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212

1313
import XCTest
1414
import SwiftSyntax
15+
import SwiftParser
1516
import _SwiftSyntaxTestSupport
1617

1718
let longString = """

0 commit comments

Comments
 (0)