1 //== HTMLRewrite.cpp - Translate source code into prettified HTML --*- C++ -*-// 2 // 3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. 4 // See https://llvm.org/LICENSE.txt for license information. 5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception 6 // 7 //===----------------------------------------------------------------------===// 8 // 9 // This file defines the HTMLRewriter class, which is used to translate the 10 // text of a source file into prettified HTML. 11 // 12 //===----------------------------------------------------------------------===// 13 14 #include "clang/Rewrite/Core/HTMLRewrite.h" 15 #include "clang/Basic/SourceManager.h" 16 #include "clang/Lex/Preprocessor.h" 17 #include "clang/Lex/TokenConcatenation.h" 18 #include "clang/Rewrite/Core/Rewriter.h" 19 #include "llvm/ADT/RewriteBuffer.h" 20 #include "llvm/Support/ErrorHandling.h" 21 #include "llvm/Support/raw_ostream.h" 22 #include <memory> 23 24 using namespace clang; 25 using namespace llvm; 26 using namespace html; 27 28 /// HighlightRange - Highlight a range in the source code with the specified 29 /// start/end tags. B/E must be in the same file. This ensures that 30 /// start/end tags are placed at the start/end of each line if the range is 31 /// multiline. 32 void html::HighlightRange(Rewriter &R, SourceLocation B, SourceLocation E, 33 const char *StartTag, const char *EndTag, 34 bool IsTokenRange) { 35 SourceManager &SM = R.getSourceMgr(); 36 B = SM.getExpansionLoc(B); 37 E = SM.getExpansionLoc(E); 38 FileID FID = SM.getFileID(B); 39 assert(SM.getFileID(E) == FID && "B/E not in the same file!"); 40 41 unsigned BOffset = SM.getFileOffset(B); 42 unsigned EOffset = SM.getFileOffset(E); 43 44 // Include the whole end token in the range. 45 if (IsTokenRange) 46 EOffset += Lexer::MeasureTokenLength(E, R.getSourceMgr(), R.getLangOpts()); 47 48 bool Invalid = false; 49 const char *BufferStart = SM.getBufferData(FID, &Invalid).data(); 50 if (Invalid) 51 return; 52 53 HighlightRange(R.getEditBuffer(FID), BOffset, EOffset, 54 BufferStart, StartTag, EndTag); 55 } 56 57 /// HighlightRange - This is the same as the above method, but takes 58 /// decomposed file locations. 59 void html::HighlightRange(RewriteBuffer &RB, unsigned B, unsigned E, 60 const char *BufferStart, 61 const char *StartTag, const char *EndTag) { 62 // Insert the tag at the absolute start/end of the range. 63 RB.InsertTextAfter(B, StartTag); 64 RB.InsertTextBefore(E, EndTag); 65 66 // Scan the range to see if there is a \r or \n. If so, and if the line is 67 // not blank, insert tags on that line as well. 68 bool HadOpenTag = true; 69 70 unsigned LastNonWhiteSpace = B; 71 for (unsigned i = B; i != E; ++i) { 72 switch (BufferStart[i]) { 73 case '\r': 74 case '\n': 75 // Okay, we found a newline in the range. If we have an open tag, we need 76 // to insert a close tag at the first non-whitespace before the newline. 77 if (HadOpenTag) 78 RB.InsertTextBefore(LastNonWhiteSpace+1, EndTag); 79 80 // Instead of inserting an open tag immediately after the newline, we 81 // wait until we see a non-whitespace character. This prevents us from 82 // inserting tags around blank lines, and also allows the open tag to 83 // be put *after* whitespace on a non-blank line. 84 HadOpenTag = false; 85 break; 86 case '\0': 87 case ' ': 88 case '\t': 89 case '\f': 90 case '\v': 91 // Ignore whitespace. 92 break; 93 94 default: 95 // If there is no tag open, do it now. 96 if (!HadOpenTag) { 97 RB.InsertTextAfter(i, StartTag); 98 HadOpenTag = true; 99 } 100 101 // Remember this character. 102 LastNonWhiteSpace = i; 103 break; 104 } 105 } 106 } 107 108 namespace clang::html { 109 struct RelexRewriteCache { 110 // These structs mimic input arguments of HighlightRange(). 111 struct Highlight { 112 SourceLocation B, E; 113 std::string StartTag, EndTag; 114 bool IsTokenRange; 115 }; 116 struct RawHighlight { 117 unsigned B, E; 118 std::string StartTag, EndTag; 119 }; 120 121 // SmallVector isn't appropriate because these vectors are almost never small. 122 using HighlightList = std::vector<Highlight>; 123 using RawHighlightList = std::vector<RawHighlight>; 124 125 DenseMap<FileID, RawHighlightList> SyntaxHighlights; 126 DenseMap<FileID, HighlightList> MacroHighlights; 127 }; 128 } // namespace clang::html 129 130 html::RelexRewriteCacheRef html::instantiateRelexRewriteCache() { 131 return std::make_shared<RelexRewriteCache>(); 132 } 133 134 void html::EscapeText(Rewriter &R, FileID FID, 135 bool EscapeSpaces, bool ReplaceTabs) { 136 137 llvm::MemoryBufferRef Buf = R.getSourceMgr().getBufferOrFake(FID); 138 const char* C = Buf.getBufferStart(); 139 const char* FileEnd = Buf.getBufferEnd(); 140 141 assert (C <= FileEnd); 142 143 RewriteBuffer &RB = R.getEditBuffer(FID); 144 145 unsigned ColNo = 0; 146 for (unsigned FilePos = 0; C != FileEnd ; ++C, ++FilePos) { 147 switch (*C) { 148 default: ++ColNo; break; 149 case '\n': 150 case '\r': 151 ColNo = 0; 152 break; 153 154 case ' ': 155 if (EscapeSpaces) 156 RB.ReplaceText(FilePos, 1, " "); 157 ++ColNo; 158 break; 159 case '\f': 160 RB.ReplaceText(FilePos, 1, "<hr>"); 161 ColNo = 0; 162 break; 163 164 case '\t': { 165 if (!ReplaceTabs) 166 break; 167 unsigned NumSpaces = 8-(ColNo&7); 168 if (EscapeSpaces) 169 RB.ReplaceText(FilePos, 1, 170 StringRef(" " 171 " ", 6*NumSpaces)); 172 else 173 RB.ReplaceText(FilePos, 1, StringRef(" ", NumSpaces)); 174 ColNo += NumSpaces; 175 break; 176 } 177 case '<': 178 RB.ReplaceText(FilePos, 1, "<"); 179 ++ColNo; 180 break; 181 182 case '>': 183 RB.ReplaceText(FilePos, 1, ">"); 184 ++ColNo; 185 break; 186 187 case '&': 188 RB.ReplaceText(FilePos, 1, "&"); 189 ++ColNo; 190 break; 191 } 192 } 193 } 194 195 std::string html::EscapeText(StringRef s, bool EscapeSpaces, bool ReplaceTabs) { 196 197 unsigned len = s.size(); 198 std::string Str; 199 llvm::raw_string_ostream os(Str); 200 201 for (unsigned i = 0 ; i < len; ++i) { 202 203 char c = s[i]; 204 switch (c) { 205 default: 206 os << c; break; 207 208 case ' ': 209 if (EscapeSpaces) os << " "; 210 else os << ' '; 211 break; 212 213 case '\t': 214 if (ReplaceTabs) { 215 if (EscapeSpaces) 216 for (unsigned i = 0; i < 4; ++i) 217 os << " "; 218 else 219 for (unsigned i = 0; i < 4; ++i) 220 os << " "; 221 } 222 else 223 os << c; 224 225 break; 226 227 case '<': os << "<"; break; 228 case '>': os << ">"; break; 229 case '&': os << "&"; break; 230 } 231 } 232 233 return Str; 234 } 235 236 static void AddLineNumber(RewriteBuffer &RB, unsigned LineNo, 237 unsigned B, unsigned E) { 238 SmallString<256> Str; 239 llvm::raw_svector_ostream OS(Str); 240 241 OS << "<tr class=\"codeline\" data-linenumber=\"" << LineNo << "\">" 242 << "<td class=\"num\" id=\"LN" << LineNo << "\">" << LineNo 243 << "</td><td class=\"line\">"; 244 245 if (B == E) { // Handle empty lines. 246 OS << " </td></tr>"; 247 RB.InsertTextBefore(B, OS.str()); 248 } else { 249 RB.InsertTextBefore(B, OS.str()); 250 RB.InsertTextBefore(E, "</td></tr>"); 251 } 252 } 253 254 void html::AddLineNumbers(Rewriter& R, FileID FID) { 255 256 llvm::MemoryBufferRef Buf = R.getSourceMgr().getBufferOrFake(FID); 257 const char* FileBeg = Buf.getBufferStart(); 258 const char* FileEnd = Buf.getBufferEnd(); 259 const char* C = FileBeg; 260 RewriteBuffer &RB = R.getEditBuffer(FID); 261 262 assert (C <= FileEnd); 263 264 unsigned LineNo = 0; 265 unsigned FilePos = 0; 266 267 while (C != FileEnd) { 268 269 ++LineNo; 270 unsigned LineStartPos = FilePos; 271 unsigned LineEndPos = FileEnd - FileBeg; 272 273 assert (FilePos <= LineEndPos); 274 assert (C < FileEnd); 275 276 // Scan until the newline (or end-of-file). 277 278 while (C != FileEnd) { 279 char c = *C; 280 ++C; 281 282 if (c == '\n') { 283 LineEndPos = FilePos++; 284 break; 285 } 286 287 ++FilePos; 288 } 289 290 AddLineNumber(RB, LineNo, LineStartPos, LineEndPos); 291 } 292 293 // Add one big table tag that surrounds all of the code. 294 std::string s; 295 llvm::raw_string_ostream os(s); 296 os << "<table class=\"code\" data-fileid=\"" << FID.getHashValue() << "\">\n"; 297 RB.InsertTextBefore(0, os.str()); 298 RB.InsertTextAfter(FileEnd - FileBeg, "</table>"); 299 } 300 301 void html::AddHeaderFooterInternalBuiltinCSS(Rewriter &R, FileID FID, 302 StringRef title) { 303 304 llvm::MemoryBufferRef Buf = R.getSourceMgr().getBufferOrFake(FID); 305 const char* FileStart = Buf.getBufferStart(); 306 const char* FileEnd = Buf.getBufferEnd(); 307 308 SourceLocation StartLoc = R.getSourceMgr().getLocForStartOfFile(FID); 309 SourceLocation EndLoc = StartLoc.getLocWithOffset(FileEnd-FileStart); 310 311 std::string s; 312 llvm::raw_string_ostream os(s); 313 os << "<!doctype html>\n" // Use HTML 5 doctype 314 "<html>\n<head>\n"; 315 316 if (!title.empty()) 317 os << "<title>" << html::EscapeText(title) << "</title>\n"; 318 319 os << R"<<<( 320 <style type="text/css"> 321 body { color:#000000; background-color:#ffffff } 322 body { font-family:Helvetica, sans-serif; font-size:10pt } 323 h1 { font-size:14pt } 324 .FileName { margin-top: 5px; margin-bottom: 5px; display: inline; } 325 .FileNav { margin-left: 5px; margin-right: 5px; display: inline; } 326 .FileNav a { text-decoration:none; font-size: larger; } 327 .divider { margin-top: 30px; margin-bottom: 30px; height: 15px; } 328 .divider { background-color: gray; } 329 .code { border-collapse:collapse; width:100%; } 330 .code { font-family: "Monospace", monospace; font-size:10pt } 331 .code { line-height: 1.2em } 332 .comment { color: green; font-style: oblique } 333 .keyword { color: blue } 334 .string_literal { color: red } 335 .directive { color: darkmagenta } 336 337 /* Macros and variables could have pop-up notes hidden by default. 338 - Macro pop-up: expansion of the macro 339 - Variable pop-up: value (table) of the variable */ 340 .macro_popup, .variable_popup { display: none; } 341 342 /* Pop-up appears on mouse-hover event. */ 343 .macro:hover .macro_popup, .variable:hover .variable_popup { 344 display: block; 345 padding: 2px; 346 -webkit-border-radius:5px; 347 -webkit-box-shadow:1px 1px 7px #000; 348 border-radius:5px; 349 box-shadow:1px 1px 7px #000; 350 position: absolute; 351 top: -1em; 352 left:10em; 353 z-index: 1 354 } 355 356 .macro_popup { 357 border: 2px solid red; 358 background-color:#FFF0F0; 359 font-weight: normal; 360 } 361 362 .variable_popup { 363 border: 2px solid blue; 364 background-color:#F0F0FF; 365 font-weight: bold; 366 font-family: Helvetica, sans-serif; 367 font-size: 9pt; 368 } 369 370 /* Pop-up notes needs a relative position as a base where they pops up. */ 371 .macro, .variable { 372 background-color: PaleGoldenRod; 373 position: relative; 374 } 375 .macro { color: DarkMagenta; } 376 377 #tooltiphint { 378 position: fixed; 379 width: 50em; 380 margin-left: -25em; 381 left: 50%; 382 padding: 10px; 383 border: 1px solid #b0b0b0; 384 border-radius: 2px; 385 box-shadow: 1px 1px 7px black; 386 background-color: #c0c0c0; 387 z-index: 2; 388 } 389 390 .num { width:2.5em; padding-right:2ex; background-color:#eeeeee } 391 .num { text-align:right; font-size:8pt } 392 .num { color:#444444 } 393 .line { padding-left: 1ex; border-left: 3px solid #ccc } 394 .line { white-space: pre } 395 .msg { -webkit-box-shadow:1px 1px 7px #000 } 396 .msg { box-shadow:1px 1px 7px #000 } 397 .msg { -webkit-border-radius:5px } 398 .msg { border-radius:5px } 399 .msg { font-family:Helvetica, sans-serif; font-size:8pt } 400 .msg { float:left } 401 .msg { position:relative } 402 .msg { padding:0.25em 1ex 0.25em 1ex } 403 .msg { margin-top:10px; margin-bottom:10px } 404 .msg { font-weight:bold } 405 .msg { max-width:60em; word-wrap: break-word; white-space: pre-wrap } 406 .msgT { padding:0x; spacing:0x } 407 .msgEvent { background-color:#fff8b4; color:#000000 } 408 .msgControl { background-color:#bbbbbb; color:#000000 } 409 .msgNote { background-color:#ddeeff; color:#000000 } 410 .mrange { background-color:#dfddf3 } 411 .mrange { border-bottom:1px solid #6F9DBE } 412 .PathIndex { font-weight: bold; padding:0px 5px; margin-right:5px; } 413 .PathIndex { -webkit-border-radius:8px } 414 .PathIndex { border-radius:8px } 415 .PathIndexEvent { background-color:#bfba87 } 416 .PathIndexControl { background-color:#8c8c8c } 417 .PathIndexPopUp { background-color: #879abc; } 418 .PathNav a { text-decoration:none; font-size: larger } 419 .CodeInsertionHint { font-weight: bold; background-color: #10dd10 } 420 .CodeRemovalHint { background-color:#de1010 } 421 .CodeRemovalHint { border-bottom:1px solid #6F9DBE } 422 .msg.selected{ background-color:orange !important; } 423 424 table.simpletable { 425 padding: 5px; 426 font-size:12pt; 427 margin:20px; 428 border-collapse: collapse; border-spacing: 0px; 429 } 430 td.rowname { 431 text-align: right; 432 vertical-align: top; 433 font-weight: bold; 434 color:#444444; 435 padding-right:2ex; 436 } 437 438 /* Hidden text. */ 439 input.spoilerhider + label { 440 cursor: pointer; 441 text-decoration: underline; 442 display: block; 443 } 444 input.spoilerhider { 445 display: none; 446 } 447 input.spoilerhider ~ .spoiler { 448 overflow: hidden; 449 margin: 10px auto 0; 450 height: 0; 451 opacity: 0; 452 } 453 input.spoilerhider:checked + label + .spoiler{ 454 height: auto; 455 opacity: 1; 456 } 457 </style> 458 </head> 459 <body>)<<<"; 460 461 // Generate header 462 R.InsertTextBefore(StartLoc, os.str()); 463 // Generate footer 464 465 R.InsertTextAfter(EndLoc, "</body></html>\n"); 466 } 467 468 /// SyntaxHighlight - Relex the specified FileID and annotate the HTML with 469 /// information about keywords, macro expansions etc. This uses the macro 470 /// table state from the end of the file, so it won't be perfectly perfect, 471 /// but it will be reasonably close. 472 static void SyntaxHighlightImpl( 473 Rewriter &R, FileID FID, const Preprocessor &PP, 474 llvm::function_ref<void(RewriteBuffer &, unsigned, unsigned, const char *, 475 const char *, const char *)> 476 HighlightRangeCallback) { 477 478 RewriteBuffer &RB = R.getEditBuffer(FID); 479 const SourceManager &SM = PP.getSourceManager(); 480 llvm::MemoryBufferRef FromFile = SM.getBufferOrFake(FID); 481 const char *BufferStart = FromFile.getBuffer().data(); 482 483 Lexer L(FID, FromFile, SM, PP.getLangOpts()); 484 485 // Inform the preprocessor that we want to retain comments as tokens, so we 486 // can highlight them. 487 L.SetCommentRetentionState(true); 488 489 // Lex all the tokens in raw mode, to avoid entering #includes or expanding 490 // macros. 491 Token Tok; 492 L.LexFromRawLexer(Tok); 493 494 while (Tok.isNot(tok::eof)) { 495 // Since we are lexing unexpanded tokens, all tokens are from the main 496 // FileID. 497 unsigned TokOffs = SM.getFileOffset(Tok.getLocation()); 498 unsigned TokLen = Tok.getLength(); 499 switch (Tok.getKind()) { 500 default: break; 501 case tok::identifier: 502 llvm_unreachable("tok::identifier in raw lexing mode!"); 503 case tok::raw_identifier: { 504 // Fill in Result.IdentifierInfo and update the token kind, 505 // looking up the identifier in the identifier table. 506 PP.LookUpIdentifierInfo(Tok); 507 508 // If this is a pp-identifier, for a keyword, highlight it as such. 509 if (Tok.isNot(tok::identifier)) 510 HighlightRangeCallback(RB, TokOffs, TokOffs + TokLen, BufferStart, 511 "<span class='keyword'>", "</span>"); 512 break; 513 } 514 case tok::comment: 515 HighlightRangeCallback(RB, TokOffs, TokOffs + TokLen, BufferStart, 516 "<span class='comment'>", "</span>"); 517 break; 518 case tok::utf8_string_literal: 519 // Chop off the u part of u8 prefix 520 ++TokOffs; 521 --TokLen; 522 // FALL THROUGH to chop the 8 523 [[fallthrough]]; 524 case tok::wide_string_literal: 525 case tok::utf16_string_literal: 526 case tok::utf32_string_literal: 527 // Chop off the L, u, U or 8 prefix 528 ++TokOffs; 529 --TokLen; 530 [[fallthrough]]; 531 case tok::string_literal: 532 // FIXME: Exclude the optional ud-suffix from the highlighted range. 533 HighlightRangeCallback(RB, TokOffs, TokOffs + TokLen, BufferStart, 534 "<span class='string_literal'>", "</span>"); 535 break; 536 case tok::hash: { 537 // If this is a preprocessor directive, all tokens to end of line are too. 538 if (!Tok.isAtStartOfLine()) 539 break; 540 541 // Eat all of the tokens until we get to the next one at the start of 542 // line. 543 unsigned TokEnd = TokOffs+TokLen; 544 L.LexFromRawLexer(Tok); 545 while (!Tok.isAtStartOfLine() && Tok.isNot(tok::eof)) { 546 TokEnd = SM.getFileOffset(Tok.getLocation())+Tok.getLength(); 547 L.LexFromRawLexer(Tok); 548 } 549 550 // Find end of line. This is a hack. 551 HighlightRangeCallback(RB, TokOffs, TokEnd, BufferStart, 552 "<span class='directive'>", "</span>"); 553 554 // Don't skip the next token. 555 continue; 556 } 557 } 558 559 L.LexFromRawLexer(Tok); 560 } 561 } 562 void html::SyntaxHighlight(Rewriter &R, FileID FID, const Preprocessor &PP, 563 RelexRewriteCacheRef Cache) { 564 RewriteBuffer &RB = R.getEditBuffer(FID); 565 const SourceManager &SM = PP.getSourceManager(); 566 llvm::MemoryBufferRef FromFile = SM.getBufferOrFake(FID); 567 const char *BufferStart = FromFile.getBuffer().data(); 568 569 if (Cache) { 570 auto CacheIt = Cache->SyntaxHighlights.find(FID); 571 if (CacheIt != Cache->SyntaxHighlights.end()) { 572 for (const RelexRewriteCache::RawHighlight &H : CacheIt->second) { 573 HighlightRange(RB, H.B, H.E, BufferStart, H.StartTag.data(), 574 H.EndTag.data()); 575 } 576 return; 577 } 578 } 579 580 // "Every time you would call HighlightRange, cache the inputs as well." 581 auto HighlightRangeCallback = [&](RewriteBuffer &RB, unsigned B, unsigned E, 582 const char *BufferStart, 583 const char *StartTag, const char *EndTag) { 584 HighlightRange(RB, B, E, BufferStart, StartTag, EndTag); 585 586 if (Cache) 587 Cache->SyntaxHighlights[FID].push_back({B, E, StartTag, EndTag}); 588 }; 589 590 SyntaxHighlightImpl(R, FID, PP, HighlightRangeCallback); 591 } 592 593 static void HighlightMacrosImpl( 594 Rewriter &R, FileID FID, const Preprocessor &PP, 595 llvm::function_ref<void(Rewriter &, SourceLocation, SourceLocation, 596 const char *, const char *, bool)> 597 HighlightRangeCallback) { 598 599 // Re-lex the raw token stream into a token buffer. 600 const SourceManager &SM = PP.getSourceManager(); 601 std::vector<Token> TokenStream; 602 603 llvm::MemoryBufferRef FromFile = SM.getBufferOrFake(FID); 604 Lexer L(FID, FromFile, SM, PP.getLangOpts()); 605 606 // Lex all the tokens in raw mode, to avoid entering #includes or expanding 607 // macros. 608 while (true) { 609 Token Tok; 610 L.LexFromRawLexer(Tok); 611 612 // If this is a # at the start of a line, discard it from the token stream. 613 // We don't want the re-preprocess step to see #defines, #includes or other 614 // preprocessor directives. 615 if (Tok.is(tok::hash) && Tok.isAtStartOfLine()) 616 continue; 617 618 // If this is a ## token, change its kind to unknown so that repreprocessing 619 // it will not produce an error. 620 if (Tok.is(tok::hashhash)) 621 Tok.setKind(tok::unknown); 622 623 // If this raw token is an identifier, the raw lexer won't have looked up 624 // the corresponding identifier info for it. Do this now so that it will be 625 // macro expanded when we re-preprocess it. 626 if (Tok.is(tok::raw_identifier)) 627 PP.LookUpIdentifierInfo(Tok); 628 629 TokenStream.push_back(Tok); 630 631 if (Tok.is(tok::eof)) break; 632 } 633 634 // Temporarily change the diagnostics object so that we ignore any generated 635 // diagnostics from this pass. 636 DiagnosticsEngine TmpDiags(PP.getDiagnostics().getDiagnosticIDs(), 637 PP.getDiagnostics().getDiagnosticOptions(), 638 new IgnoringDiagConsumer); 639 640 // FIXME: This is a huge hack; we reuse the input preprocessor because we want 641 // its state, but we aren't actually changing it (we hope). This should really 642 // construct a copy of the preprocessor. 643 Preprocessor &TmpPP = const_cast<Preprocessor&>(PP); 644 DiagnosticsEngine *OldDiags = &TmpPP.getDiagnostics(); 645 TmpPP.setDiagnostics(TmpDiags); 646 647 // Inform the preprocessor that we don't want comments. 648 TmpPP.SetCommentRetentionState(false, false); 649 650 // We don't want pragmas either. Although we filtered out #pragma, removing 651 // _Pragma and __pragma is much harder. 652 bool PragmasPreviouslyEnabled = TmpPP.getPragmasEnabled(); 653 TmpPP.setPragmasEnabled(false); 654 655 // Enter the tokens we just lexed. This will cause them to be macro expanded 656 // but won't enter sub-files (because we removed #'s). 657 TmpPP.EnterTokenStream(TokenStream, false, /*IsReinject=*/false); 658 659 TokenConcatenation ConcatInfo(TmpPP); 660 661 // Lex all the tokens. 662 Token Tok; 663 TmpPP.Lex(Tok); 664 while (Tok.isNot(tok::eof)) { 665 // Ignore non-macro tokens. 666 if (!Tok.getLocation().isMacroID()) { 667 TmpPP.Lex(Tok); 668 continue; 669 } 670 671 // Okay, we have the first token of a macro expansion: highlight the 672 // expansion by inserting a start tag before the macro expansion and 673 // end tag after it. 674 CharSourceRange LLoc = SM.getExpansionRange(Tok.getLocation()); 675 676 // Ignore tokens whose instantiation location was not the main file. 677 if (SM.getFileID(LLoc.getBegin()) != FID) { 678 TmpPP.Lex(Tok); 679 continue; 680 } 681 682 assert(SM.getFileID(LLoc.getEnd()) == FID && 683 "Start and end of expansion must be in the same ultimate file!"); 684 685 std::string Expansion = EscapeText(TmpPP.getSpelling(Tok)); 686 unsigned LineLen = Expansion.size(); 687 688 Token PrevPrevTok; 689 Token PrevTok = Tok; 690 // Okay, eat this token, getting the next one. 691 TmpPP.Lex(Tok); 692 693 // Skip all the rest of the tokens that are part of this macro 694 // instantiation. It would be really nice to pop up a window with all the 695 // spelling of the tokens or something. 696 while (!Tok.is(tok::eof) && 697 SM.getExpansionLoc(Tok.getLocation()) == LLoc.getBegin()) { 698 // Insert a newline if the macro expansion is getting large. 699 if (LineLen > 60) { 700 Expansion += "<br>"; 701 LineLen = 0; 702 } 703 704 LineLen -= Expansion.size(); 705 706 // If the tokens were already space separated, or if they must be to avoid 707 // them being implicitly pasted, add a space between them. 708 if (Tok.hasLeadingSpace() || 709 ConcatInfo.AvoidConcat(PrevPrevTok, PrevTok, Tok)) 710 Expansion += ' '; 711 712 // Escape any special characters in the token text. 713 Expansion += EscapeText(TmpPP.getSpelling(Tok)); 714 LineLen += Expansion.size(); 715 716 PrevPrevTok = PrevTok; 717 PrevTok = Tok; 718 TmpPP.Lex(Tok); 719 } 720 721 // Insert the 'macro_popup' as the end tag, so that multi-line macros all 722 // get highlighted. 723 Expansion = "<span class='macro_popup'>" + Expansion + "</span></span>"; 724 725 HighlightRangeCallback(R, LLoc.getBegin(), LLoc.getEnd(), 726 "<span class='macro'>", Expansion.c_str(), 727 LLoc.isTokenRange()); 728 } 729 730 // Restore the preprocessor's old state. 731 TmpPP.setDiagnostics(*OldDiags); 732 TmpPP.setPragmasEnabled(PragmasPreviouslyEnabled); 733 } 734 735 /// HighlightMacros - This uses the macro table state from the end of the 736 /// file, to re-expand macros and insert (into the HTML) information about the 737 /// macro expansions. This won't be perfectly perfect, but it will be 738 /// reasonably close. 739 void html::HighlightMacros(Rewriter &R, FileID FID, const Preprocessor &PP, 740 RelexRewriteCacheRef Cache) { 741 if (Cache) { 742 auto CacheIt = Cache->MacroHighlights.find(FID); 743 if (CacheIt != Cache->MacroHighlights.end()) { 744 for (const RelexRewriteCache::Highlight &H : CacheIt->second) { 745 HighlightRange(R, H.B, H.E, H.StartTag.data(), H.EndTag.data(), 746 H.IsTokenRange); 747 } 748 return; 749 } 750 } 751 752 // "Every time you would call HighlightRange, cache the inputs as well." 753 auto HighlightRangeCallback = [&](Rewriter &R, SourceLocation B, 754 SourceLocation E, const char *StartTag, 755 const char *EndTag, bool isTokenRange) { 756 HighlightRange(R, B, E, StartTag, EndTag, isTokenRange); 757 758 if (Cache) { 759 Cache->MacroHighlights[FID].push_back( 760 {B, E, StartTag, EndTag, isTokenRange}); 761 } 762 }; 763 764 HighlightMacrosImpl(R, FID, PP, HighlightRangeCallback); 765 } 766