//===-- include/flang/Semantics/semantics.h ---------------------*- 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 FORTRAN_SEMANTICS_SEMANTICS_H_
#define FORTRAN_SEMANTICS_SEMANTICS_H_

#include "module-dependences.h"
#include "program-tree.h"
#include "scope.h"
#include "symbol.h"
#include "flang/Common/Fortran-features.h"
#include "flang/Common/LangOptions.h"
#include "flang/Evaluate/common.h"
#include "flang/Evaluate/intrinsics.h"
#include "flang/Evaluate/target.h"
#include "flang/Parser/message.h"
#include <iosfwd>
#include <set>
#include <string>
#include <vector>

namespace llvm {
class raw_ostream;
}

namespace Fortran::common {
class IntrinsicTypeDefaultKinds;
}

namespace Fortran::parser {
struct Name;
struct Program;
class AllCookedSources;
struct AssociateConstruct;
struct BlockConstruct;
struct CaseConstruct;
struct DoConstruct;
struct ChangeTeamConstruct;
struct CriticalConstruct;
struct ForallConstruct;
struct IfConstruct;
struct SelectRankConstruct;
struct SelectTypeConstruct;
struct Variable;
struct WhereConstruct;
} // namespace Fortran::parser

namespace Fortran::semantics {

class Symbol;
class CommonBlockMap;
using CommonBlockList = std::vector<std::pair<SymbolRef, std::size_t>>;

using ConstructNode = std::variant<const parser::AssociateConstruct *,
    const parser::BlockConstruct *, const parser::CaseConstruct *,
    const parser::ChangeTeamConstruct *, const parser::CriticalConstruct *,
    const parser::DoConstruct *, const parser::ForallConstruct *,
    const parser::IfConstruct *, const parser::SelectRankConstruct *,
    const parser::SelectTypeConstruct *, const parser::WhereConstruct *>;
using ConstructStack = std::vector<ConstructNode>;

class SemanticsContext {
public:
  SemanticsContext(const common::IntrinsicTypeDefaultKinds &,
      const common::LanguageFeatureControl &, const common::LangOptions &,
      parser::AllCookedSources &);
  ~SemanticsContext();

  const common::IntrinsicTypeDefaultKinds &defaultKinds() const {
    return defaultKinds_;
  }
  const common::LanguageFeatureControl &languageFeatures() const {
    return languageFeatures_;
  }
  const common::LangOptions &langOptions() const { return langOpts_; }
  int GetDefaultKind(TypeCategory) const;
  int doublePrecisionKind() const {
    return defaultKinds_.doublePrecisionKind();
  }
  int quadPrecisionKind() const { return defaultKinds_.quadPrecisionKind(); }
  bool IsEnabled(common::LanguageFeature feature) const {
    return languageFeatures_.IsEnabled(feature);
  }
  template <typename A> bool ShouldWarn(A x) const {
    return languageFeatures_.ShouldWarn(x);
  }
  const std::optional<parser::CharBlock> &location() const { return location_; }
  const std::vector<std::string> &searchDirectories() const {
    return searchDirectories_;
  }
  const std::vector<std::string> &intrinsicModuleDirectories() const {
    return intrinsicModuleDirectories_;
  }
  const std::string &moduleDirectory() const { return moduleDirectory_; }
  const std::string &moduleFileSuffix() const { return moduleFileSuffix_; }
  bool underscoring() const { return underscoring_; }
  bool warningsAreErrors() const { return warningsAreErrors_; }
  bool debugModuleWriter() const { return debugModuleWriter_; }
  const evaluate::IntrinsicProcTable &intrinsics() const { return intrinsics_; }
  const evaluate::TargetCharacteristics &targetCharacteristics() const {
    return targetCharacteristics_;
  }
  evaluate::TargetCharacteristics &targetCharacteristics() {
    return targetCharacteristics_;
  }
  Scope &globalScope() { return globalScope_; }
  Scope &intrinsicModulesScope() { return intrinsicModulesScope_; }
  Scope *currentHermeticModuleFileScope() {
    return currentHermeticModuleFileScope_;
  }
  void set_currentHermeticModuleFileScope(Scope *scope) {
    currentHermeticModuleFileScope_ = scope;
  }
  parser::Messages &messages() { return messages_; }
  evaluate::FoldingContext &foldingContext() { return foldingContext_; }
  parser::AllCookedSources &allCookedSources() { return allCookedSources_; }
  ModuleDependences &moduleDependences() { return moduleDependences_; }
  std::map<const Symbol *, SourceName> &moduleFileOutputRenamings() {
    return moduleFileOutputRenamings_;
  }

