//===--- ParserActions.cpp ------------------------------------------------===//
//
// 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
//
//===----------------------------------------------------------------------===//
//
// Coding style: https://mlir.llvm.org/getting_started/DeveloperGuide/
//
//===----------------------------------------------------------------------===//

#include "flang/Frontend/ParserActions.h"
#include "flang/Frontend/CompilerInstance.h"
#include "flang/Lower/Bridge.h"
#include "flang/Lower/PFTBuilder.h"
#include "flang/Parser/dump-parse-tree.h"
#include "flang/Parser/parsing.h"
#include "flang/Parser/provenance.h"
#include "flang/Parser/source.h"
#include "flang/Parser/unparse.h"
#include "flang/Semantics/unparse-with-symbols.h"
#include "llvm/Support/raw_ostream.h"

namespace Fortran::frontend {

parser::AllCookedSources &getAllCooked(CompilerInstance &ci) {
  return ci.getParsing().allCooked();
}

void parseAndLowerTree(CompilerInstance &ci, lower::LoweringBridge &lb) {
  parser::Program &parseTree{*ci.getParsing().parseTree()};
  lb.lower(parseTree, ci.getSemanticsContext());
}

void dumpTree(CompilerInstance &ci) {
  auto &parseTree{ci.getParsing().parseTree()};
  llvm::outs() << "========================";
  llvm::outs() << " Flang: parse tree dump ";
  llvm::outs() << "========================\n";
  parser::DumpTree(llvm::outs(), parseTree, &ci.getInvocation().getAsFortran());
}

void dumpProvenance(CompilerInstance &ci) {
  ci.getParsing().DumpProvenance(llvm::outs());
}

void dumpPreFIRTree(CompilerInstance &ci) {
  auto &parseTree{*ci.getParsing().parseTree()};

  if (auto ast{lower::createPFT(parseTree, ci.getSemanticsContext())}) {
    lower::dumpPFT(llvm::outs(), *ast);
  } else {
    unsigned diagID = ci.getDiagnostics().getCustomDiagID(
        clang::DiagnosticsEngine::Error, "Pre FIR Tree is NULL.");
    ci.getDiagnostics().Report(diagID);
  }
}

void formatOrDumpPrescanner(std::string &buf,
                            llvm::raw_string_ostream &outForPP,
                            CompilerInstance &ci) {
  if (ci.getInvocation().getPreprocessorOpts().showMacros) {
    ci.getParsing().EmitPreprocessorMacros(outForPP);
  } else if (ci.getInvocation().getPreprocessorOpts().noReformat) {
    ci.getParsing().DumpCookedChars(outForPP);
  } else {
    ci.getParsing().EmitPreprocessedSource(
        outForPP, !ci.getInvocation().getPreprocessorOpts().noLineDirectives);
  }

  // Print getDiagnostics from the prescanner
  ci.getParsing().messages().Emit(llvm::errs(), ci.getAllCookedSources());
}

struct MeasurementVisitor {
  template <typename A>
  bool Pre(const A &) {
    return true;
  }
  template <typename A>
  void Post(const A &) {
    ++objects;
    bytes += sizeof(A);
  }
  size_t objects{0}, bytes{0};
};

void debugMeasureParseTree(CompilerInstance &ci, llvm::StringRef filename) {
  // Parse. In case of failure, report and return.
  ci.getParsing().Parse(llvm::outs());

  if ((ci.getParsing().parseTree().has_value() &&
       !ci.getParsing().consumedWholeFile()) ||
      (!ci.getParsing().messages().empty() &&
       (ci.getInvocation().getWarnAsErr() ||
        ci.getParsing().messages().AnyFatalError()))) {
    unsigned diagID = ci.getDiagnostics().getCustomDiagID(
        clang::DiagnosticsEngine::Error, "Could not parse %0");
    ci.getDiagnostics().Report(diagID) << filename;

    ci.getParsing().messages().Emit(llvm::errs(), ci.getAllCookedSources());
    return;
  }

  // Report the getDiagnostics from parsing
  ci.getParsing().messages().Emit(llvm::errs(), ci.getAllCookedSources());

  auto &parseTree{ci.getParsing().parseTree()};
  MeasurementVisitor visitor;
  parser::Walk(parseTree, visitor);
  llvm::outs() << "Parse tree comprises " << visitor.objects
               << " objects and occupies " << visitor.bytes
               << " total bytes.\n";
}

void debugUnparseNoSema(CompilerInstance &ci, llvm::raw_ostream &out) {
  auto &invoc = ci.getInvocation();
  auto &parseTree{ci.getParsing().parseTree()};

  // TODO: Options should come from CompilerInvocation
  Unparse(out, *parseTree, ci.getInvocation().getLangOpts(),
          /*encoding=*/parser::Encoding::UTF_8,
          /*capitalizeKeywords=*/true, /*backslashEscapes=*/false,
          /*preStatement=*/nullptr,
          invoc.getUseAnalyzedObjectsForUnparse() ? &invoc.getAsFortran()
                                                  : nullptr);
}

void debugUnparseWithSymbols(CompilerInstance &ci) {
  auto &parseTree{*ci.getParsing().parseTree()};

  semantics::UnparseWithSymbols(llvm::outs(), parseTree,
                                ci.getInvocation().getLangOpts(),
                                /*encoding=*/parser::Encoding::UTF_8);
}

void debugUnparseWithModules(CompilerInstance &ci) {
  auto &parseTree{*ci.getParsing().parseTree()};
  semantics::UnparseWithModules(llvm::outs(), ci.getSemantics().context(),
                                parseTree,
                                /*encoding=*/parser::Encoding::UTF_8);
}

void debugDumpParsingLog(CompilerInstance &ci) {
  ci.getParsing().Parse(llvm::errs());
  ci.getParsing().DumpParsingLog(llvm::outs());
}
} // namespace Fortran::frontend
