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