//===- CompilerInvocation.h - Compiler Invocation Helper Data ---*- 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
//
//===----------------------------------------------------------------------===//
//
// Coding style: https://mlir.llvm.org/getting_started/DeveloperGuide/
//
//===----------------------------------------------------------------------===//

#ifndef FORTRAN_FRONTEND_COMPILERINVOCATION_H
#define FORTRAN_FRONTEND_COMPILERINVOCATION_H

#include "flang/Common/LangOptions.h"
#include "flang/Frontend/CodeGenOptions.h"
#include "flang/Frontend/FrontendOptions.h"
#include "flang/Frontend/PreprocessorOptions.h"
#include "flang/Frontend/TargetOptions.h"
#include "flang/Lower/LoweringOptions.h"
#include "flang/Parser/parsing.h"
#include "flang/Semantics/semantics.h"
#include "mlir/Support/Timing.h"
#include "clang/Basic/Diagnostic.h"
#include "clang/Basic/DiagnosticOptions.h"
#include "llvm/Option/ArgList.h"
#include <memory>

namespace llvm {
class TargetMachine;
}

namespace Fortran::frontend {

/// Fill out Opts based on the options given in Args.
///
/// When errors are encountered, return false and, if Diags is non-null,
/// report the error(s).
bool parseDiagnosticArgs(clang::DiagnosticOptions &opts,
                         llvm::opt::ArgList &args);

class CompilerInvocationBase {
public:
  /// Options controlling the diagnostic engine.
  llvm::IntrusiveRefCntPtr<clang::DiagnosticOptions> diagnosticOpts;
  /// Options for the preprocessor.
  std::shared_ptr<Fortran::frontend::PreprocessorOptions> preprocessorOpts;

  CompilerInvocationBase();
  CompilerInvocationBase(const CompilerInvocationBase &x);
  ~CompilerInvocationBase();

  clang::DiagnosticOptions &getDiagnosticOpts() {
    return *diagnosticOpts.get();
  }
  const clang::DiagnosticOptions &getDiagnosticOpts() const {
    return *diagnosticOpts.get();
  }

  PreprocessorOptions &getPreprocessorOpts() { return *preprocessorOpts; }
  const PreprocessorOptions &getPreprocessorOpts() const {
    return *preprocessorOpts;
  }
};

class CompilerInvocation : public CompilerInvocationBase {
  /// Options for the frontend driver
  // TODO: Merge with or translate to parserOpts_. We shouldn't need two sets of
  // options.
  FrontendOptions frontendOpts;

  /// Options for Flang parser
  // TODO: Merge with or translate to frontendOpts. We shouldn't need two sets
  // of options.
  Fortran::parser::Options parserOpts;

  /// Options controlling lowering.
  Fortran::lower::LoweringOptions loweringOpts;

  /// Options controlling the target.
  Fortran::frontend::TargetOptions targetOpts;

  /// Options controlling IRgen and the backend.
  Fortran::frontend::CodeGenOptions codeGenOpts;

  /// Options controlling language dialect.
  Fortran::common::LangOptions langOpts;

  // The original invocation of the compiler driver.
  // This string will be set as the return value from the COMPILER_OPTIONS
  // intrinsic of iso_fortran_env.
  std::string allCompilerInvocOpts;

  /// Semantic options
  // TODO: Merge with or translate to frontendOpts. We shouldn't need two sets
  // of options.
  std::string moduleDir = ".";

  std::string moduleFileSuffix = ".mod";

  bool debugModuleDir = false;
  bool hermeticModuleFileOutput = false;

  bool warnAsErr = false;

  // Executable name
  const char *argv0;

  /// This flag controls the unparsing and is used to decide whether to print
  /// out the semantically analyzed version of an object or expression or the
  /// plain version that does not include any information from semantic
  /// analysis.
  bool useAnalyzedObjectsForUnparse = true;

  // Fortran Dialect options
  Fortran::common::IntrinsicTypeDefaultKinds defaultKinds;

  // Fortran Warning options
  bool enableConformanceChecks = false;
  bool enableUsageChecks = false;
  bool disableWarnings = false;

  /// Used in e.g. unparsing to dump the analyzed rather than the original
  /// parse-tree objects.
  Fortran::parser::AnalyzedObjectsAsFortran asFortran{
      [](llvm::raw_ostream &o, const Fortran::evaluate::GenericExprWrapper &x) {
        if (x.v) {
          x.v->AsFortran(o);
        } else {
          o << "(bad expression)";
        }
      },
      [](llvm::raw_ostream &o,
         const Fortran::evaluate::GenericAssignmentWrapper &x) {
        if (x.v) {
          x.v->AsFortran(o);
        } else {
          o << "(bad assignment)";
        }
      },
      [](llvm::raw_ostream &o, const Fortran::evaluate::ProcedureRef &x) {
        x.AsFortran(o << "CALL ");
      },
  };

  /// Whether to time the invocation. Set when -ftime-report or -ftime-report=
  /// is enabled.
  bool enableTimers;

public:
  CompilerInvocation() = default;

  FrontendOptions &getFrontendOpts() { return frontendOpts; }
  const FrontendOptions &getFrontendOpts() const { return frontendOpts; }

  Fortran::parser::Options &getFortranOpts() { return parserOpts; }
  const Fortran::parser::Options &getFortranOpts() const { return parserOpts; }

