forked from OSchip/llvm-project
[FileCheck] Add a literal check directive modifier
Introduce CHECK modifiers that change the behavior of the CHECK directive. Also add a LITERAL modifier for cases where matching could end requiring escaping strings interpreted as regex where only literal/fixed string matching is desired (making the CHECK's more difficult to write/fragile and difficult to interpret).
This commit is contained in:
parent
2b62e62328
commit
44f399ccc1
|
@ -660,6 +660,30 @@ simply uniquely match a single line in the file being verified.
|
|||
|
||||
``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 the only supported value for ``<modifier>`` is
|
||||
``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
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
#include "llvm/Support/MemoryBuffer.h"
|
||||
#include "llvm/Support/Regex.h"
|
||||
#include "llvm/Support/SourceMgr.h"
|
||||
#include <bitset>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
|
@ -64,12 +65,23 @@ enum FileCheckKind {
|
|||
CheckBadCount
|
||||
};
|
||||
|
||||
enum FileCheckKindModifier {
|
||||
/// Modifies directive to perform literal match.
|
||||
ModifierLiteral = 0,
|
||||
|
||||
// The number of modifier.
|
||||
Size
|
||||
};
|
||||
|
||||
class FileCheckType {
|
||||
FileCheckKind Kind;
|
||||
int Count; ///< optional Count for some checks
|
||||
/// Modifers for the check directive.
|
||||
std::bitset<FileCheckKindModifier::Size> 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 @@ public:
|
|||
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
|
||||
|
||||
|
|
|
@ -917,6 +917,12 @@ bool Pattern::parsePattern(StringRef PatternStr, StringRef Prefix,
|
|||
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 @@ Check::FileCheckType &Check::FileCheckType::setCount(int C) {
|
|||
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,45 @@ FindCheckType(const FileCheckRequest &Req, StringRef Buffer, StringRef Prefix) {
|
|||
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()};
|
||||
}
|
||||
|
||||
// Verify that the : is present after the prefix.
|
||||
if (NextChar == ':')
|
||||
return {Check::CheckPlain, Rest};
|
||||
auto ConsumeModifiers = [&](Check::FileCheckType Ret)
|
||||
-> std::pair<Check::FileCheckType, StringRef> {
|
||||
if (Rest.consume_front(":"))
|
||||
return {Ret, Rest};
|
||||
if (!Rest.consume_front("{"))
|
||||
return {Check::CheckNone, StringRef()};
|
||||
|
||||
if (NextChar != '-')
|
||||
// Parse the modifiers, speparated by commas.
|
||||
do {
|
||||
// Allow whitespace in modifiers list.
|
||||
Rest = Rest.ltrim();
|
||||
if (Rest.consume_front("LITERAL"))
|
||||
Ret.setLiteralMatch();
|
||||
else
|
||||
return {Check::CheckNone, Rest};
|
||||
// Allow whitespace in modifiers list.
|
||||
Rest = Rest.ltrim();
|
||||
} while (Rest.consume_front(","));
|
||||
if (!Rest.consume_front("}:"))
|
||||
return {Check::CheckNone, Rest};
|
||||
return {Ret, Rest};
|
||||
};
|
||||
|
||||
// Verify that the prefix is followed by directive modifiers or a colon.
|
||||
if (Rest.consume_front(":"))
|
||||
return {Check::CheckPlain, Rest};
|
||||
if (Rest.front() == '{')
|
||||
return ConsumeModifiers(Check::CheckPlain);
|
||||
|
||||
if (!Rest.consume_front("-"))
|
||||
return {Check::CheckNone, StringRef()};
|
||||
|
||||
if (Rest.consume_front("COUNT-")) {
|
||||
|
@ -1651,29 +1696,12 @@ FindCheckType(const FileCheckRequest &Req, StringRef Buffer, StringRef Prefix) {
|
|||
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 +1709,24 @@ FindCheckType(const FileCheckRequest &Req, StringRef Buffer, StringRef Prefix) {
|
|||
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};
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,62 @@
|
|||
; RUN: FileCheck -check-prefix=A -input-file %s %s
|
||||
|
||||
;; This tests the LITERAL directive modifier.
|
||||
|
||||
The result is "5371, 5372, 5373, 5374"
|
||||
|
||||
The result is "[[[5371]], [[5372]], [[5373]], [[5374]]]"
|
||||
[[[5375]], [[5376]],
|
||||
[[[5377]], [[5378]],
|
||||
{{there you go.*}}
|
||||
|
||||
[[10]]
|
||||
[[20]]
|
||||
[[50]]
|
||||
|
||||
;; These should all not match.
|
||||
; A{}: 5371, 5372,
|
||||
; A{LITERAL} 5371, 5372,
|
||||
; A{LITERAL 5371, 5372,
|
||||
; A{LITERAL,} 5371, 5372,
|
||||
; A{, LITERAL} 5371, 5372,
|
||||
|
||||
; A: 5371, 5372,
|
||||
; A-SAME: 5373, 5374
|
||||
; A{LITERAL}: [[[5371]], [[5372]],
|
||||
; A-SAME{LITERAL}: [[5373]], [[5374]]]
|
||||
|
||||
;; Modifier list allows whitespace.
|
||||
; A{ LITERAL }: [[[5375]], [[5376]],
|
||||
;; Modifiers are combined into a set and repetition is allowed.
|
||||
; A{LITERAL , LITERAL}: [[[5377]], [[5378]],
|
||||
|
||||
; 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=INVALID 2>&1 | \
|
||||
; RUN: FileCheck %s --check-prefix=CHECK-INVALID
|
||||
|
||||
;; Ensure invalid modifier skipped.
|
||||
|
||||
; INVALID{BADMODIFIER}: 6371, 6372,
|
||||
; CHECK-INVALID: no check strings found with prefix 'INVALID
|
||||
|
||||
; RUN: %ProtectFileCheckOutput \
|
||||
; RUN: not FileCheck %s --input-file %s --check-prefix=CHECK-ERRNOT 2>&1 | \
|
||||
; RUN: FileCheck %s --check-prefix=ERRNOT
|
||||
|
||||
;; This ensures a failure is correctly reported when a NOT directive with a
|
||||
;; LITERAL modifier matches.
|
||||
|
||||
[[a]]
|
||||
[[b]]
|
||||
[[c]]
|
||||
|
||||
; CHECK-ERRNOT{LITERAL}: [[a]]
|
||||
; CHECK-ERRNOT-NOT{LITERAL}: [[b]]
|
||||
; CHECK-ERRNOT{LITERAL}: [[c]]
|
||||
; ERRNOT: no match expected
|
Loading…
Reference in New Issue