//===- 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
//
//===----------------------------------------------------------------------===//
#ifndef LLVM_FLANG_FRONTEND_COMPILERINVOCATION_H
#define LLVM_FLANG_FRONTEND_COMPILERINVOCATION_H

#include "flang/Frontend/FrontendOptions.h"
#include "flang/Frontend/PreprocessorOptions.h"
#include "flang/Parser/parsing.h"
#include "flang/Semantics/semantics.h"
#include "clang/Basic/Diagnostic.h"
#include "clang/Basic/DiagnosticOptions.h"
#include "llvm/Option/ArgList.h"
#include <memory>

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, bool defaultDiagColor = true);

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 &preprocessorOpts() { return *preprocessorOpts_; }
  const PreprocessorOptions &preprocessorOpts() 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_;

  // Semantics context
  std::unique_ptr<Fortran::semantics::SemanticsContext> semanticsContext_;

  /// 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 warnAsErr_ = false;

  /// 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_;

  bool EnableConformanceChecks_ = 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 ");
      },
  };

public:
  CompilerInvocation() = default;

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

  Fortran::parser::Options &fortranOpts() { return parserOpts_; }
  const Fortran::parser::Options &fortranOpts() const { return parserOpts_; }

  Fortran::semantics::SemanticsContext &semanticsContext() {
    return *semanticsContext_;
  }
  const Fortran::semantics::SemanticsContext &semanticsContext() const {
    return *semanticsContext_;
  }

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

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

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

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

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

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

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

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

  /// 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);

  // Enables the std=f2018 conformance check
  void set_EnableConformanceChecks() { EnableConformanceChecks_ = true; }

  /// Useful setters
  void SetModuleDir(std::string &moduleDir) { moduleDir_ = moduleDir; }

  void SetModuleFileSuffix(const char *moduleFileSuffix) {
    moduleFileSuffix_ = std::string(moduleFileSuffix);
  }

  void SetDebugModuleDir(bool flag) { debugModuleDir_ = 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 &);
};

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