  TargetOptions &getTargetOpts() { return targetOpts; }
  const TargetOptions &getTargetOpts() const { return targetOpts; }

  CodeGenOptions &getCodeGenOpts() { return codeGenOpts; }
  const CodeGenOptions &getCodeGenOpts() const { return codeGenOpts; }

  Fortran::common::LangOptions &getLangOpts() { return langOpts; }
  const Fortran::common::LangOptions &getLangOpts() const { return langOpts; }

  Fortran::lower::LoweringOptions &getLoweringOpts() { return loweringOpts; }
  const Fortran::lower::LoweringOptions &getLoweringOpts() const {
    return loweringOpts;
  }

  /// Creates and configures semantics context based on the compilation flags.
  std::unique_ptr<Fortran::semantics::SemanticsContext>
  getSemanticsCtx(Fortran::parser::AllCookedSources &allCookedSources,
                  const llvm::TargetMachine &);

  std::string &getModuleDir() { return moduleDir; }
  const std::string &getModuleDir() const { return moduleDir; }

  std::string &getModuleFileSuffix() { return moduleFileSuffix; }
  const std::string &getModuleFileSuffix() const { return moduleFileSuffix; }

  bool &getDebugModuleDir() { return debugModuleDir; }
  const bool &getDebugModuleDir() const { return debugModuleDir; }

  bool &getHermeticModuleFileOutput() { return hermeticModuleFileOutput; }
  const bool &getHermeticModuleFileOutput() const {
    return hermeticModuleFileOutput;
  }

  bool &getWarnAsErr() { return warnAsErr; }
  const bool &getWarnAsErr() const { return warnAsErr; }

  bool &getUseAnalyzedObjectsForUnparse() {
    return useAnalyzedObjectsForUnparse;
  }
  const bool &getUseAnalyzedObjectsForUnparse() const {
    return useAnalyzedObjectsForUnparse;
  }

  bool &getEnableConformanceChecks() { return enableConformanceChecks; }
  const bool &getEnableConformanceChecks() const {
    return enableConformanceChecks;
  }

  const char *getArgv0() { return argv0; }

  bool &getEnableUsageChecks() { return enableUsageChecks; }
  const bool &getEnableUsageChecks() const { return enableUsageChecks; }

  bool &getDisableWarnings() { return disableWarnings; }
  const bool &getDisableWarnings() const { return disableWarnings; }

  Fortran::parser::AnalyzedObjectsAsFortran &getAsFortran() {
    return asFortran;
  }
  const Fortran::parser::AnalyzedObjectsAsFortran &getAsFortran() const {
    return asFortran;
  }

  Fortran::common::IntrinsicTypeDefaultKinds &getDefaultKinds() {
    return defaultKinds;
  }
  const Fortran::common::IntrinsicTypeDefaultKinds &getDefaultKinds() const {
    return defaultKinds;
  }

  bool getEnableTimers() const { return enableTimers; }

  /// Create a compiler invocation from a list of input options.
  /// \returns true on success.
  /// \returns false if an error was encountered while parsing the arguments
  /// \param [out] res - The resulting invocation.
  static bool createFromArgs(CompilerInvocation &res,
                             llvm::ArrayRef<const char *> commandLineArgs,
                             clang::DiagnosticsEngine &diags,
                             const char *argv0 = nullptr);

  // Enables the std=f2018 conformance check
  void setEnableConformanceChecks() { enableConformanceChecks = true; }

  // Enables the usage checks
  void setEnableUsageChecks() { enableUsageChecks = true; }

  // Disables all Warnings
  void setDisableWarnings() { disableWarnings = true; }

  /// Useful setters
  void setArgv0(const char *dir) { argv0 = dir; }

  void setModuleDir(std::string &dir) { moduleDir = dir; }

  void setModuleFileSuffix(const char *suffix) {
    moduleFileSuffix = std::string(suffix);
  }

  void setDebugModuleDir(bool flag) { debugModuleDir = flag; }
  void setHermeticModuleFileOutput(bool flag) {
    hermeticModuleFileOutput = flag;
  }

  void setWarnAsErr(bool flag) { warnAsErr = flag; }

  void setUseAnalyzedObjectsForUnparse(bool flag) {
    useAnalyzedObjectsForUnparse = flag;
  }

  /// Set the Fortran options to predefined defaults.
  // TODO: We should map frontendOpts_ to parserOpts_ instead. For that, we
  // need to extend frontendOpts_ first. Next, we need to add the corresponding
  // compiler driver options in libclangDriver.
  void setDefaultFortranOpts();

  /// Set the default predefinitions.
  void setDefaultPredefinitions();

  /// Collect the macro definitions from preprocessorOpts_ and prepare them for
  /// the parser (i.e. copy into parserOpts_)
  void collectMacroDefinitions();

  /// Set the Fortran options to user-specified values.
  /// These values are found in the preprocessor options.
  void setFortranOpts();

  /// Set the Semantic Options
  void setSemanticsOpts(Fortran::parser::AllCookedSources &);

  /// Set \p loweringOptions controlling lowering behavior based
  /// on the \p optimizationLevel.
  void setLoweringOptions();
};

} // end namespace Fortran::frontend
#endif // FORTRAN_FRONTEND_COMPILERINVOCATION_H
