diff --git a/llvm/docs/CommandGuide/FileCheck.rst b/llvm/docs/CommandGuide/FileCheck.rst --- a/llvm/docs/CommandGuide/FileCheck.rst +++ b/llvm/docs/CommandGuide/FileCheck.rst @@ -660,6 +660,30 @@ ``CHECK-LABEL:`` directives cannot contain variable definitions or uses. +Directive modifiers +~~~~~~~~~~~~~~~~~~~ + +A directive modifier can be append to a directive by following the directive +with ``{modifier}``. Where modifier can be one of the following: + +LITERAL + The ``LITERAL`` directive modifier can be used to perform a literal match. The + modifier results in the directive not recognizing any syntax to perform regex + matching, variable capture or any substitutions. This is useful when the text + to match would require excessive escaping otherwise. For example, the + following will perform literal matches rather than considering these as + regular expressions: + + .. code-block:: text + + Input: [[[10, 20]], [[30, 40]]] + Output %r10: [[10, 20]] + Output %r10: [[30, 40]] + + ; CHECK{LITERAL}: [[[10, 20]], [[30, 40]]] + ; CHECK-DAG{LITERAL}: [[30, 40]] + ; CHECK-DAG{LITERAL}: [[10, 20]] + FileCheck Regex Matching Syntax ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/llvm/include/llvm/FileCheck/FileCheck.h b/llvm/include/llvm/FileCheck/FileCheck.h --- a/llvm/include/llvm/FileCheck/FileCheck.h +++ b/llvm/include/llvm/FileCheck/FileCheck.h @@ -17,6 +17,7 @@ #include "llvm/Support/MemoryBuffer.h" #include "llvm/Support/Regex.h" #include "llvm/Support/SourceMgr.h" +#include #include #include @@ -64,12 +65,23 @@ CheckBadCount }; +enum FileCheckKindModifier { + /// Modifies directive to perform literal match. + ModifierLiteral = 0, + + // The number of modifier. + Size = 1 +}; + class FileCheckType { FileCheckKind Kind; int Count; ///< optional Count for some checks + /// Modifers for the check directive. + std::bitset Modifiers; public: - FileCheckType(FileCheckKind Kind = CheckNone) : Kind(Kind), Count(1) {} + FileCheckType(FileCheckKind Kind = CheckNone) + : Kind(Kind), Count(1), Modifiers() {} FileCheckType(const FileCheckType &) = default; FileCheckType &operator=(const FileCheckType &) = default; @@ -78,8 +90,19 @@ int getCount() const { return Count; } FileCheckType &setCount(int C); + bool isLiteralMatch() const { + return Modifiers[FileCheckKindModifier::ModifierLiteral]; + } + FileCheckType &setLiteralMatch(bool Literal = true) { + Modifiers.set(FileCheckKindModifier::ModifierLiteral, Literal); + return *this; + } + // \returns a description of \p Prefix. std::string getDescription(StringRef Prefix) const; + + // \returns a description of \p Modifiers. + std::string getModifiersDescription() const; }; } // namespace Check diff --git a/llvm/lib/FileCheck/FileCheck.cpp b/llvm/lib/FileCheck/FileCheck.cpp --- a/llvm/lib/FileCheck/FileCheck.cpp +++ b/llvm/lib/FileCheck/FileCheck.cpp @@ -917,6 +917,12 @@ return false; } + // If literal check, set fixed string. + if (CheckTy.isLiteralMatch()) { + FixedStr = PatternStr; + return false; + } + // Check to see if this is a fixed string, or if it has regex pieces. if (!MatchFullLinesHere && (PatternStr.size() < 2 || (PatternStr.find("{{") == StringRef::npos && @@ -1588,26 +1594,43 @@ return *this; } +std::string Check::FileCheckType::getModifiersDescription() const { + if (Modifiers.none()) + return ""; + std::string Ret; + raw_string_ostream OS(Ret); + OS << '{'; + if (isLiteralMatch()) + OS << "LITERAL"; + OS << '}'; + return OS.str(); +} + std::string Check::FileCheckType::getDescription(StringRef Prefix) const { + // Append directive modifiers. + auto WithModifiers = [this, Prefix](StringRef Str) -> std::string { + return (Prefix + Str + getModifiersDescription()).str(); + }; + switch (Kind) { case Check::CheckNone: return "invalid"; case Check::CheckPlain: if (Count > 1) - return Prefix.str() + "-COUNT"; - return std::string(Prefix); + return WithModifiers("-COUNT"); + return WithModifiers(""); case Check::CheckNext: - return Prefix.str() + "-NEXT"; + return WithModifiers("-NEXT"); case Check::CheckSame: - return Prefix.str() + "-SAME"; + return WithModifiers("-SAME"); case Check::CheckNot: - return Prefix.str() + "-NOT"; + return WithModifiers("-NOT"); case Check::CheckDAG: - return Prefix.str() + "-DAG"; + return WithModifiers("-DAG"); case Check::CheckLabel: - return Prefix.str() + "-LABEL"; + return WithModifiers("-LABEL"); case Check::CheckEmpty: - return Prefix.str() + "-EMPTY"; + return WithModifiers("-EMPTY"); case Check::CheckComment: return std::string(Prefix); case Check::CheckEOF: @@ -1625,23 +1648,39 @@ if (Buffer.size() <= Prefix.size()) return {Check::CheckNone, StringRef()}; - char NextChar = Buffer[Prefix.size()]; - - StringRef Rest = Buffer.drop_front(Prefix.size() + 1); - + StringRef Rest = Buffer.drop_front(Prefix.size()); // Check for comment. if (llvm::is_contained(Req.CommentPrefixes, Prefix)) { - if (NextChar == ':') + if (Rest.consume_front(":")) return {Check::CheckComment, Rest}; // Ignore a comment prefix if it has a suffix like "-NOT". return {Check::CheckNone, StringRef()}; } + auto ConsumeModifiers = [&](Check::FileCheckType Ret) + -> std::pair { + if (Rest.consume_front(":")) + return {Ret, Rest}; + if (!Rest.consume_front("{")) + return {Check::CheckNone, StringRef()}; + + // Parse the modifiers, speparated by commas. + do { + if (Rest.consume_front("LITERAL")) + Ret.setLiteralMatch(); + else + return {Check::CheckNone, Rest}; + } while (Rest.consume_front(",")); + if (!Rest.consume_front("}:")) + return {Check::CheckNone, Rest}; + return {Ret, Rest}; + }; + // Verify that the : is present after the prefix. - if (NextChar == ':') - return {Check::CheckPlain, Rest}; + if (Rest.front() == ':' || Rest.front() == '{') + return ConsumeModifiers(Check::CheckPlain); - if (NextChar != '-') + if (!Rest.consume_front("-")) return {Check::CheckNone, StringRef()}; if (Rest.consume_front("COUNT-")) { @@ -1651,29 +1690,12 @@ return {Check::CheckBadCount, Rest}; if (Count <= 0 || Count > INT32_MAX) return {Check::CheckBadCount, Rest}; - if (!Rest.consume_front(":")) + if (Rest.front() != ':' && Rest.front() != '{') return {Check::CheckBadCount, Rest}; - return {Check::FileCheckType(Check::CheckPlain).setCount(Count), Rest}; + return ConsumeModifiers( + Check::FileCheckType(Check::CheckPlain).setCount(Count)); } - if (Rest.consume_front("NEXT:")) - return {Check::CheckNext, Rest}; - - if (Rest.consume_front("SAME:")) - return {Check::CheckSame, Rest}; - - if (Rest.consume_front("NOT:")) - return {Check::CheckNot, Rest}; - - if (Rest.consume_front("DAG:")) - return {Check::CheckDAG, Rest}; - - if (Rest.consume_front("LABEL:")) - return {Check::CheckLabel, Rest}; - - if (Rest.consume_front("EMPTY:")) - return {Check::CheckEmpty, Rest}; - // You can't combine -NOT with another suffix. if (Rest.startswith("DAG-NOT:") || Rest.startswith("NOT-DAG:") || Rest.startswith("NEXT-NOT:") || Rest.startswith("NOT-NEXT:") || @@ -1681,6 +1703,24 @@ Rest.startswith("EMPTY-NOT:") || Rest.startswith("NOT-EMPTY:")) return {Check::CheckBadNot, Rest}; + if (Rest.consume_front("NEXT")) + return ConsumeModifiers(Check::CheckNext); + + if (Rest.consume_front("SAME")) + return ConsumeModifiers(Check::CheckSame); + + if (Rest.consume_front("NOT")) + return ConsumeModifiers(Check::CheckNot); + + if (Rest.consume_front("DAG")) + return ConsumeModifiers(Check::CheckDAG); + + if (Rest.consume_front("LABEL")) + return ConsumeModifiers(Check::CheckLabel); + + if (Rest.consume_front("EMPTY")) + return ConsumeModifiers(Check::CheckEmpty); + return {Check::CheckNone, Rest}; } diff --git a/llvm/test/FileCheck/check-literal.txt b/llvm/test/FileCheck/check-literal.txt new file mode 100644 --- /dev/null +++ b/llvm/test/FileCheck/check-literal.txt @@ -0,0 +1,40 @@ +; RUN: FileCheck -check-prefix=A -input-file %s %s + +;; This tests the LITERAL directive modifier. + +; A{LITERAL} is not incorrectly flagged. Neither is +; A{LITERAL + +The result is "5371, 5372, 5373, 5374" + +The result is "[[[5371]], [[5372]], [[5373]], [[5374]]]" +{{there you go.*}} + +[[10]] +[[20]] +[[50]] + +; A: 5371, 5372, +; A-SAME: 5373, 5374 +; A{LITERAL}: [[[5371]], [[5372]], +; A-SAME{LITERAL}: [[5373]], [[5374]]] +; A-NEXT{LITERAL}: {{there you go.*}} +; A-NOT{LITERAL}: [[50]] +; A-DAG{LITERAL}: [[20]] +; A-DAG{LITERAL}: [[10]] +; A{LITERAL}: [[50]] + +; RUN: %ProtectFileCheckOutput \ +; RUN: not FileCheck %s --input-file %s --check-prefix=CHECK-ERRNOT 2>&1 | \ +; RUN: FileCheck %s --check-prefix=ERRNOT + +;; This ensures LITERAL applied to not in error cases reports an error. + +[[a]] +[[b]] +[[c]] + +; CHECK-ERRNOT{LITERAL}: [[a]] +; CHECK-ERRNOT-NOT{LITERAL}: [[b]] +; CHECK-ERRNOT{LITERAL}: [[c]] +; ERRNOT: no match expected