Skip to content

Commit 71e749e

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 71e749e

File tree

3 files changed

+104
-9
lines changed

3 files changed

+104
-9
lines changed

Sources/SwiftSyntax/RawSyntax.swift

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -466,3 +466,34 @@ extension SyntaxNodeId: ByteTreeScalarDecodable {
466466
return SyntaxNodeId(rawValue: UInt(rawValue))
467467
}
468468
}
469+
470+
extension RawSyntax {
471+
func accept(_ visitor: RawSyntaxVisitor) {
472+
defer { visitor.moveUp() }
473+
guard isPresent else { return }
474+
switch data {
475+
case .node(let kind,let layout):
476+
let shouldVisit = visitor.shouldVisit(kind)
477+
if shouldVisit {
478+
479+
// Visit this node realizes a syntax node.
480+
visitor.visitCurrent()
481+
}
482+
483+
for (offset, element) in layout.enumerated() {
484+
485+
// Teach the visitor to navigate to this child.
486+
visitor.addChildIdx(offset)
487+
element?.accept(visitor)
488+
}
489+
if shouldVisit {
490+
visitor.visitPost()
491+
}
492+
case .token(let kind, _, _):
493+
if visitor.shouldVisit(kind) {
494+
visitor.visitCurrent()
495+
visitor.visitPost()
496+
}
497+
}
498+
}
499+
}

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: 72 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,79 @@ 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
128131
}
129132
}
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 var childIdxChain: [Int]
143+
private var nodeChain: [Syntax]
144+
145+
required init(_ visitor: SyntaxVisitor, _ root: Syntax) {
146+
self.visitor = visitor
147+
self.nodeChain = [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 addChildIdx(_ idx: Int) {
164+
childIdxChain.append(idx)
165+
}
166+
167+
func moveUp() {
168+
if childIdxChain.isEmpty {
169+
nodeChain.removeLast()
170+
} else {
171+
childIdxChain.removeLast()
172+
}
173+
}
174+
175+
func visitPost() {
176+
visitor.visitPost(nodeChain.last!)
177+
}
178+
179+
// The current raw syntax node is interesting for the user, so realize a
180+
// correponding syntax node and feed it into the visitor.
181+
func visitCurrent() {
182+
for id in childIdxChain {
183+
nodeChain.append(nodeChain.last!.child(at: id)!)
184+
}
185+
childIdxChain.removeAll(keepingCapacity: true)
186+
let node = nodeChain.last!
187+
visitor.visitPre(node)
188+
visitor.visit(node)
189+
}
190+
}
191+
192+
extension Syntax {
193+
public func walk(_ visitor: SyntaxVisitor) {
130194

131-
func visitChildren(_ node: Syntax) {
132-
node.children.forEach { visit($0) }
195+
// Traverse the raw syntax tree by using the current syntax node as root.
196+
data.raw.accept(RawSyntaxVisitor(visitor, self))
133197
}
134198
}

0 commit comments

Comments
 (0)