text: //===-- lib/Semantics/check-directive-structure.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
  //
  //===----------------------------------------------------------------------===//
  
  // Directive structure validity checks common to OpenMP, OpenACC and other
  // directive language.
  
  #ifndef FORTRAN_SEMANTICS_CHECK_DIRECTIVE_STRUCTURE_H_
  #define FORTRAN_SEMANTICS_CHECK_DIRECTIVE_STRUCTURE_H_
  
  #include "flang/Common/enum-set.h"
  #include "flang/Semantics/semantics.h"
  #include "flang/Semantics/tools.h"
  #include <unordered_map>
  
  namespace Fortran::semantics {
  
  template <typename C, std::size_t ClauseEnumSize> struct DirectiveClauses {
    const common::EnumSet<C, ClauseEnumSize> allowed;
    const common::EnumSet<C, ClauseEnumSize> allowedOnce;
    const common::EnumSet<C, ClauseEnumSize> allowedExclusive;
    const common::EnumSet<C, ClauseEnumSize> requiredOneOf;
  };
  
  // Generic branching checker for invalid branching out of OpenMP/OpenACC
  // directive.
  // typename D is the directive enumeration.
  template <typename D> class NoBranchingEnforce {
  public:
    NoBranchingEnforce(SemanticsContext &context,
        parser::CharBlock sourcePosition, D directive,
        std::string &&upperCaseDirName)
        : context_{context}, sourcePosition_{sourcePosition},
          upperCaseDirName_{std::move(upperCaseDirName)}, currentDirective_{
                                                              directive} {}
    template <typename T> bool Pre(const T &) { return true; }
    template <typename T> void Post(const T &) {}
  
    template <typename T> bool Pre(const parser::Statement<T> &statement) {
      currentStatementSourcePosition_ = statement.source;
      if (statement.label.has_value()) {
        labels_.insert(*statement.label);
      }
      return true;
    }
  
    void Post(const parser::ReturnStmt &) { EmitBranchOutError("RETURN"); }
    void Post(const parser::ExitStmt &exitStmt) {
      if (const auto &exitName{exitStmt.v}) {
        CheckConstructNameBranching("EXIT", exitName.value());
      }
    }
    void Post(const parser::StopStmt &) { EmitBranchOutError("STOP"); }
  
    std::set<parser::Label> labels() { return labels_; }
  
  private:
    parser::MessageFormattedText GetEnclosingMsg() const {
      return {"Enclosing %s construct"_en_US, upperCaseDirName_};
    }
  
    void EmitBranchOutError(const char *stmt) const {
      context_
          .Say(currentStatementSourcePosition_,
              "%s statement is not allowed in a %s construct"_err_en_US, stmt,
              upperCaseDirName_)
          .Attach(sourcePosition_, GetEnclosingMsg());
    }
  
    void EmitBranchOutErrorWithName(
        const char *stmt, const parser::Name &toName) const {
      const std::string branchingToName{toName.ToString()};
      context_
          .Say(currentStatementSourcePosition_,
              "%s to construct '%s' outside of %s construct is not allowed"_err_en_US,
              stmt, branchingToName, upperCaseDirName_)
          .Attach(sourcePosition_, GetEnclosingMsg());
    }
  
    // Current semantic checker is not following OpenACC/OpenMP constructs as they
    // are not Fortran constructs. Hence the ConstructStack doesn't capture
    // OpenACC/OpenMP constructs. Apply an inverse way to figure out if a
    // construct-name is branching out of an OpenACC/OpenMP construct. The control
    // flow goes out of an OpenACC/OpenMP construct, if a construct-name from
    // statement is found in ConstructStack.
    void CheckConstructNameBranching(
        const char *stmt, const parser::Name &stmtName) {
      const ConstructStack &stack{context_.constructStack()};
      for (auto iter{stack.cend()}; iter-- != stack.cbegin();) {
        const ConstructNode &construct{*iter};
        const auto &constructName{MaybeGetNodeName(construct)};
        if (constructName) {
          if (stmtName.source == constructName->source) {
            EmitBranchOutErrorWithName(stmt, stmtName);
            return;
          }
        }
      }
    }
  
    SemanticsContext &context_;
    parser::CharBlock currentStatementSourcePosition_;
    parser::CharBlock sourcePosition_;
    std::string upperCaseDirName_;
    D currentDirective_;
    std::set<parser::Label> labels_;
  };
  
  // Generic structure checker for directives/clauses language such as OpenMP
  // and OpenACC.
  // typename D is the directive enumeration.
  // tyepname C is the clause enumeration.
  // typename PC is the parser class defined in parse-tree.h for the clauses.
  template <typename D, typename C, typename PC, std::size_t ClauseEnumSize>
  class DirectiveStructureChecker : public virtual BaseChecker {
  protected:
    DirectiveStructureChecker(SemanticsContext &context,
        std::unordered_map<D, DirectiveClauses<C, ClauseEnumSize>>
            directiveClausesMap)
        : context_{context}, directiveClausesMap_(directiveClausesMap) {}
    virtual ~DirectiveStructureChecker() {}
  
    struct DirectiveContext {
      DirectiveContext(parser::CharBlock source, D d)
          : directiveSource{source}, directive{d} {}
  
      parser::CharBlock directiveSource{nullptr};
      parser::CharBlock clauseSource{nullptr};
      D directive;
      common::EnumSet<C, ClauseEnumSize> allowedClauses{};
      common::EnumSet<C, ClauseEnumSize> allowedOnceClauses{};
      common::EnumSet<C, ClauseEnumSize> allowedExclusiveClauses{};
      common::EnumSet<C, ClauseEnumSize> requiredClauses{};
  
      const PC *clause{nullptr};
      std::multimap<C, const PC *> clauseInfo;
      std::list<C> actualClauses;
      Symbol *loopIV{nullptr};
    };
  
    void SetLoopIv(Symbol *symbol) { GetContext().loopIV = symbol; }
  
    // back() is the top of the stack
    DirectiveContext &GetContext() {
      CHECK(!dirContext_.empty());
      return dirContext_.back();
    }
  
    void SetContextClause(const PC &clause) {
      GetContext().clauseSource = clause.source;
      GetContext().clause = &clause;
    }
  
    void ResetPartialContext(const parser::CharBlock &source) {
      CHECK(!dirContext_.empty());
      SetContextDirectiveSource(source);
      GetContext().allowedClauses = {};
      GetContext().allowedOnceClauses = {};
      GetContext().allowedExclusiveClauses = {};
      GetContext().requiredClauses = {};
      GetContext().clauseInfo = {};
      GetContext().loopIV = {nullptr};
    }
  
    void SetContextDirectiveSource(const parser::CharBlock &directive) {
      GetContext().directiveSource = directive;
    }
  
    void SetContextDirectiveEnum(D dir) { GetContext().directive = dir; }
  
    void SetContextAllowed(const common::EnumSet<C, ClauseEnumSize> &allowed) {
      GetContext().allowedClauses = allowed;
    }
  
    void SetContextAllowedOnce(
        const common::EnumSet<C, ClauseEnumSize> &allowedOnce) {
      GetContext().allowedOnceClauses = allowedOnce;
    }
  
    void SetContextAllowedExclusive(
        const common::EnumSet<C, ClauseEnumSize> &allowedExclusive) {
      GetContext().allowedExclusiveClauses = allowedExclusive;
    }
  
    void SetContextRequired(const common::EnumSet<C, ClauseEnumSize> &required) {
      GetContext().requiredClauses = required;
    }
  
    void SetContextClauseInfo(C type) {
      GetContext().clauseInfo.emplace(type, GetContext().clause);
    }
  
    void AddClauseToCrtContext(C type) {
      GetContext().actualClauses.push_back(type);
    }
  
    const PC *FindClause(C type) {
      auto it{GetContext().clauseInfo.find(type)};
      if (it != GetContext().clauseInfo.end()) {
        return it->second;
      }
      return nullptr;
    }
  
    void PushContext(const parser::CharBlock &source, D dir) {
      dirContext_.emplace_back(source, dir);
    }
  
    bool CurrentDirectiveIsNested() { return dirContext_.size() > 0; };
  
    void SetClauseSets(D dir) {
      dirContext_.back().allowedClauses = directiveClausesMap_[dir].allowed;
      dirContext_.back().allowedOnceClauses =
          directiveClausesMap_[dir].allowedOnce;
      dirContext_.back().allowedExclusiveClauses =
          directiveClausesMap_[dir].allowedExclusive;
      dirContext_.back().requiredClauses =
          directiveClausesMap_[dir].requiredOneOf;
    }
    void PushContextAndClauseSets(const parser::CharBlock &source, D dir) {
      PushContext(source, dir);
      SetClauseSets(dir);
    }
  
    void SayNotMatching(const parser::CharBlock &, const parser::CharBlock &);
  
    template <typename B> void CheckMatching(const B &beginDir, const B &endDir) {
      const auto &begin{beginDir.v};
      const auto &end{endDir.v};
      if (begin != end) {
        SayNotMatching(beginDir.source, endDir.source);
      }
    }
    // Check illegal branching out of `Parser::Block` for `Parser::Name` based
    // nodes (examples `Parser::ExitStmt`) along with `Parser::Label`
    // based nodes (example `Parser::GotoStmt`).
    void CheckNoBranching(const parser::Block &block, D directive,
        const parser::CharBlock &directiveSource);
  
    // Check that only clauses in set are after the specific clauses.
    void CheckOnlyAllowedAfter(C clause, common::EnumSet<C, ClauseEnumSize> set);
  
    void CheckRequireAtLeastOneOf();
  
    void CheckAllowed(C clause);
  
    void CheckAtLeastOneClause();
  
    void CheckNotAllowedIfClause(
        C clause, common::EnumSet<C, ClauseEnumSize> set);
  
    std::string ContextDirectiveAsFortran();
  
    void RequiresConstantPositiveParameter(
        const C &clause, const parser::ScalarIntConstantExpr &i);
  
    void RequiresPositiveParameter(const C &clause,
        const parser::ScalarIntExpr &i, llvm::StringRef paramName = "parameter");
  
    void OptionalConstantPositiveParameter(
        const C &clause, const std::optional<parser::ScalarIntConstantExpr> &o);
  
    virtual llvm::StringRef getClauseName(C clause) { return ""; };
  
    virtual llvm::StringRef getDirectiveName(D directive) { return ""; };
  
    SemanticsContext &context_;
    std::vector<DirectiveContext> dirContext_; // used as a stack
    std::unordered_map<D, DirectiveClauses<C, ClauseEnumSize>>
        directiveClausesMap_;
  
    std::string ClauseSetToString(const common::EnumSet<C, ClauseEnumSize> set);
  };
  
  template <typename D, typename C, typename PC, std::size_t ClauseEnumSize>
  void DirectiveStructureChecker<D, C, PC, ClauseEnumSize>::CheckNoBranching(
      const parser::Block &block, D directive,
      const parser::CharBlock &directiveSource) {
    NoBranchingEnforce<D> noBranchingEnforce{
        context_, directiveSource, directive, ContextDirectiveAsFortran()};
    parser::Walk(block, noBranchingEnforce);
  
    auto construct{parser::ToUpperCaseLetters(getDirectiveName(directive).str())};
    LabelEnforce directiveLabelEnforce{context_, noBranchingEnforce.labels(),
        directiveSource, construct.c_str()};
    parser::Walk(block, directiveLabelEnforce);
  }
  
  // Check that only clauses included in the given set are present after the given
  // clause.
  template <typename D, typename C, typename PC, std::size_t ClauseEnumSize>
  void DirectiveStructureChecker<D, C, PC, ClauseEnumSize>::CheckOnlyAllowedAfter(
      C clause, common::EnumSet<C, ClauseEnumSize> set) {
    bool enforceCheck = false;
    for (auto cl : GetContext().actualClauses) {
      if (cl == clause) {
        enforceCheck = true;
        continue;
      } else if (enforceCheck && !set.test(cl)) {
        auto parserClause = GetContext().clauseInfo.find(cl);
        context_.Say(parserClause->second->source,
            "Clause %s is not allowed after clause %s on the %s "
            "directive"_err_en_US,
            parser::ToUpperCaseLetters(getClauseName(cl).str()),
            parser::ToUpperCaseLetters(getClauseName(clause).str()),
            ContextDirectiveAsFortran());
      }
    }
  }
  
  // Check that at least one clause is attached to the directive.
  template <typename D, typename C, typename PC, std::size_t ClauseEnumSize>
  void DirectiveStructureChecker<D, C, PC,
      ClauseEnumSize>::CheckAtLeastOneClause() {
    if (GetContext().actualClauses.empty()) {
      context_.Say(GetContext().directiveSource,
          "At least one clause is required on the %s directive"_err_en_US,
          ContextDirectiveAsFortran());
    }
  }
  
  template <typename D, typename C, typename PC, std::size_t ClauseEnumSize>
  std::string
  DirectiveStructureChecker<D, C, PC, ClauseEnumSize>::ClauseSetToString(
      const common::EnumSet<C, ClauseEnumSize> set) {
    std::string list;
    set.IterateOverMembers([&](C o) {
      if (!list.empty())
        list.append(", ");
      list.append(parser::ToUpperCaseLetters(getClauseName(o).str()));
    });
    return list;
  }
  
  // Check that at least one clause in the required set is present on the
  // directive.
  template <typename D, typename C, typename PC, std::size_t ClauseEnumSize>
  void DirectiveStructureChecker<D, C, PC,
      ClauseEnumSize>::CheckRequireAtLeastOneOf() {
    if (GetContext().requiredClauses.empty())
      return;
    for (auto cl : GetContext().actualClauses) {
      if (GetContext().requiredClauses.test(cl))
        return;
    }
    // No clause matched in the actual clauses list
    context_.Say(GetContext().directiveSource,
        "At least one of %s clause must appear on the %s directive"_err_en_US,
        ClauseSetToString(GetContext().requiredClauses),
        ContextDirectiveAsFortran());
  }
  
  template <typename D, typename C, typename PC, std::size_t ClauseEnumSize>
  std::string DirectiveStructureChecker<D, C, PC,
      ClauseEnumSize>::ContextDirectiveAsFortran() {
    return parser::ToUpperCaseLetters(
        getDirectiveName(GetContext().directive).str());
  }
  
  // Check that clauses present on the directive are allowed clauses.
  template <typename D, typename C, typename PC, std::size_t ClauseEnumSize>
  void DirectiveStructureChecker<D, C, PC, ClauseEnumSize>::CheckAllowed(
      C clause) {
    if (!GetContext().allowedClauses.test(clause) &&
        !GetContext().allowedOnceClauses.test(clause) &&
        !GetContext().allowedExclusiveClauses.test(clause) &&
        !GetContext().requiredClauses.test(clause)) {
      context_.Say(GetContext().clauseSource,
          "%s clause is not allowed on the %s directive"_err_en_US,
          parser::ToUpperCaseLetters(getClauseName(clause).str()),
          parser::ToUpperCaseLetters(GetContext().directiveSource.ToString()));
      return;
    }
    if ((GetContext().allowedOnceClauses.test(clause) ||
            GetContext().allowedExclusiveClauses.test(clause)) &&
        FindClause(clause)) {
      context_.Say(GetContext().clauseSource,
          "At most one %s clause can appear on the %s directive"_err_en_US,
          parser::ToUpperCaseLetters(getClauseName(clause).str()),
          parser::ToUpperCaseLetters(GetContext().directiveSource.ToString()));
      return;
    }
    if (GetContext().allowedExclusiveClauses.test(clause)) {
      std::vector<C> others;
      GetContext().allowedExclusiveClauses.IterateOverMembers([&](C o) {
        if (FindClause(o)) {
          others.emplace_back(o);
        }
      });
      for (const auto &e : others) {
        context_.Say(GetContext().clauseSource,
            "%s and %s clauses are mutually exclusive and may not appear on the "
            "same %s directive"_err_en_US,
            parser::ToUpperCaseLetters(getClauseName(clause).str()),
            parser::ToUpperCaseLetters(getClauseName(e).str()),
            parser::ToUpperCaseLetters(GetContext().directiveSource.ToString()));
      }
      if (!others.empty()) {
        return;
      }
    }
    SetContextClauseInfo(clause);
    AddClauseToCrtContext(clause);
  }
  
  // Enforce restriction where clauses in the given set are not allowed if the
  // given clause appears.
  template <typename D, typename C, typename PC, std::size_t ClauseEnumSize>
  void DirectiveStructureChecker<D, C, PC,
      ClauseEnumSize>::CheckNotAllowedIfClause(C clause,
      common::EnumSet<C, ClauseEnumSize> set) {
    if (std::find(GetContext().actualClauses.begin(),
            GetContext().actualClauses.end(),
            clause) == GetContext().actualClauses.end()) {
      return; // Clause is not present
    }
  
    for (auto cl : GetContext().actualClauses) {
      if (set.test(cl)) {
        context_.Say(GetContext().directiveSource,
            "Clause %s is not allowed if clause %s appears on the %s directive"_err_en_US,
            parser::ToUpperCaseLetters(getClauseName(cl).str()),
            parser::ToUpperCaseLetters(getClauseName(clause).str()),
            ContextDirectiveAsFortran());
      }
    }
  }
  
  // Check the value of the clause is a constant positive integer.
  template <typename D, typename C, typename PC, std::size_t ClauseEnumSize>
  void DirectiveStructureChecker<D, C, PC,
      ClauseEnumSize>::RequiresConstantPositiveParameter(const C &clause,
      const parser::ScalarIntConstantExpr &i) {
    if (const auto v{GetIntValue(i)}) {
      if (*v <= 0) {
        context_.Say(GetContext().clauseSource,
            "The parameter of the %s clause must be "
            "a constant positive integer expression"_err_en_US,
            parser::ToUpperCaseLetters(getClauseName(clause).str()));
      }
    }
  }
  
  // Check the value of the clause is a constant positive parameter.
  template <typename D, typename C, typename PC, std::size_t ClauseEnumSize>
  void DirectiveStructureChecker<D, C, PC,
      ClauseEnumSize>::OptionalConstantPositiveParameter(const C &clause,
      const std::optional<parser::ScalarIntConstantExpr> &o) {
    if (o != std::nullopt) {
      RequiresConstantPositiveParameter(clause, o.value());
    }
  }
  
  template <typename D, typename C, typename PC, std::size_t ClauseEnumSize>
  void DirectiveStructureChecker<D, C, PC, ClauseEnumSize>::SayNotMatching(
      const parser::CharBlock &beginSource, const parser::CharBlock &endSource) {
    context_
        .Say(endSource, "Unmatched %s directive"_err_en_US,
            parser::ToUpperCaseLetters(endSource.ToString()))
        .Attach(beginSource, "Does not match directive"_en_US);
  }
  
  // Check the value of the clause is a positive parameter.
  template <typename D, typename C, typename PC, std::size_t ClauseEnumSize>
  void DirectiveStructureChecker<D, C, PC,
      ClauseEnumSize>::RequiresPositiveParameter(const C &clause,
      const parser::ScalarIntExpr &i, llvm::StringRef paramName) {
    if (const auto v{GetIntValue(i)}) {
      if (*v <= 0) {
        context_.Say(GetContext().clauseSource,
            "The %s of the %s clause must be "
            "a positive integer expression"_err_en_US,
            paramName.str(),
            parser::ToUpperCaseLetters(getClauseName(clause).str()));
      }
    }
  }
  
  } // namespace Fortran::semantics
  
  #endif // FORTRAN_SEMANTICS_CHECK_DIRECTIVE_STRUCTURE_H_
  