Support for [[@LINE]], [[@LINE+<offset>]], [[@LINE-<offset>]] expressions in

FileCheck.


git-svn-id: https://llvm.org/svn/llvm-project/llvm/trunk@167978 91177308-0d34-0410-b5e6-96231b3b80d8
This commit is contained in:
Alexander Kornienko
2012-11-14 21:07:37 +00:00
parent 7454fc2e87
commit 70a870add8
2 changed files with 121 additions and 27 deletions

View File

@ -252,3 +252,30 @@ advantage of the fact that FileCheck is not actually line-oriented when it
matches, this allows you to define two separate "``CHECK``" lines that match on matches, this allows you to define two separate "``CHECK``" lines that match on
the same line. the same line.
FileCheck Expressions
~~~~~~~~~~~~~~~~~~~~~
Sometimes there's a need to verify output which refers line numbers of the match
file, e.g. when testing compiler diagnostics. This introduces a certain
fragility of the match file structure, as CHECK: lines contain absolute line
numbers in the same file, which have to be updated whenever line numbers change
due to text addition or deletion.
To support this case, FileCheck allows using ``[[@LINE]]``,
``[[@LINE+<offset>]]``, ``[[@LINE-<offset>]]`` expressions in patterns. These
expressions expand to a number of the line where a pattern is located (with an
optional integer offset).
This way match patterns can be put near the relevant test lines and include
relative line number references, for example:
.. code-block:: c++
// CHECK: test.cpp:[[@LINE+4]]:6: error: expected ';' after top level declarator
// CHECK-NEXT: {{^int a}}
// CHECK-NEXT: {{^ \^}}
// CHECK-NEXT: {{^ ;}}
int a

View File

