Skip to content

Commit 9513ca5

Browse files
committed
WIP: port incremental parse to CodeBlockItem
1 parent da479d3 commit 9513ca5

File tree

11 files changed

+198
-55
lines changed

11 files changed

+198
-55
lines changed

Sources/SwiftParser/IncrementalParseTransition.swift

Lines changed: 37 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -26,29 +26,40 @@ public protocol IncrementalParseReusedNodeDelegate {
2626
/// - range: The source region of the currently parsed source.
2727
/// - previousNode: The node from the previous tree that is associated with
2828
/// the skipped source region.
29-
func parserReusedNode(range: ByteSourceRange, previousNode: Syntax)
29+
func parserReusedNode(previousNode: Syntax)
3030
}
3131

3232
/// An implementation of `IncrementalParseReusedNodeDelegate` that just collects
3333
/// the range and re-used node into an array.
3434
public final class IncrementalParseReusedNodeCollector:
3535
IncrementalParseReusedNodeDelegate
3636
{
37-
public var rangeAndNodes: [(ByteSourceRange, Syntax)] = []
37+
public var nodes: [Syntax] = []
3838

3939
public init() {}
4040

41-
public func parserReusedNode(range: ByteSourceRange, previousNode: Syntax) {
42-
rangeAndNodes.append((range, previousNode))
41+
public func parserReusedNode(previousNode: Syntax) {
42+
nodes.append(previousNode)
43+
}
44+
}
45+
46+
public final class IncrementalParseNodeAffectRangeCollector {
47+
fileprivate var nodeAffectRange: [RawSyntax.ID: Int] = [:]
48+
49+
public init() {}
50+
51+
@_spi(RawSyntax)
52+
public func registerNodeForIncrementalParse(node: RawSyntax, length: Int) {
53+
self.nodeAffectRange[node.id] = length
4354
}
4455
}
4556

4657
/// Keeps track of a previously parsed syntax tree and the source edits that
4758
/// occurred since it was created.
4859
public final class IncrementalParseTransition {
49-
fileprivate let previousTree: SourceFileSyntax
50-
fileprivate let edits: ConcurrentEdits
51-
fileprivate let reusedDelegate: IncrementalParseReusedNodeDelegate?
60+
fileprivate var previousTree: SourceFileSyntax
61+
fileprivate var edits: ConcurrentEdits
62+
fileprivate var reusedDelegate: IncrementalParseReusedNodeDelegate?
5263

5364
/// - Parameters:
5465
/// - previousTree: The previous tree to do lookups on.
@@ -59,7 +70,7 @@ public final class IncrementalParseTransition {
5970
public init(
6071
previousTree: SourceFileSyntax,
6172
edits: ConcurrentEdits,
62-
reusedNodeDelegate: IncrementalParseReusedNodeDelegate? = nil
73+
reusedNodeDelegate: IncrementalParseReusedNodeDelegate?
6374
) {
6475
self.previousTree = previousTree
6576
self.edits = edits
@@ -71,23 +82,31 @@ public final class IncrementalParseTransition {
7182
/// updated source that was already parsed during a previous parse invocation.
7283
public struct IncrementalParseLookup {
7384
fileprivate let transition: IncrementalParseTransition
85+
7486
fileprivate var cursor: SyntaxCursor
7587

88+
fileprivate let nodeAffectRangeCollector: IncrementalParseNodeAffectRangeCollector
89+
7690
/// Create a new ``IncrementalParseLookup`` that can look nodes up based on the
7791
/// given ``IncrementalParseTransition``.
78-
public init(transition: IncrementalParseTransition) {
92+
public init(transition: IncrementalParseTransition, nodeAffectRangeCollector: IncrementalParseNodeAffectRangeCollector) {
7993
self.transition = transition
8094
self.cursor = .init(root: Syntax(transition.previousTree))
95+
self.nodeAffectRangeCollector = nodeAffectRangeCollector
8196
}
8297

83-
fileprivate var edits: ConcurrentEdits {
98+
fileprivate var edits: ConcurrentEdits? {
8499
return transition.edits
85100
}
86101

87102
fileprivate var reusedDelegate: IncrementalParseReusedNodeDelegate? {
88103
return transition.reusedDelegate
89104
}
90105

106+
fileprivate var nodeAffectRange: [RawSyntax.ID: Int] {
107+
return nodeAffectRangeCollector.nodeAffectRange
108+
}
109+
91110
/// Does a lookup to see if the current source `offset` should be associated
92111
/// with a known ``Syntax`` node and its region skipped during parsing.
93112
///
@@ -109,7 +128,6 @@ 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),
113131
previousNode: node
114132
)
115133
}
@@ -134,6 +152,8 @@ public struct IncrementalParseLookup {
134152
prevPosition: AbsolutePosition,
135153
kind: SyntaxKind
136154
) -> Bool {
155+
guard let edits = edits else { return false }
156+
137157
let node = cursor.node
138158
if node.position != prevPosition {
139159
return false
@@ -148,24 +168,13 @@ public struct IncrementalParseLookup {
148168
return true
149169
}
150170

151-
// Node can also not be reused if an edit has been made in the next token's
152-
// text, e.g. because `private struct Foo {}` parses as a CodeBlockItem with
153-
// a StructDecl inside and `private struc Foo {}` parses as two
154-
// CodeBlockItems one for `private` and one for `struc Foo {}`
155-
var nextLeafNodeLength: SourceLength = .zero
156-
if let nextSibling = cursor.nextSibling {
157-
// Fast path check: if next sibling is before all the edits then we can
158-
// re-use the node.
159-
if !edits.edits.isEmpty && edits.edits.first!.range.offset > nextSibling.endPosition.utf8Offset {
160-
return true
161-
}
162-
if let nextToken = nextSibling.firstToken(viewMode: .sourceAccurate) {
163-
nextLeafNodeLength = nextToken.leadingTriviaLength + nextToken.contentLength
164-
}
171+
guard let nodeAffectRangeLength = self.nodeAffectRange[node.raw.id] else {
172+
return false
165173
}
174+
166175
let nodeAffectRange = ByteSourceRange(
167176
offset: node.position.utf8Offset,
168-
length: (node.totalLength + nextLeafNodeLength).utf8Length
177+
length: nodeAffectRangeLength
169178
)
170179

171180
for edit in edits.edits {
@@ -184,6 +193,8 @@ public struct IncrementalParseLookup {
184193
}
185194

186195
fileprivate func translateToPreEditOffset(_ postEditOffset: Int) -> Int? {
196+
guard let edits = edits else { return nil }
197+
187198
var offset = postEditOffset
188199
for edit in edits.edits {
189200
if edit.range.offset > offset {

Sources/SwiftParser/Lexer/LexemeSequence.swift

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,17 +32,28 @@ extension Lexer {
3232
/// usually less than 0.1% of the memory allocated by the syntax arena.
3333
var lexerStateAllocator = BumpPtrAllocator(slabSize: 256)
3434

35+
var offsetToNextTokenEnd: Int {
36+
self.getOffsetToStart(self.nextToken) + self.nextToken.byteLength
37+
}
38+
39+
var lookaheadTracker: UnsafeMutablePointer<LookaheadTracker>
40+
3541
fileprivate init(sourceBufferStart: Lexer.Cursor, cursor: Lexer.Cursor) {
3642
self.sourceBufferStart = sourceBufferStart
3743
self.cursor = cursor
3844
self.nextToken = self.cursor.nextToken(sourceBufferStart: self.sourceBufferStart, stateAllocator: lexerStateAllocator)
45+
self.lookaheadTracker = .allocate(capacity: 1)
3946
}
4047

4148
@_spi(Testing)
4249
public mutating func next() -> Lexer.Lexeme? {
4350
return self.advance()
4451
}
4552

53+
public func recordFurthestOffset() {
54+
self.lookaheadTracker.pointee.furthestOffset = self.offsetToNextTokenEnd
55+
}
56+
4657
mutating func advance() -> Lexer.Lexeme {
4758
defer {
4859
self.nextToken = self.cursor.nextToken(sourceBufferStart: self.sourceBufferStart, stateAllocator: lexerStateAllocator)

Sources/SwiftParser/Lookahead.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ extension Parser {
3333
) {
3434
self.lexemes = lexemes
3535
self.currentToken = currentToken
36+
self.lexemes.recordFurthestOffset()
3637
}
3738

3839
fileprivate init(cloning other: Parser) {
@@ -90,6 +91,7 @@ extension Parser.Lookahead {
9091
mutating func consumeAnyToken() {
9192
tokensConsumed += 1
9293
self.currentToken = self.lexemes.advance()
94+
self.lexemes.recordFurthestOffset()
9395
}
9496

9597
mutating func consumeAnyToken(remapping: RawTokenKind) {

Sources/SwiftParser/Parser.swift

Lines changed: 51 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,8 @@ public struct Parser {
103103

104104
let parseTransition: IncrementalParseTransition?
105105

106+
var parseNodeAffectRange: IncrementalParseNodeAffectRangeCollector?
107+
106108
/// A default maximum nesting level that is used if the client didn't
107109
/// explicitly specify one. Debug builds of the parser comume a lot more stack
108110
/// space and thus have a lower default maximum nesting level.
@@ -113,7 +115,12 @@ public struct Parser {
113115
#endif
114116

115117
/// Initializes a ``Parser`` from the given string.
116-
public init(_ input: String, maximumNestingLevel: Int? = nil, parseTransition: IncrementalParseTransition? = nil) {
118+
public init(
119+
_ input: String,
120+
maximumNestingLevel: Int? = nil,
121+
parseNodeAffectRange: IncrementalParseNodeAffectRangeCollector? = nil,
122+
parseTransition: IncrementalParseTransition? = nil
123+
) {
117124
self.maximumNestingLevel = maximumNestingLevel ?? Self.defaultMaximumNestingLevel
118125

119126
self.arena = ParsingSyntaxArena(
@@ -129,6 +136,7 @@ public struct Parser {
129136
self.parseTransition = parseTransition
130137
self.lexemes = Lexer.tokenize(interned)
131138
self.currentToken = self.lexemes.advance()
139+
self.parseNodeAffectRange = parseNodeAffectRange
132140
}
133141

134142
/// Initializes a ``Parser`` from the given input buffer.
@@ -145,7 +153,13 @@ public struct Parser {
145153
/// arena is created automatically, and `input` copied into the
146154
/// arena. If non-`nil`, `input` must be within its registered
147155
/// source buffer or allocator.
148-
public init(_ input: UnsafeBufferPointer<UInt8>, maximumNestingLevel: Int? = nil, arena: ParsingSyntaxArena? = nil, parseTransition: IncrementalParseTransition? = nil) {
156+
public init(
157+
_ input: UnsafeBufferPointer<UInt8>,
158+
maximumNestingLevel: Int? = nil,
159+
parseNodeAffectRange: IncrementalParseNodeAffectRangeCollector? = nil,
160+
parseTransition: IncrementalParseTransition? = nil,
161+
arena: ParsingSyntaxArena? = nil
162+
) {
149163
self.maximumNestingLevel = maximumNestingLevel ?? Self.defaultMaximumNestingLevel
150164

151165
var sourceBuffer: UnsafeBufferPointer<UInt8>
@@ -163,6 +177,7 @@ public struct Parser {
163177
self.parseTransition = parseTransition
164178
self.lexemes = Lexer.tokenize(sourceBuffer)
165179
self.currentToken = self.lexemes.advance()
180+
self.parseNodeAffectRange = parseNodeAffectRange
166181
}
167182

168183
mutating func missingToken(_ kind: RawTokenKind, text: SyntaxText? = nil) -> RawTokenSyntax {
@@ -241,6 +256,7 @@ public struct Parser {
241256
extension Parser {
242257
/// Retrieves the token following the current token without consuming it.
243258
func peek() -> Lexer.Lexeme {
259+
lexemes.recordFurthestOffset()
244260
return self.lexemes.peek()
245261
}
246262
}
@@ -637,9 +653,12 @@ extension Parser {
637653
// MARK: Incremental Parsing
638654
extension Parser {
639655
mutating func loadCurrentSyntaxNodeFromCache(for kind: SyntaxKind) -> Syntax? {
640-
guard let parseTransition = self.parseTransition else { return nil }
641-
642-
var lookUpHelper = IncrementalParseLookup(transition: parseTransition)
656+
guard let parseTransition = self.parseTransition,
657+
let parseNodeAffectRange = self.parseNodeAffectRange
658+
else {
659+
return nil
660+
}
661+
var lookUpHelper = IncrementalParseLookup(transition: parseTransition, nodeAffectRangeCollector: parseNodeAffectRange)
643662
let currentOffset = self.lexemes.getOffsetToStart(self.currentToken)
644663
if let node = lookUpHelper.lookUp(currentOffset, kind: kind) {
645664
self.lexemes.advance(by: node.byteSize, currentToken: &self.currentToken)
@@ -648,4 +667,31 @@ extension Parser {
648667

649668
return nil
650669
}
670+
671+
func registerNodeForIncrementalParse(node: RawSyntax, startToken: Lexer.Lexeme) {
672+
if let parseNodeAffectRange {
673+
parseNodeAffectRange.registerNodeForIncrementalParse(
674+
node: node,
675+
length: max(lookaheadFurthestOffset - self.lexemes.getOffsetToStart(startToken), node.byteLength + currentToken.byteLength)
676+
)
677+
}
678+
}
679+
680+
public var lookaheadFurthestOffset: Int {
681+
return lexemes.lookaheadTracker.pointee.furthestOffset
682+
}
683+
}
684+
685+
/// Record the furthest offset to `sourceBufferStart` that is reached by ``Parser.Lookahead`` or ``Parser.peek()`` in ``Lexer.LexemeSequence``
686+
public struct LookaheadTracker {
687+
private var _furthestOffset: Int
688+
689+
public var furthestOffset: Int {
690+
get { _furthestOffset }
691+
set { _furthestOffset = max(newValue, furthestOffset) }
692+
}
693+
694+
init(furthestOffset: Int = 0) {
695+
self._furthestOffset = furthestOffset
696+
}
651697
}

Sources/SwiftParser/TopLevel.swift

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -151,7 +151,9 @@ extension Parser {
151151
/// statement → compiler-control-statement
152152
/// statements → statement statements?
153153
mutating func parseCodeBlockItem(isAtTopLevel: Bool, allowInitDecl: Bool) -> RawCodeBlockItemSyntax? {
154+
let startToken = self.currentToken
154155
if let syntax = self.loadCurrentSyntaxNodeFromCache(for: .codeBlockItem) {
156+
self.registerNodeForIncrementalParse(node: syntax.raw, startToken: startToken)
155157
return RawCodeBlockItemSyntax(syntax.raw)
156158
}
157159

@@ -187,12 +189,17 @@ extension Parser {
187189
if item.raw.isEmpty && semi == nil && trailingSemis.isEmpty {
188190
return nil
189191
}
190-
return RawCodeBlockItemSyntax(
192+
193+
let result = RawCodeBlockItemSyntax(
191194
item: item,
192195
semicolon: semi,
193196
RawUnexpectedNodesSyntax(trailingSemis, arena: self.arena),
194197
arena: self.arena
195198
)
199+
200+
self.registerNodeForIncrementalParse(node: result.raw, startToken: startToken)
201+
202+
return result
196203
}
197204

198205
private mutating func parseStatementItem() -> RawCodeBlockItemSyntax.Item {

Sources/SwiftParser/generated/Parser+Entry.swift

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,10 @@ extension Parser {
1919
/// `Parser.init` for more details.
2020
public static func parse(
2121
source: String,
22+
parseNodeAffectRange: IncrementalParseNodeAffectRangeCollector? = nil,
2223
parseTransition: IncrementalParseTransition? = nil
2324
) -> SourceFileSyntax {
24-
var parser = Parser(source, parseTransition: parseTransition)
25+
var parser = Parser(source, parseNodeAffectRange: parseNodeAffectRange, parseTransition: parseTransition)
2526
return SourceFileSyntax.parse(from: &parser)
2627
}
2728

@@ -30,9 +31,10 @@ extension Parser {
3031
public static func parse(
3132
source: UnsafeBufferPointer<UInt8>,
3233
maximumNestingLevel: Int? = nil,
34+
parseNodeAffectRange: IncrementalParseNodeAffectRangeCollector? = nil,
3335
parseTransition: IncrementalParseTransition? = nil
3436
) -> SourceFileSyntax {
35-
var parser = Parser(source, maximumNestingLevel: maximumNestingLevel, parseTransition: parseTransition)
37+
var parser = Parser(source, maximumNestingLevel: maximumNestingLevel, parseNodeAffectRange: parseNodeAffectRange, parseTransition: parseTransition)
3638
return SourceFileSyntax.parse(from: &parser)
3739
}
3840
}

Sources/SwiftSyntax/Raw/RawSyntax.swift

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -918,6 +918,12 @@ extension RawSyntax {
918918
}
919919
}
920920

921+
extension RawSyntax: Identifiable {
922+
public var id: UnsafeRawPointer {
923+
return UnsafeRawPointer(pointer)
924+
}
925+
}
926+
921927
#if DEBUG
922928
/// See `SyntaxMemoryLayout`.
923929
var RawSyntaxDataMemoryLayouts: [String: SyntaxMemoryLayout.Value] = [

Sources/SwiftSyntax/Syntax.swift

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -448,6 +448,11 @@ public extension SyntaxProtocol {
448448
return ByteSourceRange(offset: position.utf8Offset, length: byteSize)
449449
}
450450

451+
/// The byte source range of this node exluding leading and trailing trivia.
452+
var byteRangeAfterTrimmingTrivia: ByteSourceRange {
453+
return ByteSourceRange(offset: positionAfterSkippingLeadingTrivia.utf8Offset, length: byteSizeAfterTrimmingTrivia)
454+
}
455+
451456
/// The length this node takes up spelled out in the source, excluding its
452457
/// leading or trailing trivia.
453458
var contentLength: SourceLength {

0 commit comments

Comments
 (0)