clang 24.0.0git
HTMLRewrite.cpp
Go to the documentation of this file.
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
19#include "llvm/ADT/RewriteBuffer.h"
20#include "llvm/Support/ErrorHandling.h"
21#include "llvm/Support/raw_ostream.h"
22#include <memory>
23
24using namespace clang;
25using namespace llvm;
26using 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.
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.
59void 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
108namespace clang::html {
110 // These structs mimic input arguments of HighlightRange().
111 struct Highlight {
113 std::string StartTag, EndTag;
115 };
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
131 return std::make_shared<RelexRewriteCache>();
132}
133
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, "&nbsp;");
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("&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;"
171 "&nbsp;&nbsp;&nbsp;", 6*NumSpaces));
172 else
173 RB.ReplaceText(FilePos, 1, StringRef(" ", NumSpaces));
174 ColNo += NumSpaces;
175 break;
176 }
177 case '<':
178 RB.ReplaceText(FilePos, 1, "&lt;");
179 ++ColNo;
180 break;
181
182 case '>':
183 RB.ReplaceText(FilePos, 1, "&gt;");
184 ++ColNo;
185 break;
186
187 case '&':
188 RB.ReplaceText(FilePos, 1, "&amp;");
189 ++ColNo;
190 break;
191 }
192 }
193}
194
195std::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 << "&nbsp;";
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 << "&nbsp;";
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 << "&lt;"; break;
228 case '>': os << "&gt;"; break;
229 case '&': os << "&amp;"; break;
230 }
231 }
232
233 return Str;
234}
235
236static void AddLineNumber(RewriteBuffer &RB, unsigned LineNo,
237 unsigned B, unsigned E) {
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.InsertTextAfter(E, "</td></tr>");
251 }
252}
253
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.getOpaqueValue()
297 << "\">\n";
298 RB.InsertTextBefore(0, os.str());
299 RB.InsertTextAfter(FileEnd - FileBeg, "</table>");
300}
301
303 StringRef title) {
304
305 llvm::MemoryBufferRef Buf = R.getSourceMgr().getBufferOrFake(FID);
306 const char* FileStart = Buf.getBufferStart();
307 const char* FileEnd = Buf.getBufferEnd();
308
309 SourceLocation StartLoc = R.getSourceMgr().getLocForStartOfFile(FID);
310 SourceLocation EndLoc = StartLoc.getLocWithOffset(FileEnd-FileStart);
311
312 std::string s;
313 llvm::raw_string_ostream os(s);
314 os << "<!doctype html>\n" // Use HTML 5 doctype
315 "<html>\n<head>\n";
316
317 if (!title.empty())
318 os << "<title>" << html::EscapeText(title) << "</title>\n";
319
320 os << R"<<<(
321<style type="text/css">
322body { color:#000000; background-color:#ffffff }
323body { font-family:Helvetica, sans-serif; font-size:10pt }
324h1 { font-size:14pt }
325.FileName { margin-top: 5px; margin-bottom: 5px; display: inline; }
326.FileNav { margin-left: 5px; margin-right: 5px; display: inline; }
327.FileNav a { text-decoration:none; font-size: larger; }
328.divider { margin-top: 30px; margin-bottom: 30px; height: 15px; }
329.divider { background-color: gray; }
330.code { border-collapse:collapse; width:100%; }
331.code { font-family: "Monospace", monospace; font-size:10pt }
332.code { line-height: 1.2em }
333.comment { color: green; font-style: oblique }
334.keyword { color: blue }
335.string_literal { color: red }
336.directive { color: darkmagenta }
337
338/* Macros and variables could have pop-up notes hidden by default.
339 - Macro pop-up: expansion of the macro
340 - Variable pop-up: value (table) of the variable */
341.macro_popup, .variable_popup { display: none; }
342
343/* Pop-up appears on mouse-hover event. */
344.macro:hover .macro_popup, .variable:hover .variable_popup {
345 display: block;
346 padding: 2px;
347 -webkit-border-radius:5px;
348 -webkit-box-shadow:1px 1px 7px #000;
349 border-radius:5px;
350 box-shadow:1px 1px 7px #000;
351 position: absolute;
352 top: -1em;
353 left:10em;
354 z-index: 1
355}
356
357.macro_popup {
358 border: 2px solid red;
359 background-color:#FFF0F0;
360 font-weight: normal;
361}
362
363.variable_popup {
364 border: 2px solid blue;
365 background-color:#F0F0FF;
366 font-weight: bold;
367 font-family: Helvetica, sans-serif;
368 font-size: 9pt;
369}
370
371/* Pop-up notes needs a relative position as a base where they pops up. */
372.macro, .variable {
373 background-color: PaleGoldenRod;
374 position: relative;
375}
376.macro { color: DarkMagenta; }
377
378#tooltiphint {
379 position: fixed;
380 width: 50em;
381 margin-left: -25em;
382 left: 50%;
383 padding: 10px;
384 border: 1px solid #b0b0b0;
385 border-radius: 2px;
386 box-shadow: 1px 1px 7px black;
387 background-color: #c0c0c0;
388 z-index: 2;
389}
390
391.num { width:2.5em; padding-right:2ex; background-color:#eeeeee }
392.num { text-align:right; font-size:8pt }
393.num { color:#444444 }
394.line { padding-left: 1ex; border-left: 3px solid #ccc }
395.line { white-space: pre }
396.msg { -webkit-box-shadow:1px 1px 7px #000 }
397.msg { box-shadow:1px 1px 7px #000 }
398.msg { -webkit-border-radius:5px }
399.msg { border-radius:5px }
400.msg { font-family:Helvetica, sans-serif; font-size:8pt }
401.msg { float:left }
402.msg { position:relative }
403.msg { padding:0.25em 1ex 0.25em 1ex }
404.msg { margin-top:10px; margin-bottom:10px }
405.msg { font-weight:bold }
406.msg { max-width:60em; word-wrap: break-word; white-space: pre-wrap }
407.msgT { padding:0x; spacing:0x }
408.msgEvent { background-color:#fff8b4; color:#000000 }
409.msgControl { background-color:#bbbbbb; color:#000000 }
410.msgNote { background-color:#ddeeff; color:#000000 }
411.mrange { background-color:#dfddf3 }
412.mrange { border-bottom:1px solid #6F9DBE }
413.PathIndex { font-weight: bold; padding:0px 5px; margin-right:5px; }
414.PathIndex { -webkit-border-radius:8px }
415.PathIndex { border-radius:8px }
416.PathIndexEvent { background-color:#bfba87 }
417.PathIndexControl { background-color:#8c8c8c }
418.PathIndexPopUp { background-color: #879abc; }
419.PathNav a { text-decoration:none; font-size: larger }
420.CodeInsertionHint { font-weight: bold; background-color: #10dd10 }
421.CodeRemovalHint { background-color:#de1010 }
422.CodeRemovalHint { border-bottom:1px solid #6F9DBE }
423.msg.selected{ background-color:orange !important; }
424
425table.simpletable {
426 padding: 5px;
427 font-size:12pt;
428 margin:20px;
429 border-collapse: collapse; border-spacing: 0px;
430}
431td.rowname {
432 text-align: right;
433 vertical-align: top;
434 font-weight: bold;
435 color:#444444;
436 padding-right:2ex;
437}
438
439/* Hidden text. */
440input.spoilerhider + label {
441 cursor: pointer;
442 text-decoration: underline;
443 display: block;
444}
445input.spoilerhider {
446 display: none;
447}
448input.spoilerhider ~ .spoiler {
449 overflow: hidden;
450 margin: 10px auto 0;
451 height: 0;
452 opacity: 0;
453}
454input.spoilerhider:checked + label + .spoiler{
455 height: auto;
456 opacity: 1;
457}
458</style>
459</head>
460<body>)<<<";
461
462 // Generate header
463 R.InsertTextBefore(StartLoc, os.str());
464 // Generate footer
465
466 R.InsertTextAfter(EndLoc, "</body></html>\n");
467}
468
469/// SyntaxHighlight - Relex the specified FileID and annotate the HTML with
470/// information about keywords, macro expansions etc. This uses the macro
471/// table state from the end of the file, so it won't be perfectly perfect,
472/// but it will be reasonably close.
473static void SyntaxHighlightImpl(
474 Rewriter &R, FileID FID, const Preprocessor &PP,
475 llvm::function_ref<void(RewriteBuffer &, unsigned, unsigned, const char *,
476 const char *, const char *)>
477 HighlightRangeCallback) {
478
479 RewriteBuffer &RB = R.getEditBuffer(FID);
480 const SourceManager &SM = PP.getSourceManager();
481 llvm::MemoryBufferRef FromFile = SM.getBufferOrFake(FID);
482 const char *BufferStart = FromFile.getBuffer().data();
483
484 Lexer L(FID, FromFile, SM, PP.getLangOpts());
485
486 // Inform the preprocessor that we want to retain comments as tokens, so we
487 // can highlight them.
489
490 // Lex all the tokens in raw mode, to avoid entering #includes or expanding
491 // macros.
492 Token Tok;
494
495 while (Tok.isNot(tok::eof)) {
496 // Since we are lexing unexpanded tokens, all tokens are from the main
497 // FileID.
498 unsigned TokOffs = SM.getFileOffset(Tok.getLocation());
499 unsigned TokLen = Tok.getLength();
500 switch (Tok.getKind()) {
501 default: break;
502 case tok::identifier:
503 llvm_unreachable("tok::identifier in raw lexing mode!");
504 case tok::raw_identifier: {
505 // Fill in Result.IdentifierInfo and update the token kind,
506 // looking up the identifier in the identifier table.
508
509 // If this is a pp-identifier, for a keyword, highlight it as such.
510 if (Tok.isNot(tok::identifier))
511 HighlightRangeCallback(RB, TokOffs, TokOffs + TokLen, BufferStart,
512 "<span class='keyword'>", "</span>");
513 break;
514 }
515 case tok::comment:
516 HighlightRangeCallback(RB, TokOffs, TokOffs + TokLen, BufferStart,
517 "<span class='comment'>", "</span>");
518 break;
519 case tok::utf8_string_literal:
520 // Chop off the u part of u8 prefix
521 ++TokOffs;
522 --TokLen;
523 // FALL THROUGH to chop the 8
524 [[fallthrough]];
525 case tok::wide_string_literal:
526 case tok::utf16_string_literal:
527 case tok::utf32_string_literal:
528 // Chop off the L, u, U or 8 prefix
529 ++TokOffs;
530 --TokLen;
531 [[fallthrough]];
532 case tok::string_literal:
533 // FIXME: Exclude the optional ud-suffix from the highlighted range.
534 HighlightRangeCallback(RB, TokOffs, TokOffs + TokLen, BufferStart,
535 "<span class='string_literal'>", "</span>");
536 break;
537 case tok::hash: {
538 // If this is a preprocessor directive, all tokens to end of line are too.
539 if (!Tok.isAtStartOfLine())
540 break;
541
542 // Eat all of the tokens until we get to the next one at the start of
543 // line.
544 unsigned TokEnd = TokOffs+TokLen;
546 while (!Tok.isAtStartOfLine() && Tok.isNot(tok::eof)) {
547 TokEnd = SM.getFileOffset(Tok.getLocation())+Tok.getLength();
549 }
550
551 // Find end of line. This is a hack.
552 HighlightRangeCallback(RB, TokOffs, TokEnd, BufferStart,
553 "<span class='directive'>", "</span>");
554
555 // Don't skip the next token.
556 continue;
557 }
558 }
559
561 }
562}
563void html::SyntaxHighlight(Rewriter &R, FileID FID, const Preprocessor &PP,
565 RewriteBuffer &RB = R.getEditBuffer(FID);
566 const SourceManager &SM = PP.getSourceManager();
567 llvm::MemoryBufferRef FromFile = SM.getBufferOrFake(FID);
568 const char *BufferStart = FromFile.getBuffer().data();
569
570 if (Cache) {
571 auto CacheIt = Cache->SyntaxHighlights.find(FID);
572 if (CacheIt != Cache->SyntaxHighlights.end()) {
573 for (const RelexRewriteCache::RawHighlight &H : CacheIt->second) {
574 HighlightRange(RB, H.B, H.E, BufferStart, H.StartTag.data(),
575 H.EndTag.data());
576 }
577 return;
578 }
579 }
580
581 // "Every time you would call HighlightRange, cache the inputs as well."
582 auto HighlightRangeCallback = [&](RewriteBuffer &RB, unsigned B, unsigned E,
583 const char *BufferStart,
584 const char *StartTag, const char *EndTag) {
585 HighlightRange(RB, B, E, BufferStart, StartTag, EndTag);
586
587 if (Cache)
588 Cache->SyntaxHighlights[FID].push_back({B, E, StartTag, EndTag});
589 };
590
591 SyntaxHighlightImpl(R, FID, PP, HighlightRangeCallback);
592}
593
594static void HighlightMacrosImpl(
595 Rewriter &R, FileID FID, const Preprocessor &PP,
596 llvm::function_ref<void(Rewriter &, SourceLocation, SourceLocation,
597 const char *, const char *, bool)>
598 HighlightRangeCallback) {
599
600 // Re-lex the raw token stream into a token buffer.
601 const SourceManager &SM = PP.getSourceManager();
602 std::vector<Token> TokenStream;
603
604 llvm::MemoryBufferRef FromFile = SM.getBufferOrFake(FID);
605 Lexer L(FID, FromFile, SM, PP.getLangOpts());
606
607 // Lex all the tokens in raw mode, to avoid entering #includes or expanding
608 // macros.
609 while (true) {
610 Token Tok;
611 L.LexFromRawLexer(Tok);
612
613 // If this is a # at the start of a line, discard it from the token stream.
614 // We don't want the re-preprocess step to see #defines, #includes or other
615 // preprocessor directives.
616 if (Tok.is(tok::hash) && Tok.isAtStartOfLine())
617 continue;
618
619 // If this is a ## token, change its kind to unknown so that repreprocessing
620 // it will not produce an error.
621 if (Tok.is(tok::hashhash))
622 Tok.setKind(tok::unknown);
623
624 // If this raw token is an identifier, the raw lexer won't have looked up
625 // the corresponding identifier info for it. Do this now so that it will be
626 // macro expanded when we re-preprocess it.
627 if (Tok.is(tok::raw_identifier))
629
630 TokenStream.push_back(Tok);
631
632 if (Tok.is(tok::eof)) break;
633 }
634
635 // Temporarily change the diagnostics object so that we ignore any generated
636 // diagnostics from this pass.
640
641 // FIXME: This is a huge hack; we reuse the input preprocessor because we want
642 // its state, but we aren't actually changing it (we hope). This should really
643 // construct a copy of the preprocessor.
644 Preprocessor &TmpPP = const_cast<Preprocessor&>(PP);
645 DiagnosticsEngine *OldDiags = &TmpPP.getDiagnostics();
646 TmpPP.setDiagnostics(TmpDiags);
647
648 // Inform the preprocessor that we don't want comments.
649 TmpPP.SetCommentRetentionState(false, false);
650
651 // We don't want pragmas either. Although we filtered out #pragma, removing
652 // _Pragma and __pragma is much harder.
653 bool PragmasPreviouslyEnabled = TmpPP.getPragmasEnabled();
654 TmpPP.setPragmasEnabled(false);
655
656 // Enter the tokens we just lexed. This will cause them to be macro expanded
657 // but won't enter sub-files (because we removed #'s).
658 TmpPP.EnterTokenStream(TokenStream, false, /*IsReinject=*/false);
659
660 TokenConcatenation ConcatInfo(TmpPP);
661
662 // Lex all the tokens.
663 Token Tok;
664 TmpPP.Lex(Tok);
665 while (Tok.isNot(tok::eof)) {
666 // Ignore non-macro tokens.
667 if (!Tok.getLocation().isMacroID()) {
668 TmpPP.Lex(Tok);
669 continue;
670 }
671
672 // Okay, we have the first token of a macro expansion: highlight the
673 // expansion by inserting a start tag before the macro expansion and
674 // end tag after it.
675 CharSourceRange LLoc = SM.getExpansionRange(Tok.getLocation());
676
677 // Ignore tokens whose instantiation location was not the main file.
678 if (SM.getFileID(LLoc.getBegin()) != FID) {
679 TmpPP.Lex(Tok);
680 continue;
681 }
682
683 assert(SM.getFileID(LLoc.getEnd()) == FID &&
684 "Start and end of expansion must be in the same ultimate file!");
685
686 std::string Expansion = EscapeText(TmpPP.getSpelling(Tok));
687 unsigned LineLen = Expansion.size();
688
689 Token PrevPrevTok;
690 Token PrevTok = Tok;
691 // Okay, eat this token, getting the next one.
692 TmpPP.Lex(Tok);
693
694 // Skip all the rest of the tokens that are part of this macro
695 // instantiation. It would be really nice to pop up a window with all the
696 // spelling of the tokens or something.
697 while (!Tok.is(tok::eof) &&
698 SM.getExpansionLoc(Tok.getLocation()) == LLoc.getBegin()) {
699 // Insert a newline if the macro expansion is getting large.
700 if (LineLen > 60) {
701 Expansion += "<br>";
702 LineLen = 0;
703 }
704
705 LineLen -= Expansion.size();
706
707 // If the tokens were already space separated, or if they must be to avoid
708 // them being implicitly pasted, add a space between them.
709 if (Tok.hasLeadingSpace() ||
710 ConcatInfo.AvoidConcat(PrevPrevTok, PrevTok, Tok))
711 Expansion += ' ';
712
713 // Escape any special characters in the token text.
714 Expansion += EscapeText(TmpPP.getSpelling(Tok));
715 LineLen += Expansion.size();
716
717 PrevPrevTok = PrevTok;
718 PrevTok = Tok;
719 TmpPP.Lex(Tok);
720 }
721
722 // Insert the 'macro_popup' as the end tag, so that multi-line macros all
723 // get highlighted.
724 Expansion = "<span class='macro_popup'>" + Expansion + "</span></span>";
725
726 HighlightRangeCallback(R, LLoc.getBegin(), LLoc.getEnd(),
727 "<span class='macro'>", Expansion.c_str(),
728 LLoc.isTokenRange());
729 }
730
731 // Restore the preprocessor's old state.
732 TmpPP.setDiagnostics(*OldDiags);
733 TmpPP.setPragmasEnabled(PragmasPreviouslyEnabled);
734}
735
736/// HighlightMacros - This uses the macro table state from the end of the
737/// file, to re-expand macros and insert (into the HTML) information about the
738/// macro expansions. This won't be perfectly perfect, but it will be
739/// reasonably close.
740void html::HighlightMacros(Rewriter &R, FileID FID, const Preprocessor &PP,
742 if (Cache) {
743 auto CacheIt = Cache->MacroHighlights.find(FID);
744 if (CacheIt != Cache->MacroHighlights.end()) {
745 for (const RelexRewriteCache::Highlight &H : CacheIt->second) {
746 HighlightRange(R, H.B, H.E, H.StartTag.data(), H.EndTag.data(),
747 H.IsTokenRange);
748 }
749 return;
750 }
751 }
752
753 // "Every time you would call HighlightRange, cache the inputs as well."
754 auto HighlightRangeCallback = [&](Rewriter &R, SourceLocation B,
755 SourceLocation E, const char *StartTag,
756 const char *EndTag, bool isTokenRange) {
757 HighlightRange(R, B, E, StartTag, EndTag, isTokenRange);
758
759 if (Cache) {
760 Cache->MacroHighlights[FID].push_back(
761 {B, E, StartTag, EndTag, isTokenRange});
762 }
763 };
764
765 HighlightMacrosImpl(R, FID, PP, HighlightRangeCallback);
766}
Token Tok
The Token.
static void AddLineNumber(RewriteBuffer &RB, unsigned LineNo, unsigned B, unsigned E)
static void HighlightMacrosImpl(Rewriter &R, FileID FID, const Preprocessor &PP, llvm::function_ref< void(Rewriter &, SourceLocation, SourceLocation, const char *, const char *, bool)> HighlightRangeCallback)
static void SyntaxHighlightImpl(Rewriter &R, FileID FID, const Preprocessor &PP, llvm::function_ref< void(RewriteBuffer &, unsigned, unsigned, const char *, const char *, const char *)> HighlightRangeCallback)
SyntaxHighlight - Relex the specified FileID and annotate the HTML with information about keywords,...
Defines the clang::Preprocessor interface.
Defines the SourceManager interface.
TypePropertyCache< Private > Cache
Definition Type.cpp:5083
Represents a byte-granular source range.
bool isTokenRange() const
Return true if the end of this range specifies the start of the last token.
SourceLocation getEnd() const
SourceLocation getBegin() const
Concrete class used by the front-end to report problems and issues.
Definition Diagnostic.h:232
DiagnosticOptions & getDiagnosticOptions() const
Retrieve the diagnostic options.
Definition Diagnostic.h:613
const IntrusiveRefCntPtr< DiagnosticIDs > & getDiagnosticIDs() const
Definition Diagnostic.h:608
An opaque identifier used by SourceManager which refers to a source file (MemoryBuffer) along with it...
int getOpaqueValue() const
Returns the raw integer representation of this FileID.
A diagnostic client that ignores all diagnostics.
Lexer - This provides a simple interface that turns a text buffer into a stream of tokens.
Definition Lexer.h:79
bool LexFromRawLexer(Token &Result)
LexFromRawLexer - Lex a token from a designated raw lexer (one with no associated preprocessor object...
Definition Lexer.h:236
void SetCommentRetentionState(bool Mode)
SetCommentRetentionMode - Change the comment retention mode of the lexer to the specified mode.
Definition Lexer.h:269
static unsigned MeasureTokenLength(SourceLocation Loc, const SourceManager &SM, const LangOptions &LangOpts)
MeasureTokenLength - Relex the token at the specified location and return its length in bytes in the ...
Definition Lexer.cpp:509
Engages in a tight little dance with the lexer to efficiently preprocess tokens.
IdentifierInfo * LookUpIdentifierInfo(Token &Identifier) const
Given a tok::raw_identifier token, look up the identifier information for the token and install it in...
void setDiagnostics(DiagnosticsEngine &D)
void Lex(Token &Result)
Lex the next token for this preprocessor.
SourceManager & getSourceManager() const
StringRef getSpelling(SourceLocation loc, SmallVectorImpl< char > &buffer, bool *invalid=nullptr) const
Return the 'spelling' of the token at the given location; does not go up to the spelling location or ...
const LangOptions & getLangOpts() const
void setPragmasEnabled(bool Enabled)
void SetCommentRetentionState(bool KeepComments, bool KeepMacroComments)
Control whether the preprocessor retains comments in output.
bool getPragmasEnabled() const
DiagnosticsEngine & getDiagnostics() const
Rewriter - This is the main interface to the rewrite buffers.
Definition Rewriter.h:32
Encodes a location in the source.
SourceLocation getLocWithOffset(IntTy Offset) const
Return a source location with the specified offset from this SourceLocation.
This class handles loading and caching of source files into memory.
FileID getFileID(SourceLocation SpellingLoc) const
Return the FileID for a SourceLocation.
unsigned getFileOffset(SourceLocation SpellingLoc) const
Returns the offset from the start of the file that the specified SourceLocation represents.
StringRef getBufferData(FileID FID, bool *Invalid=nullptr) const
Return a StringRef to the source buffer data for the specified FileID.
llvm::MemoryBufferRef getBufferOrFake(FileID FID, SourceLocation Loc=SourceLocation()) const
Return the buffer for the specified FileID.
CharSourceRange getExpansionRange(SourceLocation Loc) const
Given a SourceLocation object, return the range of tokens covered by the expansion in the ultimate fi...
SourceLocation getExpansionLoc(SourceLocation Loc) const
Given a SourceLocation object Loc, return the expansion location referenced by the ID.
TokenConcatenation class, which answers the question of "Is it safe to emit two tokens without a whit...
Token - This structure provides full information about a lexed token.
Definition Token.h:36
void AddHeaderFooterInternalBuiltinCSS(Rewriter &R, FileID FID, StringRef title)
void HighlightRange(Rewriter &R, SourceLocation B, SourceLocation E, const char *StartTag, const char *EndTag, bool IsTokenRange=true)
HighlightRange - Highlight a range in the source code with the specified start/end tags.
RelexRewriteCacheRef instantiateRelexRewriteCache()
If you need to rewrite the same file multiple times, you can instantiate a RelexRewriteCache and refe...
void AddLineNumbers(Rewriter &R, FileID FID)
void SyntaxHighlight(Rewriter &R, FileID FID, const Preprocessor &PP, RelexRewriteCacheRef Cache=nullptr)
SyntaxHighlight - Relex the specified FileID and annotate the HTML with information about keywords,...
void HighlightMacros(Rewriter &R, FileID FID, const Preprocessor &PP, RelexRewriteCacheRef Cache=nullptr)
HighlightMacros - This uses the macro table state from the end of the file, to reexpand macros and in...
void EscapeText(Rewriter &R, FileID FID, bool EscapeSpaces=false, bool ReplaceTabs=false)
EscapeText - HTMLize a specified file so that special characters are are translated so that they are ...
std::shared_ptr< RelexRewriteCache > RelexRewriteCacheRef
Definition HTMLRewrite.h:31
Top level wrappers for InstallAPI frontend operations.
Diagnostic wrappers for TextAPI types for error reporting.
Definition Dominators.h:30
std::vector< RawHighlight > RawHighlightList
DenseMap< FileID, RawHighlightList > SyntaxHighlights
std::vector< Highlight > HighlightList
DenseMap< FileID, HighlightList > MacroHighlights