Skip to content

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

Merged
merged 5 commits into from
Nov 12, 2018

Conversation

nkcsgexi
Copy link
Contributor

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.

@nkcsgexi
Copy link
Contributor Author

@ahoppen does this approach make sense to you?

@nkcsgexi nkcsgexi force-pushed the wip-visitor branch 2 times, most recently from 71e749e to 919983b Compare November 11, 2018 00:13
Copy link
Member

@ahoppen ahoppen left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great PR overall. I knew this was a bottleneck for quite some time. Thanks for tackling this. I would like to get rid of childIdxChain and nodeChain and represent those by a recursive enum which I believe would make the design a lot easier to understand. Comments for those inline

class RawSyntaxVisitor {
private let visitor: SyntaxVisitor
private var childIdxChain: [Int]
private var nodeChain: [Syntax]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't really like the idea of having those two data structures that together form the path to the current node. Couldn't we define an enum like this (names may be changed)

enum SyntaxTreeNode {
  /// 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(parent: SyntaxTreePathNode, index: Int)
}

Then have one private var currentNode: SyntaxTreeNode. Then, when moving down, we just need to wrap the current currentNode in one more level of virtual, moving up needs to be a switch on the current currentNode. If it is a virtual node, we just need to reference its parent, if it is realized, we can use node.parent.

Realising a node is then just a recursive operation on SyntaxTreeNode:

enum SyntaxTreeNode {
  // …
  func realize() -> Syntax {
    switch self {
    case .realized(let node):
      return node
    case .virtual(let parent, let index):
      return parent.realized().child(at: index)
    }
  }
}

I think that should make the entire visitor a lot simpler to understand.

childIdxChain.removeAll(keepingCapacity: true)
let node = nodeChain.last!
visitor.visitPre(node)
visitor.visit(node)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would like to either have the responsibility of calling visitPre and calling visitPost in RawSyntax or RawSyntaxVisitor, not spread across them. If it's not possible to call visitPost in here (which I think it isn't), I would also move the call to visitPre to RawSyntax.accept.

self.childIdxChain = []
}

convenience init(_ another: RawSyntaxVisitor, _ root: Syntax) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need this constructor anywhere?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, i'll remove

case .node(let kind,let layout):
let shouldVisit = visitor.shouldVisit(kind)
if shouldVisit {

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nitpick: There is a spare empty line here for my taste

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.
@nkcsgexi
Copy link
Contributor Author

@swift-ci please test

@swift-ci
Copy link
Contributor

Build failed
Swift Test OS X Platform
Git Sha - f8621ee

@nkcsgexi nkcsgexi merged commit de11883 into swiftlang:master Nov 12, 2018
Copy link
Member

@ahoppen ahoppen left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some post-merge review comments inline. Nothing urgent, I just think we could make the code still a bit cleaner.

@@ -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 {
Copy link
Member

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?

/// 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 {
Copy link
Member

Choose a reason for hiding this comment

The 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?

open func shouldVisit(_ kind: SyntaxKind) -> Bool {
return true
}
open func shouldVisit(_ kind: TokenKind) -> Bool {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nitpick: Missing empty line between functions.

/// not interesting to users' SyntaxVisitor.
class RawSyntaxVisitor {
private let visitor: SyntaxVisitor
private var currentNode: PendingSyntaxNode?
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wouldn‘t it be cleaner to habe currentNode be non-optional and make moveUp a no-op when we are already at the root? Or make this an IUO, because we always force unwrap it anyway and I can’t think of any reason where checking it for nil would make sense.

adevress pushed a commit to adevress/swift-syntax that referenced this pull request Jan 14, 2024
Migrate leading trivia when adding an access-level modifier.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants