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