  SemanticsContext &set_location(
      const std::optional<parser::CharBlock> &location) {
    location_ = location;
    return *this;
  }
  SemanticsContext &set_searchDirectories(const std::vector<std::string> &x) {
    searchDirectories_ = x;
    return *this;
  }
  SemanticsContext &set_intrinsicModuleDirectories(
      const std::vector<std::string> &x) {
    intrinsicModuleDirectories_ = x;
    return *this;
  }
  SemanticsContext &set_moduleDirectory(const std::string &x) {
    moduleDirectory_ = x;
    return *this;
  }
  SemanticsContext &set_moduleFileSuffix(const std::string &x) {
    moduleFileSuffix_ = x;
    return *this;
  }
  SemanticsContext &set_underscoring(bool x) {
    underscoring_ = x;
    return *this;
  }
  SemanticsContext &set_warnOnNonstandardUsage(bool x) {
    warnOnNonstandardUsage_ = x;
    return *this;
  }
  SemanticsContext &set_warningsAreErrors(bool x) {
    warningsAreErrors_ = x;
    return *this;
  }

  SemanticsContext &set_debugModuleWriter(bool x) {
    debugModuleWriter_ = x;
    return *this;
  }

  const DeclTypeSpec &MakeNumericType(TypeCategory, int kind = 0);
  const DeclTypeSpec &MakeLogicalType(int kind = 0);

  bool AnyFatalError() const;

  // Test or set the Error flag on a Symbol
  bool HasError(const Symbol &);
  bool HasError(const Symbol *);
  bool HasError(const parser::Name &);
  void SetError(const Symbol &, bool = true);

  template <typename... A> parser::Message &Say(A &&...args) {
    CHECK(location_);
    return messages_.Say(*location_, std::forward<A>(args)...);
  }
  template <typename... A>
  parser::Message &Say(parser::CharBlock at, A &&...args) {
    return messages_.Say(at, std::forward<A>(args)...);
  }
  parser::Message &Say(parser::Message &&msg) {
    return messages_.Say(std::move(msg));
  }
  template <typename... A>
  parser::Message &SayWithDecl(const Symbol &symbol,
      const parser::CharBlock &at, parser::MessageFixedText &&msg,
      A &&...args) {
    auto &message{Say(at, std::move(msg), args...)};
    evaluate::AttachDeclaration(&message, symbol);
    return message;
  }

  template <typename FeatureOrUsageWarning, typename... A>
  parser::Message *Warn(
      FeatureOrUsageWarning warning, parser::CharBlock at, A &&...args) {
    if (languageFeatures_.ShouldWarn(warning) && !IsInModuleFile(at)) {
      parser::Message &msg{
          messages_.Say(warning, at, std::forward<A>(args)...)};
      return &msg;
    } else {
      return nullptr;
    }
  }

  template <typename FeatureOrUsageWarning, typename... A>
  parser::Message *Warn(FeatureOrUsageWarning warning, A &&...args) {
    CHECK(location_);
    return Warn(warning, *location_, std::forward<A>(args)...);
  }

  const Scope &FindScope(parser::CharBlock) const;
  Scope &FindScope(parser::CharBlock);
  void UpdateScopeIndex(Scope &, parser::CharBlock);

  bool IsInModuleFile(parser::CharBlock) const;

  const ConstructStack &constructStack() const { return constructStack_; }
  template <typename N> void PushConstruct(const N &node) {
    constructStack_.emplace_back(&node);
  }
  void PopConstruct();

  ENUM_CLASS(IndexVarKind, DO, FORALL)
  // Check to see if a variable being redefined is a DO or FORALL index.
  // If so, emit a message.
  void WarnIndexVarRedefine(const parser::CharBlock &, const Symbol &);
  void CheckIndexVarRedefine(const parser::CharBlock &, const Symbol &);
  void CheckIndexVarRedefine(const parser::Variable &);
  void CheckIndexVarRedefine(const parser::Name &);
  void ActivateIndexVar(const parser::Name &, IndexVarKind);
  void DeactivateIndexVar(const parser::Name &);
  SymbolVector GetIndexVars(IndexVarKind);
  SourceName SaveTempName(std::string &&);
  SourceName GetTempName(const Scope &);
  static bool IsTempName(const std::string &);

  // Locate and process the contents of a built-in module on demand
  Scope *GetBuiltinModule(const char *name);

  // Defines builtinsScope_ from the __Fortran_builtins module
  void UseFortranBuiltinsModule();
  const Scope *GetBuiltinsScope() const { return builtinsScope_; }

  const Scope &GetCUDABuiltinsScope();
  const Scope &GetCUDADeviceScope();

  void UsePPCBuiltinTypesModule();
  void UsePPCBuiltinsModule();
  Scope *GetPPCBuiltinTypesScope() { return ppcBuiltinTypesScope_; }
  const Scope *GetPPCBuiltinsScope() const { return ppcBuiltinsScope_; }

  // Saves a module file's parse tree so that it remains available
  // during semantics.
  parser::Program &SaveParseTree(parser::Program &&);

  // Ensures a common block definition does not conflict with previous
  // appearances in the program and consolidate information about
  // common blocks at the program level for later checks and lowering.
  // This can obviously not check any conflicts between different compilation
  // units (in case such conflicts exist, the behavior will depend on the
  // linker).
  void MapCommonBlockAndCheckConflicts(const Symbol &);

