Skip to content

Commit f8621ee

Browse files
committed
Reimplement SyntaxVisitor to improve visiting performance.
This implementation allows users to specify interested SyntaxNodeKind and TokenKind; thus we can traverse the RawSyntax Tree instead of SyntaxTree and realize only the interested RawSyntax nodes into SyntaxNodes.
1 parent 41fdeb3 commit f8621ee

File tree

3 files changed

+114
-9
lines changed

3 files changed

+114
-9
lines changed

Sources/SwiftSyntax/RawSyntax.swift

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -466,3 +466,43 @@ extension SyntaxNodeId: ByteTreeScalarDecodable {
466466
return SyntaxNodeId(rawValue: UInt(rawValue))
467467
}
468468
}
469+
470+
extension RawSyntax {
471+
func accept(_ visitor: RawSyntaxVisitor) {
472+
guard isPresent else { return }
473+
switch data {
474+
case .node(let kind,let layout):
475+
let shouldVisit = visitor.shouldVisit(kind)
476+
var visitedNode: Syntax?
477+
var forwardVisitor = visitor
478+
if shouldVisit {
479+
480+
// Visit this node realizes a syntax node, so we can re-create a forward
481+
// visitor rooted on this node.
482+
visitedNode = visitor.visitCurrent()
483+
forwardVisitor = RawSyntaxVisitor(visitor, visitedNode!)
484+
}
485+
486+
// Create a slot for child index
487+
forwardVisitor.initializeChildIdx()
488+
layout.forEach { l in
489+
490+
// Increment child index for next child to visit
491+
forwardVisitor.incrementLastChildIdx()
492+
l?.accept(forwardVisitor)
493+
}
494+
495+
// Remove the slot for child index
496+
forwardVisitor.removeLastChildIdx()
497+
if shouldVisit {
498+
499+
// We finished visiting the current node.
500+
forwardVisitor.visitPost()
501+
}
502+
case .token(let kind, _, _):
503+
if visitor.shouldVisit(kind) {
504+
_ = visitor.visitCurrent()
505+
}
506+
}
507+
}
508+
}

Sources/SwiftSyntax/SyntaxKind.swift.gyb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424
import Foundation
2525

2626
/// Enumerates the known kinds of Syntax represented in the Syntax tree.
27-
internal enum SyntaxKind: String, Codable {
27+
public enum SyntaxKind: String, Codable {
2828
case token = "Token"
2929
case unknown = "Unknown"
3030
% for node in SYNTAX_NODES:

Sources/SwiftSyntax/SyntaxRewriter.swift.gyb

Lines changed: 73 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -98,12 +98,17 @@ open class SyntaxVisitor {
9898
public init() {}
9999
% for node in SYNTAX_NODES:
100100
% if is_visitable(node):
101-
open func visit(_ node: ${node.name}) {
102-
visitChildren(node)
103-
}
101+
open func visit(_ node: ${node.name}) {}
104102
% end
105103
% end
106104

105+
open func shouldVisit(_ kind: SyntaxKind) -> Bool {
106+
return true
107+
}
108+
open func shouldVisit(_ kind: TokenKind) -> Bool {
109+
return true
110+
}
111+
107112
open func visit(_ token: TokenSyntax) {}
108113

109114
/// The function called before visiting the node and its descendents.
@@ -115,20 +120,80 @@ open class SyntaxVisitor {
115120
open func visitPost(_ node: Syntax) {}
116121

117122
public func visit(_ node: Syntax) {
118-
visitPre(node)
119-
defer { visitPost(node) }
120123
switch node.raw.kind {
121124
case .token: visit(node as! TokenSyntax)
122125
% for node in SYNTAX_NODES:
123126
% if is_visitable(node):
124127
case .${node.swift_syntax_kind}: visit(node as! ${node.name})
125128
% end
126129
% end
127-
default: visitChildren(node)
130+
default: return
131+
}
132+
}
133+
}
134+
135+
/// The raw syntax walker traverses the raw syntax tree to find
136+
/// node kinds the SyntaxVisitor is interested and feed these syntax nodes to
137+
/// SyntaxVisitor.
138+
/// By traversing the raw syntax tree, we avoid realizing syntax nodes that're
139+
/// not interesting to users' SyntaxVisitor.
140+
class RawSyntaxVisitor {
141+
private let visitor: SyntaxVisitor
142+
private let root: Syntax
143+
private var childIdxChain: [Int]
144+
145+
required init(_ visitor: SyntaxVisitor, _ root: Syntax) {
146+
self.visitor = visitor
147+
self.root = root
148+
self.childIdxChain = []
149+
}
150+
151+
convenience init(_ another: RawSyntaxVisitor, _ root: Syntax) {
152+
self.init(another.visitor, root)
153+
}
154+
155+
func shouldVisit(_ kind: SyntaxKind) -> Bool {
156+
return visitor.shouldVisit(kind)
157+
}
158+
159+
func shouldVisit(_ kind: TokenKind) -> Bool {
160+
return visitor.shouldVisit(kind)
161+
}
162+
163+
func initializeChildIdx() {
164+
childIdxChain.append(-1)
165+
}
166+
167+
func incrementLastChildIdx() {
168+
childIdxChain[childIdxChain.count - 1] += 1
169+
}
170+
171+
func removeLastChildIdx() {
172+
childIdxChain.removeLast()
173+
}
174+
175+
// The current raw syntax node is interesting for the user, so realize a
176+
// correponding syntax node and feed it into the visitor.
177+
func visitCurrent() -> Syntax {
178+
var node = root
179+
for id in childIdxChain {
180+
node = node.child(at: id)!
128181
}
182+
visitor.visitPre(node)
183+
visitor.visit(node)
184+
return node
129185
}
130186

131-
func visitChildren(_ node: Syntax) {
132-
node.children.forEach { visit($0) }
187+
// Called when we finished traversing the tree.
188+
func visitPost() {
189+
visitor.visitPost(root)
190+
}
191+
}
192+
193+
extension Syntax {
194+
public func walk(_ visitor: SyntaxVisitor) {
195+
196+
// Traverse the raw syntax tree by using the current syntax node as root.
197+
data.raw.accept(RawSyntaxVisitor(visitor, self))
133198
}
134199
}

0 commit comments

Comments
 (0)