blob: 1e073cbbb17fa491e2926c3ade171ae1044c8fe3 [file] [log] [blame]
Bill Wendling37b1dde2007-06-07 09:34:54 +00001//===--- TextDiagnosticPrinter.cpp - Diagnostic Printer -------------------===//
2//
3// The LLVM Compiler Infrastructure
4//
5// This file was developed by Bill Wendling and is distributed under the
6// University of Illinois Open Source License. See LICENSE.TXT for details.
7//
8//===----------------------------------------------------------------------===//
9//
10// This diagnostic client prints out their diagnostic messages.
11//
12//===----------------------------------------------------------------------===//
13
14#include "TextDiagnosticPrinter.h"
15#include "clang/Basic/FileManager.h"
16#include "clang/Basic/SourceManager.h"
17#include "clang/Lex/HeaderSearch.h"
18#include "clang/Lex/Lexer.h"
19#include "llvm/Support/CommandLine.h"
20#include "llvm/Support/MemoryBuffer.h"
21#include "llvm/Support/Streams.h"
22#include <iostream>
23#include <string>
24using namespace llvm;
25using namespace clang;
26
27static cl::opt<bool>
28NoShowColumn("fno-show-column",
29 cl::desc("Do not include column number on diagnostics"));
30static cl::opt<bool>
31NoCaretDiagnostics("fno-caret-diagnostics",
32 cl::desc("Do not include source line and caret with"
33 " diagnostics"));
34
35void TextDiagnosticPrinter::
36PrintIncludeStack(SourceLocation Pos) {
37 unsigned FileID = Pos.getFileID();
38 if (FileID == 0) return;
39
40 // Print out the other include frames first.
41 PrintIncludeStack(SourceMgr.getIncludeLoc(FileID));
42
43 unsigned LineNo = SourceMgr.getLineNumber(Pos);
44
45 const MemoryBuffer *Buffer = SourceMgr.getBuffer(FileID);
46 cerr << "In file included from " << Buffer->getBufferIdentifier()
47 << ":" << LineNo << ":\n";
48}
49
50
51/// HighlightRange - Given a SourceRange and a line number, highlight (with ~'s)
52/// any characters in LineNo that intersect the SourceRange.
53void TextDiagnosticPrinter::HighlightRange(const SourceRange &R,
54 unsigned LineNo,
55 std::string &CaratLine,
56 const std::string &SourceLine) {
57 assert(CaratLine.size() == SourceLine.size() &&
58 "Expect a correspondence between source and carat line!");
59 if (!R.isValid()) return;
60
61 unsigned StartLineNo = SourceMgr.getLineNumber(R.Begin());
62 if (StartLineNo > LineNo) return; // No intersection.
63
64 unsigned EndLineNo = SourceMgr.getLineNumber(R.End());
65 if (EndLineNo < LineNo) return; // No intersection.
66
67 // Compute the column number of the start.
68 unsigned StartColNo = 0;
69 if (StartLineNo == LineNo) {
70 StartColNo = SourceMgr.getColumnNumber(R.Begin());
71 if (StartColNo) --StartColNo; // Zero base the col #.
72 }
73
74 // Pick the first non-whitespace column.
75 while (StartColNo < SourceLine.size() &&
76 (SourceLine[StartColNo] == ' ' || SourceLine[StartColNo] == '\t'))
77 ++StartColNo;
78
79 // Compute the column number of the end.
80 unsigned EndColNo = CaratLine.size();
81 if (EndLineNo == LineNo) {
82 EndColNo = SourceMgr.getColumnNumber(R.End());
83 if (EndColNo) {
84 --EndColNo; // Zero base the col #.
85
86 // Add in the length of the token, so that we cover multi-char tokens.
87 EndColNo += GetTokenLength(R.End());
88 } else {
89 EndColNo = CaratLine.size();
90 }
91 }
92
93 // Pick the last non-whitespace column.
94 while (EndColNo-1 &&
95 (SourceLine[EndColNo-1] == ' ' || SourceLine[EndColNo-1] == '\t'))
96 --EndColNo;
97
98 // Fill the range with ~'s.
99 assert(StartColNo <= EndColNo && "Invalid range!");
100 for (unsigned i = StartColNo; i != EndColNo; ++i)
101 CaratLine[i] = '~';
102}
103
104
105/// GetTokenLength - Given the source location of a token, determine its length.
106/// This is a fully general function that uses a lexer to relex the token.
107unsigned TextDiagnosticPrinter::GetTokenLength(SourceLocation Loc) {
108 const char *StrData =
109 SourceMgr.getCharacterData(SourceMgr.getLogicalLoc(Loc));
110
111 // Note, this could be special cased for common tokens like identifiers, ')',
112 // etc to make this faster, if it mattered.
113
114 unsigned FileID = Loc.getFileID();
115
116 // Create a lexer starting at the beginning of this token.
117 Lexer TheLexer(SourceMgr.getBuffer(FileID), FileID,
118 *ThePreprocessor, StrData);
119
120 LexerToken TheTok;
121 TheLexer.LexRawToken(TheTok);
122
123 return TheTok.getLength();
124}
125
126
127void TextDiagnosticPrinter::HandleDiagnostic(Diagnostic::Level Level,
128 SourceLocation Pos,
129 diag::kind ID,
130 const std::string *Strs,
131 unsigned NumStrs,
132 const SourceRange *Ranges,
133 unsigned NumRanges) {
134 unsigned LineNo = 0, FilePos = 0, FileID = 0, ColNo = 0;
135 unsigned LineStart = 0, LineEnd = 0;
136 const MemoryBuffer *Buffer = 0;
137
138 if (Pos.isValid()) {
139 LineNo = SourceMgr.getLineNumber(Pos);
140 FileID = SourceMgr.getLogicalLoc(Pos).getFileID();
141
142 // If this is a warning or note, and if it a system header, suppress the
143 // diagnostic.
144 if (Level == Diagnostic::Warning ||
145 Level == Diagnostic::Note) {
146 SourceLocation PhysLoc = SourceMgr.getPhysicalLoc(Pos);
147 const FileEntry *F = SourceMgr.getFileEntryForFileID(PhysLoc.getFileID());
148 DirectoryLookup::DirType DirInfo = TheHeaderSearch->getFileDirFlavor(F);
149 if (DirInfo == DirectoryLookup::SystemHeaderDir ||
150 DirInfo == DirectoryLookup::ExternCSystemHeaderDir)
151 return;
152 }
153
154 // First, if this diagnostic is not in the main file, print out the
155 // "included from" lines.
156 if (LastWarningLoc != SourceMgr.getIncludeLoc(FileID)) {
157 LastWarningLoc = SourceMgr.getIncludeLoc(FileID);
158 PrintIncludeStack(LastWarningLoc);
159 }
160
161 // Compute the column number. Rewind from the current position to the start
162 // of the line.
163 ColNo = SourceMgr.getColumnNumber(Pos);
164 FilePos = SourceMgr.getSourceFilePos(Pos);
165 LineStart = FilePos-ColNo+1; // Column # is 1-based
166
167 // Compute the line end. Scan forward from the error position to the end of
168 // the line.
169 Buffer = SourceMgr.getBuffer(FileID);
170 const char *Buf = Buffer->getBufferStart();
171 const char *BufEnd = Buffer->getBufferEnd();
172 LineEnd = FilePos;
173 while (Buf+LineEnd != BufEnd &&
174 Buf[LineEnd] != '\n' && Buf[LineEnd] != '\r')
175 ++LineEnd;
176
177 cerr << Buffer->getBufferIdentifier()
178 << ":" << LineNo << ":";
179 if (ColNo && !NoShowColumn)
180 cerr << ColNo << ":";
181 cerr << " ";
182 }
183
184 switch (Level) {
185 default: assert(0 && "Unknown diagnostic type!");
186 case Diagnostic::Note: cerr << "note: "; break;
187 case Diagnostic::Warning: cerr << "warning: "; break;
188 case Diagnostic::Error: ++NumErrors; cerr << "error: "; break;
189 case Diagnostic::Fatal: ++NumErrors; cerr << "fatal error: "; break;
190 case Diagnostic::Sorry: ++NumErrors; cerr << "sorry, unimplemented: ";
191 break;
192 }
193
194 std::string Msg = Diagnostic::getDescription(ID);
195
196 // Replace all instances of %0 in Msg with 'Extra'.
197 for (unsigned i = 0; i < Msg.size()-1; ++i) {
198 if (Msg[i] == '%' && isdigit(Msg[i+1])) {
199 unsigned StrNo = Msg[i+1]-'0';
200 Msg = std::string(Msg.begin(), Msg.begin()+i) +
201 (StrNo < NumStrs ? Strs[StrNo] : "<<<INTERNAL ERROR>>>") +
202 std::string(Msg.begin()+i+2, Msg.end());
203 }
204 }
205 cerr << Msg << "\n";
206
207 if (!NoCaretDiagnostics && Pos.isValid()) {
208 // Get the line of the source file.
209 const char *Buf = Buffer->getBufferStart();
210 std::string SourceLine(Buf+LineStart, Buf+LineEnd);
211
212 // Create a line for the carat that is filled with spaces that is the same
213 // length as the line of source code.
214 std::string CaratLine(LineEnd-LineStart, ' ');
215
216 // Highlight all of the characters covered by Ranges with ~ characters.
217 for (unsigned i = 0; i != NumRanges; ++i)
218 HighlightRange(Ranges[i], LineNo, CaratLine, SourceLine);
219
220 // Next, insert the carat itself.
221 if (ColNo-1 < CaratLine.size())
222 CaratLine[ColNo-1] = '^';
223 else
224 CaratLine.push_back('^');
225
226 // Scan the source line, looking for tabs. If we find any, manually expand
227 // them to 8 characters and update the CaratLine to match.
228 for (unsigned i = 0; i != SourceLine.size(); ++i) {
229 if (SourceLine[i] != '\t') continue;
230
231 // Replace this tab with at least one space.
232 SourceLine[i] = ' ';
233
234 // Compute the number of spaces we need to insert.
235 unsigned NumSpaces = ((i+8)&~7) - (i+1);
236 assert(NumSpaces < 8 && "Invalid computation of space amt");
237
238 // Insert spaces into the SourceLine.
239 SourceLine.insert(i+1, NumSpaces, ' ');
240
241 // Insert spaces or ~'s into CaratLine.
242 CaratLine.insert(i+1, NumSpaces, CaratLine[i] == '~' ? '~' : ' ');
243 }
244
245 // Finally, remove any blank spaces from the end of CaratLine.
246 while (CaratLine[CaratLine.size()-1] == ' ')
247 CaratLine.erase(CaratLine.end()-1);
248
249 // Emit what we have computed.
250 cerr << SourceLine << "\n";
251 cerr << CaratLine << "\n";
252 }
253
254 ++NumDiagnostics;
255}