diff --git a/flang/lib/parser/message.cc b/flang/lib/parser/message.cc index 5f5af00e0d35..616c6d3f9bea 100644 --- a/flang/lib/parser/message.cc +++ b/flang/lib/parser/message.cc @@ -35,19 +35,6 @@ std::ostream &operator<<(std::ostream &o, const MessageFixedText &t) { MessageFormattedText::MessageFormattedText(MessageFixedText text, ...) : isFatal_{text.isFatal()} { - va_list ap; - va_start(ap, text); - SetMessageFormattedText(text, ap); - va_end(ap); -} - -MessageFormattedText::MessageFormattedText(MessageFixedText text, va_list ap) - : isFatal_{text.isFatal()} { - SetMessageFormattedText(text, ap); -} - -void MessageFormattedText::SetMessageFormattedText(MessageFixedText text, - va_list ap) { const char *p{text.text().begin()}; std::string asString; if (*text.text().end() != '\0') { @@ -56,7 +43,10 @@ void MessageFormattedText::SetMessageFormattedText(MessageFixedText text, p = asString.data(); } char buffer[256]; + va_list ap; + va_start(ap, text); vsnprintf(buffer, sizeof buffer, p, ap); + va_end(ap); string_ = buffer; } diff --git a/flang/lib/parser/message.h b/flang/lib/parser/message.h index da47982bb1b6..6368e2868b60 100644 --- a/flang/lib/parser/message.h +++ b/flang/lib/parser/message.h @@ -68,7 +68,6 @@ constexpr MessageFixedText operator""_err_en_US( class MessageFormattedText { public: MessageFormattedText(MessageFixedText, ...); - MessageFormattedText(MessageFixedText, va_list); MessageFormattedText(const MessageFormattedText &) = default; MessageFormattedText(MessageFormattedText &&) = default; MessageFormattedText &operator=(const MessageFormattedText &) = default; @@ -78,7 +77,6 @@ public: std::string MoveString() { return std::move(string_); } private: - void SetMessageFormattedText(MessageFixedText, va_list); std::string string_; bool isFatal_{false}; }; diff --git a/flang/lib/semantics/resolve-labels.cc b/flang/lib/semantics/resolve-labels.cc index c56f3062fb19..17d46b2faecb 100644 --- a/flang/lib/semantics/resolve-labels.cc +++ b/flang/lib/semantics/resolve-labels.cc @@ -1,4 +1,3 @@ -/* -*- mode: c++; c-basic-offset: 2 -*- */ // Copyright (c) 2018, NVIDIA CORPORATION. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -14,161 +13,160 @@ // limitations under the License. #include "resolve-labels.h" +#include "../common/enum-set.h" #include "../parser/message.h" #include "../parser/parse-tree-visitor.h" +#include +#include #include #include -#include -#include namespace { using namespace Fortran; using namespace parser::literals; -using ParseTree_t = parser::Program; -using CookedSource_t = parser::CookedSource; -using Index_t = parser::CharBlock; -using IndexList = std::vector>; -using Scope_t = unsigned; -using LblStmt_t = std::tuple; -using ArcTrgt_t = std::map; -using ArcBase_t = std::vector>; -const bool StrictF18 = false; // FIXME - make a command-line option +ENUM_CLASS(TargetStatementEnum, Do, Branch, Format) +using TargetStmtType = common::EnumSet; -const unsigned DO_TERM_FLAG = 1u; -const unsigned BRANCH_TARGET_FLAG = 2u; -const unsigned FORMAT_STMT_FLAG = 4u; +using IndexList = std::vector>; +using ScopeProxy = unsigned; +using LabelStmtInfo = std::tuple; +using TargetStmtMap = std::map; +using SourceStmtList = + std::vector>; +using ErrorHandler = parser::Messages; -// convenient package for error reporting -struct ErrorHandler { -public: - explicit ErrorHandler(const parser::CookedSource& CookedSource) - : cookedSource{CookedSource}, messages{parser::Messages()} {} - ~ErrorHandler() = default; - ErrorHandler(ErrorHandler&&) = default; - ErrorHandler() = delete; - ErrorHandler(const ErrorHandler&) = delete; - ErrorHandler& operator=(const ErrorHandler&) = delete; +const bool isStrictF18{false}; // FIXME - make a command-line option - parser::Message& Report(const parser::CharBlock& CB, - const parser::MessageFixedText& Fixed, ...) { - va_list ap; - va_start(ap, Fixed); - parser::MessageFormattedText Msg{Fixed, ap}; - va_end(ap); - return messages.Say(parser::Message{CB, Msg}); - } +inline bool HasScope(ScopeProxy scope) { return scope != ScopeProxy{0u}; } - const parser::CookedSource& cookedSource; - parser::Messages messages; -}; +inline bool HasNoScope(ScopeProxy scope) { return !HasScope(scope); } + +parser::Message &Report(ErrorHandler &eh, const parser::CharBlock &ip, + parser::MessageFormattedText &&msg) { + return eh.Say(parser::Message{ip, msg}); +} + +inline bool HasNoErrors(const ErrorHandler &eh) { return !eh.AnyFatalError(); } /// \brief Is this a legal DO terminator? /// Pattern match dependent on the standard we're enforcing /// F18:R1131 (must be CONTINUE or END DO) -template constexpr bool IsLegalDoTerm(const parser::Statement&) { - using EDS = parser::EndDoStmt; - return std::disjunction>, - std::is_same>::value; +template +constexpr bool IsLegalDoTerm(const parser::Statement &) { + return std::disjunction_v< + std::is_same>, + std::is_same>; } -template<> constexpr bool IsLegalDoTerm(const parser::Statement& A) { - if (std::get_if(&A.statement.u)) { - // See F08:C816 +template<> +constexpr bool IsLegalDoTerm( + const parser::Statement &actionStmt) { + if (std::holds_alternative(actionStmt.statement.u)) { + // See F08:C816 return true; - } - if (StrictF18) + } else if (isStrictF18) { return false; - // Applies in F08 and earlier - const auto* P{&A.statement.u}; - return !(std::get_if>(P) || - std::get_if>(P) || - std::get_if>(P) || - std::get_if>(P) || - std::get_if>(P) || - std::get_if>(P)); + } else { + // Applies in F08 and earlier + return !( + std::holds_alternative>( + actionStmt.statement.u) || + std::holds_alternative>( + actionStmt.statement.u) || + std::holds_alternative>( + actionStmt.statement.u) || + std::holds_alternative>( + actionStmt.statement.u) || + std::holds_alternative>( + actionStmt.statement.u) || + std::holds_alternative>( + actionStmt.statement.u)); + } } /// \brief Is this a FORMAT stmt? /// Pattern match for FORMAT statement -template constexpr bool IsFormat(const parser::Statement&) { - return std::is_same>::value; +template constexpr bool IsFormat(const parser::Statement &) { + return std::is_same_v>; } /// \brief Is this a legal branch target? /// Pattern match dependent on the standard we're enforcing -template constexpr bool IsLegalBranchTarget(const parser:: - Statement&) { - using LDS = parser::LabelDoStmt; - using EDS = parser::EndDoStmt; - return std::disjunction, - std::is_same, - std::is_same, - std::is_same, - std::is_same, - std::is_same, - std::is_same, - std::is_same, - std::is_same>, - std::is_same, - std::is_same, - std::is_same>, - std::is_same, - std::is_same, - std::is_same, - std::is_same, - std::is_same, - std::is_same, - std::is_same, - std::is_same, - std::is_same, - std::is_same, - std::is_same>::value; +template +constexpr bool IsLegalBranchTarget(const parser::Statement &) { + return std::disjunction_v, + std::is_same, + std::is_same, std::is_same, + std::is_same, + std::is_same, + std::is_same, + std::is_same, + std::is_same>, + std::is_same, + std::is_same, + std::is_same>, + std::is_same, std::is_same, + std::is_same, + std::is_same, + std::is_same, + std::is_same, + std::is_same, + std::is_same, + std::is_same, + std::is_same, + std::is_same>; } -template<> constexpr bool IsLegalBranchTarget(const parser::Statement& A) { - if (!StrictF18) +template<> +constexpr bool IsLegalBranchTarget( + const parser::Statement &actionStmt) { + if (!isStrictF18) { return true; - // XXX: do we care to flag these as errors? If we want strict F18, these - // statements should not even be present - const auto* P{&A.statement.u}; - return !(std::get_if>(P) || - std::get_if>(P) || - std::get_if>(P) || - std::get_if>(P)); + } else { + // XXX: do we care to flag these as errors? If we want strict F18, these + // statements should not even be present + return !( + std::holds_alternative>( + actionStmt.statement.u) || + std::holds_alternative>( + actionStmt.statement.u) || + std::holds_alternative>( + actionStmt.statement.u) || + std::holds_alternative>( + actionStmt.statement.u)); + } } - template -constexpr unsigned ConsTrgtFlags(const parser::Statement& S) { - unsigned Flags{0u}; - if (IsLegalDoTerm(S)) - Flags |= DO_TERM_FLAG; - if (IsLegalBranchTarget(S)) - Flags |= BRANCH_TARGET_FLAG; - if (IsFormat(S)) - Flags |= FORMAT_STMT_FLAG; - return Flags; +constexpr TargetStmtType ConsTrgtFlags(const parser::Statement &statement) { + TargetStmtType targetStmtType{}; + if (IsLegalDoTerm(statement)) { + targetStmtType.set(TargetStatementEnum::Do); + } + if (IsLegalBranchTarget(statement)) { + targetStmtType.set(TargetStatementEnum::Branch); + } + if (IsFormat(statement)) { + targetStmtType.set(TargetStatementEnum::Format); + } + return targetStmtType; } /// \brief \p opt1 and \p opt2 must be either present and identical or absent -/// \param opt1 an optional construct-name (opening statement) -/// \param opt2 an optional construct-name (ending statement) -template inline bool BothEqOrNone(const A& opt1, const A& opt2) { - return (opt1.has_value() == opt2.has_value()) - ? (opt1.has_value() - ? (opt1.value().ToString() == opt2.value().ToString()) : true) - : false; +/// \param a an optional construct-name (opening statement) +/// \param b an optional construct-name (ending statement) +template inline bool BothEqOrNone(const A &a, const A &b) { + return (a.has_value() == b.has_value()) + ? (a.has_value() ? (a.value().ToString() == b.value().ToString()) : true) + : false; } -/// \brief \p opt1 must either be absent or identical to \p opt2 -/// \param opt1 an optional construct-name for an optional constraint -/// \param opt2 an optional construct-name (opening statement) -template inline bool PresentAndEq(const A& opt1, const A& opt2) { - return (!opt1.has_value()) || - (opt2.has_value() && - (opt1.value().ToString() == opt2.value().ToString())); +/// \brief \p opt1 must either be absent or identical to \p b +/// \param a an optional construct-name for an optional constraint +/// \param b an optional construct-name (opening statement) +template inline bool PresentAndEq(const A &a, const A &b) { + return (!a.has_value()) || + (b.has_value() && (a.value().ToString() == b.value().ToString())); } /// \brief Iterates over parse tree, creates the analysis result @@ -178,194 +176,272 @@ struct ParseTreeAnalyzer { public: struct UnitAnalysis { public: - ArcBase_t DoArcBases; ///< bases of label-do-stmts - ArcBase_t FmtArcBases; ///< bases of all other stmts with labels - ArcBase_t ArcBases; ///< bases of all other stmts with labels - ArcTrgt_t ArcTrgts; ///< unique map of labels to stmt info - std::vector Scopes; ///< scope stack model + SourceStmtList doStmtSources_; ///< bases of label-do-stmts + SourceStmtList + formatStmtSources_; ///< bases of all other stmts with labels + SourceStmtList otherStmtSources_; ///< bases of all other stmts with labels + TargetStmtMap targetStmts_; ///< unique map of labels to stmt info + std::vector scopeModel_; ///< scope stack model - explicit UnitAnalysis() { Scopes.push_back(0); } - UnitAnalysis(UnitAnalysis&&) = default; + UnitAnalysis() { scopeModel_.push_back(0); } + UnitAnalysis(UnitAnalysis &&) = default; ~UnitAnalysis() = default; - UnitAnalysis(const UnitAnalysis&) = delete; - UnitAnalysis& operator=(const UnitAnalysis&) = delete; + UnitAnalysis(const UnitAnalysis &) = delete; + UnitAnalysis &operator=(const UnitAnalysis &) = delete; - const ArcBase_t& GetLabelDos() const { return DoArcBases; } - const ArcBase_t& GetDataXfers() const { return FmtArcBases; } - const ArcBase_t& GetBranches() const { return ArcBases; } - const ArcTrgt_t& GetLabels() const { return ArcTrgts; } - const std::vector& GetScopes() const { return Scopes; } + const SourceStmtList &GetLabelDos() const { return doStmtSources_; } + const SourceStmtList &GetDataXfers() const { return formatStmtSources_; } + const SourceStmtList &GetBranches() const { return otherStmtSources_; } + const TargetStmtMap &GetLabels() const { return targetStmts_; } + const std::vector &GetScopes() const { return scopeModel_; } }; - explicit ParseTreeAnalyzer(const parser::CookedSource& Src) : EH{Src} {} + ParseTreeAnalyzer() {} ~ParseTreeAnalyzer() = default; - ParseTreeAnalyzer(ParseTreeAnalyzer&&) = default; - ParseTreeAnalyzer() = delete; - ParseTreeAnalyzer(const ParseTreeAnalyzer&) = delete; - ParseTreeAnalyzer& operator=(const ParseTreeAnalyzer&) = delete; + ParseTreeAnalyzer(ParseTreeAnalyzer &&) = default; + ParseTreeAnalyzer(const ParseTreeAnalyzer &) = delete; + ParseTreeAnalyzer &operator=(const ParseTreeAnalyzer &) = delete; // Default Pre() and Post() - template constexpr bool Pre(const A&) { return true; } - template constexpr void Post(const A&) {} + template constexpr bool Pre(const A &) { return true; } + template constexpr void Post(const A &) {} // Specializations of Pre() and Post() /// \brief Generic handling of all statements - template bool Pre(const parser::Statement& Stmt) { - Index = Stmt.source; + template bool Pre(const parser::Statement &Stmt) { + currentPosition_ = Stmt.source; if (Stmt.label.has_value()) AddTrgt(Stmt.label.value(), ConsTrgtFlags(Stmt)); return true; } // Inclusive scopes (see 11.1.1) - bool Pre(const parser::ProgramUnit&) { return PushNewScope(); } - bool Pre(const parser::AssociateConstruct& A) { return PushName(A); } - bool Pre(const parser::BlockConstruct& Blk) { return PushName(Blk); } - bool Pre(const parser::ChangeTeamConstruct& Ctm) { return PushName(Ctm); } - bool Pre(const parser::CriticalConstruct& Crit) { return PushName(Crit); } - bool Pre(const parser::DoConstruct& Do) { return PushName(Do); } - bool Pre(const parser::IfConstruct& If) { return PushName(If); } - bool Pre(const parser::IfConstruct::ElseIfBlock&) { return SwScope(); } - bool Pre(const parser::IfConstruct::ElseBlock&) { return SwScope(); } - bool Pre(const parser::CaseConstruct& Case) { return PushName(Case); } - bool Pre(const parser::CaseConstruct::Case&) { return SwScope(); } - bool Pre(const parser::SelectRankConstruct& SRk) { return PushName(SRk); } - bool Pre(const parser::SelectRankConstruct::RankCase&) { return SwScope(); } - bool Pre(const parser::SelectTypeConstruct& STy) { return PushName(STy); } - bool Pre(const parser::SelectTypeConstruct::TypeCase&) { return SwScope(); } - bool Pre(const parser::WhereConstruct& W) { return PushNonBlockName(W); } - bool Pre(const parser::ForallConstruct& F) { return PushNonBlockName(F); } + bool Pre(const parser::ProgramUnit &) { return PushNewScope(); } + bool Pre(const parser::AssociateConstruct &associateConstruct) { + return PushName(associateConstruct); + } + bool Pre(const parser::BlockConstruct &blockConstruct) { + return PushName(blockConstruct); + } + bool Pre(const parser::ChangeTeamConstruct &changeTeamConstruct) { + return PushName(changeTeamConstruct); + } + bool Pre(const parser::CriticalConstruct &criticalConstruct) { + return PushName(criticalConstruct); + } + bool Pre(const parser::DoConstruct &doConstruct) { + return PushName(doConstruct); + } + bool Pre(const parser::IfConstruct &ifConstruct) { + return PushName(ifConstruct); + } + bool Pre(const parser::IfConstruct::ElseIfBlock &) { return SwScope(); } + bool Pre(const parser::IfConstruct::ElseBlock &) { return SwScope(); } + bool Pre(const parser::CaseConstruct &caseConstruct) { + return PushName(caseConstruct); + } + bool Pre(const parser::CaseConstruct::Case &) { return SwScope(); } + bool Pre(const parser::SelectRankConstruct &selectRankConstruct) { + return PushName(selectRankConstruct); + } + bool Pre(const parser::SelectRankConstruct::RankCase &) { return SwScope(); } + bool Pre(const parser::SelectTypeConstruct &selectTypeConstruct) { + return PushName(selectTypeConstruct); + } + bool Pre(const parser::SelectTypeConstruct::TypeCase &) { return SwScope(); } + bool Pre(const parser::WhereConstruct &whereConstruct) { + return PushNonBlockName(whereConstruct); + } + bool Pre(const parser::ForallConstruct &forallConstruct) { + return PushNonBlockName(forallConstruct); + } - void Post(const parser::ProgramUnit&) { PopScope(); } - void Post(const parser::AssociateConstruct& A) { PopName(A); } - void Post(const parser::BlockConstruct& Blk) { PopName(Blk); } - void Post(const parser::ChangeTeamConstruct& Ctm) { PopName(Ctm); } - void Post(const parser::CriticalConstruct& Crit) { PopName(Crit); } - void Post(const parser::DoConstruct& Do) { PopName(Do); } - void Post(const parser::IfConstruct& If) { PopName(If); } - void Post(const parser::CaseConstruct& Case) { PopName(Case); } - void Post(const parser::SelectRankConstruct& SelRk) { PopName(SelRk); } - void Post(const parser::SelectTypeConstruct& SelTy) { PopName(SelTy); } + void Post(const parser::ProgramUnit &) { PopScope(); } + void Post(const parser::AssociateConstruct &associateConstruct) { + PopName(associateConstruct); + } + void Post(const parser::BlockConstruct &blockConstruct) { + PopName(blockConstruct); + } + void Post(const parser::ChangeTeamConstruct &changeTeamConstruct) { + PopName(changeTeamConstruct); + } + void Post(const parser::CriticalConstruct &criticalConstruct) { + PopName(criticalConstruct); + } + void Post(const parser::DoConstruct &doConstruct) { PopName(doConstruct); } + void Post(const parser::IfConstruct &ifConstruct) { PopName(ifConstruct); } + void Post(const parser::CaseConstruct &caseConstruct) { + PopName(caseConstruct); + } + void Post(const parser::SelectRankConstruct &selectRankConstruct) { + PopName(selectRankConstruct); + } + void Post(const parser::SelectTypeConstruct &selectTypeConstruct) { + PopName(selectTypeConstruct); + } // Named constructs without block scope - void Post(const parser::WhereConstruct& W) { PopNonBlockConstructName(W); } - void Post(const parser::ForallConstruct& F) { PopNonBlockConstructName(F); } + void Post(const parser::WhereConstruct &whereConstruct) { + PopNonBlockConstructName(whereConstruct); + } + void Post(const parser::ForallConstruct &forallConstruct) { + PopNonBlockConstructName(forallConstruct); + } // Statements with label references - void Post(const parser::LabelDoStmt& Do) { AddDoBase(std::get<1>(Do.t)); } - void Post(const parser::GotoStmt& Goto) { AddBase(Goto.v); } - void Post(const parser::ComputedGotoStmt& C) { AddBase(std::get<0>(C.t)); } - void Post(const parser::ArithmeticIfStmt& AIf) { - AddBase(std::get<1>(AIf.t)); - AddBase(std::get<2>(AIf.t)); - AddBase(std::get<3>(AIf.t)); + void Post(const parser::LabelDoStmt &labelDoStmt) { + AddDoBase(std::get(labelDoStmt.t)); + } + void Post(const parser::GotoStmt &gotoStmt) { AddBase(gotoStmt.v); } + void Post(const parser::ComputedGotoStmt &computedGotoStmt) { + AddBase(std::get>(computedGotoStmt.t)); + } + void Post(const parser::ArithmeticIfStmt &arithmeticIfStmt) { + AddBase(std::get<1>(arithmeticIfStmt.t)); + AddBase(std::get<2>(arithmeticIfStmt.t)); + AddBase(std::get<3>(arithmeticIfStmt.t)); + } + void Post(const parser::AssignStmt &assignStmt) { + AddBase(std::get(assignStmt.t)); + } + void Post(const parser::AssignedGotoStmt &assignedGotoStmt) { + AddBase(std::get>(assignedGotoStmt.t)); + } + void Post(const parser::AltReturnSpec &altReturnSpec) { + AddBase(altReturnSpec.v); } - void Post(const parser::AssignStmt& Assn) { AddBase(std::get<0>(Assn.t)); } - void Post(const parser::AssignedGotoStmt& A) { AddBase(std::get<1>(A.t)); } - void Post(const parser::AltReturnSpec& ARS) { AddBase(ARS.v); } - void Post(const parser::ErrLabel& Err) { AddBase(Err.v); } - void Post(const parser::EndLabel& End) { AddBase(End.v); } - void Post(const parser::EorLabel& Eor) { AddBase(Eor.v); } - void Post(const parser::Format& Fmt) { + void Post(const parser::ErrLabel &errLabel) { AddBase(errLabel.v); } + void Post(const parser::EndLabel &endLabel) { AddBase(endLabel.v); } + void Post(const parser::EorLabel &eorLabel) { AddBase(eorLabel.v); } + void Post(const parser::Format &format) { // BUG: the label is saved as an IntLiteralConstant rather than a Label #if 0 - if (const auto* P{std::get_if(&Fmt.u)}) + if (const auto *P{std::get_if(&format.u)}) { AddFmtBase(*P); + } #else // FIXME: this is wrong, but extracts the label's value - if (const auto* P{std::get_if<0>(&Fmt.u)}) { - parser::Label L{std::get<0>(std::get(std::get((*P->thing).u).u).t)}; - AddFmtBase(L); + if (const auto *P{std::get_if<0>(&format.u)}) { + AddFmtBase(parser::Label{std::get<0>(std::get( + std::get((*P->thing).u).u) + .t)}); } #endif } - void Post(const parser::CycleStmt& Cycle) { - if (Cycle.v.has_value()) - CheckLabelContext("CYCLE", Cycle.v.value().ToString()); + void Post(const parser::CycleStmt &cycleStmt) { + if (cycleStmt.v.has_value()) { + CheckLabelContext("CYCLE", cycleStmt.v.value().ToString()); + } } - void Post(const parser::ExitStmt& Exit) { - if (Exit.v.has_value()) - CheckLabelContext("EXIT", Exit.v.value().ToString()); + void Post(const parser::ExitStmt &exitStmt) { + if (exitStmt.v.has_value()) { + CheckLabelContext("EXIT", exitStmt.v.value().ToString()); + } } // Getters for the results - const std::vector& GetProgramUnits() const { return PUnits; } - ErrorHandler& GetEH() { return EH; } - bool HasNoErrors() const { return NoErrors; } + const std::vector &GetProgramUnits() const { + return programUnits_; + } + ErrorHandler &GetEH() { return eh; } private: bool PushScope() { - PUnits.back().Scopes.push_back(CurrScope); - CurrScope = PUnits.back().Scopes.size() - 1; + programUnits_.back().scopeModel_.push_back(currentScope_); + currentScope_ = programUnits_.back().scopeModel_.size() - 1; return true; } bool PushNewScope() { - PUnits.emplace_back(UnitAnalysis{}); + programUnits_.emplace_back(UnitAnalysis{}); + return PushScope(); + } + void PopScope() { + currentScope_ = programUnits_.back().scopeModel_[currentScope_]; + } + bool SwScope() { + PopScope(); return PushScope(); } - void PopScope() { CurrScope = PUnits.back().Scopes[CurrScope]; } - bool SwScope() { PopScope(); return PushScope(); } - template bool PushName(const A& X) { - const auto& OptName{std::get<0>(std::get<0>(X.t).statement.t)}; - if (OptName.has_value()) - Names.push_back(OptName.value().ToString()); + template bool PushName(const A &a) { + const auto &optionalName{std::get<0>(std::get<0>(a.t).statement.t)}; + if (optionalName.has_value()) { + constructNames_.push_back(optionalName.value().ToString()); + } return PushScope(); } - bool PushName(const parser::BlockConstruct& Blk) { - const auto& OptName{std::get<0>(Blk.t).statement.v}; - if (OptName.has_value()) - Names.push_back(OptName.value().ToString()); + bool PushName(const parser::BlockConstruct &blockConstruct) { + const auto &optionalName{ + std::get>(blockConstruct.t) + .statement.v}; + if (optionalName.has_value()) { + constructNames_.push_back(optionalName.value().ToString()); + } return PushScope(); } - template bool PushNonBlockName(const A& X) { - const auto& OptName{std::get<0>(std::get<0>(X.t).statement.t)}; - if (OptName.has_value()) - Names.push_back(OptName.value().ToString()); + template bool PushNonBlockName(const A &a) { + const auto &optionalName{std::get<0>(std::get<0>(a.t).statement.t)}; + if (optionalName.has_value()) { + constructNames_.push_back(optionalName.value().ToString()); + } return true; } - template void PopNonBlockConstructName(const A& X) { - CheckName(X); SelectivePopBack(X); + template void PopNonBlockConstructName(const A &a) { + CheckName(a); + SelectivePopBack(a); } - template void SelectivePopBack(const A& X) { - const auto& OptName{std::get<0>(std::get<0>(X.t).statement.t)}; - if (OptName.has_value()) - Names.pop_back(); + template void SelectivePopBack(const A &a) { + const auto &optionalName{std::get<0>(std::get<0>(a.t).statement.t)}; + if (optionalName.has_value()) { + constructNames_.pop_back(); + } } - void SelectivePopBack(const parser::BlockConstruct& Blk) { - const auto& OptName{std::get<0>(Blk.t).statement.v}; - if (OptName.has_value()) - Names.pop_back(); + void SelectivePopBack(const parser::BlockConstruct &blockConstruct) { + const auto &optionalName{ + std::get>(blockConstruct.t) + .statement.v}; + if (optionalName.has_value()) { + constructNames_.pop_back(); + } } /// \brief Check constraints and pop scope - template void PopName(const A& V) { - CheckName(V); PopScope(); SelectivePopBack(V); + template void PopName(const A &a) { + CheckName(a); + PopScope(); + SelectivePopBack(a); } /// \brief Check case-construct-name and pop the scope /// Constraint C1144 - opening and ending name must match if present, and /// case-stmt must either match or be unnamed - void PopName(const parser::CaseConstruct& Case) { - CheckName(Case, "CASE"); PopScope(); SelectivePopBack(Case); + void PopName(const parser::CaseConstruct &caseConstruct) { + CheckName(caseConstruct, "CASE"); + PopScope(); + SelectivePopBack(caseConstruct); } /// \brief Check select-rank-construct-name and pop the scope /// Constraints C1154, C1156 - opening and ending name must match if present, /// and select-rank-case-stmt must either match or be unnamed - void PopName(const parser::SelectRankConstruct& SelRk) { - CheckName(SelRk, "RANK","RANK "); PopScope(); SelectivePopBack(SelRk); + void PopName(const parser::SelectRankConstruct &selectRankConstruct) { + CheckName(selectRankConstruct, "RANK", "RANK "); + PopScope(); + SelectivePopBack(selectRankConstruct); } /// \brief Check select-construct-name and pop the scope /// Constraint C1165 - opening and ending name must match if present, and /// type-guard-stmt must either match or be unnamed - void PopName(const parser::SelectTypeConstruct& SelTy) { - CheckName(SelTy, "TYPE", "TYPE "); PopScope(); SelectivePopBack(SelTy); + void PopName(const parser::SelectTypeConstruct &selectTypeConstruct) { + CheckName(selectTypeConstruct, "TYPE", "TYPE "); + PopScope(); + SelectivePopBack(selectTypeConstruct); } // ----------------------------------------------- @@ -373,103 +449,171 @@ private: // Case 1: construct name must be absent or specified & identical on END /// \brief Check associate-construct-name, constraint C1106 - void CheckName(const parser::AssociateConstruct& A) { ChkNm(A, "ASSOCIATE"); } + void CheckName(const parser::AssociateConstruct &associateConstruct) { + CheckName("ASSOCIATE", associateConstruct); + } /// \brief Check critical-construct-name, constraint C1117 - void CheckName(const parser::CriticalConstruct& C) { ChkNm(C, "CRITICAL"); } + void CheckName(const parser::CriticalConstruct &criticalConstruct) { + CheckName("CRITICAL", criticalConstruct); + } /// \brief Check do-construct-name, constraint C1131 - void CheckName(const parser::DoConstruct& Do) { ChkNm(Do, "DO"); } + void CheckName(const parser::DoConstruct &doConstruct) { + CheckName("DO", doConstruct); + } /// \brief Check forall-construct-name, constraint C1035 - void CheckName(const parser::ForallConstruct& F) { ChkNm(F, "FORALL"); } + void CheckName(const parser::ForallConstruct &forallConstruct) { + CheckName("FORALL", forallConstruct); + } /// \brief Common code for ASSOCIATE, CRITICAL, DO, and FORALL - template void ChkNm(const A& V, const char *const Con) { - if (!BothEqOrNone(std::get<0>(std::get<0>(V.t).statement.t), - std::get<2>(V.t).statement.v)) { - EH.Report(Index, "%s construct name mismatch"_err_en_US, Con); - NoErrors = false; + template + void CheckName(const char *const constructTag, const A &a) { + if (!BothEqOrNone( + std::get>(std::get<0>(a.t).statement.t), + std::get<2>(a.t).statement.v)) { + Report(eh, currentPosition_, + parser::MessageFormattedText{ + "%s construct name mismatch"_err_en_US, constructTag}); } } - + /// \brief Check do-construct-name, constraint C1109 - void CheckName(const parser::BlockConstruct& B) { - if (!BothEqOrNone(std::get<0>(B.t).statement.v, - std::get<3>(B.t).statement.v)) { - EH.Report(Index, "BLOCK construct name mismatch"_err_en_US); - NoErrors = false; + void CheckName(const parser::BlockConstruct &blockConstruct) { + if (!BothEqOrNone( + std::get>(blockConstruct.t) + .statement.v, + std::get>(blockConstruct.t) + .statement.v)) { + Report(eh, currentPosition_, + parser::MessageFormattedText{ + "BLOCK construct name mismatch"_err_en_US}); } } /// \brief Check team-cosntruct-name, constraint C1112 - void CheckName(const parser::ChangeTeamConstruct& C) { - if (!BothEqOrNone(std::get<0>(std::get<0>(C.t).statement.t), - std::get<1>(std::get<2>(C.t).statement.t))) { - EH.Report(Index, "CHANGE TEAM construct name mismatch"_err_en_US); - NoErrors = false; + void CheckName(const parser::ChangeTeamConstruct &changeTeamConstruct) { + if (!BothEqOrNone(std::get>( + std::get>( + changeTeamConstruct.t) + .statement.t), + std::get>( + std::get>( + changeTeamConstruct.t) + .statement.t))) { + Report(eh, currentPosition_, + parser::MessageFormattedText{ + "CHANGE TEAM construct name mismatch"_err_en_US}); } } // ----------------------------------------------- - // Case 2: same as case 1, but subblock statement construct-names are + // Case 2: same as case 1, but subblock statement construct-names are // optional but if they are specified their values must be identical /// \brief Check if-construct-name /// Constraint C1142 - opening and ending name must match if present, and /// else-if-stmt and else-stmt must either match or be unnamed - void CheckName(const parser::IfConstruct& If) { - const auto& Name{std::get<0>(std::get<0>(If.t).statement.t)}; - if (!BothEqOrNone(Name, std::get<4>(If.t).statement.v)) { - EH.Report(Index, "IF construct name mismatch"_err_en_US); - NoErrors = false; + void CheckName(const parser::IfConstruct &ifConstruct) { + const auto &constructName{std::get>( + std::get>(ifConstruct.t) + .statement.t)}; + if (!BothEqOrNone(constructName, + std::get>(ifConstruct.t) + .statement.v)) { + Report(eh, currentPosition_, + parser::MessageFormattedText{"IF construct name mismatch"_err_en_US}); } - for (const auto& ElseIfBlock : std::get<2>(If.t)) { - const auto& E{std::get<0>(ElseIfBlock.t).statement.t}; - if (!PresentAndEq(std::get<1>(E), Name)) { - EH.Report(Index, "ELSE IF statement name mismatch"_err_en_US); - NoErrors = false; + for (const auto &elseIfBlock : + std::get>(ifConstruct.t)) { + if (!PresentAndEq( + std::get>( + std::get>(elseIfBlock.t) + .statement.t), + constructName)) { + Report(eh, currentPosition_, + parser::MessageFormattedText{ + "ELSE IF statement name mismatch"_err_en_US}); } } - if (std::get<3>(If.t).has_value()) { - const auto& E{std::get<3>(If.t).value().t}; - if (!PresentAndEq(std::get<0>(E).statement.v, Name)) { - EH.Report(Index, "ELSE statement name mismatch"_err_en_US); - NoErrors = false; + if (std::get>(ifConstruct.t) + .has_value()) { + if (!PresentAndEq( + std::get>( + std::get>( + ifConstruct.t) + .value() + .t) + .statement.v, + constructName)) { + Report(eh, currentPosition_, + parser::MessageFormattedText{ + "ELSE statement name mismatch"_err_en_US}); } } } /// \brief Common code for SELECT CASE, SELECT RANK, and SELECT TYPE - template void CheckName(const A& Case, const char *const Sel1, - const char *const Sel2 = "") { - const auto& Name{std::get<0>(std::get<0>(Case.t).statement.t)}; - if (!BothEqOrNone(Name, std::get<2>(Case.t).statement.v)) { - EH.Report(Index, "SELECT %s construct name mismatch"_err_en_US, Sel1); - NoErrors = false; + template + void CheckName(const A &a, const char *const selectTag, + const char *const selectSubTag = "") { + const auto &constructName{std::get<0>(std::get<0>(a.t).statement.t)}; + if (!BothEqOrNone(constructName, std::get<2>(a.t).statement.v)) { + Report(eh, currentPosition_, + parser::MessageFormattedText{ + "SELECT %s construct name mismatch"_err_en_US, selectTag}); } - for (const auto& CS : std::get<1>(Case.t)) - if (!PresentAndEq(std::get<1>(std::get<0>(CS.t).statement.t), Name)) { - EH.Report(Index, "%sCASE statement name mismatch"_err_en_US, Sel2); - NoErrors = false; + for (const auto &subpart : std::get<1>(a.t)) { + if (!PresentAndEq(std::get>( + std::get<0>(subpart.t).statement.t), + constructName)) { + Report(eh, currentPosition_, + parser::MessageFormattedText{ + "%sCASE statement name mismatch"_err_en_US, selectSubTag}); } + } } /// \brief Check where-construct-name /// Constraint C1033 - opening and ending name must match if present, and /// masked-elsewhere-stmt and elsewhere-stmt either match /// or be unnamed - void CheckName(const parser::WhereConstruct& Where) { - const auto& Name{std::get<0>(std::get<0>(Where.t).statement.t)}; - if (!BothEqOrNone(Name, std::get<4>(Where.t).statement.v)) { - EH.Report(Index, "WHERE construct name mismatch"_err_en_US); - NoErrors = false; + void CheckName(const parser::WhereConstruct &whereConstruct) { + const auto &constructName{std::get>( + std::get>( + whereConstruct.t) + .statement.t)}; + if (!BothEqOrNone(constructName, + std::get>(whereConstruct.t) + .statement.v)) { + Report(eh, currentPosition_, + parser::MessageFormattedText{ + "WHERE construct name mismatch"_err_en_US}); } - for (const auto& W : std::get<2>(Where.t)) - if (!PresentAndEq(std::get<1>(std::get<0>(W.t).statement.t), Name)) { - EH.Report(Index, - "ELSEWHERE () statement name mismatch"_err_en_US); - NoErrors = false; + for (const auto &maskedElsewhere : + std::get>( + whereConstruct.t)) { + if (!PresentAndEq( + std::get>( + std::get>( + maskedElsewhere.t) + .statement.t), + constructName)) { + Report(eh, currentPosition_, + parser::MessageFormattedText{ + "ELSEWHERE () statement name mismatch"_err_en_US}); } - if (std::get<3>(Where.t).has_value()) { - const auto& E{std::get<3>(Where.t).value().t}; - if (!PresentAndEq(std::get<0>(E).statement.v, Name)) { - EH.Report(Index, "ELSEWHERE statement name mismatch"_err_en_US); - NoErrors = false; + } + if (std::get>( + whereConstruct.t) + .has_value()) { + if (!PresentAndEq( + std::get>( + std::get>( + whereConstruct.t) + .value() + .t) + .statement.v, + constructName)) { + Report(eh, currentPosition_, + parser::MessageFormattedText{ + "ELSEWHERE statement name mismatch"_err_en_US}); } } } @@ -477,341 +621,349 @@ private: /// \brief Check constraint construct-name in scope (C1134 and C1166) /// \param SStr a string to specify the statement, \c CYCLE or \c EXIT /// \param Label the name used by the \c CYCLE or \c EXIT - template void CheckLabelContext(const char* const SStr, - const A& Name) { - const auto E{Names.crend()}; - const auto I{std::find(Names.crbegin(), E, Name)}; - if (I != E) - return; - EH.Report(Index, "%s construct-name '%s' is not in scope"_err_en_US, - SStr, Name.c_str()); - NoErrors = false; + template + void CheckLabelContext(const char *const stmtString, const A &constructName) { + const auto I{std::find( + constructNames_.crbegin(), constructNames_.crend(), constructName)}; + if (I == constructNames_.crend()) { + Report(eh, currentPosition_, + parser::MessageFormattedText{ + "%s construct-name '%s' is not in scope"_err_en_US, stmtString, + constructName.c_str()}); + } } /// \brief Check label range /// Constraint per section 6.2.5, paragraph 2 - void LabelInRange(parser::Label Label) { - if ((Label < 1) || (Label > 99999)) { + void CheckLabelInRange(parser::Label label) { + if ((label < 1) || (label > 99999)) { // this is an error: labels must have a value 1 to 99999, inclusive - EH.Report(Index, "label '%lu' is out of range"_err_en_US, Label); - NoErrors = false; + Report(eh, currentPosition_, + parser::MessageFormattedText{ + "label '%lu' is out of range"_err_en_US, label}); } } /// \brief Add a labeled statement (label must be distinct) /// Constraint per section 6.2.5., paragraph 2 - void AddTrgt(parser::Label Label, unsigned Flags) { - LabelInRange(Label); - const auto Pair{PUnits.back().ArcTrgts.insert({Label, - {CurrScope, Index, Flags}})}; - if (!Pair.second) { + void AddTrgt(parser::Label label, TargetStmtType targetStmtType) { + CheckLabelInRange(label); + const auto pair{programUnits_.back().targetStmts_.insert( + {label, {currentScope_, currentPosition_, targetStmtType}})}; + if (!pair.second) { // this is an error: labels must be pairwise distinct - EH.Report(Index, "label '%lu' is not distinct"_err_en_US, Label); - NoErrors = false; + Report(eh, currentPosition_, + parser::MessageFormattedText{ + "label '%lu' is not distinct"_err_en_US, label}); } // Don't enforce a limit to the cardinality of labels } /// \brief Reference to a labeled statement from a DO statement - void AddDoBase(parser::Label Label) { - LabelInRange(Label); - PUnits.back().DoArcBases.push_back({Label, CurrScope, Index}); + void AddDoBase(parser::Label label) { + CheckLabelInRange(label); + programUnits_.back().doStmtSources_.push_back( + {label, currentScope_, currentPosition_}); } /// \brief Reference to a labeled FORMAT statement - void AddFmtBase(parser::Label Label) { - LabelInRange(Label); - PUnits.back().FmtArcBases.push_back({Label, CurrScope, Index}); + void AddFmtBase(parser::Label label) { + CheckLabelInRange(label); + programUnits_.back().formatStmtSources_.push_back( + {label, currentScope_, currentPosition_}); } /// \brief Reference to a labeled statement as a (possible) branch - void AddBase(parser::Label Label) { - LabelInRange(Label); - PUnits.back().ArcBases.push_back({Label, CurrScope, Index}); + void AddBase(parser::Label label) { + CheckLabelInRange(label); + programUnits_.back().otherStmtSources_.push_back( + {label, currentScope_, currentPosition_}); } /// \brief References to labeled statements as (possible) branches - void AddBase(const std::list& Labels) { - for (const parser::Label& L : Labels) - AddBase(L); + void AddBase(const std::list &labels) { + for (const parser::Label &label : labels) { + AddBase(label); + } } - std::vector PUnits; ///< results for each program unit - ErrorHandler EH; ///< error handler, collects messages - Index_t Index{nullptr}; ///< current location in parse tree - Scope_t CurrScope{0}; ///< current scope in the model - bool NoErrors{true}; ///< no semantic errors found? - std::vector Names; + std::vector programUnits_; ///< results for each program unit + ErrorHandler eh; ///< error handler, collects messages + parser::CharBlock currentPosition_{ + nullptr}; ///< current location in parse tree + ScopeProxy currentScope_{0}; ///< current scope in the model + std::vector constructNames_; }; template -bool InInclusiveScope(const A& Scopes, B Tl, const B& Hd) { - assert(Hd > 0); - assert(Tl > 0); - while (Tl && (Tl != Hd)) - Tl = Scopes[Tl]; - return Tl == Hd; +bool InInclusiveScope(const A &scopes, B tail, const B &head) { + assert(HasScope(head)); + assert(HasScope(tail)); + while (HasScope(tail) && (tail != head)) { + tail = scopes[tail]; + } + return tail == head; } -ParseTreeAnalyzer LabelAnalysis(const ParseTree_t& ParseTree, - const CookedSource_t& Source) { - ParseTreeAnalyzer Analysis{Source}; - Walk(ParseTree, Analysis); - return Analysis; +ParseTreeAnalyzer LabelAnalysis(const parser::Program &program) { + ParseTreeAnalyzer analysis; + Walk(program, analysis); + return analysis; } template -inline bool InBody(const A& CP, const B& Pair) { - assert(Pair.first.begin() < Pair.second.begin()); - return (CP.begin() >= Pair.first.begin()) && - (CP.begin() < Pair.second.end()); +inline bool InBody(const A &position, const B &pair) { + assert(pair.first.begin() < pair.second.begin()); + return (position.begin() >= pair.first.begin()) && + (position.begin() < pair.second.end()); } template -LblStmt_t GetLabel(const A& Labels, const B& Label) { - const auto Iter{Labels.find(Label)}; - if (Iter == Labels.cend()) - return {0, 0, 0}; - return Iter->second; +LabelStmtInfo GetLabel(const A &labels, const B &label) { + const auto iter{labels.find(label)}; + if (iter == labels.cend()) { + return {0u, nullptr, TargetStmtType{}}; + } else { + return iter->second; + } } /// \brief Check branches into a label-do-stmt /// Relates to 11.1.7.3, loop activation template -inline bool CheckBranchesIntoDoBody(const A& Branches, const B& Labels, - const C& Scopes, const D& LoopBodies, - ErrorHandler& EH) { - auto NoErrors{true}; - for (const auto Branch : Branches) { - const auto& Label{std::get<0>(Branch)}; - auto Trgt{GetLabel(Labels, Label)}; - if (!std::get<0>(Trgt)) - continue; - const auto& FmIdx{std::get<2>(Branch)}; - const auto& ToIdx{std::get<1>(Trgt)}; - for (const auto Body : LoopBodies) { - if (!InBody(FmIdx, Body) && InBody(ToIdx, Body)) { - // this is an error: branch into labeled DO body - if (StrictF18) { - EH.Report(FmIdx, "branch into '%s' from another scope"_err_en_US, - Body.first.ToString().c_str()); - NoErrors = false; - } else { - EH.Report(FmIdx, "branch into '%s' from another scope"_en_US, - Body.first.ToString().c_str()); - } +inline void CheckBranchesIntoDoBody(const A &branches, const B &labels, + const C &scopes, const D &loopBodies, ErrorHandler &eh) { + for (const auto branch : branches) { + const auto &label{std::get(branch)}; + auto branchTarget{GetLabel(labels, label)}; + if (HasScope(std::get(branchTarget))) { + const auto &fromPosition{std::get(branch)}; + const auto &toPosition{std::get(branchTarget)}; + for (const auto body : loopBodies) { + if (!InBody(fromPosition, body) && InBody(toPosition, body)) { + // this is an error: branch into labeled DO body + if (isStrictF18) { + Report(eh, fromPosition, + parser::MessageFormattedText{ + "branch into '%s' from another scope"_err_en_US, + body.first.ToString().c_str()}); + } else { + Report(eh, fromPosition, + parser::MessageFormattedText{ + "branch into '%s' from another scope"_en_US, + body.first.ToString().c_str()}); + } + } } } } - return NoErrors; } /// \brief Check that DO loops properly nest template -inline bool CheckDoNesting(const A& LoopBodies, ErrorHandler& EH) { - auto NoErrors{true}; - auto E{LoopBodies.cend()}; - for (auto I1{LoopBodies.cbegin()}; I1 != E; ++I1) { - const auto& L1{*I1}; - for (auto I2{I1 + 1}; I2 != E; ++I2) { - const auto& L2{*I2}; - assert(L1.first.begin() != L2.first.begin()); - if ((L2.first.begin() < L1.second.end()) && - (L1.second.begin() < L2.second.begin())) { - // this is an error: DOs do not properly nest - EH.Report(L2.second, "'%s' doesn't properly nest"_err_en_US, - L1.first.ToString().c_str()); - NoErrors = false; +inline void CheckDoNesting(const A &loopBodies, ErrorHandler &eh) { + for (auto i1{loopBodies.cbegin()}; i1 != loopBodies.cend(); ++i1) { + const auto &v1{*i1}; + for (auto i2{i1 + 1}; i2 != loopBodies.cend(); ++i2) { + const auto &v2{*i2}; + assert(v1.first.begin() != v2.first.begin()); + if ((v2.first.begin() < v1.second.end()) && + (v1.second.begin() < v2.second.begin())) { + // this is an error: DOs do not properly nest + Report(eh, v2.second, + parser::MessageFormattedText{"'%s' doesn't properly nest"_err_en_US, + v1.first.ToString().c_str()}); } } } - return NoErrors; } /// \brief Advance \p Pos past any label and whitespace /// Want the statement without its label for error messages, range checking -template inline A SkipLabel(const A& Pos) { - const long Max{Pos.end() - Pos.begin()}; - if (Max && (Pos[0] >= '0') && (Pos[0] <= '9')) { +template inline A SkipLabel(const A &position) { + const long maxPosition{position.end() - position.begin()}; + if (maxPosition && (position[0] >= '0') && (position[0] <= '9')) { long i{1l}; - for (;(i < Max) && std::isdigit(Pos[i]); ++i); - for (;(i < Max) && std::isspace(Pos[i]); ++i); - return Index_t{Pos.begin() + i, Pos.end()}; + for (; (i < maxPosition) && std::isdigit(position[i]); ++i) + ; + for (; (i < maxPosition) && std::isspace(position[i]); ++i) + ; + return parser::CharBlock{position.begin() + i, position.end()}; } - return Pos; + return position; } /// \brief Check constraints on label-do-stmt template -inline bool CheckLabelDoConstraints(const A& Dos, const A& Branches, - const B& Labels, const C& Scopes, - ErrorHandler& EH) { - auto NoErrors{true}; - IndexList LoopBodies; - for (const auto Stmt : Dos) { - const auto& Label{std::get<0>(Stmt)}; - const auto& Scope{std::get<1>(Stmt)}; - const auto& Index{std::get<2>(Stmt)}; - auto Trgt{GetLabel(Labels, Label)}; - if (!std::get<0>(Trgt)) { +inline void CheckLabelDoConstraints(const A &dos, const A &branches, + const B &labels, const C &scopes, ErrorHandler &eh) { + IndexList loopBodies; + for (const auto stmt : dos) { + const auto &label{std::get(stmt)}; + const auto &scope{std::get(stmt)}; + const auto &position{std::get(stmt)}; + auto doTarget{GetLabel(labels, label)}; + if (HasNoScope(std::get(doTarget))) { // C1133: this is an error: label not found - EH.Report(Index, "label '%lu' cannot be found"_err_en_US, Label); - NoErrors = false; - continue; - } - if (std::get<1>(Trgt).begin() < Index.begin()) { + Report(eh, position, + parser::MessageFormattedText{ + "label '%lu' cannot be found"_err_en_US, label}); + } else if (std::get(doTarget).begin() < + position.begin()) { // R1119: this is an error: label does not follow DO - EH.Report(Index, "label '%lu' doesn't lexically follow DO stmt"_err_en_US, - Label); - NoErrors = false; - continue; - } - if (!InInclusiveScope(Scopes, Scope, std::get<0>(Trgt))) { + Report(eh, position, + parser::MessageFormattedText{ + "label '%lu' doesn't lexically follow DO stmt"_err_en_US, label}); + } else if (!InInclusiveScope( + scopes, scope, std::get(doTarget))) { // C1133: this is an error: label is not in scope - if (StrictF18) { - EH.Report(Index, "label '%lu' is not in scope"_err_en_US, Label); - NoErrors = false; + if (isStrictF18) { + Report(eh, position, + parser::MessageFormattedText{ + "label '%lu' is not in scope"_err_en_US, label}); } else { - EH.Report(Index, "label '%lu' is not in scope"_en_US, Label); + Report(eh, position, + parser::MessageFormattedText{ + "label '%lu' is not in scope"_en_US, label}); } - continue; + } else if ((std::get(doTarget) & + TargetStmtType{TargetStatementEnum::Do}) + .none()) { + Report(eh, std::get(doTarget), + parser::MessageFormattedText{ + "'%lu' invalid DO terminal statement"_err_en_US, label}); + } else { + // save the loop body marks + loopBodies.push_back( + {SkipLabel(position), std::get(doTarget)}); } - if (!(std::get<2>(Trgt) & DO_TERM_FLAG)) { - EH.Report(std::get(Trgt), - "'%lu' invalid DO terminal statement"_err_en_US, Label); - NoErrors = false; - } - // save the loop body marks - LoopBodies.push_back({SkipLabel(Index), std::get<1>(Trgt)}); } - - if (NoErrors) { - NoErrors = - // check that nothing jumps into the block - CheckBranchesIntoDoBody(Branches, Labels, Scopes, LoopBodies, EH) & - // check that do loops properly nest - CheckDoNesting(LoopBodies, EH); - } - return NoErrors; + + // check that nothing jumps into the block + CheckBranchesIntoDoBody(branches, labels, scopes, loopBodies, eh); + // check that do loops properly nest + CheckDoNesting(loopBodies, eh); } /// \brief General constraint, control transfers within inclusive scope /// See, for example, section 6.2.5. template -bool CheckScopeConstraints(const A& Stmts, const B& Labels, - const C& Scopes, ErrorHandler& EH) { - auto NoErrors{true}; - for (const auto Stmt : Stmts) { - const auto& Label{std::get<0>(Stmt)}; - const auto& Scope{std::get<1>(Stmt)}; - const auto& Index{std::get<2>(Stmt)}; - auto Trgt{GetLabel(Labels, Label)}; - if (!std::get<0>(Trgt)) { +void CheckScopeConstraints( + const A &stmts, const B &labels, const C &scopes, ErrorHandler &eh) { + for (const auto stmt : stmts) { + const auto &label{std::get(stmt)}; + const auto &scope{std::get(stmt)}; + const auto &position{std::get(stmt)}; + auto target{GetLabel(labels, label)}; + if (HasNoScope(std::get(target))) { // this is an error: label not found - EH.Report(Index, "label '%lu' was not found"_err_en_US, Label); - NoErrors = false; - continue; - } - if (!InInclusiveScope(Scopes, Scope, std::get<0>(Trgt))) { + Report(eh, position, + parser::MessageFormattedText{ + "label '%lu' was not found"_err_en_US, label}); + } else if (!InInclusiveScope(scopes, scope, std::get(target))) { // this is an error: label not in scope - if (StrictF18) { - EH.Report(Index, "label '%lu' is not in scope"_err_en_US, Label); - NoErrors = false; + if (isStrictF18) { + Report(eh, position, + parser::MessageFormattedText{ + "label '%lu' is not in scope"_err_en_US, label}); } else { - EH.Report(Index, "label '%lu' is not in scope"_en_US, Label); + Report(eh, position, + parser::MessageFormattedText{ + "label '%lu' is not in scope"_en_US, label}); } } } - return NoErrors; } template -inline bool CheckBranchTargetConstraints(const A& Stmts, const B& Labels, - ErrorHandler& EH) { - auto NoErrors{true}; - for (const auto Stmt : Stmts) { - const auto& Label{std::get<0>(Stmt)}; - auto Trgt{GetLabel(Labels, Label)}; - if (!std::get<0>(Trgt)) - continue; - if (!(std::get<2>(Trgt) & BRANCH_TARGET_FLAG)) { - // this is an error: label statement is not a branch target - EH.Report(std::get(Trgt), "'%lu' not a branch target"_err_en_US, - Label); - NoErrors = false; +inline void CheckBranchTargetConstraints( + const A &stmts, const B &labels, ErrorHandler &eh) { + for (const auto stmt : stmts) { + const auto &label{std::get(stmt)}; + auto branchTarget{GetLabel(labels, label)}; + if (HasScope(std::get(branchTarget))) { + if ((std::get(branchTarget) & + TargetStmtType{TargetStatementEnum::Branch}) + .none()) { + // this is an error: label statement is not a branch target + Report(eh, std::get(branchTarget), + parser::MessageFormattedText{ + "'%lu' not a branch target"_err_en_US, label}); + } } } - return NoErrors; } /// \brief Validate the constraints on branches /// \param Analysis the analysis result template -inline bool CheckBranchConstraints(const A& Branches, const B& Labels, - const C& Scopes, ErrorHandler& EH) { - return CheckScopeConstraints(Branches, Labels, Scopes, EH) & - CheckBranchTargetConstraints(Branches, Labels, EH); +inline void CheckBranchConstraints( + const A &branches, const B &labels, const C &scopes, ErrorHandler &eh) { + CheckScopeConstraints(branches, labels, scopes, eh); + CheckBranchTargetConstraints(branches, labels, eh); } template -inline bool CheckDataXferTargetConstraints(const A& Stmts, const B& Labels, - ErrorHandler& EH) { - auto NoErrors{true}; - for (const auto Stmt : Stmts) { - const auto& Label{std::get<0>(Stmt)}; - auto Trgt{GetLabel(Labels, Label)}; - if (!std::get<0>(Trgt)) - continue; - if (!(std::get<2>(Trgt) & FORMAT_STMT_FLAG)) { - // this is an error: label not a FORMAT - EH.Report(std::get(Trgt), "'%lu' not a FORMAT"_err_en_US, Label); - NoErrors = false; +inline void CheckDataXferTargetConstraints( + const A &stmts, const B &labels, ErrorHandler &eh) { + for (const auto stmt : stmts) { + const auto &label{std::get(stmt)}; + auto ioTarget{GetLabel(labels, label)}; + if (HasScope(std::get(ioTarget))) { + if ((std::get(ioTarget) & + TargetStmtType{TargetStatementEnum::Format}) + .none()) { + // this is an error: label not a FORMAT + Report(eh, std::get(ioTarget), + parser::MessageFormattedText{ + "'%lu' not a FORMAT"_err_en_US, label}); + } } } - return NoErrors; } /// \brief Validate that data transfers reference FORMATs in scope /// \param Analysis the analysis result /// These label uses are disjoint from branching (control flow) template -inline bool CheckDataTransferConstraints(const A& DataXfers, const B& Labels, - const C& Scopes, ErrorHandler& EH) { - return CheckScopeConstraints(DataXfers, Labels, Scopes, EH) & - CheckDataXferTargetConstraints(DataXfers, Labels, EH); +inline void CheckDataTransferConstraints(const A &dataTransfers, + const B &labels, const C &scopes, ErrorHandler &eh) { + CheckScopeConstraints(dataTransfers, labels, scopes, eh); + CheckDataXferTargetConstraints(dataTransfers, labels, eh); } /// \brief Validate label related constraints on the parse tree -/// \param Analysis the analysis results as run of the parse tree -/// \param EH the error handler +/// \param analysis the analysis results as run of the parse tree +/// \param cookedSrc cooked source for error report /// \return true iff all the semantics checks passed -bool CheckConstraints(ParseTreeAnalyzer&& Analysis) { - auto result{Analysis.HasNoErrors()}; - auto& EH{Analysis.GetEH()}; - for (const auto& A : Analysis.GetProgramUnits()) { - const auto& Dos{A.GetLabelDos()}; - const auto& Branches{A.GetBranches()}; - const auto& DataXfers{A.GetDataXfers()}; - const auto& Labels{A.GetLabels()}; - const auto& Scopes{A.GetScopes()}; - result &= CheckLabelDoConstraints(Dos, Branches, Labels, Scopes, EH) & - CheckBranchConstraints(Branches, Labels, Scopes, EH) & - CheckDataTransferConstraints(DataXfers, Labels, Scopes, EH); +bool CheckConstraints(ParseTreeAnalyzer &&parseTreeAnalysis, + const parser::CookedSource &cookedSource) { + auto &eh{parseTreeAnalysis.GetEH()}; + for (const auto &programUnit : parseTreeAnalysis.GetProgramUnits()) { + const auto &dos{programUnit.GetLabelDos()}; + const auto &branches{programUnit.GetBranches()}; + const auto &labels{programUnit.GetLabels()}; + const auto &scopes{programUnit.GetScopes()}; + CheckLabelDoConstraints(dos, branches, labels, scopes, eh); + CheckBranchConstraints(branches, labels, scopes, eh); + const auto &dataTransfers{programUnit.GetDataXfers()}; + CheckDataTransferConstraints(dataTransfers, labels, scopes, eh); } - if (!EH.messages.empty()) - EH.messages.Emit(std::cerr, EH.cookedSource); - return result; + if (!eh.empty()) { + eh.Emit(std::cerr, cookedSource); + } + return HasNoErrors(eh); } -} // +} // namespace namespace Fortran::semantics { /// \brief Check the semantics of LABELs in the program /// \return true iff the program's use of LABELs is semantically correct -bool ValidateLabels(const parser::Program& ParseTree, - const parser::CookedSource& Source) { - return CheckConstraints(LabelAnalysis(ParseTree, Source)); +bool ValidateLabels( + const parser::Program &program, const parser::CookedSource &cookedSource) { + return CheckConstraints(LabelAnalysis(program), cookedSource); } -} // Fortran::semantics +} // namespace Fortran::semantics diff --git a/flang/lib/semantics/resolve-labels.h b/flang/lib/semantics/resolve-labels.h index 6154af6f7c30..42c9a9dab9c7 100644 --- a/flang/lib/semantics/resolve-labels.h +++ b/flang/lib/semantics/resolve-labels.h @@ -1,3 +1,4 @@ +/* -*- mode: c++; c-basic-offset: 2 -*- */ // Copyright (c) 2018, NVIDIA CORPORATION. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -15,26 +16,19 @@ #ifndef FORTRAN_SEMANTICS_RESOLVE_LABELS_H_ #define FORTRAN_SEMANTICS_RESOLVE_LABELS_H_ -namespace Fortran { -namespace parser { +namespace Fortran::parser { struct Program; class CookedSource; -} // parser +} // namespace Fortran::parser -namespace semantics { +namespace Fortran::semantics { /// \brief Validate the labels in the program /// \param ParseTree the parse tree /// \param Source the cooked source /// \return true, iff the program's labels pass semantics checks -bool ValidateLabels(const parser::Program &ParseTree, - const parser::CookedSource &Source); -} // semantics -} // Fortran +bool ValidateLabels( + const parser::Program &ParseTree, const parser::CookedSource &Source); +} // namespace Fortran::semantics -#endif // FORTRAN_SEMANTICS_RESOLVE_LABELS_H_ - -// Local Variables: -// mode: C++ -// c-basic-offset: 2 -// End: +#endif // FORTRAN_SEMANTICS_RESOLVE_LABELS_H_ diff --git a/flang/test/semantics/CMakeLists.txt b/flang/test/semantics/CMakeLists.txt index 419e85615499..05866bb6c4f7 100644 --- a/flang/test/semantics/CMakeLists.txt +++ b/flang/test/semantics/CMakeLists.txt @@ -84,6 +84,10 @@ set(MODFILE_TESTS modfile11.f90 ) +set(LABEL_TESTS + label*.[Ff]90 +) + foreach(test ${ERROR_TESTS}) add_test(NAME ${test} COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/test_errors.sh ${test}) endforeach() @@ -95,3 +99,7 @@ endforeach() foreach(test ${MODFILE_TESTS}) add_test(NAME ${test} COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/test_modfile.sh ${test}) endforeach() + +foreach(test ${LABEL_TESTS}) + add_test(NAME ${test} COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/test_any.sh ${test}) +endforeach() diff --git a/flang/test/semantics/label01.F90 b/flang/test/semantics/label01.F90 index a964d0f5d95f..b243f614c21c 100644 --- a/flang/test/semantics/label01.F90 +++ b/flang/test/semantics/label01.F90 @@ -12,13 +12,17 @@ ! See the License for the specific language governing permissions and ! limitations under the License. -! RUN: f18 < %s | FileCheck %s -! CHECK-NOT: +! RUN: ${F18} -funparse-with-symbols %s -o /dev/null 2>&1 | grep -v 'procedure conflicts' | ${FileCheck} %s +! CHECK-NOT: error:[[:space:]] + +! FIXME: filter out the array/function syntax issues (procedure conflicts) +! for now... ! these are the conformance tests ! define STRICT_F18 to eliminate tests of features not in F18 ! define ARCHAIC_FORTRAN to add test of feature found in Fortran before F95 + subroutine sub00(a,b,n,m) real a(n) real :: b(m) diff --git a/flang/test/semantics/label02.f90 b/flang/test/semantics/label02.f90 index d6580ada229f..5200ab9be9b6 100644 --- a/flang/test/semantics/label02.f90 +++ b/flang/test/semantics/label02.f90 @@ -14,7 +14,7 @@ ! negative test -- invalid labels, out of range -! RUN: f18 < %s | FileCheck %s +! RUN: ${F18} -funparse-with-symbols %s 2>&1 | ${FileCheck} %s ! CHECK: label '0' is out of range ! CHECK: label '100000' is out of range ! CHECK: label '123456' is out of range diff --git a/flang/test/semantics/label03.f90 b/flang/test/semantics/label03.f90 index 1d0d9dc38a27..b5a470b18074 100644 --- a/flang/test/semantics/label03.f90 +++ b/flang/test/semantics/label03.f90 @@ -14,7 +14,7 @@ ! negative test -- invalid labels, out of range -! RUN: f18 < %s | FileCheck %s +! RUN: ${F18} -funparse-with-symbols %s 2>&1 | ${FileCheck} %s ! CHECK: 'do 10 i = 1, m' doesn't properly nest ! CHECK: label '30' cannot be found ! CHECK: label '40' cannot be found diff --git a/flang/test/semantics/label04.f90 b/flang/test/semantics/label04.f90 index 5666772ab951..c8c65e82a4e3 100644 --- a/flang/test/semantics/label04.f90 +++ b/flang/test/semantics/label04.f90 @@ -14,7 +14,7 @@ ! negative test -- invalid labels, out of range -! RUN: f18 < %s | FileCheck %s +! RUN: ${F18} -funparse-with-symbols %s 2>&1 | ${FileCheck} %s ! CHECK: branch into 'do 10 i = 1, m' from another scope ! CHECK: branch into 'do 20 j = 1, n' from another scope diff --git a/flang/test/semantics/label05.f90 b/flang/test/semantics/label05.f90 index b15e781f7b4d..947d2bf74141 100644 --- a/flang/test/semantics/label05.f90 +++ b/flang/test/semantics/label05.f90 @@ -14,10 +14,10 @@ ! negative test -- invalid labels, out of range -! RUN: f18 < %s | FileCheck %s +! RUN: ${F18} -funparse-with-symbols %s 2>&1 | ${FileCheck} %s ! CHECK: label '50' was not found ! CHECK: label '55' is not in scope -! CHECK: label '70' is not an action stmt +! CHECK: '70' not a branch target subroutine sub00(a,b,n,m) real a(n,m) diff --git a/flang/test/semantics/label06.f90 b/flang/test/semantics/label06.f90 index dc866bcdaf00..fb0aa7c084b9 100644 --- a/flang/test/semantics/label06.f90 +++ b/flang/test/semantics/label06.f90 @@ -14,11 +14,11 @@ ! negative test -- invalid labels, out of range -! RUN: f18 < %s | FileCheck %s +! RUN: ${F18} -funparse-with-symbols %s 2>&1 | ${FileCheck} %s ! CHECK: label '10' is not in scope ! CHECK: label '20' was not found ! CHECK: label '40' is not in scope -! CHECK: label '50' is not in scope (FIXME is that correct?) +! CHECK: label '50' is not in scope subroutine sub00(n) GOTO (10,20,30) n diff --git a/flang/test/semantics/label07.f90 b/flang/test/semantics/label07.f90 index 1dd38d5203e9..e9c85526e449 100644 --- a/flang/test/semantics/label07.f90 +++ b/flang/test/semantics/label07.f90 @@ -14,10 +14,10 @@ ! negative test -- invalid labels, out of range -! RUN: f18 < %s | FileCheck %s +! RUN: ${F18} -funparse-with-symbols %s 2>&1 | ${FileCheck} %s ! CHECK: label '10' is not in scope ! CHECK: label '20' was not found -! CHECK: label '30' is not an action stmt +! CHECK: '30' not a branch target ! CHECK: label '60' was not found subroutine sub00(n,m) diff --git a/flang/test/semantics/label08.f90 b/flang/test/semantics/label08.f90 index ea02f59bc15a..b86ab020f9b8 100644 --- a/flang/test/semantics/label08.f90 +++ b/flang/test/semantics/label08.f90 @@ -14,10 +14,10 @@ ! negative test -- invalid labels, out of range -! RUN: f18 < %s | FileCheck %s +! RUN: ${F18} -funparse-with-symbols %s 2>&1 | ${FileCheck} %s ! CHECK: IF construct name mismatch ! CHECK: DO construct name mismatch -! CHECK: CYCLE construct name mismatch +! CHECK: CYCLE construct-name 'label3' is not in scope subroutine sub00(a,b,n,m) real a(n,m) diff --git a/flang/test/semantics/label09.f90 b/flang/test/semantics/label09.f90 index 1a2221a58221..8535cff97e8b 100644 --- a/flang/test/semantics/label09.f90 +++ b/flang/test/semantics/label09.f90 @@ -12,8 +12,8 @@ ! See the License for the specific language governing permissions and ! limitations under the License. -! RUN: f18 < %s | FileCheck %s -! CHECK: +! RUN: ${F18} -funparse-with-symbols %s 2>&1 | ${FileCheck} %s +! CHECK: label '60' was not found subroutine s(a) real a(10) diff --git a/flang/test/semantics/label10.f90 b/flang/test/semantics/label10.f90 index de9eb3ad34c1..a56377aba5e1 100644 --- a/flang/test/semantics/label10.f90 +++ b/flang/test/semantics/label10.f90 @@ -12,8 +12,8 @@ ! See the License for the specific language governing permissions and ! limitations under the License. -! RUN: f18 < %s | FileCheck %s -! CHECK: +! RUN: ${F18} -funparse-with-symbols %s 2>&1 | ${FileCheck} %s +! CHECK: '60' not a FORMAT subroutine s(a) real a(10) diff --git a/flang/test/semantics/test_any.sh b/flang/test/semantics/test_any.sh new file mode 100755 index 000000000000..214c6f6fc1ee --- /dev/null +++ b/flang/test/semantics/test_any.sh @@ -0,0 +1,58 @@ +#!/usr/bin/env bash +# Copyright (c) 2018, NVIDIA CORPORATION. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Compile a source file with '-funparse-with-symbols' and verify +# we get the right symbols in the output, i.e. the output should be +# the same as the input, except for the copyright comment. +# Change the compiler by setting the F18 environment variable. + +PATH=/usr/bin:/bin +srcdir=$(dirname $0) +F18=${F18:=../../tools/f18/f18} +FileCheck=${FileCheck:=internal_check} + +function internal_check() { + r=true + linput="$1" + lstdin=`mktemp` + lcheck=`mktemp` + cat - > ${lstdin} + egrep '^[[:space:]]*![[:space:]]*CHECK:[[:space:]]*' ${linput} | sed -e 's/^[[:space:]]*![[:space:]]*CHECK:[[:space:]]*//' > ${lcheck} 2>/dev/null + while read p; do + if egrep "${p}" ${lstdin} >/dev/null 2>&1; then + true + else + echo "Not found: ${p}" >&2 + r=false + fi + done < ${lcheck} + egrep '^[[:space:]]*![[:space:]]*CHECK-NOT:[[:space:]]*' ${linput} | sed -e 's/^[[:space:]]*![[:space:]]*CHECK-NOT:[[:space:]]*//' > ${lcheck} 2>/dev/null + while read p; do + if egrep ${p} ${lstdin} >/dev/null 2>&1; then + echo "Found: ${p}" >&2 + r=false + fi + done < ${lcheck} + rm -f ${lstdin} ${lcheck} + ${r} +} + +r=0 +for input in $*; do + CMD=$(cat ${input} | egrep '^[[:space:]]*![[:space:]]*RUN:[[:space:]]*' | sed -e 's/^[[:space:]]*![[:space:]]*RUN:[[:space:]]*//') + CMD=$(echo ${CMD} | sed -e "s:%s:${input}:g") + eval "( ${CMD} )" || (echo "test ${input} failed"; r=1) +done +exit $r