/Users/buildslave/jenkins/workspace/coverage/llvm-project/clang/tools/clang-diff/ClangDiff.cpp
Line | Count | Source (jump to first uncovered line) |
1 | | //===- ClangDiff.cpp - compare source files by AST nodes ------*- 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 implements a tool for syntax tree based comparison using |
10 | | // Tooling/ASTDiff. |
11 | | // |
12 | | //===----------------------------------------------------------------------===// |
13 | | |
14 | | #include "clang/Tooling/ASTDiff/ASTDiff.h" |
15 | | #include "clang/Tooling/CommonOptionsParser.h" |
16 | | #include "clang/Tooling/Tooling.h" |
17 | | #include "llvm/Support/CommandLine.h" |
18 | | |
19 | | using namespace llvm; |
20 | | using namespace clang; |
21 | | using namespace clang::tooling; |
22 | | |
23 | | static cl::OptionCategory ClangDiffCategory("clang-diff options"); |
24 | | |
25 | | static cl::opt<bool> |
26 | | ASTDump("ast-dump", |
27 | | cl::desc("Print the internal representation of the AST."), |
28 | | cl::init(false), cl::cat(ClangDiffCategory)); |
29 | | |
30 | | static cl::opt<bool> ASTDumpJson( |
31 | | "ast-dump-json", |
32 | | cl::desc("Print the internal representation of the AST as JSON."), |
33 | | cl::init(false), cl::cat(ClangDiffCategory)); |
34 | | |
35 | | static cl::opt<bool> PrintMatches("dump-matches", |
36 | | cl::desc("Print the matched nodes."), |
37 | | cl::init(false), cl::cat(ClangDiffCategory)); |
38 | | |
39 | | static cl::opt<bool> HtmlDiff("html", |
40 | | cl::desc("Output a side-by-side diff in HTML."), |
41 | | cl::init(false), cl::cat(ClangDiffCategory)); |
42 | | |
43 | | static cl::opt<std::string> SourcePath(cl::Positional, cl::desc("<source>"), |
44 | | cl::Required, |
45 | | cl::cat(ClangDiffCategory)); |
46 | | |
47 | | static cl::opt<std::string> DestinationPath(cl::Positional, |
48 | | cl::desc("<destination>"), |
49 | | cl::Optional, |
50 | | cl::cat(ClangDiffCategory)); |
51 | | |
52 | | static cl::opt<std::string> StopAfter("stop-diff-after", |
53 | | cl::desc("<topdown|bottomup>"), |
54 | | cl::Optional, cl::init(""), |
55 | | cl::cat(ClangDiffCategory)); |
56 | | |
57 | | static cl::opt<int> MaxSize("s", cl::desc("<maxsize>"), cl::Optional, |
58 | | cl::init(-1), cl::cat(ClangDiffCategory)); |
59 | | |
60 | | static cl::opt<std::string> BuildPath("p", cl::desc("Build path"), cl::init(""), |
61 | | cl::Optional, cl::cat(ClangDiffCategory)); |
62 | | |
63 | | static cl::list<std::string> ArgsAfter( |
64 | | "extra-arg", |
65 | | cl::desc("Additional argument to append to the compiler command line"), |
66 | | cl::cat(ClangDiffCategory)); |
67 | | |
68 | | static cl::list<std::string> ArgsBefore( |
69 | | "extra-arg-before", |
70 | | cl::desc("Additional argument to prepend to the compiler command line"), |
71 | | cl::cat(ClangDiffCategory)); |
72 | | |
73 | 28 | static void addExtraArgs(std::unique_ptr<CompilationDatabase> &Compilations) { |
74 | 28 | if (!Compilations) |
75 | 17 | return; |
76 | 11 | auto AdjustingCompilations = |
77 | 11 | std::make_unique<ArgumentsAdjustingCompilations>( |
78 | 11 | std::move(Compilations)); |
79 | 11 | AdjustingCompilations->appendArgumentsAdjuster( |
80 | 11 | getInsertArgumentAdjuster(ArgsBefore, ArgumentInsertPosition::BEGIN)); |
81 | 11 | AdjustingCompilations->appendArgumentsAdjuster( |
82 | 11 | getInsertArgumentAdjuster(ArgsAfter, ArgumentInsertPosition::END)); |
83 | 11 | Compilations = std::move(AdjustingCompilations); |
84 | 11 | } |
85 | | |
86 | | static std::unique_ptr<ASTUnit> |
87 | | getAST(const std::unique_ptr<CompilationDatabase> &CommonCompilations, |
88 | 17 | const StringRef Filename) { |
89 | 17 | std::string ErrorMessage; |
90 | 17 | std::unique_ptr<CompilationDatabase> Compilations; |
91 | 17 | if (!CommonCompilations) { |
92 | 0 | Compilations = CompilationDatabase::autoDetectFromSource( |
93 | 0 | BuildPath.empty() ? Filename : BuildPath, ErrorMessage); |
94 | 0 | if (!Compilations) { |
95 | 0 | llvm::errs() |
96 | 0 | << "Error while trying to load a compilation database, running " |
97 | 0 | "without flags.\n" |
98 | 0 | << ErrorMessage; |
99 | 0 | Compilations = |
100 | 0 | std::make_unique<clang::tooling::FixedCompilationDatabase>( |
101 | 0 | ".", std::vector<std::string>()); |
102 | 0 | } |
103 | 0 | } |
104 | 17 | addExtraArgs(Compilations); |
105 | 17 | std::array<std::string, 1> Files = {{std::string(Filename)}}; |
106 | 17 | ClangTool Tool(Compilations ? *Compilations0 : *CommonCompilations, Files); |
107 | 17 | std::vector<std::unique_ptr<ASTUnit>> ASTs; |
108 | 17 | Tool.buildASTs(ASTs); |
109 | 17 | if (ASTs.size() != Files.size()) |
110 | 0 | return nullptr; |
111 | 17 | return std::move(ASTs[0]); |
112 | 17 | } |
113 | | |
114 | 4 | static char hexdigit(int N) { return N &= 0xf, N + (N < 10 ? '0'3 : 'a' - 101 ); } |
115 | | |
116 | | static const char HtmlDiffHeader[] = R"( |
117 | | <html> |
118 | | <head> |
119 | | <meta charset='utf-8'/> |
120 | | <style> |
121 | | span.d { color: red; } |
122 | | span.u { color: #cc00cc; } |
123 | | span.i { color: green; } |
124 | | span.m { font-weight: bold; } |
125 | | span { font-weight: normal; color: black; } |
126 | | div.code { |
127 | | width: 48%; |
128 | | height: 98%; |
129 | | overflow: scroll; |
130 | | float: left; |
131 | | padding: 0 0 0.5% 0.5%; |
132 | | border: solid 2px LightGrey; |
133 | | border-radius: 5px; |
134 | | } |
135 | | </style> |
136 | | </head> |
137 | | <script type='text/javascript'> |
138 | | highlightStack = [] |
139 | | function clearHighlight() { |
140 | | while (highlightStack.length) { |
141 | | var [l, r] = highlightStack.pop() |
142 | | document.getElementById(l).style.backgroundColor = 'inherit' |
143 | | if (r[1] != '-') |
144 | | document.getElementById(r).style.backgroundColor = 'inherit' |
145 | | } |
146 | | } |
147 | | function highlight(event) { |
148 | | var id = event.target['id'] |
149 | | doHighlight(id) |
150 | | } |
151 | | function doHighlight(id) { |
152 | | clearHighlight() |
153 | | source = document.getElementById(id) |
154 | | if (!source.attributes['tid']) |
155 | | return |
156 | | var mapped = source |
157 | | while (mapped && mapped.parentElement && mapped.attributes['tid'].value.substr(1) === '-1') |
158 | | mapped = mapped.parentElement |
159 | | var tid = null, target = null |
160 | | if (mapped) { |
161 | | tid = mapped.attributes['tid'].value |
162 | | target = document.getElementById(tid) |
163 | | } |
164 | | if (source.parentElement && source.parentElement.classList.contains('code')) |
165 | | return |
166 | | source.style.backgroundColor = 'lightgrey' |
167 | | source.scrollIntoView() |
168 | | if (target) { |
169 | | if (mapped === source) |
170 | | target.style.backgroundColor = 'lightgrey' |
171 | | target.scrollIntoView() |
172 | | } |
173 | | highlightStack.push([id, tid]) |
174 | | location.hash = '#' + id |
175 | | } |
176 | | function scrollToBoth() { |
177 | | doHighlight(location.hash.substr(1)) |
178 | | } |
179 | | function changed(elem) { |
180 | | return elem.classList.length == 0 |
181 | | } |
182 | | function nextChangedNode(prefix, increment, number) { |
183 | | do { |
184 | | number += increment |
185 | | var elem = document.getElementById(prefix + number) |
186 | | } while(elem && !changed(elem)) |
187 | | return elem ? number : null |
188 | | } |
189 | | function handleKey(e) { |
190 | | var down = e.code === "KeyJ" |
191 | | var up = e.code === "KeyK" |
192 | | if (!down && !up) |
193 | | return |
194 | | var id = highlightStack[0] ? highlightStack[0][0] : 'R0' |
195 | | var oldelem = document.getElementById(id) |
196 | | var number = parseInt(id.substr(1)) |
197 | | var increment = down ? 1 : -1 |
198 | | var lastnumber = number |
199 | | var prefix = id[0] |
200 | | do { |
201 | | number = nextChangedNode(prefix, increment, number) |
202 | | var elem = document.getElementById(prefix + number) |
203 | | if (up && elem) { |
204 | | while (elem.parentElement && changed(elem.parentElement)) |
205 | | elem = elem.parentElement |
206 | | number = elem.id.substr(1) |
207 | | } |
208 | | } while ((down && id !== 'R0' && oldelem.contains(elem))) |
209 | | if (!number) |
210 | | number = lastnumber |
211 | | elem = document.getElementById(prefix + number) |
212 | | doHighlight(prefix + number) |
213 | | } |
214 | | window.onload = scrollToBoth |
215 | | window.onkeydown = handleKey |
216 | | </script> |
217 | | <body> |
218 | | <div onclick='highlight(event)'> |
219 | | )"; |
220 | | |
221 | 4.30k | static void printHtml(raw_ostream &OS, char C) { |
222 | 4.30k | switch (C) { |
223 | 0 | case '&': |
224 | 0 | OS << "&"; |
225 | 0 | break; |
226 | 0 | case '<': |
227 | 0 | OS << "<"; |
228 | 0 | break; |
229 | 0 | case '>': |
230 | 0 | OS << ">"; |
231 | 0 | break; |
232 | 0 | case '\'': |
233 | 0 | OS << "'"; |
234 | 0 | break; |
235 | 14 | case '"': |
236 | 14 | OS << """; |
237 | 14 | break; |
238 | 4.29k | default: |
239 | 4.29k | OS << C; |
240 | 4.30k | } |
241 | 4.30k | } |
242 | | |
243 | 236 | static void printHtml(raw_ostream &OS, const StringRef Str) { |
244 | 236 | for (char C : Str) |
245 | 2.18k | printHtml(OS, C); |
246 | 236 | } |
247 | | |
248 | 53 | static std::string getChangeKindAbbr(diff::ChangeKind Kind) { |
249 | 53 | switch (Kind) { |
250 | 0 | case diff::None: |
251 | 0 | return ""; |
252 | 11 | case diff::Delete: |
253 | 11 | return "d"; |
254 | 18 | case diff::Update: |
255 | 18 | return "u"; |
256 | 10 | case diff::Insert: |
257 | 10 | return "i"; |
258 | 10 | case diff::Move: |
259 | 10 | return "m"; |
260 | 4 | case diff::UpdateMove: |
261 | 4 | return "u m"; |
262 | 53 | } |
263 | 0 | llvm_unreachable("Invalid enumeration value."); |
264 | 0 | } |
265 | | |
266 | | static unsigned printHtmlForNode(raw_ostream &OS, const diff::ASTDiff &Diff, |
267 | | diff::SyntaxTree &Tree, bool IsLeft, |
268 | 139 | diff::NodeId Id, unsigned Offset) { |
269 | 139 | const diff::Node &Node = Tree.getNode(Id); |
270 | 139 | char MyTag, OtherTag; |
271 | 139 | diff::NodeId LeftId, RightId; |
272 | 139 | diff::NodeId TargetId = Diff.getMapped(Tree, Id); |
273 | 139 | if (IsLeft) { |
274 | 70 | MyTag = 'L'; |
275 | 70 | OtherTag = 'R'; |
276 | 70 | LeftId = Id; |
277 | 70 | RightId = TargetId; |
278 | 70 | } else { |
279 | 69 | MyTag = 'R'; |
280 | 69 | OtherTag = 'L'; |
281 | 69 | LeftId = TargetId; |
282 | 69 | RightId = Id; |
283 | 69 | } |
284 | 139 | unsigned Begin, End; |
285 | 139 | std::tie(Begin, End) = Tree.getSourceRangeOffsets(Node); |
286 | 139 | const SourceManager &SrcMgr = Tree.getASTContext().getSourceManager(); |
287 | 139 | auto Code = SrcMgr.getBufferOrFake(SrcMgr.getMainFileID()).getBuffer(); |
288 | 1.95k | for (; Offset < Begin; ++Offset1.81k ) |
289 | 1.81k | printHtml(OS, Code[Offset]); |
290 | 139 | OS << "<span id='" << MyTag << Id << "' " |
291 | 139 | << "tid='" << OtherTag << TargetId << "' "; |
292 | 139 | OS << "title='"; |
293 | 139 | printHtml(OS, Node.getTypeLabel()); |
294 | 139 | OS << "\n" << LeftId << " -> " << RightId; |
295 | 139 | std::string Value = Tree.getNodeValue(Node); |
296 | 139 | if (!Value.empty()) { |
297 | 97 | OS << "\n"; |
298 | 97 | printHtml(OS, Value); |
299 | 97 | } |
300 | 139 | OS << "'"; |
301 | 139 | if (Node.Change != diff::None) |
302 | 53 | OS << " class='" << getChangeKindAbbr(Node.Change) << "'"; |
303 | 139 | OS << ">"; |
304 | | |
305 | 139 | for (diff::NodeId Child : Node.Children) |
306 | 137 | Offset = printHtmlForNode(OS, Diff, Tree, IsLeft, Child, Offset); |
307 | | |
308 | 375 | for (; Offset < End; ++Offset236 ) |
309 | 236 | printHtml(OS, Code[Offset]); |
310 | 139 | if (Id == Tree.getRootId()) { |
311 | 2 | End = Code.size(); |
312 | 76 | for (; Offset < End; ++Offset74 ) |
313 | 74 | printHtml(OS, Code[Offset]); |
314 | 2 | } |
315 | 139 | OS << "</span>"; |
316 | 139 | return Offset; |
317 | 139 | } |
318 | | |
319 | 11 | static void printJsonString(raw_ostream &OS, const StringRef Str) { |
320 | 142 | for (signed char C : Str) { |
321 | 142 | switch (C) { |
322 | 0 | case '"': |
323 | 0 | OS << R"(\")"; |
324 | 0 | break; |
325 | 0 | case '\\': |
326 | 0 | OS << R"(\\)"; |
327 | 0 | break; |
328 | 1 | case '\n': |
329 | 1 | OS << R"(\n)"; |
330 | 1 | break; |
331 | 1 | case '\t': |
332 | 1 | OS << R"(\t)"; |
333 | 1 | break; |
334 | 140 | default: |
335 | 140 | if ('\x00' <= C && C <= '\x1f') { |
336 | 2 | OS << R"(\u00)" << hexdigit(C >> 4) << hexdigit(C); |
337 | 138 | } else { |
338 | 138 | OS << C; |
339 | 138 | } |
340 | 142 | } |
341 | 142 | } |
342 | 11 | } |
343 | | |
344 | | static void printNodeAttributes(raw_ostream &OS, diff::SyntaxTree &Tree, |
345 | 7 | diff::NodeId Id) { |
346 | 7 | const diff::Node &N = Tree.getNode(Id); |
347 | 7 | OS << R"("id":)" << int(Id); |
348 | 7 | OS << R"(,"type":")" << N.getTypeLabel() << '"'; |
349 | 7 | auto Offsets = Tree.getSourceRangeOffsets(N); |
350 | 7 | OS << R"(,"begin":)" << Offsets.first; |
351 | 7 | OS << R"(,"end":)" << Offsets.second; |
352 | 7 | std::string Value = Tree.getNodeValue(N); |
353 | 7 | if (!Value.empty()) { |
354 | 5 | OS << R"(,"value":")"; |
355 | 5 | printJsonString(OS, Value); |
356 | 5 | OS << '"'; |
357 | 5 | } |
358 | 7 | } |
359 | | |
360 | | static void printNodeAsJson(raw_ostream &OS, diff::SyntaxTree &Tree, |
361 | 7 | diff::NodeId Id) { |
362 | 7 | const diff::Node &N = Tree.getNode(Id); |
363 | 7 | OS << "{"; |
364 | 7 | printNodeAttributes(OS, Tree, Id); |
365 | 7 | auto Identifier = N.getIdentifier(); |
366 | 7 | auto QualifiedIdentifier = N.getQualifiedIdentifier(); |
367 | 7 | if (Identifier) { |
368 | 4 | OS << R"(,"identifier":")"; |
369 | 4 | printJsonString(OS, *Identifier); |
370 | 4 | OS << R"(")"; |
371 | 4 | if (QualifiedIdentifier && *Identifier != *QualifiedIdentifier) { |
372 | 1 | OS << R"(,"qualified_identifier":")"; |
373 | 1 | printJsonString(OS, *QualifiedIdentifier); |
374 | 1 | OS << R"(")"; |
375 | 1 | } |
376 | 4 | } |
377 | 7 | OS << R"(,"children":[)"; |
378 | 7 | if (N.Children.size() > 0) { |
379 | 4 | printNodeAsJson(OS, Tree, N.Children[0]); |
380 | 6 | for (size_t I = 1, E = N.Children.size(); I < E; ++I2 ) { |
381 | 2 | OS << ","; |
382 | 2 | printNodeAsJson(OS, Tree, N.Children[I]); |
383 | 2 | } |
384 | 4 | } |
385 | 7 | OS << "]}"; |
386 | 7 | } |
387 | | |
388 | | static void printNode(raw_ostream &OS, diff::SyntaxTree &Tree, |
389 | 443 | diff::NodeId Id) { |
390 | 443 | if (Id.isInvalid()) { |
391 | 1 | OS << "None"; |
392 | 1 | return; |
393 | 1 | } |
394 | 442 | OS << Tree.getNode(Id).getTypeLabel(); |
395 | 442 | std::string Value = Tree.getNodeValue(Id); |
396 | 442 | if (!Value.empty()) |
397 | 227 | OS << ": " << Value; |
398 | 442 | OS << "(" << Id << ")"; |
399 | 442 | } |
400 | | |
401 | 4 | static void printTree(raw_ostream &OS, diff::SyntaxTree &Tree) { |
402 | 65 | for (diff::NodeId Id : Tree) { |
403 | 285 | for (int I = 0; I < Tree.getNode(Id).Depth; ++I220 ) |
404 | 220 | OS << " "; |
405 | 65 | printNode(OS, Tree, Id); |
406 | 65 | OS << "\n"; |
407 | 65 | } |
408 | 4 | } |
409 | | |
410 | | static void printDstChange(raw_ostream &OS, diff::ASTDiff &Diff, |
411 | | diff::SyntaxTree &SrcTree, diff::SyntaxTree &DstTree, |
412 | 216 | diff::NodeId Dst) { |
413 | 216 | const diff::Node &DstNode = DstTree.getNode(Dst); |
414 | 216 | diff::NodeId Src = Diff.getMapped(DstTree, Dst); |
415 | 216 | switch (DstNode.Change) { |
416 | 157 | case diff::None: |
417 | 157 | break; |
418 | 0 | case diff::Delete: |
419 | 0 | llvm_unreachable("The destination tree can't have deletions."); |
420 | 9 | case diff::Update: |
421 | 9 | OS << "Update "; |
422 | 9 | printNode(OS, SrcTree, Src); |
423 | 9 | OS << " to " << DstTree.getNodeValue(Dst) << "\n"; |
424 | 9 | break; |
425 | 34 | case diff::Insert: |
426 | 48 | case diff::Move: |
427 | 50 | case diff::UpdateMove: |
428 | 50 | if (DstNode.Change == diff::Insert) |
429 | 34 | OS << "Insert"; |
430 | 16 | else if (DstNode.Change == diff::Move) |
431 | 14 | OS << "Move"; |
432 | 2 | else if (DstNode.Change == diff::UpdateMove) |
433 | 2 | OS << "Update and Move"; |
434 | 50 | OS << " "; |
435 | 50 | printNode(OS, DstTree, Dst); |
436 | 50 | OS << " into "; |
437 | 50 | printNode(OS, DstTree, DstNode.Parent); |
438 | 50 | OS << " at " << DstTree.findPositionInParent(Dst) << "\n"; |
439 | 50 | break; |
440 | 216 | } |
441 | 216 | } |
442 | | |
443 | 11 | int main(int argc, const char **argv) { |
444 | 11 | std::string ErrorMessage; |
445 | 11 | std::unique_ptr<CompilationDatabase> CommonCompilations = |
446 | 11 | FixedCompilationDatabase::loadFromCommandLine(argc, argv, ErrorMessage); |
447 | 11 | if (!CommonCompilations && !ErrorMessage.empty()0 ) |
448 | 0 | llvm::errs() << ErrorMessage; |
449 | 11 | cl::HideUnrelatedOptions(ClangDiffCategory); |
450 | 11 | if (!cl::ParseCommandLineOptions(argc, argv)) { |
451 | 0 | cl::PrintOptionValues(); |
452 | 0 | return 1; |
453 | 0 | } |
454 | | |
455 | 11 | addExtraArgs(CommonCompilations); |
456 | | |
457 | 11 | if (ASTDump || ASTDumpJson7 ) { |
458 | 5 | if (!DestinationPath.empty()) { |
459 | 0 | llvm::errs() << "Error: Please specify exactly one filename.\n"; |
460 | 0 | return 1; |
461 | 0 | } |
462 | 5 | std::unique_ptr<ASTUnit> AST = getAST(CommonCompilations, SourcePath); |
463 | 5 | if (!AST) |
464 | 0 | return 1; |
465 | 5 | diff::SyntaxTree Tree(AST->getASTContext()); |
466 | 5 | if (ASTDump) { |
467 | 4 | printTree(llvm::outs(), Tree); |
468 | 4 | return 0; |
469 | 4 | } |
470 | 1 | llvm::outs() << R"({"filename":")"; |
471 | 1 | printJsonString(llvm::outs(), SourcePath); |
472 | 1 | llvm::outs() << R"(","root":)"; |
473 | 1 | printNodeAsJson(llvm::outs(), Tree, Tree.getRootId()); |
474 | 1 | llvm::outs() << "}\n"; |
475 | 1 | return 0; |
476 | 5 | } |
477 | | |
478 | 6 | if (DestinationPath.empty()) { |
479 | 0 | llvm::errs() << "Error: Exactly two paths are required.\n"; |
480 | 0 | return 1; |
481 | 0 | } |
482 | | |
483 | 6 | std::unique_ptr<ASTUnit> Src = getAST(CommonCompilations, SourcePath); |
484 | 6 | std::unique_ptr<ASTUnit> Dst = getAST(CommonCompilations, DestinationPath); |
485 | 6 | if (!Src || !Dst) |
486 | 0 | return 1; |
487 | | |
488 | 6 | diff::ComparisonOptions Options; |
489 | 6 | if (MaxSize != -1) |
490 | 2 | Options.MaxSize = MaxSize; |
491 | 6 | if (!StopAfter.empty()) { |
492 | 1 | if (StopAfter == "topdown") |
493 | 1 | Options.StopAfterTopDown = true; |
494 | 0 | else if (StopAfter != "bottomup") { |
495 | 0 | llvm::errs() << "Error: Invalid argument for -stop-after\n"; |
496 | 0 | return 1; |
497 | 0 | } |
498 | 1 | } |
499 | 6 | diff::SyntaxTree SrcTree(Src->getASTContext()); |
500 | 6 | diff::SyntaxTree DstTree(Dst->getASTContext()); |
501 | 6 | diff::ASTDiff Diff(SrcTree, DstTree, Options); |
502 | | |
503 | 6 | if (HtmlDiff) { |
504 | 1 | llvm::outs() << HtmlDiffHeader << "<pre>"; |
505 | 1 | llvm::outs() << "<div id='L' class='code'>"; |
506 | 1 | printHtmlForNode(llvm::outs(), Diff, SrcTree, true, SrcTree.getRootId(), 0); |
507 | 1 | llvm::outs() << "</div>"; |
508 | 1 | llvm::outs() << "<div id='R' class='code'>"; |
509 | 1 | printHtmlForNode(llvm::outs(), Diff, DstTree, false, DstTree.getRootId(), |
510 | 1 | 0); |
511 | 1 | llvm::outs() << "</div>"; |
512 | 1 | llvm::outs() << "</pre></div></body></html>\n"; |
513 | 1 | return 0; |
514 | 1 | } |
515 | | |
516 | 216 | for (diff::NodeId Dst : DstTree)5 { |
517 | 216 | diff::NodeId Src = Diff.getMapped(DstTree, Dst); |
518 | 216 | if (PrintMatches && Src.isValid()154 ) { |
519 | 120 | llvm::outs() << "Match "; |
520 | 120 | printNode(llvm::outs(), SrcTree, Src); |
521 | 120 | llvm::outs() << " to "; |
522 | 120 | printNode(llvm::outs(), DstTree, Dst); |
523 | 120 | llvm::outs() << "\n"; |
524 | 120 | } |
525 | 216 | printDstChange(llvm::outs(), Diff, SrcTree, DstTree, Dst); |
526 | 216 | } |
527 | 211 | for (diff::NodeId Src : SrcTree) { |
528 | 211 | if (Diff.getMapped(SrcTree, Src).isInvalid()) { |
529 | 29 | llvm::outs() << "Delete "; |
530 | 29 | printNode(llvm::outs(), SrcTree, Src); |
531 | 29 | llvm::outs() << "\n"; |
532 | 29 | } |
533 | 211 | } |
534 | | |
535 | 5 | return 0; |
536 | 6 | } |