537 lines
		
	
	
		
			16 KiB
		
	
	
	
		
			C++
		
	
	
	
			
		
		
	
	
			537 lines
		
	
	
		
			16 KiB
		
	
	
	
		
			C++
		
	
	
	
//===- ClangDiff.cpp - compare source files by AST nodes ------*- C++ -*- -===//
 | 
						|
//
 | 
						|
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
 | 
						|
// See https://llvm.org/LICENSE.txt for license information.
 | 
						|
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
 | 
						|
//
 | 
						|
//===----------------------------------------------------------------------===//
 | 
						|
//
 | 
						|
// This file implements a tool for syntax tree based comparison using
 | 
						|
// Tooling/ASTDiff.
 | 
						|
//
 | 
						|
//===----------------------------------------------------------------------===//
 | 
						|
 | 
						|
#include "clang/Tooling/ASTDiff/ASTDiff.h"
 | 
						|
#include "clang/Tooling/CommonOptionsParser.h"
 | 
						|
#include "clang/Tooling/Tooling.h"
 | 
						|
#include "llvm/Support/CommandLine.h"
 | 
						|
 | 
						|
using namespace llvm;
 | 
						|
using namespace clang;
 | 
						|
using namespace clang::tooling;
 | 
						|
 | 
						|
static cl::OptionCategory ClangDiffCategory("clang-diff options");
 | 
						|
 | 
						|
static cl::opt<bool>
 | 
						|
    ASTDump("ast-dump",
 | 
						|
            cl::desc("Print the internal representation of the AST."),
 | 
						|
            cl::init(false), cl::cat(ClangDiffCategory));
 | 
						|
 | 
						|
static cl::opt<bool> ASTDumpJson(
 | 
						|
    "ast-dump-json",
 | 
						|
    cl::desc("Print the internal representation of the AST as JSON."),
 | 
						|
    cl::init(false), cl::cat(ClangDiffCategory));
 | 
						|
 | 
						|
static cl::opt<bool> PrintMatches("dump-matches",
 | 
						|
                                  cl::desc("Print the matched nodes."),
 | 
						|
                                  cl::init(false), cl::cat(ClangDiffCategory));
 | 
						|
 | 
						|
static cl::opt<bool> HtmlDiff("html",
 | 
						|
                              cl::desc("Output a side-by-side diff in HTML."),
 | 
						|
                              cl::init(false), cl::cat(ClangDiffCategory));
 | 
						|
 | 
						|
static cl::opt<std::string> SourcePath(cl::Positional, cl::desc("<source>"),
 | 
						|
                                       cl::Required,
 | 
						|
                                       cl::cat(ClangDiffCategory));
 | 
						|
 | 
						|
static cl::opt<std::string> DestinationPath(cl::Positional,
 | 
						|
                                            cl::desc("<destination>"),
 | 
						|
                                            cl::Optional,
 | 
						|
                                            cl::cat(ClangDiffCategory));
 | 
						|
 | 
						|
static cl::opt<std::string> StopAfter("stop-diff-after",
 | 
						|
                                      cl::desc("<topdown|bottomup>"),
 | 
						|
                                      cl::Optional, cl::init(""),
 | 
						|
                                      cl::cat(ClangDiffCategory));
 | 
						|
 | 
						|
static cl::opt<int> MaxSize("s", cl::desc("<maxsize>"), cl::Optional,
 | 
						|
                            cl::init(-1), cl::cat(ClangDiffCategory));
 | 
						|
 | 
						|
static cl::opt<std::string> BuildPath("p", cl::desc("Build path"), cl::init(""),
 | 
						|
                                      cl::Optional, cl::cat(ClangDiffCategory));
 | 
						|
 | 
						|
static cl::list<std::string> ArgsAfter(
 | 
						|
    "extra-arg",
 | 
						|
    cl::desc("Additional argument to append to the compiler command line"),
 | 
						|
    cl::cat(ClangDiffCategory));
 | 
						|
 | 
						|
