Skip to content

Commit 45dcd15

Browse files
committed
Register affect range in lookahead
1 parent 401725e commit 45dcd15

File tree

5 files changed

+103
-22
lines changed

5 files changed

+103
-22
lines changed

Sources/SwiftParser/IncrementalParseTransition.swift

Lines changed: 55 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -46,9 +46,14 @@ public final class IncrementalParseReusedNodeCollector:
4646
/// Keeps track of a previously parsed syntax tree and the source edits that
4747
/// occurred since it was created.
4848
public final class IncrementalParseTransition {
49-
fileprivate let previousTree: SourceFileSyntax
50-
fileprivate let edits: ConcurrentEdits
51-
fileprivate let reusedDelegate: IncrementalParseReusedNodeDelegate?
49+
fileprivate var previousTree: SourceFileSyntax?
50+
fileprivate var edits: ConcurrentEdits?
51+
fileprivate var reusedDelegate: IncrementalParseReusedNodeDelegate?
52+
53+
fileprivate var previousLookaheadRange: [Int: Int] = [:]
54+
/// Keep track of how far we would look when calling ``Lookahead``
55+
/// Key is offset to buffer start and value is the length of we lookahead
56+
fileprivate var cursorLookaheadRange: [Int: Int] = [:]
5257

5358
/// - Parameters:
5459
/// - previousTree: The previous tree to do lookups on.
@@ -57,30 +62,44 @@ public final class IncrementalParseTransition {
5762
/// - reusedNodeDelegate: Optional delegate to accept information about the
5863
/// reused regions and nodes.
5964
public init(
60-
previousTree: SourceFileSyntax,
61-
edits: ConcurrentEdits,
65+
previousTree: SourceFileSyntax? = nil,
66+
edits: ConcurrentEdits? = nil,
6267
reusedNodeDelegate: IncrementalParseReusedNodeDelegate? = nil
6368
) {
6469
self.previousTree = previousTree
6570
self.edits = edits
6671
self.reusedDelegate = reusedNodeDelegate
6772
}
73+
74+
public func setupTransition(tree: SourceFileSyntax, edits: ConcurrentEdits, delegate: IncrementalParseReusedNodeDelegate? = nil) {
75+
self.previousTree = tree
76+
self.edits = edits
77+
self.reusedDelegate = delegate
78+
self.previousLookaheadRange = cursorLookaheadRange
79+
self.cursorLookaheadRange = [:]
80+
}
81+
82+
public func registerAffectRange(at offset: Int, length: Int) {
83+
self.cursorLookaheadRange[offset] = length
84+
}
85+
86+
public func isValidTransition() -> Bool {
87+
return previousTree != nil
88+
}
6889
}
6990

7091
/// Provides a mechanism for the parser to skip regions of an incrementally
7192
/// updated source that was already parsed during a previous parse invocation.
7293
public struct IncrementalParseLookup {
7394
fileprivate let transition: IncrementalParseTransition
74-
fileprivate var cursor: SyntaxCursor
7595

7696
/// Create a new ``IncrementalParseLookup`` that can look nodes up based on the
7797
/// given ``IncrementalParseTransition``.
7898
public init(transition: IncrementalParseTransition) {
7999
self.transition = transition
80-
self.cursor = .init(root: Syntax(transition.previousTree))
81100
}
82101

83-
fileprivate var edits: ConcurrentEdits {
102+
fileprivate var edits: ConcurrentEdits? {
84103
return transition.edits
85104
}
86105

@@ -109,7 +128,7 @@ public struct IncrementalParseLookup {
109128
let node = cursorLookup(prevPosition: prevPosition, kind: kind)
110129
if let delegate = reusedDelegate, let node {
111130
delegate.parserReusedNode(
112-
range: ByteSourceRange(offset: newOffset, length: node.byteSizeAfterTrimmingTrivia),
131+
range: ByteSourceRange(offset: node.positionAfterSkippingLeadingTrivia.utf8Offset, length: node.byteSizeAfterTrimmingTrivia),
113132
previousNode: node
114133
)
115134
}
@@ -120,20 +139,28 @@ public struct IncrementalParseLookup {
120139
prevPosition: AbsolutePosition,
121140
kind: SyntaxKind
122141
) -> Syntax? {
142+
guard let previousTree = transition.previousTree else {
143+
return nil
144+
}
145+
146+
var cursor = SyntaxCursor(root: Syntax(previousTree))
123147
guard !cursor.finished else { return nil }
124148

125149
while true {
126-
if nodeAtCursorCanBeReused(prevPosition: prevPosition, kind: kind) {
150+
if nodeAtCursorCanBeReused(cursor, prevPosition: prevPosition, kind: kind) {
127151
return cursor.asSyntaxNode
128152
}
129153
guard cursor.advanceToNextNode(at: prevPosition) else { return nil }
130154
}
131155
}
132156

133157
fileprivate func nodeAtCursorCanBeReused(
158+
_ cursor: SyntaxCursor,
134159
prevPosition: AbsolutePosition,
135160
kind: SyntaxKind
136161
) -> Bool {
162+
guard let edits = edits else { return false }
163+
137164
let node = cursor.node
138165
if node.position != prevPosition {
139166
return false
@@ -165,7 +192,7 @@ public struct IncrementalParseLookup {
165192
}
166193
let nodeAffectRange = ByteSourceRange(
167194
offset: node.position.utf8Offset,
168-
length: (node.totalLength + nextLeafNodeLength).utf8Length
195+
length: max(mergeLookaheadRange(at: node.position.utf8Offset, length: node.totalLength.utf8Length), (node.totalLength + nextLeafNodeLength).utf8Length)
169196
)
170197

171198
for edit in edits.edits {
@@ -184,6 +211,8 @@ public struct IncrementalParseLookup {
184211
}
185212

186213
fileprivate func translateToPreEditOffset(_ postEditOffset: Int) -> Int? {
214+
guard let edits = edits else { return nil }
215+
187216
var offset = postEditOffset
188217
for edit in edits.edits {
189218
if edit.range.offset > offset {
@@ -199,6 +228,21 @@ public struct IncrementalParseLookup {
199228
}
200229
return offset
201230
}
231+
232+
fileprivate func mergeLookaheadRange(at start: Int, length: Int) -> Int {
233+
var totalLength = start + length
234+
235+
let targetRanges = transition.previousLookaheadRange.filter { $0.key >= totalLength }.sorted(by: { $0.key < $1.key })
236+
237+
for targetRange in targetRanges {
238+
if targetRange.key != totalLength {
239+
break
240+
}
241+
totalLength += targetRange.value
242+
}
243+
244+
return totalLength
245+
}
202246
}
203247

204248
/// Functions as an iterator that walks the tree looking for nodes with a

Sources/SwiftParser/Lookahead.swift

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,22 +27,29 @@ extension Parser {
2727
/// i.e. how far it looked ahead.
2828
var tokensConsumed: Int = 0
2929

30+
var parseTransition: IncrementalParseTransition?
31+
/// Initialize the offset to the start of source
32+
var currentOffset: Int
33+
3034
private init(
3135
lexemes: Lexer.LexemeSequence,
32-
currentToken: Lexer.Lexeme
36+
currentToken: Lexer.Lexeme,
37+
parseTransition: IncrementalParseTransition? = nil
3338
) {
3439
self.lexemes = lexemes
3540
self.currentToken = currentToken
41+
self.parseTransition = parseTransition
42+
self.currentOffset = lexemes.getOffsetToStart(self.currentToken)
3643
}
3744

3845
fileprivate init(cloning other: Parser) {
39-
self.init(lexemes: other.lexemes, currentToken: other.currentToken)
46+
self.init(lexemes: other.lexemes, currentToken: other.currentToken, parseTransition: other.parseTransition)
4047
}
4148

4249
/// Initiates a lookahead session from the current point in this
4350
/// lookahead session.
4451
func lookahead() -> Lookahead {
45-
return Lookahead(lexemes: self.lexemes, currentToken: self.currentToken)
52+
return Lookahead(lexemes: self.lexemes, currentToken: self.currentToken, parseTransition: self.parseTransition)
4653
}
4754
}
4855

@@ -89,6 +96,7 @@ extension Parser.Lookahead {
8996

9097
mutating func consumeAnyToken() {
9198
tokensConsumed += 1
99+
registerAffectRange()
92100
self.currentToken = self.lexemes.advance()
93101
}
94102

@@ -399,4 +407,10 @@ extension Parser.Lookahead {
399407
}
400408
}
401409
}
410+
411+
fileprivate mutating func registerAffectRange() {
412+
self.parseTransition?.registerAffectRange(at: self.currentOffset, length: self.currentToken.byteLength)
413+
currentOffset += self.currentToken.byteLength
414+
self.parseTransition?.registerAffectRange(at: self.currentOffset, length: self.lexemes.peek().byteLength)
415+
}
402416
}

Sources/SwiftParser/Parser.swift

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,12 @@ public struct Parser {
145145
/// arena is created automatically, and `input` copied into the
146146
/// arena. If non-`nil`, `input` must be within its registered
147147
/// source buffer or allocator.
148-
public init(_ input: UnsafeBufferPointer<UInt8>, maximumNestingLevel: Int? = nil, arena: ParsingSyntaxArena? = nil, parseTransition: IncrementalParseTransition? = nil) {
148+
public init(
149+
_ input: UnsafeBufferPointer<UInt8>,
150+
maximumNestingLevel: Int? = nil,
151+
arena: ParsingSyntaxArena? = nil,
152+
parseTransition: IncrementalParseTransition? = nil
153+
) {
149154
self.maximumNestingLevel = maximumNestingLevel ?? Self.defaultMaximumNestingLevel
150155

151156
var sourceBuffer: UnsafeBufferPointer<UInt8>
@@ -637,7 +642,11 @@ extension Parser {
637642
// MARK: Incremental Parsing
638643
extension Parser {
639644
mutating func loadCurrentSyntaxNodeFromCache(for kind: SyntaxKind) -> Syntax? {
640-
guard let parseTransition = self.parseTransition else { return nil }
645+
guard let parseTransition = self.parseTransition,
646+
parseTransition.isValidTransition()
647+
else {
648+
return nil
649+
}
641650

642651
var lookUpHelper = IncrementalParseLookup(transition: parseTransition)
643652
let currentOffset = self.lexemes.getOffsetToStart(self.currentToken)

Sources/_SwiftSyntaxTestSupport/IncrementalParseTestUtils.swift

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,10 +30,11 @@ public func assertIncrementalParse(
3030
let originalString = String(originalSource)
3131
let editedString = String(editedSource)
3232

33-
let originalTree = Parser.parse(source: originalString)
33+
let transition = IncrementalParseTransition()
34+
let originalTree = Parser.parse(source: originalString, parseTransition: transition)
3435

3536
let reusedNodesCollector = IncrementalParseReusedNodeCollector()
36-
let transition = IncrementalParseTransition(previousTree: originalTree, edits: concurrentEdits, reusedNodeDelegate: reusedNodesCollector)
37+
transition.setupTransition(tree: originalTree, edits: concurrentEdits, delegate: reusedNodesCollector)
3738

3839
let newTree = Parser.parse(source: editedString)
3940
let incrementallyParsedNewTree = Parser.parse(source: editedString, parseTransition: transition)

Tests/SwiftParserTest/IncrementalParsingTests.swift

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,7 @@ public class IncrementalParsingTests: XCTestCase {
2525
)
2626
}
2727

28-
public func testReusedNode() throws {
29-
try XCTSkipIf(true, "Swift parser does not handle node reuse yet")
28+
public func testReusedNode() {
3029
assertIncrementalParse(
3130
"""
3231
struct A⏩️⏸️A⏪️ {}
@@ -37,9 +36,8 @@ public class IncrementalParsingTests: XCTestCase {
3736
]
3837
)
3938
}
40-
39+
4140
public func testTrailingClosure() {
42-
XCTExpectFailure("WIP: Add lookahead for incremental parse")
4341
assertIncrementalParse(
4442
"""
4543
foo() {}
@@ -49,4 +47,19 @@ public class IncrementalParsingTests: XCTestCase {
4947
"""
5048
)
5149
}
50+
51+
public func testMultiFunctionCall() {
52+
assertIncrementalParse(
53+
"""
54+
foo() {}
55+
foo1() {}
56+
foo2() {}
57+
⏩️⏸️foo3() {}⏪️
58+
""",
59+
reusedNodes: [
60+
ReusedNodeSpec("foo() {}", kind: .codeBlockItem),
61+
ReusedNodeSpec("foo1() {}", kind: .codeBlockItem),
62+
]
63+
)
64+
}
5265
}

0 commit comments

Comments
 (0)