Skip to content

Commit ad71928

Browse files
authored
Add leading comment support (#1550)
1 parent 4b555de commit ad71928

File tree

6 files changed

+431
-0
lines changed

6 files changed

+431
-0
lines changed

include/slang/syntax/SyntaxNode.h

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,36 @@ class SLANG_EXPORT SyntaxNode {
146146
/// Gets the number of (direct) children underneath this node in the tree.
147147
size_t getChildCount() const; // Note: implemented in AllSyntax.cpp
148148

149+
/// An iterator that walks through all tokens in a syntax node in order.
150+
class SLANG_EXPORT token_iterator : public iterator_facade<token_iterator> {
151+
public:
152+
token_iterator() : node(nullptr), childIndex(0), currentToken() {}
153+
154+
parsing::Token dereference() const { return currentToken; }
155+
156+
void increment();
157+
158+
bool equals(const token_iterator& other) const;
159+
160+
private:
161+
friend class SyntaxNode;
162+
163+
explicit token_iterator(const SyntaxNode* node);
164+
165+
void findNextToken();
166+
167+
const SyntaxNode* node;
168+
size_t childIndex;
169+
parsing::Token currentToken;
170+
SmallVector<std::pair<const SyntaxNode*, size_t>, 4> stack;
171+
};
172+
173+
/// Gets an iterator to the first token in this subtree.
174+
token_iterator tokens_begin() const { return token_iterator(this); }
175+
176+
/// Gets an iterator representing the end of tokens in this subtree.
177+
token_iterator tokens_end() const { return token_iterator(); }
178+
149179
/// Returns true if this syntax node is "equivalent" to the other provided
150180
/// syntax node. Equivalence here is determined by the entire subtrees having
151181
/// the same kinds of syntax nodes in the same order and all leaf tokens

include/slang/syntax/SyntaxPrinter.h

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,23 @@ class SLANG_EXPORT SyntaxPrinter {
4343
/// @return a reference to this object, to allow chaining additional method calls.
4444
SyntaxPrinter& print(const SyntaxNode& node);
4545

46+
/// Print the leading comments and trivia for the provided @a node.
47+
/// This will attempt to grab the leading trivia that is intended to provide info for this
48+
/// syntax node. This includes doc comments, but also other typical annotation styles. See
49+
/// SyntaxPrinterTests.cpp for more detail.
50+
/// @return a reference to this object, to allow chaining additional method calls.
51+
SyntaxPrinter& printLeadingComments(const SyntaxNode& node);
52+
53+
/// Print the provided @a node to the internal buffer, with the leading comments and trivia on
54+
/// the primary node. See @a printLeadingComments for detail on what is considered a leading
55+
/// comment.
56+
/// @return a reference to this object, to allow chaining additional method calls.
57+
SyntaxPrinter& printWithLeadingComments(const SyntaxNode& node);
58+
59+
/// Print the provided @a node to the internal buffer, excluding any leading trivia.
60+
/// @return a reference to this object, to allow chaining additional method calls.
61+
SyntaxPrinter& printExcludingLeadingComments(const SyntaxNode& node);
62+
4663
/// Print the provided @a tree to the internal buffer.
4764
/// @return a reference to this object, to allow chaining additional method calls.
4865
SyntaxPrinter& print(const SyntaxTree& tree);

source/syntax/SyntaxNode.cpp

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,63 @@ TokenOrSyntax SyntaxNode::getChild(size_t index) {
148148
return visit(visitor, index);
149149
}
150150

151+
SyntaxNode::token_iterator::token_iterator(const SyntaxNode* node) :
152+
node(node), childIndex(0), currentToken() {
153+
if (node)
154+
findNextToken();
155+
}
156+
157+
void SyntaxNode::token_iterator::increment() {
158+
if (!node)
159+
return;
160+
161+
// Move to the next child in the current node
162+
childIndex++;
163+
findNextToken();
164+
}
165+
166+
bool SyntaxNode::token_iterator::equals(const token_iterator& other) const {
167+
// begin iterator will have node set, end will have nullptr node
168+
return node == other.node && childIndex == other.childIndex;
169+
}
170+
171+
void SyntaxNode::token_iterator::findNextToken() {
172+
while (true) {
173+
// If we're out of children in the current node, pop from the stack
174+
if (childIndex >= node->getChildCount()) {
175+
if (stack.empty()) {
176+
// We're done iterating
177+
node = nullptr;
178+
childIndex = 0;
179+
return;
180+
}
181+
182+
// Pop the last saved state
183+
auto [parentNode, parentIndex] = stack.back();
184+
stack.pop_back();
185+
node = parentNode;
186+
childIndex = parentIndex + 1;
187+
continue;
188+
}
189+
190+
auto token = node->childToken(childIndex);
191+
if (token) {
192+
currentToken = token;
193+
return;
194+
}
195+
196+
auto childNode = node->childNode(childIndex);
197+
if (childNode) {
198+
stack.push_back({node, childIndex});
199+
node = childNode;
200+
childIndex = 0;
201+
}
202+
else {
203+
childIndex++;
204+
}
205+
}
206+
}
207+
151208
const SyntaxNode* SyntaxNode::childNode(size_t index) const {
152209
auto child = getChild(index);
153210
if (child.isToken())

source/syntax/SyntaxPrinter.cpp

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,12 @@
88
#include "slang/syntax/SyntaxPrinter.h"
99

1010
#include "slang/parsing/ParserMetadata.h"
11+
#include "slang/parsing/Token.h"
1112
#include "slang/syntax/SyntaxNode.h"
1213
#include "slang/syntax/SyntaxTree.h"
1314
#include "slang/text/SourceLocation.h"
1415
#include "slang/text/SourceManager.h"
16+
#include "slang/util/Util.h"
1517

1618
namespace slang::syntax {
1719

@@ -118,6 +120,82 @@ SyntaxPrinter& SyntaxPrinter::print(const SyntaxNode& node) {
118120
return *this;
119121
}
120122

123+
SyntaxPrinter& SyntaxPrinter::printLeadingComments(const SyntaxNode& node) {
124+
auto triviaSpan = node.getFirstToken().trivia();
125+
using Iterator = std::span<const Trivia>::iterator;
126+
std::optional<Iterator> lastComment;
127+
std::optional<Iterator> leadingCommentStart;
128+
129+
// Walk backwards through trivia until
130+
// - block comment
131+
// - double new line after seeing a comment
132+
// This misses leading trivia at first line, although that's typically for license/file
133+
auto findDocBoundary = [&]() {
134+
bool lastIsNewline = false;
135+
for (auto it = triviaSpan.rbegin(); it != triviaSpan.rend(); it++) {
136+
const auto& trivia = *it;
137+
switch (trivia.kind) {
138+
case TriviaKind::EndOfLine:
139+
if (lastIsNewline && lastComment) {
140+
// found a double newline after a comment, stop here
141+
return;
142+
}
143+
leadingCommentStart = lastComment;
144+
lastIsNewline = true;
145+
break;
146+
case TriviaKind::BlockComment:
147+
// the first block comment is the start
148+
leadingCommentStart = it.base() - 1;
149+
return;
150+
case TriviaKind::LineComment:
151+
lastComment = it.base() - 1;
152+
[[fallthrough]];
153+
default:
154+
lastIsNewline = false;
155+
}
156+
}
157+
};
158+
findDocBoundary();
159+
160+
if (leadingCommentStart) {
161+
for (auto it = *leadingCommentStart; it != triviaSpan.end(); it++) {
162+
print(*it);
163+
}
164+
}
165+
166+
return *this;
167+
}
168+
169+
SyntaxPrinter& SyntaxPrinter::printExcludingLeadingComments(const SyntaxNode& node) {
170+
if (!includeTrivia) {
171+
print(node);
172+
return *this;
173+
}
174+
175+
auto it = node.tokens_begin();
176+
auto end = node.tokens_end();
177+
178+
if (it == end)
179+
return *this;
180+
181+
// Print first token without leading trivia
182+
includeTrivia = false;
183+
print(*it);
184+
includeTrivia = true;
185+
186+
for (++it; it != end; ++it) {
187+
print(*it);
188+
}
189+
190+
return *this;
191+
}
192+
193+
SyntaxPrinter& SyntaxPrinter::printWithLeadingComments(const SyntaxNode& node) {
194+
printLeadingComments(node);
195+
printExcludingLeadingComments(node);
196+
return *this;
197+
}
198+
121199
SyntaxPrinter& SyntaxPrinter::print(const SyntaxTree& tree) {
122200
print(tree.root());
123201
if (tree.root().kind != SyntaxKind::CompilationUnit && tree.getMetadata().eofToken)

tests/unittests/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ add_executable(
3838
parsing/RewriterExpandTests.cpp
3939
parsing/VisitorTests.cpp
4040
parsing/StatementParsingTests.cpp
41+
parsing/SyntaxPrinterTests.cpp
4142
util/CommandLineTests.cpp
4243
util/IntervalMapTests.cpp
4344
util/NumericTests.cpp

0 commit comments

Comments
 (0)