static cl::list<std::string> ArgsBefore(
 | 
						|
    "extra-arg-before",
 | 
						|
    cl::desc("Additional argument to prepend to the compiler command line"),
 | 
						|
    cl::cat(ClangDiffCategory));
 | 
						|
 | 
						|
static void addExtraArgs(std::unique_ptr<CompilationDatabase> &Compilations) {
 | 
						|
  if (!Compilations)
 | 
						|
    return;
 | 
						|
  auto AdjustingCompilations =
 | 
						|
      llvm::make_unique<ArgumentsAdjustingCompilations>(
 | 
						|
          std::move(Compilations));
 | 
						|
  AdjustingCompilations->appendArgumentsAdjuster(
 | 
						|
      getInsertArgumentAdjuster(ArgsBefore, ArgumentInsertPosition::BEGIN));
 | 
						|
  AdjustingCompilations->appendArgumentsAdjuster(
 | 
						|
      getInsertArgumentAdjuster(ArgsAfter, ArgumentInsertPosition::END));
 | 
						|
  Compilations = std::move(AdjustingCompilations);
 | 
						|
}
 | 
						|
 | 
						|
static std::unique_ptr<ASTUnit>
 | 
						|
getAST(const std::unique_ptr<CompilationDatabase> &CommonCompilations,
 | 
						|
       const StringRef Filename) {
 | 
						|
  std::string ErrorMessage;
 | 
						|
  std::unique_ptr<CompilationDatabase> Compilations;
 | 
						|
  if (!CommonCompilations) {
 | 
						|
    Compilations = CompilationDatabase::autoDetectFromSource(
 | 
						|
        BuildPath.empty() ? Filename : BuildPath, ErrorMessage);
 | 
						|
    if (!Compilations) {
 | 
						|
      llvm::errs()
 | 
						|
          << "Error while trying to load a compilation database, running "
 | 
						|
             "without flags.\n"
 | 
						|
          << ErrorMessage;
 | 
						|
      Compilations =
 | 
						|
          llvm::make_unique<clang::tooling::FixedCompilationDatabase>(
 | 
						|
              ".", std::vector<std::string>());
 | 
						|
    }
 | 
						|
  }
 | 
						|
  addExtraArgs(Compilations);
 | 
						|
  std::array<std::string, 1> Files = {{Filename}};
 | 
						|
  ClangTool Tool(Compilations ? *Compilations : *CommonCompilations, Files);
 | 
						|
  std::vector<std::unique_ptr<ASTUnit>> ASTs;
 | 
						|
  Tool.buildASTs(ASTs);
 | 
						|
  if (ASTs.size() != Files.size())
 | 
						|
    return nullptr;
 | 
						|
  return std::move(ASTs[0]);
 | 
						|
}
 | 
						|
 | 
						|
static char hexdigit(int N) { return N &= 0xf, N + (N < 10 ? '0' : 'a' - 10); }
 | 
						|
 | 
						|
static const char HtmlDiffHeader[] = R"(
 | 
						|
<html>
 | 
						|
<head>
 | 
						|
<meta charset='utf-8'/>
 | 
						|
<style>
 | 
						|
span.d { color: red; }
 | 
						|
