Path: blob/main/contrib/llvm-project/clang/lib/AST/CommentParser.cpp
35260 views
//===--- CommentParser.cpp - Doxygen comment parser -----------------------===//1//2// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.3// See https://llvm.org/LICENSE.txt for license information.4// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception5//6//===----------------------------------------------------------------------===//78#include "clang/AST/CommentParser.h"9#include "clang/AST/CommentCommandTraits.h"10#include "clang/AST/CommentDiagnostic.h"11#include "clang/AST/CommentSema.h"12#include "clang/Basic/CharInfo.h"13#include "clang/Basic/SourceManager.h"14#include "llvm/Support/ErrorHandling.h"1516namespace clang {1718static inline bool isWhitespace(llvm::StringRef S) {19for (StringRef::const_iterator I = S.begin(), E = S.end(); I != E; ++I) {20if (!isWhitespace(*I))21return false;22}23return true;24}2526namespace comments {2728/// Re-lexes a sequence of tok::text tokens.29class TextTokenRetokenizer {30llvm::BumpPtrAllocator &Allocator;31Parser &P;3233/// This flag is set when there are no more tokens we can fetch from lexer.34bool NoMoreInterestingTokens;3536/// Token buffer: tokens we have processed and lookahead.37SmallVector<Token, 16> Toks;3839/// A position in \c Toks.40struct Position {41const char *BufferStart;42const char *BufferEnd;43const char *BufferPtr;44SourceLocation BufferStartLoc;45unsigned CurToken;46};4748/// Current position in Toks.49Position Pos;5051bool isEnd() const {52return Pos.CurToken >= Toks.size();53}5455/// Sets up the buffer pointers to point to current token.56void setupBuffer() {57assert(!isEnd());58const Token &Tok = Toks[Pos.CurToken];5960Pos.BufferStart = Tok.getText().begin();61Pos.BufferEnd = Tok.getText().end();62Pos.BufferPtr = Pos.BufferStart;63Pos.BufferStartLoc = Tok.getLocation();64}6566SourceLocation getSourceLocation() const {67const unsigned CharNo = Pos.BufferPtr - Pos.BufferStart;68return Pos.BufferStartLoc.getLocWithOffset(CharNo);69}7071char peek() const {72assert(!isEnd());73assert(Pos.BufferPtr != Pos.BufferEnd);74return *Pos.BufferPtr;75}7677void consumeChar() {78assert(!isEnd());79assert(Pos.BufferPtr != Pos.BufferEnd);80Pos.BufferPtr++;81if (Pos.BufferPtr == Pos.BufferEnd) {82Pos.CurToken++;83if (isEnd() && !addToken())84return;8586assert(!isEnd());87setupBuffer();88}89}9091/// Extract a template type92bool lexTemplate(SmallString<32> &WordText) {93unsigned BracketCount = 0;94while (!isEnd()) {95const char C = peek();96WordText.push_back(C);97consumeChar();98switch (C) {99case '<': {100BracketCount++;101break;102}103case '>': {104BracketCount--;105if (!BracketCount)106return true;107break;108}109default:110break;111}112}113return false;114}115116/// Add a token.117/// Returns true on success, false if there are no interesting tokens to118/// fetch from lexer.119bool addToken() {120if (NoMoreInterestingTokens)121return false;122123if (P.Tok.is(tok::newline)) {124// If we see a single newline token between text tokens, skip it.125Token Newline = P.Tok;126P.consumeToken();127if (P.Tok.isNot(tok::text)) {128P.putBack(Newline);129NoMoreInterestingTokens = true;130return false;131}132}133if (P.Tok.isNot(tok::text)) {134NoMoreInterestingTokens = true;135return false;136}137138Toks.push_back(P.Tok);139P.consumeToken();140if (Toks.size() == 1)141setupBuffer();142return true;143}144145void consumeWhitespace() {146while (!isEnd()) {147if (isWhitespace(peek()))148consumeChar();149else150break;151}152}153154void formTokenWithChars(Token &Result,155SourceLocation Loc,156const char *TokBegin,157unsigned TokLength,158StringRef Text) {159Result.setLocation(Loc);160Result.setKind(tok::text);161Result.setLength(TokLength);162#ifndef NDEBUG163Result.TextPtr = "<UNSET>";164Result.IntVal = 7;165#endif166Result.setText(Text);167}168169public:170TextTokenRetokenizer(llvm::BumpPtrAllocator &Allocator, Parser &P):171Allocator(Allocator), P(P), NoMoreInterestingTokens(false) {172Pos.CurToken = 0;173addToken();174}175176/// Extract a type argument177bool lexType(Token &Tok) {178if (isEnd())179return false;180181// Save current position in case we need to rollback because the type is182// empty.183Position SavedPos = Pos;184185// Consume any leading whitespace.186consumeWhitespace();187SmallString<32> WordText;188const char *WordBegin = Pos.BufferPtr;189SourceLocation Loc = getSourceLocation();190191while (!isEnd()) {192const char C = peek();193// For non-whitespace characters we check if it's a template or otherwise194// continue reading the text into a word.195if (!isWhitespace(C)) {196if (C == '<') {197if (!lexTemplate(WordText))198return false;199} else {200WordText.push_back(C);201consumeChar();202}203} else {204consumeChar();205break;206}207}208209const unsigned Length = WordText.size();210if (Length == 0) {211Pos = SavedPos;212return false;213}214215char *TextPtr = Allocator.Allocate<char>(Length + 1);216217memcpy(TextPtr, WordText.c_str(), Length + 1);218StringRef Text = StringRef(TextPtr, Length);219220formTokenWithChars(Tok, Loc, WordBegin, Length, Text);221return true;222}223224// Check if this line starts with @par or \par225bool startsWithParCommand() {226unsigned Offset = 1;227228// Skip all whitespace characters at the beginning.229// This needs to backtrack because Pos has already advanced past the230// actual \par or @par command by the time this function is called.231while (isWhitespace(*(Pos.BufferPtr - Offset)))232Offset++;233234// Once we've reached the whitespace, backtrack and check if the previous235// four characters are \par or @par.236llvm::StringRef LineStart(Pos.BufferPtr - Offset - 3, 4);237return LineStart.starts_with("\\par") || LineStart.starts_with("@par");238}239240/// Extract a par command argument-header.241bool lexParHeading(Token &Tok) {242if (isEnd())243return false;244245Position SavedPos = Pos;246247consumeWhitespace();248SmallString<32> WordText;249const char *WordBegin = Pos.BufferPtr;250SourceLocation Loc = getSourceLocation();251252if (!startsWithParCommand())253return false;254255// Read until the end of this token, which is effectively the end of the256// line. This gets us the content of the par header, if there is one.257while (!isEnd()) {258WordText.push_back(peek());259if (Pos.BufferPtr + 1 == Pos.BufferEnd) {260consumeChar();261break;262}263consumeChar();264}265266unsigned Length = WordText.size();267if (Length == 0) {268Pos = SavedPos;269return false;270}271272char *TextPtr = Allocator.Allocate<char>(Length + 1);273274memcpy(TextPtr, WordText.c_str(), Length + 1);275StringRef Text = StringRef(TextPtr, Length);276277formTokenWithChars(Tok, Loc, WordBegin, Length, Text);278return true;279}280281/// Extract a word -- sequence of non-whitespace characters.282bool lexWord(Token &Tok) {283if (isEnd())284return false;285286Position SavedPos = Pos;287288consumeWhitespace();289SmallString<32> WordText;290const char *WordBegin = Pos.BufferPtr;291SourceLocation Loc = getSourceLocation();292while (!isEnd()) {293const char C = peek();294if (!isWhitespace(C)) {295WordText.push_back(C);296consumeChar();297} else298break;299}300const unsigned Length = WordText.size();301if (Length == 0) {302Pos = SavedPos;303return false;304}305306char *TextPtr = Allocator.Allocate<char>(Length + 1);307308memcpy(TextPtr, WordText.c_str(), Length + 1);309StringRef Text = StringRef(TextPtr, Length);310311formTokenWithChars(Tok, Loc, WordBegin, Length, Text);312return true;313}314315bool lexDelimitedSeq(Token &Tok, char OpenDelim, char CloseDelim) {316if (isEnd())317return false;318319Position SavedPos = Pos;320321consumeWhitespace();322SmallString<32> WordText;323const char *WordBegin = Pos.BufferPtr;324SourceLocation Loc = getSourceLocation();325bool Error = false;326if (!isEnd()) {327const char C = peek();328if (C == OpenDelim) {329WordText.push_back(C);330consumeChar();331} else332Error = true;333}334char C = '\0';335while (!Error && !isEnd()) {336C = peek();337WordText.push_back(C);338consumeChar();339if (C == CloseDelim)340break;341}342if (!Error && C != CloseDelim)343Error = true;344345if (Error) {346Pos = SavedPos;347return false;348}349350const unsigned Length = WordText.size();351char *TextPtr = Allocator.Allocate<char>(Length + 1);352353memcpy(TextPtr, WordText.c_str(), Length + 1);354StringRef Text = StringRef(TextPtr, Length);355356formTokenWithChars(Tok, Loc, WordBegin,357Pos.BufferPtr - WordBegin, Text);358return true;359}360361/// Put back tokens that we didn't consume.362void putBackLeftoverTokens() {363if (isEnd())364return;365366bool HavePartialTok = false;367Token PartialTok;368if (Pos.BufferPtr != Pos.BufferStart) {369formTokenWithChars(PartialTok, getSourceLocation(),370Pos.BufferPtr, Pos.BufferEnd - Pos.BufferPtr,371StringRef(Pos.BufferPtr,372Pos.BufferEnd - Pos.BufferPtr));373HavePartialTok = true;374Pos.CurToken++;375}376377P.putBack(llvm::ArrayRef(Toks.begin() + Pos.CurToken, Toks.end()));378Pos.CurToken = Toks.size();379380if (HavePartialTok)381P.putBack(PartialTok);382}383};384385Parser::Parser(Lexer &L, Sema &S, llvm::BumpPtrAllocator &Allocator,386const SourceManager &SourceMgr, DiagnosticsEngine &Diags,387const CommandTraits &Traits):388L(L), S(S), Allocator(Allocator), SourceMgr(SourceMgr), Diags(Diags),389Traits(Traits) {390consumeToken();391}392393void Parser::parseParamCommandArgs(ParamCommandComment *PC,394TextTokenRetokenizer &Retokenizer) {395Token Arg;396// Check if argument looks like direction specification: [dir]397// e.g., [in], [out], [in,out]398if (Retokenizer.lexDelimitedSeq(Arg, '[', ']'))399S.actOnParamCommandDirectionArg(PC,400Arg.getLocation(),401Arg.getEndLocation(),402Arg.getText());403404if (Retokenizer.lexWord(Arg))405S.actOnParamCommandParamNameArg(PC,406Arg.getLocation(),407Arg.getEndLocation(),408Arg.getText());409}410411void Parser::parseTParamCommandArgs(TParamCommandComment *TPC,412TextTokenRetokenizer &Retokenizer) {413Token Arg;414if (Retokenizer.lexWord(Arg))415S.actOnTParamCommandParamNameArg(TPC,416Arg.getLocation(),417Arg.getEndLocation(),418Arg.getText());419}420421ArrayRef<Comment::Argument>422Parser::parseCommandArgs(TextTokenRetokenizer &Retokenizer, unsigned NumArgs) {423auto *Args = new (Allocator.Allocate<Comment::Argument>(NumArgs))424Comment::Argument[NumArgs];425unsigned ParsedArgs = 0;426Token Arg;427while (ParsedArgs < NumArgs && Retokenizer.lexWord(Arg)) {428Args[ParsedArgs] = Comment::Argument{429SourceRange(Arg.getLocation(), Arg.getEndLocation()), Arg.getText()};430ParsedArgs++;431}432433return llvm::ArrayRef(Args, ParsedArgs);434}435436ArrayRef<Comment::Argument>437Parser::parseThrowCommandArgs(TextTokenRetokenizer &Retokenizer,438unsigned NumArgs) {439auto *Args = new (Allocator.Allocate<Comment::Argument>(NumArgs))440Comment::Argument[NumArgs];441unsigned ParsedArgs = 0;442Token Arg;443444while (ParsedArgs < NumArgs && Retokenizer.lexType(Arg)) {445Args[ParsedArgs] = Comment::Argument{446SourceRange(Arg.getLocation(), Arg.getEndLocation()), Arg.getText()};447ParsedArgs++;448}449450return llvm::ArrayRef(Args, ParsedArgs);451}452453ArrayRef<Comment::Argument>454Parser::parseParCommandArgs(TextTokenRetokenizer &Retokenizer,455unsigned NumArgs) {456assert(NumArgs > 0);457auto *Args = new (Allocator.Allocate<Comment::Argument>(NumArgs))458Comment::Argument[NumArgs];459unsigned ParsedArgs = 0;460Token Arg;461462while (ParsedArgs < NumArgs && Retokenizer.lexParHeading(Arg)) {463Args[ParsedArgs] = Comment::Argument{464SourceRange(Arg.getLocation(), Arg.getEndLocation()), Arg.getText()};465ParsedArgs++;466}467468return llvm::ArrayRef(Args, ParsedArgs);469}470471BlockCommandComment *Parser::parseBlockCommand() {472assert(Tok.is(tok::backslash_command) || Tok.is(tok::at_command));473474ParamCommandComment *PC = nullptr;475TParamCommandComment *TPC = nullptr;476BlockCommandComment *BC = nullptr;477const CommandInfo *Info = Traits.getCommandInfo(Tok.getCommandID());478CommandMarkerKind CommandMarker =479Tok.is(tok::backslash_command) ? CMK_Backslash : CMK_At;480if (Info->IsParamCommand) {481PC = S.actOnParamCommandStart(Tok.getLocation(),482Tok.getEndLocation(),483Tok.getCommandID(),484CommandMarker);485} else if (Info->IsTParamCommand) {486TPC = S.actOnTParamCommandStart(Tok.getLocation(),487Tok.getEndLocation(),488Tok.getCommandID(),489CommandMarker);490} else {491BC = S.actOnBlockCommandStart(Tok.getLocation(),492Tok.getEndLocation(),493Tok.getCommandID(),494CommandMarker);495}496consumeToken();497498if (isTokBlockCommand()) {499// Block command ahead. We can't nest block commands, so pretend that this500// command has an empty argument.501ParagraphComment *Paragraph = S.actOnParagraphComment(std::nullopt);502if (PC) {503S.actOnParamCommandFinish(PC, Paragraph);504return PC;505} else if (TPC) {506S.actOnTParamCommandFinish(TPC, Paragraph);507return TPC;508} else {509S.actOnBlockCommandFinish(BC, Paragraph);510return BC;511}512}513514if (PC || TPC || Info->NumArgs > 0) {515// In order to parse command arguments we need to retokenize a few516// following text tokens.517TextTokenRetokenizer Retokenizer(Allocator, *this);518519if (PC)520parseParamCommandArgs(PC, Retokenizer);521else if (TPC)522parseTParamCommandArgs(TPC, Retokenizer);523else if (Info->IsThrowsCommand)524S.actOnBlockCommandArgs(525BC, parseThrowCommandArgs(Retokenizer, Info->NumArgs));526else if (Info->IsParCommand)527S.actOnBlockCommandArgs(BC,528parseParCommandArgs(Retokenizer, Info->NumArgs));529else530S.actOnBlockCommandArgs(BC, parseCommandArgs(Retokenizer, Info->NumArgs));531532Retokenizer.putBackLeftoverTokens();533}534535// If there's a block command ahead, we will attach an empty paragraph to536// this command.537bool EmptyParagraph = false;538if (isTokBlockCommand())539EmptyParagraph = true;540else if (Tok.is(tok::newline)) {541Token PrevTok = Tok;542consumeToken();543EmptyParagraph = isTokBlockCommand();544putBack(PrevTok);545}546547ParagraphComment *Paragraph;548if (EmptyParagraph)549Paragraph = S.actOnParagraphComment(std::nullopt);550else {551BlockContentComment *Block = parseParagraphOrBlockCommand();552// Since we have checked for a block command, we should have parsed a553// paragraph.554Paragraph = cast<ParagraphComment>(Block);555}556557if (PC) {558S.actOnParamCommandFinish(PC, Paragraph);559return PC;560} else if (TPC) {561S.actOnTParamCommandFinish(TPC, Paragraph);562return TPC;563} else {564S.actOnBlockCommandFinish(BC, Paragraph);565return BC;566}567}568569InlineCommandComment *Parser::parseInlineCommand() {570assert(Tok.is(tok::backslash_command) || Tok.is(tok::at_command));571const CommandInfo *Info = Traits.getCommandInfo(Tok.getCommandID());572573const Token CommandTok = Tok;574consumeToken();575576TextTokenRetokenizer Retokenizer(Allocator, *this);577ArrayRef<Comment::Argument> Args =578parseCommandArgs(Retokenizer, Info->NumArgs);579580InlineCommandComment *IC = S.actOnInlineCommand(581CommandTok.getLocation(), CommandTok.getEndLocation(),582CommandTok.getCommandID(), Args);583584if (Args.size() < Info->NumArgs) {585Diag(CommandTok.getEndLocation().getLocWithOffset(1),586diag::warn_doc_inline_command_not_enough_arguments)587<< CommandTok.is(tok::at_command) << Info->Name << Args.size()588<< Info->NumArgs589<< SourceRange(CommandTok.getLocation(), CommandTok.getEndLocation());590}591592Retokenizer.putBackLeftoverTokens();593594return IC;595}596597HTMLStartTagComment *Parser::parseHTMLStartTag() {598assert(Tok.is(tok::html_start_tag));599HTMLStartTagComment *HST =600S.actOnHTMLStartTagStart(Tok.getLocation(),601Tok.getHTMLTagStartName());602consumeToken();603604SmallVector<HTMLStartTagComment::Attribute, 2> Attrs;605while (true) {606switch (Tok.getKind()) {607case tok::html_ident: {608Token Ident = Tok;609consumeToken();610if (Tok.isNot(tok::html_equals)) {611Attrs.push_back(HTMLStartTagComment::Attribute(Ident.getLocation(),612Ident.getHTMLIdent()));613continue;614}615Token Equals = Tok;616consumeToken();617if (Tok.isNot(tok::html_quoted_string)) {618Diag(Tok.getLocation(),619diag::warn_doc_html_start_tag_expected_quoted_string)620<< SourceRange(Equals.getLocation());621Attrs.push_back(HTMLStartTagComment::Attribute(Ident.getLocation(),622Ident.getHTMLIdent()));623while (Tok.is(tok::html_equals) ||624Tok.is(tok::html_quoted_string))625consumeToken();626continue;627}628Attrs.push_back(HTMLStartTagComment::Attribute(629Ident.getLocation(),630Ident.getHTMLIdent(),631Equals.getLocation(),632SourceRange(Tok.getLocation(),633Tok.getEndLocation()),634Tok.getHTMLQuotedString()));635consumeToken();636continue;637}638639case tok::html_greater:640S.actOnHTMLStartTagFinish(HST, S.copyArray(llvm::ArrayRef(Attrs)),641Tok.getLocation(),642/* IsSelfClosing = */ false);643consumeToken();644return HST;645646case tok::html_slash_greater:647S.actOnHTMLStartTagFinish(HST, S.copyArray(llvm::ArrayRef(Attrs)),648Tok.getLocation(),649/* IsSelfClosing = */ true);650consumeToken();651return HST;652653case tok::html_equals:654case tok::html_quoted_string:655Diag(Tok.getLocation(),656diag::warn_doc_html_start_tag_expected_ident_or_greater);657while (Tok.is(tok::html_equals) ||658Tok.is(tok::html_quoted_string))659consumeToken();660if (Tok.is(tok::html_ident) ||661Tok.is(tok::html_greater) ||662Tok.is(tok::html_slash_greater))663continue;664665S.actOnHTMLStartTagFinish(HST, S.copyArray(llvm::ArrayRef(Attrs)),666SourceLocation(),667/* IsSelfClosing = */ false);668return HST;669670default:671// Not a token from an HTML start tag. Thus HTML tag prematurely ended.672S.actOnHTMLStartTagFinish(HST, S.copyArray(llvm::ArrayRef(Attrs)),673SourceLocation(),674/* IsSelfClosing = */ false);675bool StartLineInvalid;676const unsigned StartLine = SourceMgr.getPresumedLineNumber(677HST->getLocation(),678&StartLineInvalid);679bool EndLineInvalid;680const unsigned EndLine = SourceMgr.getPresumedLineNumber(681Tok.getLocation(),682&EndLineInvalid);683if (StartLineInvalid || EndLineInvalid || StartLine == EndLine)684Diag(Tok.getLocation(),685diag::warn_doc_html_start_tag_expected_ident_or_greater)686<< HST->getSourceRange();687else {688Diag(Tok.getLocation(),689diag::warn_doc_html_start_tag_expected_ident_or_greater);690Diag(HST->getLocation(), diag::note_doc_html_tag_started_here)691<< HST->getSourceRange();692}693return HST;694}695}696}697698HTMLEndTagComment *Parser::parseHTMLEndTag() {699assert(Tok.is(tok::html_end_tag));700Token TokEndTag = Tok;701consumeToken();702SourceLocation Loc;703if (Tok.is(tok::html_greater)) {704Loc = Tok.getLocation();705consumeToken();706}707708return S.actOnHTMLEndTag(TokEndTag.getLocation(),709Loc,710TokEndTag.getHTMLTagEndName());711}712713BlockContentComment *Parser::parseParagraphOrBlockCommand() {714SmallVector<InlineContentComment *, 8> Content;715716while (true) {717switch (Tok.getKind()) {718case tok::verbatim_block_begin:719case tok::verbatim_line_name:720case tok::eof:721break; // Block content or EOF ahead, finish this parapgaph.722723case tok::unknown_command:724Content.push_back(S.actOnUnknownCommand(Tok.getLocation(),725Tok.getEndLocation(),726Tok.getUnknownCommandName()));727consumeToken();728continue;729730case tok::backslash_command:731case tok::at_command: {732const CommandInfo *Info = Traits.getCommandInfo(Tok.getCommandID());733if (Info->IsBlockCommand) {734if (Content.size() == 0)735return parseBlockCommand();736break; // Block command ahead, finish this parapgaph.737}738if (Info->IsVerbatimBlockEndCommand) {739Diag(Tok.getLocation(),740diag::warn_verbatim_block_end_without_start)741<< Tok.is(tok::at_command)742<< Info->Name743<< SourceRange(Tok.getLocation(), Tok.getEndLocation());744consumeToken();745continue;746}747if (Info->IsUnknownCommand) {748Content.push_back(S.actOnUnknownCommand(Tok.getLocation(),749Tok.getEndLocation(),750Info->getID()));751consumeToken();752continue;753}754assert(Info->IsInlineCommand);755Content.push_back(parseInlineCommand());756continue;757}758759case tok::newline: {760consumeToken();761if (Tok.is(tok::newline) || Tok.is(tok::eof)) {762consumeToken();763break; // Two newlines -- end of paragraph.764}765// Also allow [tok::newline, tok::text, tok::newline] if the middle766// tok::text is just whitespace.767if (Tok.is(tok::text) && isWhitespace(Tok.getText())) {768Token WhitespaceTok = Tok;769consumeToken();770if (Tok.is(tok::newline) || Tok.is(tok::eof)) {771consumeToken();772break;773}774// We have [tok::newline, tok::text, non-newline]. Put back tok::text.775putBack(WhitespaceTok);776}777if (Content.size() > 0)778Content.back()->addTrailingNewline();779continue;780}781782// Don't deal with HTML tag soup now.783case tok::html_start_tag:784Content.push_back(parseHTMLStartTag());785continue;786787case tok::html_end_tag:788Content.push_back(parseHTMLEndTag());789continue;790791case tok::text:792Content.push_back(S.actOnText(Tok.getLocation(),793Tok.getEndLocation(),794Tok.getText()));795consumeToken();796continue;797798case tok::verbatim_block_line:799case tok::verbatim_block_end:800case tok::verbatim_line_text:801case tok::html_ident:802case tok::html_equals:803case tok::html_quoted_string:804case tok::html_greater:805case tok::html_slash_greater:806llvm_unreachable("should not see this token");807}808break;809}810811return S.actOnParagraphComment(S.copyArray(llvm::ArrayRef(Content)));812}813814VerbatimBlockComment *Parser::parseVerbatimBlock() {815assert(Tok.is(tok::verbatim_block_begin));816817VerbatimBlockComment *VB =818S.actOnVerbatimBlockStart(Tok.getLocation(),819Tok.getVerbatimBlockID());820consumeToken();821822// Don't create an empty line if verbatim opening command is followed823// by a newline.824if (Tok.is(tok::newline))825consumeToken();826827SmallVector<VerbatimBlockLineComment *, 8> Lines;828while (Tok.is(tok::verbatim_block_line) ||829Tok.is(tok::newline)) {830VerbatimBlockLineComment *Line;831if (Tok.is(tok::verbatim_block_line)) {832Line = S.actOnVerbatimBlockLine(Tok.getLocation(),833Tok.getVerbatimBlockText());834consumeToken();835if (Tok.is(tok::newline)) {836consumeToken();837}838} else {839// Empty line, just a tok::newline.840Line = S.actOnVerbatimBlockLine(Tok.getLocation(), "");841consumeToken();842}843Lines.push_back(Line);844}845846if (Tok.is(tok::verbatim_block_end)) {847const CommandInfo *Info = Traits.getCommandInfo(Tok.getVerbatimBlockID());848S.actOnVerbatimBlockFinish(VB, Tok.getLocation(), Info->Name,849S.copyArray(llvm::ArrayRef(Lines)));850consumeToken();851} else {852// Unterminated \\verbatim block853S.actOnVerbatimBlockFinish(VB, SourceLocation(), "",854S.copyArray(llvm::ArrayRef(Lines)));855}856857return VB;858}859860VerbatimLineComment *Parser::parseVerbatimLine() {861assert(Tok.is(tok::verbatim_line_name));862863Token NameTok = Tok;864consumeToken();865866SourceLocation TextBegin;867StringRef Text;868// Next token might not be a tok::verbatim_line_text if verbatim line869// starting command comes just before a newline or comment end.870if (Tok.is(tok::verbatim_line_text)) {871TextBegin = Tok.getLocation();872Text = Tok.getVerbatimLineText();873} else {874TextBegin = NameTok.getEndLocation();875Text = "";876}877878VerbatimLineComment *VL = S.actOnVerbatimLine(NameTok.getLocation(),879NameTok.getVerbatimLineID(),880TextBegin,881Text);882consumeToken();883return VL;884}885886BlockContentComment *Parser::parseBlockContent() {887switch (Tok.getKind()) {888case tok::text:889case tok::unknown_command:890case tok::backslash_command:891case tok::at_command:892case tok::html_start_tag:893case tok::html_end_tag:894return parseParagraphOrBlockCommand();895896case tok::verbatim_block_begin:897return parseVerbatimBlock();898899case tok::verbatim_line_name:900return parseVerbatimLine();901902case tok::eof:903case tok::newline:904case tok::verbatim_block_line:905case tok::verbatim_block_end:906case tok::verbatim_line_text:907case tok::html_ident:908case tok::html_equals:909case tok::html_quoted_string:910case tok::html_greater:911case tok::html_slash_greater:912llvm_unreachable("should not see this token");913}914llvm_unreachable("bogus token kind");915}916917FullComment *Parser::parseFullComment() {918// Skip newlines at the beginning of the comment.919while (Tok.is(tok::newline))920consumeToken();921922SmallVector<BlockContentComment *, 8> Blocks;923while (Tok.isNot(tok::eof)) {924Blocks.push_back(parseBlockContent());925926// Skip extra newlines after paragraph end.927while (Tok.is(tok::newline))928consumeToken();929}930return S.actOnFullComment(S.copyArray(llvm::ArrayRef(Blocks)));931}932933} // end namespace comments934} // end namespace clang935936937