@ -26,6 +26,7 @@
#include "llvm/Support/Signals.h" #include "llvm/Support/Signals.h"
#include "llvm/Support/system_error.h" #include "llvm/Support/system_error.h"
#include "llvm/ADT/SmallString.h" #include "llvm/ADT/SmallString.h"
#include "llvm/ADT/StringExtras.h"
#include "llvm/ADT/StringMap.h" #include "llvm/ADT/StringMap.h"
#include <algorithm> #include <algorithm>
using namespace llvm; using namespace llvm;
@ -63,6 +64,9 @@ class Pattern {
/// RegEx - If non-empty, this is a regex pattern. /// RegEx - If non-empty, this is a regex pattern.
std::string RegExStr; std::string RegExStr;
/// \brief Contains the number of line this pattern is in.
unsigned LineNumber;
/// VariableUses - Entries in this vector map to uses of a variable in the /// VariableUses - Entries in this vector map to uses of a variable in the
/// pattern, e.g. "foo[[bar]]baz". In this case, the RegExStr will contain /// pattern, e.g. "foo[[bar]]baz". In this case, the RegExStr will contain
/// "foobaz" and we'll get an entry in this vector that tells us to insert the /// "foobaz" and we'll get an entry in this vector that tells us to insert the
@ -79,7 +83,7 @@ public:
Pattern(bool matchEOF = false) : MatchEOF(matchEOF) { } Pattern(bool matchEOF = false) : MatchEOF(matchEOF) { }
bool ParsePattern(StringRef PatternStr, SourceMgr &SM); bool ParsePattern(StringRef PatternStr, SourceMgr &SM, unsigned LineNumber);
/// Match - Match the pattern string against the input buffer Buffer. This /// Match - Match the pattern string against the input buffer Buffer. This
/// returns the position that is matched or npos if there is no match. If /// returns the position that is matched or npos if there is no match. If
@ -104,10 +108,16 @@ private:
/// should correspond to a perfect match. /// should correspond to a perfect match.
unsigned ComputeMatchDistance(StringRef Buffer, unsigned ComputeMatchDistance(StringRef Buffer,
const StringMap<StringRef> &VariableTable) const; const StringMap<StringRef> &VariableTable) const;
/// \brief Evaluates expression and stores the result to \p Value.
/// \return true on success. false when the expression has invalid syntax.
bool EvaluateExpression(StringRef Expr, std::string &Value) const;
}; };
bool Pattern::ParsePattern(StringRef PatternStr, SourceMgr &SM) { bool Pattern::ParsePattern(StringRef PatternStr, SourceMgr &SM,
unsigned LineNumber) {
this->LineNumber = LineNumber;
PatternLoc = SMLoc::getFromPointer(PatternStr.data()); PatternLoc = SMLoc::getFromPointer(PatternStr.data());
// Ignore trailing whitespace. // Ignore trailing whitespace.
@ -193,13 +203,28 @@ bool Pattern::ParsePattern(StringRef PatternStr, SourceMgr &SM) {
return true; return true;
} }
// Verify that the name is well formed. // Verify that the name/expression is well formed. FileCheck currently
for (unsigned i = 0, e = Name.size(); i != e; ++i) // supports @LINE, @LINE+number, @LINE-number expressions. The check here
if (Name[i] != '_' && !isalnum(Name[i])) { // is relaxed, more strict check is performed in \c EvaluateExpression.
bool IsExpression = false;
for (unsigned i = 0, e = Name.size(); i != e; ++i) {
if (i == 0 && Name[i] == '@') {
if (NameEnd != StringRef::npos) {
SM.PrintMessage(SMLoc::getFromPointer(Name.data()),
SourceMgr::DK_Error,
"invalid name in named regex definition");
return true;
}
IsExpression = true;
continue;
}
if (Name[i] != '_' && !isalnum(Name[i]) &&
(!IsExpression || (Name[i] != '+' && Name[i] != '-'))) {
SM.PrintMessage(SMLoc::getFromPointer(Name.data()+i), SM.PrintMessage(SMLoc::getFromPointer(Name.data()+i),
SourceMgr::DK_Error, "invalid name in named regex"); SourceMgr::DK_Error, "invalid name in named regex");
return true; return true;
} }
}
// Name can't start with a digit. // Name can't start with a digit.
if (isdigit(Name[0])) { if (isdigit(Name[0])) {
@ -279,6 +304,24 @@ bool Pattern::AddRegExToRegEx(StringRef RegexStr, unsigned &CurParen,
return false; return false;
} }
bool Pattern::EvaluateExpression(StringRef Expr, std::string &Value) const {
// The only supported expression is @LINE([\+-]\d+)?
if (!Expr.startswith("@LINE"))
return false;
Expr = Expr.substr(StringRef("@LINE").size());
int Offset = 0;
if (!Expr.empty()) {
if (Expr[0] == '+')
Expr = Expr.substr(1);
else if (Expr[0] != '-')
return false;
if (Expr.getAsInteger(10, Offset))
return false;
}
Value = llvm::itostr(LineNumber + Offset);
return true;
}
/// Match - Match the pattern string against the input buffer Buffer. This /// Match - Match the pattern string against the input buffer Buffer. This
/// returns the position that is matched or npos if there is no match. If /// returns the position that is matched or npos if there is no match. If
/// there is a match, the size of the matched string is returned in MatchLen. /// there is a match, the size of the matched string is returned in MatchLen.
@ -307,15 +350,21 @@ size_t Pattern::Match(StringRef Buffer, size_t &MatchLen,
unsigned InsertOffset = 0; unsigned InsertOffset = 0;
for (unsigned i = 0, e = VariableUses.size(); i != e; ++i) { for (unsigned i = 0, e = VariableUses.size(); i != e; ++i) {
StringMap<StringRef>::iterator it =
VariableTable.find(VariableUses[i].first);
// If the variable is undefined, return an error.
if (it == VariableTable.end())
return StringRef::npos;
// Look up the value and escape it so that we can plop it into the regex.
std::string Value; std::string Value;
AddFixedStringToRegEx(it->second, Value);
if (VariableUses[i].first[0] == '@') {
if (!EvaluateExpression(VariableUses[i].first, Value))
return StringRef::npos;
} else {
StringMap<StringRef>::iterator it =
VariableTable.find(VariableUses[i].first);
// If the variable is undefined, return an error.
if (it == VariableTable.end())
return StringRef::npos;
// Look up the value and escape it so that we can plop it into the regex.
AddFixedStringToRegEx(it->second, Value);
}
// Plop it into the regex at the adjusted offset. // Plop it into the regex at the adjusted offset.
TmpStr.insert(TmpStr.begin()+VariableUses[i].second+InsertOffset, TmpStr.insert(TmpStr.begin()+VariableUses[i].second+InsertOffset,
@ -371,19 +420,31 @@ void Pattern::PrintFailureInfo(const SourceMgr &SM, StringRef Buffer,
// variable values. // variable values.
if (!VariableUses.empty()) { if (!VariableUses.empty()) {
for (unsigned i = 0, e = VariableUses.size(); i != e; ++i) { for (unsigned i = 0, e = VariableUses.size(); i != e; ++i) {
StringRef Var = VariableUses[i].first;
StringMap<StringRef>::const_iterator it = VariableTable.find(Var);
SmallString<256> Msg; SmallString<256> Msg;
raw_svector_ostream OS(Msg); raw_svector_ostream OS(Msg);
StringRef Var = VariableUses[i].first;
// Check for undefined variable references. if (Var[0] == '@') {
if (it == VariableTable.end()) { std::string Value;
OS << "uses undefined variable \""; if (EvaluateExpression(Var, Value)) {
OS.write_escaped(Var) << "\"";; OS << "with expression \"";
OS.write_escaped(Var) << "\" equal to \"";
OS.write_escaped(Value) << "\"";
} else {
OS << "uses incorrect expression \"";
OS.write_escaped(Var) << "\"";
}
} else { } else {
OS << "with variable \""; StringMap<StringRef>::const_iterator it = VariableTable.find(Var);
OS.write_escaped(Var) << "\" equal to \"";
OS.write_escaped(it->second) << "\""; // Check for undefined variable references.
if (it == VariableTable.end()) {
OS << "uses undefined variable \"";
OS.write_escaped(Var) << "\"";
} else {
OS << "with variable \"";
OS.write_escaped(Var) << "\" equal to \"";
OS.write_escaped(it->second) << "\"";
}
} }
SM.PrintMessage(SMLoc::getFromPointer(Buffer.data()), SourceMgr::DK_Note, SM.PrintMessage(SMLoc::getFromPointer(Buffer.data()), SourceMgr::DK_Note,
@ -518,14 +579,20 @@ static bool ReadCheckFile(SourceMgr &SM,
std::vector<std::pair<SMLoc, Pattern> > NotMatches; std::vector<std::pair<SMLoc, Pattern> > NotMatches;
unsigned LineNumber = 1;
while (1) { while (1) {
// See if Prefix occurs in the memory buffer. // See if Prefix occurs in the memory buffer.
Buffer = Buffer.substr(Buffer.find(CheckPrefix)); size_t PrefixLoc = Buffer.find(CheckPrefix);
// If we didn't find a match, we're done. // If we didn't find a match, we're done.
if (Buffer.empty()) if (PrefixLoc == StringRef::npos)
break; break;
// Recalculate line number.
LineNumber += Buffer.substr(0, PrefixLoc).count('\n');
Buffer = Buffer.substr(PrefixLoc);
const char *CheckPrefixStart = Buffer.data(); const char *CheckPrefixStart = Buffer.data();
// When we find a check prefix, keep track of whether we find CHECK: or // When we find a check prefix, keep track of whether we find CHECK: or
@ -560,7 +627,7 @@ static bool ReadCheckFile(SourceMgr &SM,
// Parse the pattern. // Parse the pattern.
Pattern P; Pattern P;
if (P.ParsePattern(Buffer.substr(0, EOL), SM)) if (P.ParsePattern(Buffer.substr(0, EOL), SM, LineNumber))
return true; return true;
Buffer = Buffer.substr(EOL); Buffer = Buffer.substr(EOL);