//===--- tools/pp-trace/PPTrace.cpp - Clang preprocessor tracer -----------===//
//
// 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 pp-trace, a tool for displaying a textual trace
// of the Clang preprocessor activity.  It's based on a derivation of the
// PPCallbacks class, that once registerd with Clang, receives callback calls
// to its virtual members, and outputs the information passed to the callbacks
// in a high-level YAML format.
//
// The pp-trace tool also serves as the basis for a test of the PPCallbacks
// mechanism.
//
// The pp-trace tool supports the following general command line format:
//
//    pp-trace [options] file... [-- compiler options]
//
// Basically you put the pp-trace options first, then the source file or files,
// and then -- followed by any options you want to pass to the compiler.
//
//===----------------------------------------------------------------------===//

#include "PPCallbacksTracker.h"
#include "clang/AST/ASTConsumer.h"
#include "clang/AST/ASTContext.h"
#include "clang/Basic/SourceManager.h"
#include "clang/Driver/Options.h"
#include "clang/Frontend/CompilerInstance.h"
#include "clang/Frontend/FrontendAction.h"
#include "clang/Frontend/FrontendActions.h"
#include "clang/Lex/Preprocessor.h"
#include "clang/Tooling/Execution.h"
#include "clang/Tooling/Tooling.h"
#include "llvm/Option/Arg.h"
#include "llvm/Option/ArgList.h"
#include "llvm/Option/OptTable.h"
#include "llvm/Option/Option.h"
#include "llvm/Support/CommandLine.h"
#include "llvm/Support/FileSystem.h"
#include "llvm/Support/GlobPattern.h"
#include "llvm/Support/InitLLVM.h"
#include "llvm/Support/Path.h"
#include "llvm/Support/ToolOutputFile.h"
#include "llvm/Support/WithColor.h"
#include <string>
#include <vector>

using namespace llvm;

namespace clang {
namespace pp_trace {

static cl::OptionCategory Cat("pp-trace options");

static cl::opt<std::string> Callbacks(
    "callbacks", cl::init("*"),
    cl::desc("Comma-separated list of globs describing the list of callbacks "
             "to output. Globs are processed in order of appearance. Globs "
             "with the '-' prefix remove callbacks from the set. e.g. "
             "'*,-Macro*'."),
    cl::cat(Cat));

static cl::opt<std::string> OutputFileName(
    "output", cl::init("-"),
    cl::desc("Output trace to the given file name or '-' for stdout."),
    cl::cat(Cat));

[[noreturn]] static void error(Twine Message) {
  WithColor::error() << Message << '\n';
  exit(1);
}

namespace {

class PPTraceAction : public ASTFrontendAction {
public:
  PPTraceAction(const FilterType &Filters, raw_ostream &OS)
      : Filters(Filters), OS(OS) {}

protected:
  std::unique_ptr<ASTConsumer> CreateASTConsumer(CompilerInstance &CI,
                                                 StringRef InFile) override {
    Preprocessor &PP = CI.getPreprocessor();
    PP.addPPCallbacks(
        std::make_unique<PPCallbacksTracker>(Filters, CallbackCalls, PP));
    return std::make_unique<ASTConsumer>();
  }

  void EndSourceFileAction() override {
    OS << "---\n";
    for (const CallbackCall &Callback : CallbackCalls) {
      OS << "- Callback: " << Callback.Name << "\n";
      for (const Argument &Arg : Callback.Arguments)
        OS << "  " << Arg.Name << ": " << Arg.Value << "\n";
    }
    OS << "...\n";

    CallbackCalls.clear();
  }

private:
  const FilterType &Filters;
  raw_ostream &OS;
  std::vector<CallbackCall> CallbackCalls;
};

class PPTraceFrontendActionFactory : public tooling::FrontendActionFactory {
public:
  PPTraceFrontendActionFactory(const FilterType &Filters, raw_ostream &OS)
      : Filters(Filters), OS(OS) {}

  std::unique_ptr<FrontendAction> create() override {
    return std::make_unique<PPTraceAction>(Filters, OS);
  }

private:
  const FilterType &Filters;
  raw_ostream &OS;
};
} // namespace
} // namespace pp_trace
} // namespace clang

int main(int argc, const char **argv) {
  using namespace clang::pp_trace;
  InitLLVM X(argc, argv);
  auto OptionsParser = clang::tooling::CommonOptionsParser::create(
      argc, argv, Cat, llvm::cl::ZeroOrMore);
  if (!OptionsParser)
    error(toString(OptionsParser.takeError()));
  // Parse the IgnoreCallbacks list into strings.
  SmallVector<StringRef, 32> Patterns;
  FilterType Filters;
  StringRef(Callbacks).split(Patterns, ",",
                             /*MaxSplit=*/-1, /*KeepEmpty=*/false);
  for (StringRef Pattern : Patterns) {
    Pattern = Pattern.trim();
    bool Enabled = !Pattern.consume_front("-");
    Expected<GlobPattern> Pat = GlobPattern::create(Pattern);
    if (Pat)
      Filters.emplace_back(std::move(*Pat), Enabled);
    else
      error(toString(Pat.takeError()));
  }

  // Create the tool and run the compilation.
  clang::tooling::ClangTool Tool(OptionsParser->getCompilations(),
                                 OptionsParser->getSourcePathList());

  std::error_code EC;
  llvm::ToolOutputFile Out(OutputFileName, EC, llvm::sys::fs::OF_TextWithCRLF);
  if (EC)
    error(EC.message());
  PPTraceFrontendActionFactory Factory(Filters, Out.os());
  int HadErrors = Tool.run(&Factory);

  // If we had errors, exit early.
  if (HadErrors)
    return HadErrors;

  Out.keep();

  return 0;
}