  // Get the list of common blocks appearing in the program. If a common block
  // appears in several subprograms, only one of its appearance is returned in
  // the list alongside the biggest byte size of all its appearances.
  // If a common block is initialized in any of its appearances, the list will
  // contain the appearance with the initialization, otherwise the appearance
  // with the biggest size is returned. The extra byte size information allows
  // handling the case where the common block initialization is not the
  // appearance with the biggest size: the common block will have the biggest
  // size with the first bytes initialized with the initial value. This is not
  // standard, if the initialization and biggest size appearances are in
  // different compilation units, the behavior will depend on the linker. The
  // linker may have the behavior described before, but it may also keep the
  // initialized common symbol without extending its size, or have some other
  // behavior.
  CommonBlockList GetCommonBlocks() const;

  void NoteDefinedSymbol(const Symbol &);
  bool IsSymbolDefined(const Symbol &) const;

  void DumpSymbols(llvm::raw_ostream &);

  // Top-level ProgramTrees are owned by the SemanticsContext for persistence.
  ProgramTree &SaveProgramTree(ProgramTree &&);

private:
  struct ScopeIndexComparator {
    bool operator()(parser::CharBlock, parser::CharBlock) const;
  };
  using ScopeIndex =
      std::multimap<parser::CharBlock, Scope &, ScopeIndexComparator>;
  ScopeIndex::iterator SearchScopeIndex(parser::CharBlock);

  parser::Message *CheckIndexVarRedefine(
      const parser::CharBlock &, const Symbol &, parser::MessageFixedText &&);
  void CheckError(const Symbol &);

  const common::IntrinsicTypeDefaultKinds &defaultKinds_;
  const common::LanguageFeatureControl &languageFeatures_;
  const common::LangOptions &langOpts_;
  parser::AllCookedSources &allCookedSources_;
  std::optional<parser::CharBlock> location_;
  std::vector<std::string> searchDirectories_;
  std::vector<std::string> intrinsicModuleDirectories_;
  std::string moduleDirectory_{"."s};
  std::string moduleFileSuffix_{".mod"};
  bool underscoring_{true};
  bool warnOnNonstandardUsage_{false};
  bool warningsAreErrors_{false};
  bool debugModuleWriter_{false};
  const evaluate::IntrinsicProcTable intrinsics_;
  evaluate::TargetCharacteristics targetCharacteristics_;
  Scope globalScope_;
  Scope &intrinsicModulesScope_;
  Scope *currentHermeticModuleFileScope_{nullptr};
  ScopeIndex scopeIndex_;
  parser::Messages messages_;
  evaluate::FoldingContext foldingContext_;
  ConstructStack constructStack_;
  struct IndexVarInfo {
    parser::CharBlock location;
    IndexVarKind kind;
  };
  std::map<SymbolRef, const IndexVarInfo, SymbolAddressCompare>
      activeIndexVars_;
  UnorderedSymbolSet errorSymbols_;
  std::set<std::string> tempNames_;
  const Scope *builtinsScope_{nullptr}; // module __Fortran_builtins
  Scope *ppcBuiltinTypesScope_{nullptr}; // module __Fortran_PPC_types
  std::optional<const Scope *> cudaBuiltinsScope_; // module __CUDA_builtins
  std::optional<const Scope *> cudaDeviceScope_; // module cudadevice
  const Scope *ppcBuiltinsScope_{nullptr}; // module __ppc_intrinsics
  std::list<parser::Program> modFileParseTrees_;
  std::unique_ptr<CommonBlockMap> commonBlockMap_;
  ModuleDependences moduleDependences_;
  std::map<const Symbol *, SourceName> moduleFileOutputRenamings_;
  UnorderedSymbolSet isDefined_;
  std::list<ProgramTree> programTrees_;
};

class Semantics {
public:
  explicit Semantics(SemanticsContext &context, parser::Program &program)
      : context_{context}, program_{program} {}
  Semantics &set_hermeticModuleFileOutput(bool yes = true) {
    hermeticModuleFileOutput_ = yes;
    return *this;
  }

  SemanticsContext &context() const { return context_; }
  bool Perform();
  const Scope &FindScope(const parser::CharBlock &where) const {
    return context_.FindScope(where);
  }
  bool AnyFatalError() const { return context_.AnyFatalError(); }
  void EmitMessages(llvm::raw_ostream &);
  void DumpSymbols(llvm::raw_ostream &);
  void DumpSymbolsSources(llvm::raw_ostream &) const;

private:
  SemanticsContext &context_;
  parser::Program &program_;
  bool hermeticModuleFileOutput_{false};
};

// Base class for semantics checkers.
struct BaseChecker {
  template <typename N> void Enter(const N &) {}
  template <typename N> void Leave(const N &) {}
};
} // namespace Fortran::semantics
#endif
