Skip to content

Commit de11883

Browse files
authored
Merge pull request #32 from nkcsgexi/wip-visitor
Reimplement SyntaxVisitor to improve visiting performance.
2 parents 41fdeb3 + b192307 commit de11883

9 files changed

+168
-30
lines changed

Sources/SwiftSyntax/RawSyntax.swift

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -466,3 +466,37 @@ 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+
var visitChildren = true
478+
if shouldVisit {
479+
// Visit this node realizes a syntax node.
480+
visitor.visitPre()
481+
visitChildren = visitor.visit()
482+
}
483+
if visitChildren {
484+
for (offset, element) in layout.enumerated() {
485+
guard let element = element else { continue }
486+
// Teach the visitor to navigate to this child.
487+
visitor.addChildIdx(offset)
488+
element.accept(visitor)
489+
}
490+
}
491+
if shouldVisit {
492+
visitor.visitPost()
493+
}
494+
case .token(let kind, _, _):
495+
if visitor.shouldVisit(kind) {
496+
visitor.visitPre()
497+
_ = visitor.visit()
498+
visitor.visitPost()
499+
}
500+
}
501+
}
502+
}

Sources/SwiftSyntax/SyntaxClassifier.swift.gyb

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ fileprivate class _SyntaxClassifier: SyntaxVisitor {
5050
force: Bool = false
5151
) {
5252
contextStack.append((classification: classification, force: force))
53-
visit(node)
53+
node.walk(self)
5454
contextStack.removeLast()
5555
}
5656

@@ -71,6 +71,7 @@ fileprivate class _SyntaxClassifier: SyntaxVisitor {
7171
}
7272

7373
override func visit(_ token: TokenSyntax) {
74+
assert(token.isPresent)
7475
// FIXME: We need to come up with some way in which the SyntaxClassifier can
7576
// classify trivia (i.e. comments). In particular we need to be able to
7677
// look into the comments to find things like URLs or keywords like MARK.
@@ -94,12 +95,12 @@ fileprivate class _SyntaxClassifier: SyntaxVisitor {
9495

9596
% for node in SYNTAX_NODES:
9697
% if is_visitable(node):
97-
override func visit(_ node: ${node.name}) {
98+
override func visit(_ node: ${node.name}) -> Bool {
9899
if skipNodeIds.contains(node.raw.id) {
99-
return
100+
return false
100101
}
101102
% if node.is_unknown() or node.is_syntax_collection():
102-
super.visit(node)
103+
return true
103104
% else:
104105
% for child in node.children:
105106
% if child.is_optional:
@@ -109,7 +110,7 @@ fileprivate class _SyntaxClassifier: SyntaxVisitor {
109110
classification: .${child.classification.swift_name},
110111
force: ${"true" if child.force_classification else "false"})
111112
% else:
112-
visit(${child.swift_name})
113+
${child.swift_name}.walk(self)
113114
% end
114115
}
115116
% else:
@@ -118,12 +119,12 @@ fileprivate class _SyntaxClassifier: SyntaxVisitor {
118119
classification: .${child.classification.swift_name},
119120
force: ${"true" if child.force_classification else "false"})
120121
% else:
121-
visit(node.${child.swift_name})
122+
node.${child.swift_name}.walk(self)
122123
% end
123124
% end
124125
% end
126+
return false
125127
% end
126-
127128
}
128129
% end
129130
% end
@@ -139,7 +140,7 @@ public enum SyntaxClassifier {
139140
) -> [TokenSyntax: SyntaxClassification] {
140141
let classifier = _SyntaxClassifier()
141142
classifier.skipNodeIds = skipNodes
142-
classifier.visit(syntaxTree)
143+
syntaxTree.walk(classifier)
143144
return classifier.classifications
144145
}
145146
}

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: 108 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -98,12 +98,23 @@ 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)
101+
open func visit(_ node: ${node.name}) -> Bool {
102+
return true
103103
}
104104
% end
105105
% end
106106

107+
open func visit(_ node: UnknownSyntax) -> Bool {
108+
return true
109+
}
110+
111+
open func shouldVisit(_ kind: SyntaxKind) -> Bool {
112+
return true
113+
}
114+
open func shouldVisit(_ kind: TokenKind) -> Bool {
115+
return true
116+
}
117+
107118
open func visit(_ token: TokenSyntax) {}
108119

