Skip to content

Commit 926abee

Browse files
committed
Support for parsing headers in Doxygen \par commands
1 parent 8cc6a24 commit 926abee

File tree

7 files changed

+227
-7
lines changed

7 files changed

+227
-7
lines changed

clang/include/clang/AST/CommentCommandTraits.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,10 @@ struct CommandInfo {
8888
LLVM_PREFERRED_TYPE(bool)
8989
unsigned IsHeaderfileCommand : 1;
9090

91+
/// True if this is a \\par command.
92+
LLVM_PREFERRED_TYPE(bool)
93+
unsigned IsParCommand : 1;
94+
9195
/// True if we don't want to warn about this command being passed an empty
9296
/// paragraph. Meaningful only for block commands.
9397
LLVM_PREFERRED_TYPE(bool)

clang/include/clang/AST/CommentCommands.td

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ class Command<string name> {
1818
bit IsThrowsCommand = 0;
1919
bit IsDeprecatedCommand = 0;
2020
bit IsHeaderfileCommand = 0;
21+
bit IsParCommand = 0;
2122

2223
bit IsEmptyParagraphAllowed = 0;
2324

@@ -156,7 +157,7 @@ def Date : BlockCommand<"date">;
156157
def Invariant : BlockCommand<"invariant">;
157158
def Li : BlockCommand<"li">;
158159
def Note : BlockCommand<"note">;
159-
def Par : BlockCommand<"par">;
160+
def Par : BlockCommand<"par"> { let IsParCommand = 1; let NumArgs = 1; }
160161
def Post : BlockCommand<"post">;
161162
def Pre : BlockCommand<"pre">;
162163
def Remark : BlockCommand<"remark">;

clang/include/clang/AST/CommentParser.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,9 @@ class Parser {
105105
ArrayRef<Comment::Argument>
106106
parseThrowCommandArgs(TextTokenRetokenizer &Retokenizer, unsigned NumArgs);
107107

108+
ArrayRef<Comment::Argument>
109+
parseParCommandArgs(TextTokenRetokenizer &Retokenizer, unsigned NumArgs);
110+
108111
BlockCommandComment *parseBlockCommand();
109112
InlineCommandComment *parseInlineCommand();
110113

@@ -123,4 +126,3 @@ class Parser {
123126
} // end namespace clang
124127

125128
#endif
126-

clang/lib/AST/CommentParser.cpp

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -222,6 +222,63 @@ class TextTokenRetokenizer {
222222
return true;
223223
}
224224

225+
/// Check if this line starts with @par or \par
226+
bool startsWithParCommand() {
227+
unsigned Offset = 1;
228+
229+
/// Skip all whitespace characters at the beginning.
230+
/// This needs to backtrack because Pos has already advanced past the
231+
/// actual \par or @par command by the time this function is called.
232+
while (isWhitespace(*(Pos.BufferPtr - Offset)))
233+
Offset++;
234+
235+
/// Check if next four characters are \par or @par
236+
llvm::StringRef LineStart(Pos.BufferPtr - 5, 4);
237+
return LineStart.starts_with("\\par") || LineStart.starts_with("@par");
238+
}
239+
240+
/// Extract a par command argument-header.
241+
bool lexParHeading(Token &Tok) {
242+
if (isEnd())
243+
return false;
244+
245+
Position SavedPos = Pos;
246+
247+
consumeWhitespace();
248+
SmallString<32> WordText;
249+
const char *WordBegin = Pos.BufferPtr;
250+
SourceLocation Loc = getSourceLocation();
251+
252+
if (!startsWithParCommand())
253+
return false;
254+
255+
// Read until the end of this token, which is effectively the end of the
256+
// line This gets us the content of the par header, if there is one.
257+
while (!isEnd()) {
258+
WordText.push_back(peek());
259+
if (Pos.BufferPtr + 1 == Pos.BufferEnd) {
260+
consumeChar();
261+
break;
262+
} else {
263+
consumeChar();
264+
}
265+
}
266+
267+
const unsigned Length = WordText.size();
268+
if (Length == 0) {
269+
Pos = SavedPos;
270+
return false;
271+
}
272+
273+
char *TextPtr = Allocator.Allocate<char>(Length + 1);
274+
275+
memcpy(TextPtr, WordText.c_str(), Length + 1);
276+
StringRef Text = StringRef(TextPtr, Length);
277+
278+
formTokenWithChars(Tok, Loc, WordBegin, Length, Text);
279+
return true;
280+
}
281+
225282
/// Extract a word -- sequence of non-whitespace characters.
226283
bool lexWord(Token &Tok) {
227284
if (isEnd())
@@ -394,6 +451,23 @@ Parser::parseThrowCommandArgs(TextTokenRetokenizer &Retokenizer,
394451
return llvm::ArrayRef(Args, ParsedArgs);
395452
}
396453

454+
ArrayRef<Comment::Argument>
455+
Parser::parseParCommandArgs(TextTokenRetokenizer &Retokenizer,
456+
unsigned NumArgs) {
457+
auto *Args = new (Allocator.Allocate<Comment::Argument>(NumArgs))
458+
Comment::Argument[NumArgs];
459+
unsigned ParsedArgs = 0;
460+
Token Arg;
461+
462+
while (ParsedArgs < NumArgs && Retokenizer.lexParHeading(Arg)) {
463+
Args[ParsedArgs] = Comment::Argument{
464+
SourceRange(Arg.getLocation(), Arg.getEndLocation()), Arg.getText()};
465+
ParsedArgs++;
466+
}
467+
468+
return llvm::ArrayRef(Args, ParsedArgs);
469+
}
470+
397471
BlockCommandComment *Parser::parseBlockCommand() {
398472
assert(Tok.is(tok::backslash_command) || Tok.is(tok::at_command));
399473

@@ -449,6 +523,9 @@ BlockCommandComment *Parser::parseBlockCommand() {
449523
else if (Info->IsThrowsCommand)
450524
S.actOnBlockCommandArgs(
451525
BC, parseThrowCommandArgs(Retokenizer, Info->NumArgs));
526+
else if (Info->IsParCommand)
527+
S.actOnBlockCommandArgs(BC,
528+
parseParCommandArgs(Retokenizer, Info->NumArgs));
452529
else
453530
S.actOnBlockCommandArgs(BC, parseCommandArgs(Retokenizer, Info->NumArgs));
454531

clang/test/Index/comment-misc-tags.m

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -91,18 +91,16 @@ @interface IOCommandGate
9191

9292
struct Test {int filler;};
9393

94-
// CHECK: (CXComment_BlockCommand CommandName=[par]
94+
// CHECK: (CXComment_BlockCommand CommandName=[par] Arg[0]=User defined paragraph:
9595
// CHECK-NEXT: (CXComment_Paragraph
96-
// CHECK-NEXT: (CXComment_Text Text=[ User defined paragraph:] HasTrailingNewline)
9796
// CHECK-NEXT: (CXComment_Text Text=[ Contents of the paragraph.])))
9897
// CHECK: (CXComment_BlockCommand CommandName=[par]
9998
// CHECK-NEXT: (CXComment_Paragraph
100-
// CHECK-NEXT: (CXComment_Text Text=[ New paragraph under the same heading.])))
99+
// CHECK-NEXT: (CXComment_Text Text=[New paragraph under the same heading.])))
101100
// CHECK: (CXComment_BlockCommand CommandName=[note]
102101
// CHECK-NEXT: (CXComment_Paragraph
103102
// CHECK-NEXT: (CXComment_Text Text=[ This note consists of two paragraphs.] HasTrailingNewline)
104103
// CHECK-NEXT: (CXComment_Text Text=[ This is the first paragraph.])))
105104
// CHECK: (CXComment_BlockCommand CommandName=[par]
106105
// CHECK-NEXT: (CXComment_Paragraph
107-
// CHECK-NEXT: (CXComment_Text Text=[ And this is the second paragraph.])))
108-
106+
// CHECK-NEXT: (CXComment_Text Text=[And this is the second paragraph.])))

clang/unittests/AST/CommentParser.cpp

Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1639,6 +1639,143 @@ TEST_F(CommentParserTest, ThrowsCommandHasArg9) {
16391639
}
16401640
}
16411641

1642+
TEST_F(CommentParserTest, ParCommandHasArg1) {
1643+
const char *Sources[] = {
1644+
"/// @par Paragraph header:", "/// @par Paragraph header:\n",
1645+
"/// @par Paragraph header:\r\n", "/// @par Paragraph header:\n\r",
1646+
"/** @par Paragraph header:*/",
1647+
};
1648+
1649+
for (size_t i = 0, e = std::size(Sources); i != e; i++) {
1650+
FullComment *FC = parseString(Sources[i]);
1651+
ASSERT_TRUE(HasChildCount(FC, 2));
1652+
1653+
ASSERT_TRUE(HasParagraphCommentAt(FC, 0, " "));
1654+
{
1655+
BlockCommandComment *BCC;
1656+
ParagraphComment *PC;
1657+
ASSERT_TRUE(HasBlockCommandAt(FC, Traits, 1, BCC, "par", PC));
1658+
ASSERT_TRUE(HasChildCount(PC, 0));
1659+
ASSERT_TRUE(BCC->getNumArgs() == 1);
1660+
ASSERT_TRUE(BCC->getArgText(0) == "Paragraph header:");
1661+
}
1662+
}
1663+
}
1664+
1665+
TEST_F(CommentParserTest, ParCommandHasArg2) {
1666+
const char *Sources[] = {
1667+
"/// @par Paragraph header: ", "/// @par Paragraph header: \n",
1668+
"/// @par Paragraph header: \r\n", "/// @par Paragraph header: \n\r",
1669+
"/** @par Paragraph header: */",
1670+
};
1671+
1672+
for (size_t i = 0, e = std::size(Sources); i != e; i++) {
1673+
FullComment *FC = parseString(Sources[i]);
1674+
ASSERT_TRUE(HasChildCount(FC, 2));
1675+
1676+
ASSERT_TRUE(HasParagraphCommentAt(FC, 0, " "));
1677+
{
1678+
BlockCommandComment *BCC;
1679+
ParagraphComment *PC;
1680+
ASSERT_TRUE(HasBlockCommandAt(FC, Traits, 1, BCC, "par", PC));
1681+
ASSERT_TRUE(HasChildCount(PC, 0));
1682+
ASSERT_TRUE(BCC->getNumArgs() == 1);
1683+
ASSERT_TRUE(BCC->getArgText(0) == "Paragraph header: ");
1684+
}
1685+
}
1686+
}
1687+
1688+
TEST_F(CommentParserTest, ParCommandHasArg3) {
1689+
const char *Sources[] = {
1690+
("/// @par Paragraph header:\n"
1691+
"/// Paragraph body"),
1692+
("/// @par Paragraph header:\r\n"
1693+
"/// Paragraph body"),
1694+
("/// @par Paragraph header:\n\r"
1695+
"/// Paragraph body"),
1696+
};
1697+
1698+
for (size_t i = 0, e = std::size(Sources); i != e; i++) {
1699+
FullComment *FC = parseString(Sources[i]);
1700+
ASSERT_TRUE(HasChildCount(FC, 2));
1701+
1702+
ASSERT_TRUE(HasParagraphCommentAt(FC, 0, " "));
1703+
{
1704+
BlockCommandComment *BCC;
1705+
ParagraphComment *PC;
1706+
TextComment *TC;
1707+
ASSERT_TRUE(HasBlockCommandAt(FC, Traits, 1, BCC, "par", PC));
1708+
ASSERT_TRUE(HasChildCount(PC, 1));
1709+
ASSERT_TRUE(BCC->getNumArgs() == 1);
1710+
ASSERT_TRUE(BCC->getArgText(0) == "Paragraph header:");
1711+
ASSERT_TRUE(GetChildAt(PC, 0, TC));
1712+
ASSERT_TRUE(TC->getText() == " Paragraph body");
1713+
}
1714+
}
1715+
}
1716+
1717+
TEST_F(CommentParserTest, ParCommandHasArg4) {
1718+
const char *Sources[] = {
1719+
("/// @par Paragraph header:\n"
1720+
"/// Paragraph body1\n"
1721+
"/// Paragraph body2"),
1722+
("/// @par Paragraph header:\r\n"
1723+
"/// Paragraph body1\n"
1724+
"/// Paragraph body2"),
1725+
("/// @par Paragraph header:\n\r"
1726+
"/// Paragraph body1\n"
1727+
"/// Paragraph body2"),
1728+
};
1729+
1730+
for (size_t i = 0, e = std::size(Sources); i != e; i++) {
1731+
FullComment *FC = parseString(Sources[i]);
1732+
ASSERT_TRUE(HasChildCount(FC, 2));
1733+
1734+
ASSERT_TRUE(HasParagraphCommentAt(FC, 0, " "));
1735+
{
1736+
BlockCommandComment *BCC;
1737+
ParagraphComment *PC;
1738+
TextComment *TC;
1739+
ASSERT_TRUE(HasBlockCommandAt(FC, Traits, 1, BCC, "par", PC));
1740+
ASSERT_TRUE(HasChildCount(PC, 2));
1741+
ASSERT_TRUE(BCC->getNumArgs() == 1);
1742+
ASSERT_TRUE(BCC->getArgText(0) == "Paragraph header:");
1743+
ASSERT_TRUE(GetChildAt(PC, 0, TC));
1744+
ASSERT_TRUE(TC->getText() == " Paragraph body1");
1745+
ASSERT_TRUE(GetChildAt(PC, 1, TC));
1746+
ASSERT_TRUE(TC->getText() == " Paragraph body2");
1747+
}
1748+
}
1749+
}
1750+
1751+
TEST_F(CommentParserTest, ParCommandHasArg5) {
1752+
const char *Sources[] = {
1753+
("/// @par \n"
1754+
"/// Paragraphs with no text before newline have no heading"),
1755+
("/// @par \r\n"
1756+
"/// Paragraphs with no text before newline have no heading"),
1757+
("/// @par \n\r"
1758+
"/// Paragraphs with no text before newline have no heading"),
1759+
};
1760+
1761+
for (size_t i = 0, e = std::size(Sources); i != e; i++) {
1762+
FullComment *FC = parseString(Sources[i]);
1763+
ASSERT_TRUE(HasChildCount(FC, 2));
1764+
1765+
ASSERT_TRUE(HasParagraphCommentAt(FC, 0, " "));
1766+
{
1767+
BlockCommandComment *BCC;
1768+
ParagraphComment *PC;
1769+
TextComment *TC;
1770+
ASSERT_TRUE(HasBlockCommandAt(FC, Traits, 1, BCC, "par", PC));
1771+
ASSERT_TRUE(HasChildCount(PC, 1));
1772+
ASSERT_TRUE(BCC->getNumArgs() == 0);
1773+
ASSERT_TRUE(GetChildAt(PC, 0, TC));
1774+
ASSERT_TRUE(TC->getText() ==
1775+
"Paragraphs with no text before newline have no heading");
1776+
}
1777+
}
1778+
}
16421779

16431780
} // unnamed namespace
16441781

clang/utils/TableGen/ClangCommentCommandInfoEmitter.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ void clang::EmitClangCommentCommandInfo(RecordKeeper &Records,
4444
<< Tag.getValueAsBit("IsThrowsCommand") << ", "
4545
<< Tag.getValueAsBit("IsDeprecatedCommand") << ", "
4646
<< Tag.getValueAsBit("IsHeaderfileCommand") << ", "
47+
<< Tag.getValueAsBit("IsParCommand") << ", "
4748
<< Tag.getValueAsBit("IsEmptyParagraphAllowed") << ", "
4849
<< Tag.getValueAsBit("IsVerbatimBlockCommand") << ", "
4950
<< Tag.getValueAsBit("IsVerbatimBlockEndCommand") << ", "

0 commit comments

Comments
 (0)