-
Notifications
You must be signed in to change notification settings - Fork 440
Reimplement SyntaxVisitor to improve visiting performance. #32
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
b48aabe
b445aa4
3bc84d2
f174c4d
b192307
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -98,12 +98,23 @@ open class SyntaxVisitor { | |
public init() {} | ||
% for node in SYNTAX_NODES: | ||
% if is_visitable(node): | ||
open func visit(_ node: ${node.name}) { | ||
visitChildren(node) | ||
open func visit(_ node: ${node.name}) -> Bool { | ||
return true | ||
} | ||
% end | ||
% end | ||
|
||
open func visit(_ node: UnknownSyntax) -> Bool { | ||
return true | ||
} | ||
|
||
open func shouldVisit(_ kind: SyntaxKind) -> Bool { | ||
return true | ||
} | ||
open func shouldVisit(_ kind: TokenKind) -> Bool { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nitpick: Missing empty line between functions. |
||
return true | ||
} | ||
|
||
open func visit(_ token: TokenSyntax) {} | ||
|
||
/// The function called before visiting the node and its descendents. | ||
|
@@ -114,21 +125,109 @@ open class SyntaxVisitor { | |
/// - node: the node we just finished visiting. | ||
open func visitPost(_ node: Syntax) {} | ||
|
||
public func visit(_ node: Syntax) { | ||
visitPre(node) | ||
defer { visitPost(node) } | ||
public func visit(_ node: Syntax) -> Bool { | ||
switch node.raw.kind { | ||
case .token: visit(node as! TokenSyntax) | ||
% for node in SYNTAX_NODES: | ||
% if is_visitable(node): | ||
case .${node.swift_syntax_kind}: visit(node as! ${node.name}) | ||
case .${node.swift_syntax_kind}: return visit(node as! ${node.name}) | ||
% end | ||
% end | ||
default: visitChildren(node) | ||
case .unknown: return visit(node as! UnknownSyntax) | ||
default: break | ||
} | ||
return false | ||
} | ||
} | ||
|
||
|
||
/// A wrapper over Syntax. A syntax node is only realized when explicitly asked; | ||
/// otherwise the node is represented as a child index list from a realized | ||
/// ancestor. | ||
class PendingSyntaxNode { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can’t we just have one indirect enum instead of the class wrapping the enum? |
||
let parent: PendingSyntaxNode? | ||
private var kind: PendingSyntaxNodeKind | ||
|
||
private enum PendingSyntaxNodeKind { | ||
/// We already have a `Syntax` node realised for this node | ||
case realized(node: Syntax) | ||
/// This node does not have a `Syntax` node instantiated yet. If needed, we | ||
/// need to compute it from its parent RawSyntax node | ||
case virtual(index: Int) | ||
} | ||
|
||
var node: Syntax { | ||
switch kind { | ||
case .realized(let node): | ||
return node | ||
case .virtual(let index): | ||
let _node = parent!.node.child(at: index)! | ||
kind = .realized(node: _node) | ||
return _node | ||
} | ||
} | ||
|
||
init(_ root: Syntax) { | ||
self.parent = nil | ||
self.kind = .realized(node: root) | ||
} | ||
|
||
init(_ parent: PendingSyntaxNode, _ idx: Int) { | ||
self.parent = parent | ||
self.kind = .virtual(index: idx) | ||
} | ||
} | ||
|
||
|
||
/// The raw syntax walker traverses the raw syntax tree to find | ||
/// node kinds the SyntaxVisitor is interested and feed these syntax nodes to | ||
/// SyntaxVisitor. | ||
/// By traversing the raw syntax tree, we avoid realizing syntax nodes that're | ||
/// not interesting to users' SyntaxVisitor. | ||
class RawSyntaxVisitor { | ||
private let visitor: SyntaxVisitor | ||
private var currentNode: PendingSyntaxNode? | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Wouldn‘t it be cleaner to habe |
||
|
||
required init(_ visitor: SyntaxVisitor, _ root: Syntax) { | ||
self.visitor = visitor | ||
self.currentNode = PendingSyntaxNode(root) | ||
} | ||
|
||
func shouldVisit(_ kind: SyntaxKind) -> Bool { | ||
return visitor.shouldVisit(kind) | ||
} | ||
|
||
func shouldVisit(_ kind: TokenKind) -> Bool { | ||
return visitor.shouldVisit(kind) | ||
} | ||
|
||
func addChildIdx(_ idx: Int) { | ||
currentNode = PendingSyntaxNode(currentNode!, idx) | ||
} | ||
|
||
func moveUp() { | ||
currentNode = currentNode!.parent | ||
} | ||
|
||
func visitPre() { | ||
visitor.visitPre(currentNode!.node) | ||
} | ||
|
||
func visitPost() { | ||
visitor.visitPost(currentNode!.node) | ||
} | ||
|
||
// The current raw syntax node is interesting for the user, so realize a | ||
// correponding syntax node and feed it into the visitor. | ||
func visit() -> Bool { | ||
return visitor.visit(currentNode!.node) | ||
} | ||
} | ||
|
||
extension Syntax { | ||
public func walk(_ visitor: SyntaxVisitor) { | ||
|
||
func visitChildren(_ node: Syntax) { | ||
node.children.forEach { visit($0) } | ||
// Traverse the raw syntax tree by using the current syntax node as root. | ||
data.raw.accept(RawSyntaxVisitor(visitor, self)) | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we add a doc comment specifying what the return value does? And: Do we ever return false here?