span.u { color: #cc00cc; }
 | 
						|
span.i { color: green; }
 | 
						|
span.m { font-weight: bold; }
 | 
						|
span   { font-weight: normal; color: black; }
 | 
						|
div.code {
 | 
						|
  width: 48%;
 | 
						|
  height: 98%;
 | 
						|
  overflow: scroll;
 | 
						|
  float: left;
 | 
						|
  padding: 0 0 0.5% 0.5%;
 | 
						|
  border: solid 2px LightGrey;
 | 
						|
  border-radius: 5px;
 | 
						|
}
 | 
						|
</style>
 | 
						|
</head>
 | 
						|
<script type='text/javascript'>
 | 
						|
highlightStack = []
 | 
						|
function clearHighlight() {
 | 
						|
  while (highlightStack.length) {
 | 
						|
    var [l, r] = highlightStack.pop()
 | 
						|
    document.getElementById(l).style.backgroundColor = 'inherit'
 | 
						|
    if (r[1] != '-')
 | 
						|
      document.getElementById(r).style.backgroundColor = 'inherit'
 | 
						|
  }
 | 
						|
}
 | 
						|
function highlight(event) {
 | 
						|
  var id = event.target['id']
 | 
						|
  doHighlight(id)
 | 
						|
}
 | 
						|
function doHighlight(id) {
 | 
						|
  clearHighlight()
 | 
						|
  source = document.getElementById(id)
 | 
						|
  if (!source.attributes['tid'])
 | 
						|
    return
 | 
						|
  var mapped = source
 | 
						|
  while (mapped && mapped.parentElement && mapped.attributes['tid'].value.substr(1) === '-1')
 | 
						|
    mapped = mapped.parentElement
 | 
						|
  var tid = null, target = null
 | 
						|
  if (mapped) {
 | 
						|
    tid = mapped.attributes['tid'].value
 | 
						|
    target = document.getElementById(tid)
 | 
						|
  }
 | 
						|
  if (source.parentElement && source.parentElement.classList.contains('code'))
 | 
						|
    return
 | 
						|
  source.style.backgroundColor = 'lightgrey'
 | 
						|
  source.scrollIntoView()
 | 
						|
  if (target) {
 | 
						|
    if (mapped === source)
 | 
						|
      target.style.backgroundColor = 'lightgrey'
 | 
						|
    target.scrollIntoView()
 | 
						|
  }
 | 
						|
  highlightStack.push([id, tid])
 | 
						|
  location.hash = '#' + id
 | 
						|
}
 | 
						|
function scrollToBoth() {
 | 
						|
  doHighlight(location.hash.substr(1))
 | 
						|
}
 | 
						|
function changed(elem) {
 | 
						|
  return elem.classList.length == 0
 | 
						|
}
 | 
						|
function nextChangedNode(prefix, increment, number) {
 | 
						|
  do {
 | 
						|
    number += increment
 | 
						|
    var elem = document.getElementById(prefix + number)
 | 
						|
  } while(elem && !changed(elem))
 | 
						|
  return elem ? number : null
 | 
						|
}
 | 
						|
function handleKey(e) {
 | 
						|
  var down = e.code === "KeyJ"
 | 
						|
  var up = e.code === "KeyK"
 | 
						|
  if (!down && !up)
 | 
						|
    return
 | 
						|
  var id = highlightStack[0] ? highlightStack[0][0] : 'R0'
 | 
						|
  var oldelem = document.getElementById(id)
 | 
						|
  var number = parseInt(id.substr(1))
 | 
						|
  var increment = down ? 1 : -1
 | 
						|
  var lastnumber = number
 | 
						|
  var prefix = id[0]
 | 
						|
  do {
 | 
						|
    number = nextChangedNode(prefix, increment, number)
 | 
						|
    var elem = document.getElementById(prefix + number)
 | 
						|
    if (up && elem) {
 | 
						|
      while (elem.parentElement && changed(elem.parentElement))
 | 
						|
        elem = elem.parentElement
 | 
						|
      number = elem.id.substr(1)
 | 
						|
    }
 | 
						|
  } while ((down && id !== 'R0' && oldelem.contains(elem)))
 | 
						|
  if (!number)
 | 
						|
    number = lastnumber
 | 
						|
  elem = document.getElementById(prefix + number)
 | 
						|
  doHighlight(prefix + number)
 | 
						|
}
 | 
						|
window.onload = scrollToBoth
 | 
						|
window.onkeydown = handleKey
 | 
						|
</script>
 | 
						|
<body>
 | 
						|
<div onclick='highlight(event)'>
 | 
						|
)";
 | 
						|
 | 
						|