109120
/// The function called before visiting the node and its descendents.
@@ -114,21 +125,109 @@ open class SyntaxVisitor {
114125
/// - node: the node we just finished visiting.
115126
open func visitPost(_ node: Syntax) {}
116127

117-
public func visit(_ node: Syntax) {
118-
visitPre(node)
119-
defer { visitPost(node) }
128+
public func visit(_ node: Syntax) -> Bool {
120129
switch node.raw.kind {
121130
case .token: visit(node as! TokenSyntax)
122131
% for node in SYNTAX_NODES:
123132
% if is_visitable(node):
124-
case .${node.swift_syntax_kind}: visit(node as! ${node.name})
133+
case .${node.swift_syntax_kind}: return visit(node as! ${node.name})
125134
% end
126135
% end
127-
default: visitChildren(node)
136+
case .unknown: return visit(node as! UnknownSyntax)
137+
default: break
128138
}
139+
return false
129140
}
141+
}
142+
143+
144+
/// A wrapper over Syntax. A syntax node is only realized when explicitly asked;
145+
/// otherwise the node is represented as a child index list from a realized
146+
/// ancestor.
147+
class PendingSyntaxNode {
148+
let parent: PendingSyntaxNode?
149+
private var kind: PendingSyntaxNodeKind
150+
151+
private enum PendingSyntaxNodeKind {
152+
/// We already have a `Syntax` node realised for this node
153+
case realized(node: Syntax)
154+
/// This node does not have a `Syntax` node instantiated yet. If needed, we
155+
/// need to compute it from its parent RawSyntax node
156+
case virtual(index: Int)
157+
}
158+
159+
var node: Syntax {
160+
switch kind {
161+
case .realized(let node):
162+
return node
163+
case .virtual(let index):
164+
let _node = parent!.node.child(at: index)!
165+
kind = .realized(node: _node)
166+
return _node
167+
}
168+
}
169+
170+
init(_ root: Syntax) {
171+
self.parent = nil
172+
self.kind = .realized(node: root)
173+
}
174+
175+
init(_ parent: PendingSyntaxNode, _ idx: Int) {
176+
self.parent = parent
177+
self.kind = .virtual(index: idx)
178+
}
179+
}
180+
181+
182+
/// The raw syntax walker traverses the raw syntax tree to find
183+
/// node kinds the SyntaxVisitor is interested and feed these syntax nodes to
184+
/// SyntaxVisitor.
185+
/// By traversing the raw syntax tree, we avoid realizing syntax nodes that're
186+
/// not interesting to users' SyntaxVisitor.
187+
class RawSyntaxVisitor {
188+
private let visitor: SyntaxVisitor
189+
private var currentNode: PendingSyntaxNode?
190+
191+
required init(_ visitor: SyntaxVisitor, _ root: Syntax) {
192+
self.visitor = visitor
193+
self.currentNode = PendingSyntaxNode(root)
194+
}
195+
196+
func shouldVisit(_ kind: SyntaxKind) -> Bool {
197+
return visitor.shouldVisit(kind)
198+
}
199+
200+
func shouldVisit(_ kind: TokenKind) -> Bool {
201+
return visitor.shouldVisit(kind)
202+
}
203+
204+
func addChildIdx(_ idx: Int) {
205+
currentNode = PendingSyntaxNode(currentNode!, idx)
206+
}
207+
208+
func moveUp() {
209+
currentNode = currentNode!.parent
210+
}
211+
212+
func visitPre() {
213+
visitor.visitPre(currentNode!.node)
214+
}
215+
216+
func visitPost() {
217+
visitor.visitPost(currentNode!.node)
218+
}
219+
220+
// The current raw syntax node is interesting for the user, so realize a
221+
// correponding syntax node and feed it into the visitor.
222+
func visit() -> Bool {
223+
return visitor.visit(currentNode!.node)
224+
}
225+
}
226+
227+
extension Syntax {
228+
public func walk(_ visitor: SyntaxVisitor) {
130229

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

Sources/lit-test-helper/ClassifiedSyntaxTreePrinter.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ class ClassifiedSyntaxTreePrinter: SyntaxVisitor {
5252

5353
func print(tree: SourceFileSyntax) -> String {
5454
result = ""
55-
visit(tree)
55+
tree.walk(self)
5656
// Emit the last closing tag
5757
recordCurrentClassification(.none)
5858
return result

Tests/SwiftSyntaxTest/AbsolutePosition.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ public class AbsolutePositionTestCase: XCTestCase {
6969
node.position.utf8Offset + node.leadingTrivia.byteSize)
7070
}
7171
}
72-
Visitor().visit(parsed)
72+
parsed.walk(Visitor())
7373
}())
7474
}
7575

