1 | //===- Tokens.cpp - collect tokens from preprocessing ---------------------===// |
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 | #include "clang/Tooling/Syntax/Tokens.h" |
9 | |
10 | #include "clang/Basic/Diagnostic.h" |
11 | #include "clang/Basic/IdentifierTable.h" |
12 | #include "clang/Basic/LLVM.h" |
13 | #include "clang/Basic/LangOptions.h" |
14 | #include "clang/Basic/SourceLocation.h" |
15 | #include "clang/Basic/SourceManager.h" |
16 | #include "clang/Basic/TokenKinds.h" |
17 | #include "clang/Lex/PPCallbacks.h" |
18 | #include "clang/Lex/Preprocessor.h" |
19 | #include "clang/Lex/Token.h" |
20 | #include "llvm/ADT/ArrayRef.h" |
21 | #include "llvm/ADT/STLExtras.h" |
22 | #include "llvm/Support/Debug.h" |
23 | #include "llvm/Support/ErrorHandling.h" |
24 | #include "llvm/Support/FormatVariadic.h" |
25 | #include "llvm/Support/raw_ostream.h" |
26 | #include <algorithm> |
27 | #include <cassert> |
28 | #include <iterator> |
29 | #include <optional> |
30 | #include <string> |
31 | #include <utility> |
32 | #include <vector> |
33 | |
34 | using namespace clang; |
35 | using namespace clang::syntax; |
36 | |
37 | namespace { |
38 | // Finds the smallest consecutive subsuquence of Toks that covers R. |
39 | llvm::ArrayRef<syntax::Token> |
40 | getTokensCovering(llvm::ArrayRef<syntax::Token> Toks, SourceRange R, |
41 | const SourceManager &SM) { |
42 | if (R.isInvalid()) |
43 | return {}; |
44 | const syntax::Token *Begin = |
45 | llvm::partition_point(Range&: Toks, P: [&](const syntax::Token &T) { |
46 | return SM.isBeforeInTranslationUnit(LHS: T.location(), RHS: R.getBegin()); |
47 | }); |
48 | const syntax::Token *End = |
49 | llvm::partition_point(Range&: Toks, P: [&](const syntax::Token &T) { |
50 | return !SM.isBeforeInTranslationUnit(LHS: R.getEnd(), RHS: T.location()); |
51 | }); |
52 | if (Begin > End) |
53 | return {}; |
54 | return {Begin, End}; |
55 | } |
56 | |
57 | // Finds the range within FID corresponding to expanded tokens [First, Last]. |
58 | // Prev precedes First and Next follows Last, these must *not* be included. |
59 | // If no range satisfies the criteria, returns an invalid range. |
60 | // |
61 | // #define ID(x) x |
62 | // ID(ID(ID(a1) a2)) |
63 | // ~~ -> a1 |
64 | // ~~ -> a2 |
65 | // ~~~~~~~~~ -> a1 a2 |
66 | SourceRange spelledForExpandedSlow(SourceLocation First, SourceLocation Last, |
67 | SourceLocation Prev, SourceLocation Next, |
68 | FileID TargetFile, |
69 | const SourceManager &SM) { |
70 | // There are two main parts to this algorithm: |
71 | // - identifying which spelled range covers the expanded tokens |
72 | // - validating that this range doesn't cover any extra tokens (First/Last) |
73 | // |
74 | // We do these in order. However as we transform the expanded range into the |
75 | // spelled one, we adjust First/Last so the validation remains simple. |
76 | |
77 | assert(SM.getSLocEntry(TargetFile).isFile()); |
78 | // In most cases, to select First and Last we must return their expansion |
79 | // range, i.e. the whole of any macros they are included in. |
80 | // |
81 | // When First and Last are part of the *same macro arg* of a macro written |
82 | // in TargetFile, we that slice of the arg, i.e. their spelling range. |
83 | // |
84 | // Unwrap such macro calls. If the target file has A(B(C)), the |
85 | // SourceLocation stack of a token inside C shows us the expansion of A first, |
86 | // then B, then any macros inside C's body, then C itself. |
87 | // (This is the reverse of the order the PP applies the expansions in). |
88 | while (First.isMacroID() && Last.isMacroID()) { |
89 | auto DecFirst = SM.getDecomposedLoc(Loc: First); |
90 | auto DecLast = SM.getDecomposedLoc(Loc: Last); |
91 | auto &ExpFirst = SM.getSLocEntry(FID: DecFirst.first).getExpansion(); |
92 | auto &ExpLast = SM.getSLocEntry(FID: DecLast.first).getExpansion(); |
93 | |
94 | if (!ExpFirst.isMacroArgExpansion() || !ExpLast.isMacroArgExpansion()) |
95 | break; |
96 | // Locations are in the same macro arg if they expand to the same place. |
97 | // (They may still have different FileIDs - an arg can have >1 chunks!) |
98 | if (ExpFirst.getExpansionLocStart() != ExpLast.getExpansionLocStart()) |
99 | break; |
100 | // Careful, given: |
101 | // #define HIDE ID(ID(a)) |
102 | // ID(ID(HIDE)) |
103 | // The token `a` is wrapped in 4 arg-expansions, we only want to unwrap 2. |
104 | // We distinguish them by whether the macro expands into the target file. |
105 | // Fortunately, the target file ones will always appear first. |
106 | auto ExpFileID = SM.getFileID(SpellingLoc: ExpFirst.getExpansionLocStart()); |
107 | if (ExpFileID == TargetFile) |
108 | break; |
109 | // Replace each endpoint with its spelling inside the macro arg. |
110 | // (This is getImmediateSpellingLoc without repeating lookups). |
111 | First = ExpFirst.getSpellingLoc().getLocWithOffset(Offset: DecFirst.second); |
112 | Last = ExpLast.getSpellingLoc().getLocWithOffset(Offset: DecLast.second); |
113 | } |
114 | |
115 | // In all remaining cases we need the full containing macros. |
116 | // If this overlaps Prev or Next, then no range is possible. |
117 | SourceRange Candidate = |
118 | SM.getExpansionRange(Range: SourceRange(First, Last)).getAsRange(); |
119 | auto DecFirst = SM.getDecomposedExpansionLoc(Loc: Candidate.getBegin()); |
120 | auto DecLast = SM.getDecomposedExpansionLoc(Loc: Candidate.getEnd()); |
121 | // Can end up in the wrong file due to bad input or token-pasting shenanigans. |
122 | if (Candidate.isInvalid() || DecFirst.first != TargetFile || |
123 | DecLast.first != TargetFile) |
124 | return SourceRange(); |
125 | // Check bounds, which may still be inside macros. |
126 | if (Prev.isValid()) { |
127 | auto Dec = SM.getDecomposedLoc(Loc: SM.getExpansionRange(Loc: Prev).getBegin()); |
128 | if (Dec.first != DecFirst.first || Dec.second >= DecFirst.second) |
129 | return SourceRange(); |
130 | } |
131 | if (Next.isValid()) { |
132 | auto Dec = SM.getDecomposedLoc(Loc: SM.getExpansionRange(Loc: Next).getEnd()); |
133 | if (Dec.first != DecLast.first || Dec.second <= DecLast.second) |
134 | return SourceRange(); |
135 | } |
136 | // Now we know that Candidate is a file range that covers [First, Last] |
137 | // without encroaching on {Prev, Next}. Ship it! |
138 | return Candidate; |
139 | } |
140 | |
141 | } // namespace |
142 | |
143 | syntax::Token::Token(SourceLocation Location, unsigned Length, |
144 | tok::TokenKind Kind) |
145 | : Location(Location), Length(Length), Kind(Kind) { |
146 | assert(Location.isValid()); |
147 | } |
148 | |
149 | syntax::Token::Token(const clang::Token &T) |
150 | : Token(T.getLocation(), T.getLength(), T.getKind()) { |
151 | assert(!T.isAnnotation()); |
152 | } |
153 | |
154 | llvm::StringRef syntax::Token::text(const SourceManager &SM) const { |
155 | bool Invalid = false; |
156 | const char *Start = SM.getCharacterData(SL: location(), Invalid: &Invalid); |
157 | assert(!Invalid); |
158 | return llvm::StringRef(Start, length()); |
159 | } |
160 | |
161 | FileRange syntax::Token::range(const SourceManager &SM) const { |
162 | assert(location().isFileID() && "must be a spelled token" ); |
163 | FileID File; |
164 | unsigned StartOffset; |
165 | std::tie(args&: File, args&: StartOffset) = SM.getDecomposedLoc(Loc: location()); |
166 | return FileRange(File, StartOffset, StartOffset + length()); |
167 | } |
168 | |
169 | FileRange syntax::Token::range(const SourceManager &SM, |
170 | const syntax::Token &First, |
171 | const syntax::Token &Last) { |
172 | auto F = First.range(SM); |
173 | auto L = Last.range(SM); |
174 | assert(F.file() == L.file() && "tokens from different files" ); |
175 | assert((F == L || F.endOffset() <= L.beginOffset()) && |
176 | "wrong order of tokens" ); |
177 | return FileRange(F.file(), F.beginOffset(), L.endOffset()); |
178 | } |
179 | |
180 | llvm::raw_ostream &syntax::operator<<(llvm::raw_ostream &OS, const Token &T) { |
181 | return OS << T.str(); |
182 | } |
183 | |
184 | FileRange::FileRange(FileID File, unsigned BeginOffset, unsigned EndOffset) |
185 | : File(File), Begin(BeginOffset), End(EndOffset) { |
186 | assert(File.isValid()); |
187 | assert(BeginOffset <= EndOffset); |
188 | } |
189 | |
190 | FileRange::FileRange(const SourceManager &SM, SourceLocation BeginLoc, |
191 | unsigned Length) { |
192 | assert(BeginLoc.isValid()); |
193 | assert(BeginLoc.isFileID()); |
194 | |
195 | std::tie(args&: File, args&: Begin) = SM.getDecomposedLoc(Loc: BeginLoc); |
196 | End = Begin + Length; |
197 | } |
198 | FileRange::FileRange(const SourceManager &SM, SourceLocation BeginLoc, |
199 | SourceLocation EndLoc) { |
200 | assert(BeginLoc.isValid()); |
201 | assert(BeginLoc.isFileID()); |
202 | assert(EndLoc.isValid()); |
203 | assert(EndLoc.isFileID()); |
204 | assert(SM.getFileID(BeginLoc) == SM.getFileID(EndLoc)); |
205 | assert(SM.getFileOffset(BeginLoc) <= SM.getFileOffset(EndLoc)); |
206 | |
207 | std::tie(args&: File, args&: Begin) = SM.getDecomposedLoc(Loc: BeginLoc); |
208 | End = SM.getFileOffset(SpellingLoc: EndLoc); |
209 | } |
210 | |
211 | llvm::raw_ostream &syntax::operator<<(llvm::raw_ostream &OS, |
212 | const FileRange &R) { |
213 | return OS << llvm::formatv(Fmt: "FileRange(file = {0}, offsets = {1}-{2})" , |
214 | Vals: R.file().getHashValue(), Vals: R.beginOffset(), |
215 | Vals: R.endOffset()); |
216 | } |
217 | |
218 | llvm::StringRef FileRange::text(const SourceManager &SM) const { |
219 | bool Invalid = false; |
220 | StringRef Text = SM.getBufferData(FID: File, Invalid: &Invalid); |
221 | if (Invalid) |
222 | return "" ; |
223 | assert(Begin <= Text.size()); |
224 | assert(End <= Text.size()); |
225 | return Text.substr(Start: Begin, N: length()); |
226 | } |
227 | |
228 | void TokenBuffer::indexExpandedTokens() { |
229 | // No-op if the index is already created. |
230 | if (!ExpandedTokIndex.empty()) |
231 | return; |
232 | ExpandedTokIndex.reserve(NumEntries: ExpandedTokens.size()); |
233 | // Index ExpandedTokens for faster lookups by SourceLocation. |
234 | for (size_t I = 0, E = ExpandedTokens.size(); I != E; ++I) { |
235 | SourceLocation Loc = ExpandedTokens[I].location(); |
236 | if (Loc.isValid()) |
237 | ExpandedTokIndex[Loc] = I; |
238 | } |
239 | } |
240 | |
241 | llvm::ArrayRef<syntax::Token> TokenBuffer::expandedTokens(SourceRange R) const { |
242 | if (R.isInvalid()) |
243 | return {}; |
244 | if (!ExpandedTokIndex.empty()) { |
245 | // Quick lookup if `R` is a token range. |
246 | // This is a huge win since majority of the users use ranges provided by an |
247 | // AST. Ranges in AST are token ranges from expanded token stream. |
248 | const auto B = ExpandedTokIndex.find(Val: R.getBegin()); |
249 | const auto E = ExpandedTokIndex.find(Val: R.getEnd()); |
250 | if (B != ExpandedTokIndex.end() && E != ExpandedTokIndex.end()) { |
251 | const Token *L = ExpandedTokens.data() + B->getSecond(); |
252 | // Add 1 to End to make a half-open range. |
253 | const Token *R = ExpandedTokens.data() + E->getSecond() + 1; |
254 | if (L > R) |
255 | return {}; |
256 | return {L, R}; |
257 | } |
258 | } |
259 | // Slow case. Use `isBeforeInTranslationUnit` to binary search for the |
260 | // required range. |
261 | return getTokensCovering(Toks: expandedTokens(), R, SM: *SourceMgr); |
262 | } |
263 | |
264 | CharSourceRange FileRange::toCharRange(const SourceManager &SM) const { |
265 | return CharSourceRange( |
266 | SourceRange(SM.getComposedLoc(FID: File, Offset: Begin), SM.getComposedLoc(FID: File, Offset: End)), |
267 | /*IsTokenRange=*/false); |
268 | } |
269 | |
270 | std::pair<const syntax::Token *, const TokenBuffer::Mapping *> |
271 | TokenBuffer::spelledForExpandedToken(const syntax::Token *Expanded) const { |
272 | assert(Expanded); |
273 | assert(ExpandedTokens.data() <= Expanded && |
274 | Expanded < ExpandedTokens.data() + ExpandedTokens.size()); |
275 | |
276 | auto FileIt = Files.find( |
277 | Val: SourceMgr->getFileID(SpellingLoc: SourceMgr->getExpansionLoc(Loc: Expanded->location()))); |
278 | assert(FileIt != Files.end() && "no file for an expanded token" ); |
279 | |
280 | const MarkedFile &File = FileIt->second; |
281 | |
282 | unsigned ExpandedIndex = Expanded - ExpandedTokens.data(); |
283 | // Find the first mapping that produced tokens after \p Expanded. |
284 | auto It = llvm::partition_point(Range: File.Mappings, P: [&](const Mapping &M) { |
285 | return M.BeginExpanded <= ExpandedIndex; |
286 | }); |
287 | // Our token could only be produced by the previous mapping. |
288 | if (It == File.Mappings.begin()) { |
289 | // No previous mapping, no need to modify offsets. |
290 | return {&File.SpelledTokens[ExpandedIndex - File.BeginExpanded], |
291 | /*Mapping=*/nullptr}; |
292 | } |
293 | --It; // 'It' now points to last mapping that started before our token. |
294 | |
295 | // Check if the token is part of the mapping. |
296 | if (ExpandedIndex < It->EndExpanded) |
297 | return {&File.SpelledTokens[It->BeginSpelled], /*Mapping=*/&*It}; |
298 | |
299 | // Not part of the mapping, use the index from previous mapping to compute the |
300 | // corresponding spelled token. |
301 | return { |
302 | &File.SpelledTokens[It->EndSpelled + (ExpandedIndex - It->EndExpanded)], |
303 | /*Mapping=*/nullptr}; |
304 | } |
305 | |
306 | const TokenBuffer::Mapping * |
307 | TokenBuffer::mappingStartingBeforeSpelled(const MarkedFile &F, |
308 | const syntax::Token *Spelled) { |
309 | assert(F.SpelledTokens.data() <= Spelled); |
310 | unsigned SpelledI = Spelled - F.SpelledTokens.data(); |
311 | assert(SpelledI < F.SpelledTokens.size()); |
312 | |
313 | auto It = llvm::partition_point(Range: F.Mappings, P: [SpelledI](const Mapping &M) { |
314 | return M.BeginSpelled <= SpelledI; |
315 | }); |
316 | if (It == F.Mappings.begin()) |
317 | return nullptr; |
318 | --It; |
319 | return &*It; |
320 | } |
321 | |
322 | llvm::SmallVector<llvm::ArrayRef<syntax::Token>, 1> |
323 | TokenBuffer::expandedForSpelled(llvm::ArrayRef<syntax::Token> Spelled) const { |
324 | if (Spelled.empty()) |
325 | return {}; |
326 | const auto &File = fileForSpelled(Spelled); |
327 | |
328 | auto *FrontMapping = mappingStartingBeforeSpelled(F: File, Spelled: &Spelled.front()); |
329 | unsigned SpelledFrontI = &Spelled.front() - File.SpelledTokens.data(); |
330 | assert(SpelledFrontI < File.SpelledTokens.size()); |
331 | unsigned ExpandedBegin; |
332 | if (!FrontMapping) { |
333 | // No mapping that starts before the first token of Spelled, we don't have |
334 | // to modify offsets. |
335 | ExpandedBegin = File.BeginExpanded + SpelledFrontI; |
336 | } else if (SpelledFrontI < FrontMapping->EndSpelled) { |
337 | // This mapping applies to Spelled tokens. |
338 | if (SpelledFrontI != FrontMapping->BeginSpelled) { |
339 | // Spelled tokens don't cover the entire mapping, returning empty result. |
340 | return {}; // FIXME: support macro arguments. |
341 | } |
342 | // Spelled tokens start at the beginning of this mapping. |
343 | ExpandedBegin = FrontMapping->BeginExpanded; |
344 | } else { |
345 | // Spelled tokens start after the mapping ends (they start in the hole |
346 | // between 2 mappings, or between a mapping and end of the file). |
347 | ExpandedBegin = |
348 | FrontMapping->EndExpanded + (SpelledFrontI - FrontMapping->EndSpelled); |
349 | } |
350 | |
351 | auto *BackMapping = mappingStartingBeforeSpelled(F: File, Spelled: &Spelled.back()); |
352 | unsigned SpelledBackI = &Spelled.back() - File.SpelledTokens.data(); |
353 | unsigned ExpandedEnd; |
354 | if (!BackMapping) { |
355 | // No mapping that starts before the last token of Spelled, we don't have to |
356 | // modify offsets. |
357 | ExpandedEnd = File.BeginExpanded + SpelledBackI + 1; |
358 | } else if (SpelledBackI < BackMapping->EndSpelled) { |
359 | // This mapping applies to Spelled tokens. |
360 | if (SpelledBackI + 1 != BackMapping->EndSpelled) { |
361 | // Spelled tokens don't cover the entire mapping, returning empty result. |
362 | return {}; // FIXME: support macro arguments. |
363 | } |
364 | ExpandedEnd = BackMapping->EndExpanded; |
365 | } else { |
366 | // Spelled tokens end after the mapping ends. |
367 | ExpandedEnd = |
368 | BackMapping->EndExpanded + (SpelledBackI - BackMapping->EndSpelled) + 1; |
369 | } |
370 | |
371 | assert(ExpandedBegin < ExpandedTokens.size()); |
372 | assert(ExpandedEnd < ExpandedTokens.size()); |
373 | // Avoid returning empty ranges. |
374 | if (ExpandedBegin == ExpandedEnd) |
375 | return {}; |
376 | return {llvm::ArrayRef(ExpandedTokens.data() + ExpandedBegin, |
377 | ExpandedTokens.data() + ExpandedEnd)}; |
378 | } |
379 | |
380 | llvm::ArrayRef<syntax::Token> TokenBuffer::spelledTokens(FileID FID) const { |
381 | auto It = Files.find(Val: FID); |
382 | assert(It != Files.end()); |
383 | return It->second.SpelledTokens; |
384 | } |
385 | |
386 | const syntax::Token *TokenBuffer::spelledTokenAt(SourceLocation Loc) const { |
387 | assert(Loc.isFileID()); |
388 | const auto *Tok = llvm::partition_point( |
389 | Range: spelledTokens(FID: SourceMgr->getFileID(SpellingLoc: Loc)), |
390 | P: [&](const syntax::Token &Tok) { return Tok.location() < Loc; }); |
391 | if (!Tok || Tok->location() != Loc) |
392 | return nullptr; |
393 | return Tok; |
394 | } |
395 | |
396 | std::string TokenBuffer::Mapping::str() const { |
397 | return std::string( |
398 | llvm::formatv(Fmt: "spelled tokens: [{0},{1}), expanded tokens: [{2},{3})" , |
399 | Vals: BeginSpelled, Vals: EndSpelled, Vals: BeginExpanded, Vals: EndExpanded)); |
400 | } |
401 | |
402 | std::optional<llvm::ArrayRef<syntax::Token>> |
403 | TokenBuffer::spelledForExpanded(llvm::ArrayRef<syntax::Token> Expanded) const { |
404 | // In cases of invalid code, AST nodes can have source ranges that include |
405 | // the `eof` token. As there's no spelling for this token, exclude it from |
406 | // the range. |
407 | if (!Expanded.empty() && Expanded.back().kind() == tok::eof) { |
408 | Expanded = Expanded.drop_back(); |
409 | } |
410 | // Mapping an empty range is ambiguous in case of empty mappings at either end |
411 | // of the range, bail out in that case. |
412 | if (Expanded.empty()) |
413 | return std::nullopt; |
414 | const syntax::Token *First = &Expanded.front(); |
415 | const syntax::Token *Last = &Expanded.back(); |
416 | auto [FirstSpelled, FirstMapping] = spelledForExpandedToken(Expanded: First); |
417 | auto [LastSpelled, LastMapping] = spelledForExpandedToken(Expanded: Last); |
418 | |
419 | FileID FID = SourceMgr->getFileID(SpellingLoc: FirstSpelled->location()); |
420 | // FIXME: Handle multi-file changes by trying to map onto a common root. |
421 | if (FID != SourceMgr->getFileID(SpellingLoc: LastSpelled->location())) |
422 | return std::nullopt; |
423 | |
424 | const MarkedFile &File = Files.find(Val: FID)->second; |
425 | |
426 | // If the range is within one macro argument, the result may be only part of a |
427 | // Mapping. We must use the general (SourceManager-based) algorithm. |
428 | if (FirstMapping && FirstMapping == LastMapping && |
429 | SourceMgr->isMacroArgExpansion(Loc: First->location()) && |
430 | SourceMgr->isMacroArgExpansion(Loc: Last->location())) { |
431 | // We use excluded Prev/Next token for bounds checking. |
432 | SourceLocation Prev = (First == &ExpandedTokens.front()) |
433 | ? SourceLocation() |
434 | : (First - 1)->location(); |
435 | SourceLocation Next = (Last == &ExpandedTokens.back()) |
436 | ? SourceLocation() |
437 | : (Last + 1)->location(); |
438 | SourceRange Range = spelledForExpandedSlow( |
439 | First: First->location(), Last: Last->location(), Prev, Next, TargetFile: FID, SM: *SourceMgr); |
440 | if (Range.isInvalid()) |
441 | return std::nullopt; |
442 | return getTokensCovering(Toks: File.SpelledTokens, R: Range, SM: *SourceMgr); |
443 | } |
444 | |
445 | // Otherwise, use the fast version based on Mappings. |
446 | // Do not allow changes that doesn't cover full expansion. |
447 | unsigned FirstExpanded = Expanded.begin() - ExpandedTokens.data(); |
448 | unsigned LastExpanded = Expanded.end() - ExpandedTokens.data(); |
449 | if (FirstMapping && FirstExpanded != FirstMapping->BeginExpanded) |
450 | return std::nullopt; |
451 | if (LastMapping && LastMapping->EndExpanded != LastExpanded) |
452 | return std::nullopt; |
453 | return llvm::ArrayRef( |
454 | FirstMapping ? File.SpelledTokens.data() + FirstMapping->BeginSpelled |
455 | : FirstSpelled, |
456 | LastMapping ? File.SpelledTokens.data() + LastMapping->EndSpelled |
457 | : LastSpelled + 1); |
458 | } |
459 | |
460 | TokenBuffer::Expansion TokenBuffer::makeExpansion(const MarkedFile &F, |
461 | const Mapping &M) const { |
462 | Expansion E; |
463 | E.Spelled = llvm::ArrayRef(F.SpelledTokens.data() + M.BeginSpelled, |
464 | F.SpelledTokens.data() + M.EndSpelled); |
465 | E.Expanded = llvm::ArrayRef(ExpandedTokens.data() + M.BeginExpanded, |
466 | ExpandedTokens.data() + M.EndExpanded); |
467 | return E; |
468 | } |
469 | |
470 | const TokenBuffer::MarkedFile & |
471 | TokenBuffer::fileForSpelled(llvm::ArrayRef<syntax::Token> Spelled) const { |
472 | assert(!Spelled.empty()); |
473 | assert(Spelled.front().location().isFileID() && "not a spelled token" ); |
474 | auto FileIt = Files.find(Val: SourceMgr->getFileID(SpellingLoc: Spelled.front().location())); |
475 | assert(FileIt != Files.end() && "file not tracked by token buffer" ); |
476 | const auto &File = FileIt->second; |
477 | assert(File.SpelledTokens.data() <= Spelled.data() && |
478 | Spelled.end() <= |
479 | (File.SpelledTokens.data() + File.SpelledTokens.size()) && |
480 | "Tokens not in spelled range" ); |
481 | #ifndef NDEBUG |
482 | auto T1 = Spelled.back().location(); |
483 | auto T2 = File.SpelledTokens.back().location(); |
484 | assert(T1 == T2 || sourceManager().isBeforeInTranslationUnit(T1, T2)); |
485 | #endif |
486 | return File; |
487 | } |
488 | |
489 | std::optional<TokenBuffer::Expansion> |
490 | TokenBuffer::expansionStartingAt(const syntax::Token *Spelled) const { |
491 | assert(Spelled); |
492 | const auto &File = fileForSpelled(Spelled: *Spelled); |
493 | |
494 | unsigned SpelledIndex = Spelled - File.SpelledTokens.data(); |
495 | auto M = llvm::partition_point(Range: File.Mappings, P: [&](const Mapping &M) { |
496 | return M.BeginSpelled < SpelledIndex; |
497 | }); |
498 | if (M == File.Mappings.end() || M->BeginSpelled != SpelledIndex) |
499 | return std::nullopt; |
500 | return makeExpansion(F: File, M: *M); |
501 | } |
502 | |
503 | std::vector<TokenBuffer::Expansion> TokenBuffer::expansionsOverlapping( |
504 | llvm::ArrayRef<syntax::Token> Spelled) const { |
505 | if (Spelled.empty()) |
506 | return {}; |
507 | const auto &File = fileForSpelled(Spelled); |
508 | |
509 | // Find the first overlapping range, and then copy until we stop overlapping. |
510 | unsigned SpelledBeginIndex = Spelled.begin() - File.SpelledTokens.data(); |
511 | unsigned SpelledEndIndex = Spelled.end() - File.SpelledTokens.data(); |
512 | auto M = llvm::partition_point(Range: File.Mappings, P: [&](const Mapping &M) { |
513 | return M.EndSpelled <= SpelledBeginIndex; |
514 | }); |
515 | std::vector<TokenBuffer::Expansion> Expansions; |
516 | for (; M != File.Mappings.end() && M->BeginSpelled < SpelledEndIndex; ++M) |
517 | Expansions.push_back(x: makeExpansion(F: File, M: *M)); |
518 | return Expansions; |
519 | } |
520 | |
521 | llvm::ArrayRef<syntax::Token> |
522 | syntax::spelledTokensTouching(SourceLocation Loc, |
523 | llvm::ArrayRef<syntax::Token> Tokens) { |
524 | assert(Loc.isFileID()); |
525 | |
526 | auto *Right = llvm::partition_point( |
527 | Range&: Tokens, P: [&](const syntax::Token &Tok) { return Tok.location() < Loc; }); |
528 | bool AcceptRight = Right != Tokens.end() && Right->location() <= Loc; |
529 | bool AcceptLeft = |
530 | Right != Tokens.begin() && (Right - 1)->endLocation() >= Loc; |
531 | return llvm::ArrayRef(Right - (AcceptLeft ? 1 : 0), |
532 | Right + (AcceptRight ? 1 : 0)); |
533 | } |
534 | |
535 | llvm::ArrayRef<syntax::Token> |
536 | syntax::spelledTokensTouching(SourceLocation Loc, |
537 | const syntax::TokenBuffer &Tokens) { |
538 | return spelledTokensTouching( |
539 | Loc, Tokens: Tokens.spelledTokens(FID: Tokens.sourceManager().getFileID(SpellingLoc: Loc))); |
540 | } |
541 | |
542 | const syntax::Token * |
543 | syntax::spelledIdentifierTouching(SourceLocation Loc, |
544 | llvm::ArrayRef<syntax::Token> Tokens) { |
545 | for (const syntax::Token &Tok : spelledTokensTouching(Loc, Tokens)) { |
546 | if (Tok.kind() == tok::identifier) |
547 | return &Tok; |
548 | } |
549 | return nullptr; |
550 | } |
551 | |
552 | const syntax::Token * |
553 | syntax::spelledIdentifierTouching(SourceLocation Loc, |
554 | const syntax::TokenBuffer &Tokens) { |
555 | return spelledIdentifierTouching( |
556 | Loc, Tokens: Tokens.spelledTokens(FID: Tokens.sourceManager().getFileID(SpellingLoc: Loc))); |
557 | } |
558 | |
559 | std::vector<const syntax::Token *> |
560 | TokenBuffer::macroExpansions(FileID FID) const { |
561 | auto FileIt = Files.find(Val: FID); |
562 | assert(FileIt != Files.end() && "file not tracked by token buffer" ); |
563 | auto &File = FileIt->second; |
564 | std::vector<const syntax::Token *> Expansions; |
565 | auto &Spelled = File.SpelledTokens; |
566 | for (auto Mapping : File.Mappings) { |
567 | const syntax::Token *Token = &Spelled[Mapping.BeginSpelled]; |
568 | if (Token->kind() == tok::TokenKind::identifier) |
569 | Expansions.push_back(x: Token); |
570 | } |
571 | return Expansions; |
572 | } |
573 | |
574 | std::vector<syntax::Token> syntax::tokenize(const FileRange &FR, |
575 | const SourceManager &SM, |
576 | const LangOptions &LO) { |
577 | std::vector<syntax::Token> Tokens; |
578 | IdentifierTable Identifiers(LO); |
579 | auto AddToken = [&](clang::Token T) { |
580 | // Fill the proper token kind for keywords, etc. |
581 | if (T.getKind() == tok::raw_identifier && !T.needsCleaning() && |
582 | !T.hasUCN()) { // FIXME: support needsCleaning and hasUCN cases. |
583 | clang::IdentifierInfo &II = Identifiers.get(Name: T.getRawIdentifier()); |
584 | T.setIdentifierInfo(&II); |
585 | T.setKind(II.getTokenID()); |
586 | } |
587 | Tokens.push_back(x: syntax::Token(T)); |
588 | }; |
589 | |
590 | auto SrcBuffer = SM.getBufferData(FID: FR.file()); |
591 | Lexer L(SM.getLocForStartOfFile(FID: FR.file()), LO, SrcBuffer.data(), |
592 | SrcBuffer.data() + FR.beginOffset(), |
593 | // We can't make BufEnd point to FR.endOffset, as Lexer requires a |
594 | // null terminated buffer. |
595 | SrcBuffer.data() + SrcBuffer.size()); |
596 | |
597 | clang::Token T; |
598 | while (!L.LexFromRawLexer(Result&: T) && L.getCurrentBufferOffset() < FR.endOffset()) |
599 | AddToken(T); |
600 | // LexFromRawLexer returns true when it parses the last token of the file, add |
601 | // it iff it starts within the range we are interested in. |
602 | if (SM.getFileOffset(SpellingLoc: T.getLocation()) < FR.endOffset()) |
603 | AddToken(T); |
604 | return Tokens; |
605 | } |
606 | |
607 | std::vector<syntax::Token> syntax::tokenize(FileID FID, const SourceManager &SM, |
608 | const LangOptions &LO) { |
609 | return tokenize(FR: syntax::FileRange(FID, 0, SM.getFileIDSize(FID)), SM, LO); |
610 | } |
611 | |
612 | /// Records information reqired to construct mappings for the token buffer that |
613 | /// we are collecting. |
614 | class TokenCollector::CollectPPExpansions : public PPCallbacks { |
615 | public: |
616 | CollectPPExpansions(TokenCollector &C) : Collector(&C) {} |
617 | |
618 | /// Disabled instance will stop reporting anything to TokenCollector. |
619 | /// This ensures that uses of the preprocessor after TokenCollector::consume() |
620 | /// is called do not access the (possibly invalid) collector instance. |
621 | void disable() { Collector = nullptr; } |
622 | |
623 | void MacroExpands(const clang::Token &MacroNameTok, const MacroDefinition &MD, |
624 | SourceRange Range, const MacroArgs *Args) override { |
625 | if (!Collector) |
626 | return; |
627 | const auto &SM = Collector->PP.getSourceManager(); |
628 | // Only record top-level expansions that directly produce expanded tokens. |
629 | // This excludes those where: |
630 | // - the macro use is inside a macro body, |
631 | // - the macro appears in an argument to another macro. |
632 | // However macro expansion isn't really a tree, it's token rewrite rules, |
633 | // so there are other cases, e.g. |
634 | // #define B(X) X |
635 | // #define A 1 + B |
636 | // A(2) |
637 | // Both A and B produce expanded tokens, though the macro name 'B' comes |
638 | // from an expansion. The best we can do is merge the mappings for both. |
639 | |
640 | // The *last* token of any top-level macro expansion must be in a file. |
641 | // (In the example above, see the closing paren of the expansion of B). |
642 | if (!Range.getEnd().isFileID()) |
643 | return; |
644 | // If there's a current expansion that encloses this one, this one can't be |
645 | // top-level. |
646 | if (LastExpansionEnd.isValid() && |
647 | !SM.isBeforeInTranslationUnit(LHS: LastExpansionEnd, RHS: Range.getEnd())) |
648 | return; |
649 | |
650 | // If the macro invocation (B) starts in a macro (A) but ends in a file, |
651 | // we'll create a merged mapping for A + B by overwriting the endpoint for |
652 | // A's startpoint. |
653 | if (!Range.getBegin().isFileID()) { |
654 | Range.setBegin(SM.getExpansionLoc(Loc: Range.getBegin())); |
655 | assert(Collector->Expansions.count(Range.getBegin()) && |
656 | "Overlapping macros should have same expansion location" ); |
657 | } |
658 | |
659 | Collector->Expansions[Range.getBegin()] = Range.getEnd(); |
660 | LastExpansionEnd = Range.getEnd(); |
661 | } |
662 | // FIXME: handle directives like #pragma, #include, etc. |
663 | private: |
664 | TokenCollector *Collector; |
665 | /// Used to detect recursive macro expansions. |
666 | SourceLocation LastExpansionEnd; |
667 | }; |
668 | |
669 | /// Fills in the TokenBuffer by tracing the run of a preprocessor. The |
670 | /// implementation tracks the tokens, macro expansions and directives coming |
671 | /// from the preprocessor and: |
672 | /// - for each token, figures out if it is a part of an expanded token stream, |
673 | /// spelled token stream or both. Stores the tokens appropriately. |
674 | /// - records mappings from the spelled to expanded token ranges, e.g. for macro |
675 | /// expansions. |
676 | /// FIXME: also properly record: |
677 | /// - #include directives, |
678 | /// - #pragma, #line and other PP directives, |
679 | /// - skipped pp regions, |
680 | /// - ... |
681 | |
682 | TokenCollector::TokenCollector(Preprocessor &PP) : PP(PP) { |
683 | // Collect the expanded token stream during preprocessing. |
684 | PP.setTokenWatcher([this](const clang::Token &T) { |
685 | if (T.isAnnotation()) |
686 | return; |
687 | DEBUG_WITH_TYPE("collect-tokens" , llvm::dbgs() |
688 | << "Token: " |
689 | << syntax::Token(T).dumpForTests( |
690 | this->PP.getSourceManager()) |
691 | << "\n" |
692 | |
693 | ); |
694 | Expanded.push_back(x: syntax::Token(T)); |
695 | }); |
696 | // And locations of macro calls, to properly recover boundaries of those in |
697 | // case of empty expansions. |
698 | auto CB = std::make_unique<CollectPPExpansions>(args&: *this); |
699 | this->Collector = CB.get(); |
700 | PP.addPPCallbacks(C: std::move(CB)); |
701 | } |
702 | |
703 | /// Builds mappings and spelled tokens in the TokenBuffer based on the expanded |
704 | /// token stream. |
705 | class TokenCollector::Builder { |
706 | public: |
707 | Builder(std::vector<syntax::Token> Expanded, PPExpansions CollectedExpansions, |
708 | const SourceManager &SM, const LangOptions &LangOpts) |
709 | : Result(SM), CollectedExpansions(std::move(CollectedExpansions)), SM(SM), |
710 | LangOpts(LangOpts) { |
711 | Result.ExpandedTokens = std::move(Expanded); |
712 | } |
713 | |
714 | TokenBuffer build() && { |
715 | assert(!Result.ExpandedTokens.empty()); |
716 | assert(Result.ExpandedTokens.back().kind() == tok::eof); |
717 | |
718 | // Tokenize every file that contributed tokens to the expanded stream. |
719 | buildSpelledTokens(); |
720 | |
721 | // The expanded token stream consists of runs of tokens that came from |
722 | // the same source (a macro expansion, part of a file etc). |
723 | // Between these runs are the logical positions of spelled tokens that |
724 | // didn't expand to anything. |
725 | while (NextExpanded < Result.ExpandedTokens.size() - 1 /* eof */) { |
726 | // Create empty mappings for spelled tokens that expanded to nothing here. |
727 | // May advance NextSpelled, but NextExpanded is unchanged. |
728 | discard(); |
729 | // Create mapping for a contiguous run of expanded tokens. |
730 | // Advances NextExpanded past the run, and NextSpelled accordingly. |
731 | unsigned OldPosition = NextExpanded; |
732 | advance(); |
733 | if (NextExpanded == OldPosition) |
734 | diagnoseAdvanceFailure(); |
735 | } |
736 | // If any tokens remain in any of the files, they didn't expand to anything. |
737 | // Create empty mappings up until the end of the file. |
738 | for (const auto &File : Result.Files) |
739 | discard(Drain: File.first); |
740 | |
741 | #ifndef NDEBUG |
742 | for (auto &pair : Result.Files) { |
743 | auto &mappings = pair.second.Mappings; |
744 | assert(llvm::is_sorted(mappings, [](const TokenBuffer::Mapping &M1, |
745 | const TokenBuffer::Mapping &M2) { |
746 | return M1.BeginSpelled < M2.BeginSpelled && |
747 | M1.EndSpelled < M2.EndSpelled && |
748 | M1.BeginExpanded < M2.BeginExpanded && |
749 | M1.EndExpanded < M2.EndExpanded; |
750 | })); |
751 | } |
752 | #endif |
753 | |
754 | return std::move(Result); |
755 | } |
756 | |
757 | private: |
758 | // Consume a sequence of spelled tokens that didn't expand to anything. |
759 | // In the simplest case, skips spelled tokens until finding one that produced |
760 | // the NextExpanded token, and creates an empty mapping for them. |
761 | // If Drain is provided, skips remaining tokens from that file instead. |
762 | void discard(std::optional<FileID> Drain = std::nullopt) { |
763 | SourceLocation Target = |
764 | Drain ? SM.getLocForEndOfFile(FID: *Drain) |
765 | : SM.getExpansionLoc( |
766 | Loc: Result.ExpandedTokens[NextExpanded].location()); |
767 | FileID File = SM.getFileID(SpellingLoc: Target); |
768 | const auto &SpelledTokens = Result.Files[File].SpelledTokens; |
769 | auto &NextSpelled = this->NextSpelled[File]; |
770 | |
771 | TokenBuffer::Mapping Mapping; |
772 | Mapping.BeginSpelled = NextSpelled; |
773 | // When dropping trailing tokens from a file, the empty mapping should |
774 | // be positioned within the file's expanded-token range (at the end). |
775 | Mapping.BeginExpanded = Mapping.EndExpanded = |
776 | Drain ? Result.Files[*Drain].EndExpanded : NextExpanded; |
777 | // We may want to split into several adjacent empty mappings. |
778 | // FlushMapping() emits the current mapping and starts a new one. |
779 | auto FlushMapping = [&, this] { |
780 | Mapping.EndSpelled = NextSpelled; |
781 | if (Mapping.BeginSpelled != Mapping.EndSpelled) |
782 | Result.Files[File].Mappings.push_back(x: Mapping); |
783 | Mapping.BeginSpelled = NextSpelled; |
784 | }; |
785 | |
786 | while (NextSpelled < SpelledTokens.size() && |
787 | SpelledTokens[NextSpelled].location() < Target) { |
788 | // If we know mapping bounds at [NextSpelled, KnownEnd] (macro expansion) |
789 | // then we want to partition our (empty) mapping. |
790 | // [Start, NextSpelled) [NextSpelled, KnownEnd] (KnownEnd, Target) |
791 | SourceLocation KnownEnd = |
792 | CollectedExpansions.lookup(Val: SpelledTokens[NextSpelled].location()); |
793 | if (KnownEnd.isValid()) { |
794 | FlushMapping(); // Emits [Start, NextSpelled) |
795 | while (NextSpelled < SpelledTokens.size() && |
796 | SpelledTokens[NextSpelled].location() <= KnownEnd) |
797 | ++NextSpelled; |
798 | FlushMapping(); // Emits [NextSpelled, KnownEnd] |
799 | // Now the loop continues and will emit (KnownEnd, Target). |
800 | } else { |
801 | ++NextSpelled; |
802 | } |
803 | } |
804 | FlushMapping(); |
805 | } |
806 | |
807 | // Consumes the NextExpanded token and others that are part of the same run. |
808 | // Increases NextExpanded and NextSpelled by at least one, and adds a mapping |
809 | // (unless this is a run of file tokens, which we represent with no mapping). |
810 | void advance() { |
811 | const syntax::Token &Tok = Result.ExpandedTokens[NextExpanded]; |
812 | SourceLocation Expansion = SM.getExpansionLoc(Loc: Tok.location()); |
813 | FileID File = SM.getFileID(SpellingLoc: Expansion); |
814 | const auto &SpelledTokens = Result.Files[File].SpelledTokens; |
815 | auto &NextSpelled = this->NextSpelled[File]; |
816 | |
817 | if (Tok.location().isFileID()) { |
818 | // A run of file tokens continues while the expanded/spelled tokens match. |
819 | while (NextSpelled < SpelledTokens.size() && |
820 | NextExpanded < Result.ExpandedTokens.size() && |
821 | SpelledTokens[NextSpelled].location() == |
822 | Result.ExpandedTokens[NextExpanded].location()) { |
823 | ++NextSpelled; |
824 | ++NextExpanded; |
825 | } |
826 | // We need no mapping for file tokens copied to the expanded stream. |
827 | } else { |
828 | // We found a new macro expansion. We should have its spelling bounds. |
829 | auto End = CollectedExpansions.lookup(Val: Expansion); |
830 | assert(End.isValid() && "Macro expansion wasn't captured?" ); |
831 | |
832 | // Mapping starts here... |
833 | TokenBuffer::Mapping Mapping; |
834 | Mapping.BeginExpanded = NextExpanded; |
835 | Mapping.BeginSpelled = NextSpelled; |
836 | // ... consumes spelled tokens within bounds we captured ... |
837 | while (NextSpelled < SpelledTokens.size() && |
838 | SpelledTokens[NextSpelled].location() <= End) |
839 | ++NextSpelled; |
840 | // ... consumes expanded tokens rooted at the same expansion ... |
841 | while (NextExpanded < Result.ExpandedTokens.size() && |
842 | SM.getExpansionLoc( |
843 | Loc: Result.ExpandedTokens[NextExpanded].location()) == Expansion) |
844 | ++NextExpanded; |
845 | // ... and ends here. |
846 | Mapping.EndExpanded = NextExpanded; |
847 | Mapping.EndSpelled = NextSpelled; |
848 | Result.Files[File].Mappings.push_back(x: Mapping); |
849 | } |
850 | } |
851 | |
852 | // advance() is supposed to consume at least one token - if not, we crash. |
853 | void diagnoseAdvanceFailure() { |
854 | #ifndef NDEBUG |
855 | // Show the failed-to-map token in context. |
856 | for (unsigned I = (NextExpanded < 10) ? 0 : NextExpanded - 10; |
857 | I < NextExpanded + 5 && I < Result.ExpandedTokens.size(); ++I) { |
858 | const char *L = |
859 | (I == NextExpanded) ? "!! " : (I < NextExpanded) ? "ok " : " " ; |
860 | llvm::errs() << L << Result.ExpandedTokens[I].dumpForTests(SM) << "\n" ; |
861 | } |
862 | #endif |
863 | llvm_unreachable("Couldn't map expanded token to spelled tokens!" ); |
864 | } |
865 | |
866 | /// Initializes TokenBuffer::Files and fills spelled tokens and expanded |
867 | /// ranges for each of the files. |
868 | void buildSpelledTokens() { |
869 | for (unsigned I = 0; I < Result.ExpandedTokens.size(); ++I) { |
870 | const auto &Tok = Result.ExpandedTokens[I]; |
871 | auto FID = SM.getFileID(SpellingLoc: SM.getExpansionLoc(Loc: Tok.location())); |
872 | auto It = Result.Files.try_emplace(Key: FID); |
873 | TokenBuffer::MarkedFile &File = It.first->second; |
874 | |
875 | // The eof token should not be considered part of the main-file's range. |
876 | File.EndExpanded = Tok.kind() == tok::eof ? I : I + 1; |
877 | |
878 | if (!It.second) |
879 | continue; // we have seen this file before. |
880 | // This is the first time we see this file. |
881 | File.BeginExpanded = I; |
882 | File.SpelledTokens = tokenize(FID, SM, LO: LangOpts); |
883 | } |
884 | } |
885 | |
886 | TokenBuffer Result; |
887 | unsigned NextExpanded = 0; // cursor in ExpandedTokens |
888 | llvm::DenseMap<FileID, unsigned> NextSpelled; // cursor in SpelledTokens |
889 | PPExpansions CollectedExpansions; |
890 | const SourceManager &SM; |
891 | const LangOptions &LangOpts; |
892 | }; |
893 | |
894 | TokenBuffer TokenCollector::consume() && { |
895 | PP.setTokenWatcher(nullptr); |
896 | Collector->disable(); |
897 | return Builder(std::move(Expanded), std::move(Expansions), |
898 | PP.getSourceManager(), PP.getLangOpts()) |
899 | .build(); |
900 | } |
901 | |
902 | std::string syntax::Token::str() const { |
903 | return std::string(llvm::formatv(Fmt: "Token({0}, length = {1})" , |
904 | Vals: tok::getTokenName(Kind: kind()), Vals: length())); |
905 | } |
906 | |
907 | std::string syntax::Token::dumpForTests(const SourceManager &SM) const { |
908 | return std::string(llvm::formatv(Fmt: "Token(`{0}`, {1}, length = {2})" , Vals: text(SM), |
909 | Vals: tok::getTokenName(Kind: kind()), Vals: length())); |
910 | } |
911 | |
912 | std::string TokenBuffer::dumpForTests() const { |
913 | auto PrintToken = [this](const syntax::Token &T) -> std::string { |
914 | if (T.kind() == tok::eof) |
915 | return "<eof>" ; |
916 | return std::string(T.text(SM: *SourceMgr)); |
917 | }; |
918 | |
919 | auto DumpTokens = [this, &PrintToken](llvm::raw_ostream &OS, |
920 | llvm::ArrayRef<syntax::Token> Tokens) { |
921 | if (Tokens.empty()) { |
922 | OS << "<empty>" ; |
923 | return; |
924 | } |
925 | OS << Tokens[0].text(SM: *SourceMgr); |
926 | for (unsigned I = 1; I < Tokens.size(); ++I) { |
927 | if (Tokens[I].kind() == tok::eof) |
928 | continue; |
929 | OS << " " << PrintToken(Tokens[I]); |
930 | } |
931 | }; |
932 | |
933 | std::string Dump; |
934 | llvm::raw_string_ostream OS(Dump); |
935 | |
936 | OS << "expanded tokens:\n" |
937 | << " " ; |
938 | // (!) we do not show '<eof>'. |
939 | DumpTokens(OS, llvm::ArrayRef(ExpandedTokens).drop_back()); |
940 | OS << "\n" ; |
941 | |
942 | std::vector<FileID> Keys; |
943 | for (const auto &F : Files) |
944 | Keys.push_back(x: F.first); |
945 | llvm::sort(C&: Keys); |
946 | |
947 | for (FileID ID : Keys) { |
948 | const MarkedFile &File = Files.find(Val: ID)->second; |
949 | auto Entry = SourceMgr->getFileEntryRefForID(FID: ID); |
950 | if (!Entry) |
951 | continue; // Skip builtin files. |
952 | std::string Path = llvm::sys::path::convert_to_slash(path: Entry->getName()); |
953 | OS << llvm::formatv(Fmt: "file '{0}'\n" , Vals&: Path) << " spelled tokens:\n" |
954 | << " " ; |
955 | DumpTokens(OS, File.SpelledTokens); |
956 | OS << "\n" ; |
957 | |
958 | if (File.Mappings.empty()) { |
959 | OS << " no mappings.\n" ; |
960 | continue; |
961 | } |
962 | OS << " mappings:\n" ; |
963 | for (auto &M : File.Mappings) { |
964 | OS << llvm::formatv( |
965 | Fmt: " ['{0}'_{1}, '{2}'_{3}) => ['{4}'_{5}, '{6}'_{7})\n" , |
966 | Vals: PrintToken(File.SpelledTokens[M.BeginSpelled]), Vals: M.BeginSpelled, |
967 | Vals: M.EndSpelled == File.SpelledTokens.size() |
968 | ? "<eof>" |
969 | : PrintToken(File.SpelledTokens[M.EndSpelled]), |
970 | Vals: M.EndSpelled, Vals: PrintToken(ExpandedTokens[M.BeginExpanded]), |
971 | Vals: M.BeginExpanded, Vals: PrintToken(ExpandedTokens[M.EndExpanded]), |
972 | Vals: M.EndExpanded); |
973 | } |
974 | } |
975 | return Dump; |
976 | } |
977 | |