static void printHtml(raw_ostream &OS, char C) {
 | 
						|
  switch (C) {
 | 
						|
  case '&':
 | 
						|
    OS << "&";
 | 
						|
    break;
 | 
						|
  case '<':
 | 
						|
    OS << "<";
 | 
						|
    break;
 | 
						|
  case '>':
 | 
						|
    OS << ">";
 | 
						|
    break;
 | 
						|
  case '\'':
 | 
						|
    OS << "'";
 | 
						|
    break;
 | 
						|
  case '"':
 | 
						|
    OS << """;
 | 
						|
    break;
 | 
						|
  default:
 | 
						|
    OS << C;
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
static void printHtml(raw_ostream &OS, const StringRef Str) {
 | 
						|
  for (char C : Str)
 | 
						|
    printHtml(OS, C);
 | 
						|
}
 | 
						|
 | 
						|
static std::string getChangeKindAbbr(diff::ChangeKind Kind) {
 | 
						|
  switch (Kind) {
 | 
						|
  case diff::None:
 | 
						|
    return "";
 | 
						|
  case diff::Delete:
 | 
						|
    return "d";
 | 
						|
  case diff::Update:
 | 
						|
    return "u";
 | 
						|
  case diff::Insert:
 | 
						|
    return "i";
 | 
						|
  case diff::Move:
 | 
						|
    return "m";
 | 
						|
  case diff::UpdateMove:
 | 
						|
    return "u m";
 | 
						|
  }
 | 
						|
  llvm_unreachable("Invalid enumeration value.");
 | 
						|
}
 | 
						|
 | 
						|
static unsigned printHtmlForNode(raw_ostream &OS, const diff::ASTDiff &Diff,
 | 
						|
                                 diff::SyntaxTree &Tree, bool IsLeft,
 | 
						|
                                 diff::NodeId Id, unsigned Offset) {
 | 
						|
  const diff::Node &Node = Tree.getNode(Id);
 | 
						|
  char MyTag, OtherTag;
 | 
						|
  diff::NodeId LeftId, RightId;
 | 
						|
  diff::NodeId TargetId = Diff.getMapped(Tree, Id);
 | 
						|
  if (IsLeft) {
 | 
						|
    MyTag = 'L';
 | 
						|
    OtherTag = 'R';
 | 
						|
    LeftId = Id;
 | 
						|
    RightId = TargetId;
 | 
						|
  } else {
 | 
						|
    MyTag = 'R';
 | 
						|
    OtherTag = 'L';
 | 
						|
    LeftId = TargetId;
 | 
						|
    RightId = Id;
 | 
						|
  }
 | 
						|
  unsigned Begin, End;
 | 
						|
  std::tie(Begin, End) = Tree.getSourceRangeOffsets(Node);
 | 
						|
  const SourceManager &SrcMgr = Tree.getASTContext().getSourceManager();
 | 
						|
  auto Code = SrcMgr.getBuffer(SrcMgr.getMainFileID())->getBuffer();
 | 
						|
  for (; Offset < Begin; ++Offset)
 | 
						|
    printHtml(OS, Code[Offset]);
 | 
						|
  OS << "<span id='" << MyTag << Id << "' "
 | 
						|
     << "tid='" << OtherTag << TargetId << "' ";
 | 
						|
  OS << "title='";
 | 
						|
  printHtml(OS, Node.getTypeLabel());
 | 
						|
  OS << "\n" << LeftId << " -> " << RightId;
 | 
						|
  std::string Value = Tree.getNodeValue(Node);
 | 
						|
  if (!Value.empty()) {
 | 
						|
    OS << "\n";
 | 
						|
    printHtml(OS, Value);
 | 
						|
  }
 | 
						|
  OS << "'";
 | 
						|
  if (Node.Change != diff::None)
 | 
						|
    OS << " class='" << getChangeKindAbbr(Node.Change) << "'";
 | 
						|
  OS << ">";
 | 
						|
 | 
						|
  for (diff::NodeId Child : Node.Children)
 | 
						|
    Offset = printHtmlForNode(OS, Diff, Tree, IsLeft, Child, Offset);
 | 
						|
 | 
						|
  for (; Offset < End; ++Offset)
 | 
						|
    printHtml(OS, Code[Offset]);
 | 
						|
  if (Id == Tree.getRootId()) {
 | 
						|
    End = Code.size();
 | 
						|
    for (; Offset < End; ++Offset)
 | 
						|
      printHtml(OS, Code[Offset]);
 | 
						|
  }
 | 
						|
  OS << "</span>";
 | 
						|
  return Offset;
 | 
						|
}
 | 
						|
 | 
						|
static void printJsonString(raw_ostream &OS, const StringRef Str) {
 | 
						|
  for (signed char C : Str) {
 | 
						|
    switch (C) {
 | 
						|
    case '"':
 | 
						|
      OS << R"(\")";
 | 
						|
      break;
 | 
						|
    case '\\':
 | 
						|
      OS << R"(\\)";
 | 
						|
      break;
 | 
						|
    case '\n':
 | 
						|
      OS << R"(\n)";
 | 
						|
      break;
 | 
						|
    case '\t':
 | 
						|
      OS << R"(\t)";
 | 
						|
      break;
 | 
						|
    default:
 | 
						|
      if ('\x00' <= C && C <= '\x1f') {
 | 
						|
        OS << R"(\u00)" << hexdigit(C >> 4) << hexdigit(C);
 | 
						|
      } else {
 | 
						|
        OS << C;
 | 
						|
      }
 | 
						|
    }
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
static void printNodeAttributes(raw_ostream &OS, diff::SyntaxTree &Tree,
 | 
						|
                                diff::NodeId Id) {
 | 
						|
  const diff::Node &N = Tree.getNode(Id);
 | 
						|
  OS << R"("id":)" << int(Id);
 | 
						|
  OS << R"(,"type":")" << N.getTypeLabel() << '"';
 | 
						|
  auto Offsets = Tree.getSourceRangeOffsets(N);
 | 
						|
  OS << R"(,"begin":)" << Offsets.first;
 | 
						|
  OS << R"(,"end":)" << Offsets.second;
 | 
						|
  std::string Value = Tree.getNodeValue(N);
 | 
						|
  if (!Value.empty()) {
 | 
						|
    OS << R"(,"value":")";
 | 
						|
    printJsonString(OS, Value);
 | 
						|
    OS << '"';
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
static void printNodeAsJson(raw_ostream &OS, diff::SyntaxTree &Tree,
 | 
						|
                            diff::NodeId Id) {
 | 
						|
  const diff::Node &N = Tree.getNode(Id);
 | 
						|
  OS << "{";
 | 
						|
  printNodeAttributes(OS, Tree, Id);
 | 
						|
  auto Identifier = N.getIdentifier();
 | 
						|
  auto QualifiedIdentifier = N.getQualifiedIdentifier();
 | 
						|
  if (Identifier) {
 | 
						|
    OS << R"(,"identifier":")";
 | 
						|
    printJsonString(OS, *Identifier);
 | 
						|
    OS << R"(")";
 | 
						|
    if (QualifiedIdentifier && *Identifier != *QualifiedIdentifier) {
 | 
						|
      OS << R"(,"qualified_identifier":")";
 | 
						|
      printJsonString(OS, *QualifiedIdentifier);
 | 
						|
      OS << R"(")";
 | 
						|
    }
 | 
						|
  }
 | 
						|
  OS << R"(,"children":[)";
 | 
						|
  if (N.Children.size() > 0) {
 | 
						|
    printNodeAsJson(OS, Tree, N.Children[0]);
 | 
						|
    for (size_t I = 1, E = N.Children.size(); I < E; ++I) {
 | 
						|
      OS << ",";
 | 
						|
      printNodeAsJson(OS, Tree, N.Children[I]);
 | 
						|
    }
 | 
						|
  }
 | 
						|
  OS << "]}";
 | 
						|
}
 | 
						|
 | 
						|
static void printNode(raw_ostream &OS, diff::SyntaxTree &Tree,
 | 
						|
                      diff::NodeId Id) {
 | 
						|
  if (Id.isInvalid()) {
 | 
						|
    OS << "None";
 | 
						|
    return;
 | 
						|
  }
 | 
						|
  OS << Tree.getNode(Id).getTypeLabel();
 | 
						|
  std::string Value = Tree.getNodeValue(Id);
 | 
						|
  if (!Value.empty())
 | 
						|
    OS << ": " << Value;
 | 
						|
  OS << "(" << Id << ")";
 | 
						|
}
 | 
						|
 | 
						|
static void printTree(raw_ostream &OS, diff::SyntaxTree &Tree) {
 | 
						|
  for (diff::NodeId Id : Tree) {
 | 
						|
    for (int I = 0; I < Tree.getNode(Id).Depth; ++I)
 | 
						|
      OS << " ";
 | 
						|
    printNode(OS, Tree, Id);
 | 
						|
    OS << "\n";
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
static void printDstChange(raw_ostream &OS, diff::ASTDiff &Diff,
 | 
						|
                           diff::SyntaxTree &SrcTree, diff::SyntaxTree &DstTree,
 | 
						|
                           diff::NodeId Dst) {
 | 
						|
  const diff::Node &DstNode = DstTree.getNode(Dst);
 | 
						|
  diff::NodeId Src = Diff.getMapped(DstTree, Dst);
 | 
						|
  switch (DstNode.Change) {
 | 
						|
  case diff::None:
 | 
						|
    break;
 | 
						|
  case diff::Delete:
 | 
						|
    llvm_unreachable("The destination tree can't have deletions.");
 | 
						|
  case diff::Update:
 | 
						|
    OS << "Update ";
 | 
						|
    printNode(OS, SrcTree, Src);
 | 
						|
    OS << " to " << DstTree.getNodeValue(Dst) << "\n";
 | 
						|
    break;
 | 
						|
  case diff::Insert:
 | 
						|
  case diff::Move:
 | 
						|
  case diff::UpdateMove:
 | 
						|
    if (DstNode.Change == diff::Insert)
 | 
						|
      OS << "Insert";
 | 
						|
    else if (DstNode.Change == diff::Move)
 | 
						|
      OS << "Move";
 | 
						|
    else if (DstNode.Change == diff::UpdateMove)
 | 
						|
      OS << "Update and Move";
 | 
						|
    OS << " ";
 | 
						|
    printNode(OS, DstTree, Dst);
 | 
						|
    OS << " into ";
 | 
						|
    printNode(OS, DstTree, DstNode.Parent);
 | 
						|
    OS << " at " << DstTree.findPositionInParent(Dst) << "\n";
 | 
						|
    break;
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
int main(int argc, const char **argv) {
 | 
						|
  std::string ErrorMessage;
 | 
						|
  std::unique_ptr<CompilationDatabase> CommonCompilations =
 | 
						|
      FixedCompilationDatabase::loadFromCommandLine(argc, argv, ErrorMessage);
 | 
						|
  if (!CommonCompilations && !ErrorMessage.empty())
 | 
						|
    llvm::errs() << ErrorMessage;
 | 
						|
  cl::HideUnrelatedOptions(ClangDiffCategory);
 | 
						|
  if (!cl::ParseCommandLineOptions(argc, argv)) {
 | 
						|
    cl::PrintOptionValues();
 | 
						|
    return 1;
 | 
						|
  }
 | 
						|
 | 
						|
  addExtraArgs(CommonCompilations);
 | 
						|
 | 
						|
  if (ASTDump || ASTDumpJson) {
 | 
						|
    if (!DestinationPath.empty()) {
 | 
						|
      llvm::errs() << "Error: Please specify exactly one filename.\n";
 | 
						|
      return 1;
 | 
						|
    }
 | 
						|
    std::unique_ptr<ASTUnit> AST = getAST(CommonCompilations, SourcePath);
 | 
						|
    if (!AST)
 | 
						|
      return 1;
 | 
						|
    diff::SyntaxTree Tree(AST->getASTContext());
 | 
						|
    if (ASTDump) {
 | 
						|
      printTree(llvm::outs(), Tree);
 | 
						|
      return 0;
 | 
						|
    }
 | 
						|
    llvm::outs() << R"({"filename":")";
 | 
						|
    printJsonString(llvm::outs(), SourcePath);
 | 
						|
    llvm::outs() << R"(","root":)";
 | 
						|
    printNodeAsJson(llvm::outs(), Tree, Tree.getRootId());
 | 
						|
    llvm::outs() << "}\n";
 | 
						|
    return 0;
 | 
						|
  }
 | 
						|
 | 
						|
  if (DestinationPath.empty()) {
 | 
						|
    llvm::errs() << "Error: Exactly two paths are required.\n";
 | 
						|
    return 1;
 | 
						|
  }
 | 
						|
 | 
						|
  std::unique_ptr<ASTUnit> Src = getAST(CommonCompilations, SourcePath);
 | 
						|
  std::unique_ptr<ASTUnit> Dst = getAST(CommonCompilations, DestinationPath);
 | 
						|
  if (!Src || !Dst)
 | 
						|
    return 1;
 | 
						|
 | 
						|
  diff::ComparisonOptions Options;
 | 
						|
  if (MaxSize != -1)
 | 
						|
    Options.MaxSize = MaxSize;
 | 
						|
  if (!StopAfter.empty()) {
 | 
						|
    if (StopAfter == "topdown")
 | 
						|
      Options.StopAfterTopDown = true;
 | 
						|
    else if (StopAfter != "bottomup") {
 | 
						|
      llvm::errs() << "Error: Invalid argument for -stop-after\n";
 | 
						|
      return 1;
 | 
						|
    }
 | 
						|
  }
 | 
						|
  diff::SyntaxTree SrcTree(Src->getASTContext());
 | 
						|
  diff::SyntaxTree DstTree(Dst->getASTContext());
 | 
						|
  diff::ASTDiff Diff(SrcTree, DstTree, Options);
 | 
						|
 | 
						|
  if (HtmlDiff) {
 | 
						|
    llvm::outs() << HtmlDiffHeader << "<pre>";
 | 
						|
    llvm::outs() << "<div id='L' class='code'>";
 | 
						|
    printHtmlForNode(llvm::outs(), Diff, SrcTree, true, SrcTree.getRootId(), 0);
 | 
						|
    llvm::outs() << "</div>";
 | 
						|
    llvm::outs() << "<div id='R' class='code'>";
 | 
						|
    printHtmlForNode(llvm::outs(), Diff, DstTree, false, DstTree.getRootId(),
 | 
						|
                     0);
 | 
						|
    llvm::outs() << "</div>";
 | 
						|
    llvm::outs() << "</pre></div></body></html>\n";
 | 
						|
    return 0;
 | 
						|
  }
 | 
						|
 | 
						|
  for (diff::NodeId Dst : DstTree) {
 | 
						|
    diff::NodeId Src = Diff.getMapped(DstTree, Dst);
 | 
						|
    if (PrintMatches && Src.isValid()) {
 | 
						|
      llvm::outs() << "Match ";
 | 
						|
      printNode(llvm::outs(), SrcTree, Src);
 | 
						|
      llvm::outs() << " to ";
 | 
						|
      printNode(llvm::outs(), DstTree, Dst);
 | 
						|
      llvm::outs() << "\n";
 | 
						|
    }
 | 
						|
    printDstChange(llvm::outs(), Diff, SrcTree, DstTree, Dst);
 | 
						|
  }
 | 
						|
  for (diff::NodeId Src : SrcTree) {
 | 
						|
    if (Diff.getMapped(SrcTree, Src).isInvalid()) {
 | 
						|
      llvm::outs() << "Delete ";
 | 
						|
      printNode(llvm::outs(), SrcTree, Src);
 | 
						|
      llvm::outs() << "\n";
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  return 0;
 | 
						|
}
 |