Tests/SwiftSyntaxTest/DiagnosticTest.swift

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -76,19 +76,20 @@ public class DiagnosticTestCase: XCTestCase {
7676
self.url = url
7777
self.engine = engine
7878
}
79-
override func visit(_ function: FunctionDeclSyntax) {
79+
override func visit(_ function: FunctionDeclSyntax) -> Bool {
8080
let startLoc = function.identifier.startLocation(in: url)
8181
let endLoc = function.endLocation(in: url)
8282
engine.diagnose(.badFunction(function.identifier), location: startLoc) {
8383
$0.highlight(function.identifier.sourceRange(in: self.url))
8484
}
8585
engine.diagnose(.endOfFunction(function.identifier), location: endLoc)
86+
return true
8687
}
8788
}
8889

8990
XCTAssertNoThrow(try {
9091
let file = try SyntaxTreeParser.parse(url)
91-
Visitor(url: url, engine: engine).visit(file)
92+
file.walk(Visitor(url: url, engine: engine))
9293
}())
9394

9495
XCTAssertEqual(6, engine.diagnostics.count)

Tests/SwiftSyntaxTest/VisitorTest.swift

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,16 +13,16 @@ public class SyntaxVisitorTestCase: XCTestCase {
1313
public func testBasic() {
1414
class FuncCounter: SyntaxVisitor {
1515
var funcCount = 0
16-
override func visit(_ node: FunctionDeclSyntax) {
16+
override func visit(_ node: FunctionDeclSyntax) -> Bool {
1717
funcCount += 1
18-
super.visit(node)
18+
return true
1919
}
2020
}
2121
XCTAssertNoThrow(try {
2222
let parsed = try SyntaxTreeParser.parse(getInput("visitor.swift"))
2323
let counter = FuncCounter()
2424
let hashBefore = parsed.hashValue
25-
counter.visit(parsed)
25+
parsed.walk(counter)
2626
XCTAssertEqual(counter.funcCount, 3)
2727
XCTAssertEqual(hashBefore, parsed.hashValue)
2828
}())
@@ -70,16 +70,16 @@ public class SyntaxVisitorTestCase: XCTestCase {
7070
class VisitCollections: SyntaxVisitor {
7171
var numberOfCodeBlockItems = 0
7272

73-
override func visit(_ items: CodeBlockItemListSyntax) {
73+
override func visit(_ items: CodeBlockItemListSyntax) -> Bool {
7474
numberOfCodeBlockItems += items.count
75-
super.visit(items)
75+
return true
7676
}
7777
}
7878

7979
XCTAssertNoThrow(try {
8080
let parsed = try SyntaxTreeParser.parse(getInput("nested-blocks.swift"))
8181
let visitor = VisitCollections()
82-
visitor.visit(parsed)
82+
parsed.walk(visitor)
8383
XCTAssertEqual(4, visitor.numberOfCodeBlockItems)
8484
}())
8585
}

update-toolchain.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,8 +43,6 @@ def install(swiftc_exec, build_dir, dylib_dir, module_dir):
4343
'--build-dir', build_dir, '-v'])
4444

4545
def main():
46-
if not os.geteuid() == 0:
47-
fatal_error("\n!!!!!!!!Run this script with root access!!!!!!!!\n")
4846
parser = argparse.ArgumentParser(
4947
formatter_class=argparse.RawDescriptionHelpFormatter,
5048
description='''
@@ -61,6 +59,11 @@ def main():
6159
''')
6260

6361
args = parser.parse_args(sys.argv[1:])
62+
if not args.toolchain_dir:
63+
fatal_error("Need to specify --toolchain-dir")
64+
if not os.geteuid() == 0:
65+
fatal_error("\n!!!!!!!!Run this script with root access!!!!!!!!\n")
66+
6467
build_dir = tempfile.mkdtemp()
6568
swiftc_exec = args.toolchain_dir + '/usr/bin/swiftc'
6669
dylib_dir = args.toolchain_dir + '/usr/lib/swift/macosx'

0 commit comments

Comments
 (0)