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 @@ -102,8 +102,8 @@ .. option:: -D - Sets a filecheck variable ``VAR`` with value ``VALUE`` that can be used in - ``CHECK:`` lines. + Sets a filecheck pattern variable ``VAR`` with value ``VALUE`` that can be + used in ``CHECK:`` lines. .. option:: -version @@ -520,14 +520,14 @@ braces explicitly from the input, you can use something ugly like ``{{[{][{]}}`` as your pattern. -FileCheck Variables -~~~~~~~~~~~~~~~~~~~ +FileCheck Pattern Variables +~~~~~~~~~~~~~~~~~~~~~~~~~~~ It is often useful to match a pattern and then verify that it occurs again later in the file. For codegen tests, this can be useful to allow any register, but verify that that register is used consistently later. To do this, -:program:`FileCheck` allows named variables to be defined and substituted into -patterns. Here is a simple example: +:program:`FileCheck` allows pattern variables to be defined and substituted +into patterns. Here is a simple example: .. code-block:: llvm @@ -560,31 +560,38 @@ This makes it easier to ensure that individual tests are not affected by variables set in preceding tests. -FileCheck Expressions -~~~~~~~~~~~~~~~~~~~~~ +FileCheck Numeric Expressions +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Sometimes there's a need to verify output which refers line numbers of the +Sometimes there's a need to verify output which contains 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+]]``, ``[[@LINE-]]`` expressions in patterns. These -expressions expand to a number of the line where a pattern is located (with an -optional integer offset). +To support this case, FileCheck allows using ``[[#@LINE]]``, +``[[#@LINE+]]``, ``[[#@LINE-]]`` numeric expressions in +patterns, with arbitrary number of space between each elements of the +expression. 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: test.cpp:[[# @LINE + 4]]:6: error: expected ';' after top level declarator // CHECK-NEXT: {{^int a}} // CHECK-NEXT: {{^ \^}} // CHECK-NEXT: {{^ ;}} int a +To support legacy uses of ``@LINE`` as a special pattern variable, +:program:`FileCheck` also accepts the following uses of ``@LINE`` with pattern +variable syntax: ``[[@LINE]]``, ``[[@LINE+]]`` and +``[[@LINE-]]`` without any space where ``offset`` is an integer +immediate. + Matching Newline Characters ~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/llvm/include/llvm/Support/FileCheck.h b/llvm/include/llvm/Support/FileCheck.h --- a/llvm/include/llvm/Support/FileCheck.h +++ b/llvm/include/llvm/Support/FileCheck.h @@ -36,9 +36,87 @@ bool VerboseVerbose = false; }; +//===----------------------------------------------------------------------===// +// Numeric expression handling code. +//===----------------------------------------------------------------------===// + +/// Class representing a numeric expression. +class FileCheckNumExpr { +private: + /// Value of the numeric expression.. + uint64_t Value; + +public: + /// Constructor for a numeric expression with a known value at parse time, + /// e.g. the implicit numeric expression defining the @LINE numeric pseudo + /// variable. + explicit FileCheckNumExpr(uint64_t Value) : Value(Value) {} + + /// Return the value being matched against. + uint64_t getValue() const { return Value; } +}; + +class FileCheckPatternContext; + +/// Class representing a substitution to perform in the string to match. +class FileCheckPatternSubst { +private: + /// Pointer to a class instance holding among other thing the table with the + /// values of live pattern variables at the start of any given CHECK line. + /// Used for substituting pattern variables (numeric variables have their + /// value in the FileCheckNumExpr class instance pointed to by NumExpr). + FileCheckPatternContext *Context; + + /// Whether this represents a numeric expression substitution. + bool IsNumExpr; + + /// The string that needs to be substituted for something else. For a + /// pattern variable this is its name, otherwise this is the whole numeric + /// expression. + StringRef SubstStr; + + /// If this is a numeric expression substitution, this is the pointer to the + /// class representing that numeric expression. + FileCheckNumExpr *NumExpr; + + // Index in RegExStr of where to do the substitution. + unsigned InsertIdx; + +public: + /// Constructor for a pattern variable substitution. + FileCheckPatternSubst(FileCheckPatternContext *Context, StringRef VarName, + unsigned InsertIdx) + : Context(Context), IsNumExpr(false), SubstStr(VarName), + InsertIdx(InsertIdx) {} + + /// Constructor for a numeric expression substitution. + FileCheckPatternSubst(FileCheckPatternContext *Context, StringRef Expr, + FileCheckNumExpr *NumExpr, unsigned InsertIdx) + : Context(Context), IsNumExpr(true), SubstStr(Expr), NumExpr(NumExpr), + InsertIdx(InsertIdx) {} + + /// Return whether this is a numeric expression substitution. + bool isNumExpr() const { return IsNumExpr; } + + /// Return the string to be substituted. + StringRef getSubstString() const { return SubstStr; } + + /// Return the index where the substitution is to be performed. + unsigned getIndex() const { return InsertIdx; } + + /// Return the result of the substitution represented by this class instance + /// or nothing if substitution failed. For a numeric expression we substitute + /// it by its value. For a pattern variable we simply replace it by the text + /// its definition matched. + llvm::Optional getSubstitute() const; + + /// Return the name of the undefined variable used in this substitution if + /// any or an empty string otherwise. + StringRef getUndefVarName() const; +}; //===----------------------------------------------------------------------===// -// Pattern Handling Code. +// Pattern handling code. //===----------------------------------------------------------------------===// namespace Check { @@ -91,25 +169,35 @@ private: /// When matching a given pattern, this holds the value of all the FileCheck - /// variables defined in previous patterns. In a pattern only the last - /// definition for a given variable is recorded in this table, back-references - /// are used for uses after any the other definition. + /// pattern variables defined in previous patterns. In a pattern, only the + /// last definition for a given variable is recorded in this table, + /// back-references are used for uses after any the other definition. StringMap GlobalVariableTable; + /// Vector holding pointers to all numeric expressions parsed. Used to + /// automatically free the numeric expressions once they are guaranteed to no + /// longer be used. + std::vector> NumExprs; + public: - /// Return the value of variable \p VarName or None if no such variable has - /// been defined. - llvm::Optional getVarValue(StringRef VarName); + /// Return the value of pattern variable \p VarName or None if no such + /// variable has been defined. + llvm::Optional getPatternVarValue(StringRef VarName); - /// Define variables from definitions given on the command line passed as a - /// vector of VAR=VAL strings in \p CmdlineDefines. Report any error to \p SM - /// and return whether an error occured. + /// Define pattern variables from definitions given on the command line + /// passed as a vector of VAR=VAL strings in \p CmdlineDefines. Report any + /// error to \p SM and return whether an error occured. bool defineCmdlineVariables(std::vector &CmdlineDefines, SourceMgr &SM); /// Undefine local variables (variables whose name does not start with a '$' /// sign), i.e. remove them from GlobalVariableTable. void clearLocalVars(); + +private: + /// Register a numeric expression for destruction when the context is + /// destroyed. + template FileCheckNumExpr *registerNumExpr(Types... Args); }; class FileCheckPattern { @@ -123,16 +211,25 @@ /// a fixed string to match. std::string RegExStr; - /// 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 "foobaz" and - /// we'll get an entry in this vector that tells us to insert the value of - /// bar at offset 3. - std::vector> VariableUses; - - /// Maps definitions of variables to their parenthesized capture numbers. + /// Entries in this vector represent uses of a pattern variable or a numeric + /// expression in the pattern that need to be substituted in the regexp + /// pattern at match time, e.g. "foo[[bar]]baz[[#@LINE+1]]". In this case, + /// the RegExStr will contain "foobaz" and we'll get two entries in this + /// vector that tells us to insert the value of pattern variable "bar" at + /// offset 3 and the value of numeric expression "@LINE+1" at offset 6. Uses + /// are represented by a FileCheckPatternSubst class to abstract whether it + /// is a pattern variable or a numeric expression. + std::vector Substs; + + /// Maps names of pattern variables defined in a pattern to the parenthesized + /// capture numbers of their last definition. + /// + /// E.g. for the pattern "foo[[bar:.*]]baz[[bar]]quux[[bar:.*]]", + /// VariableDefs will map "bar" to 2 corresponding to the second definition + /// of "bar". /// - /// E.g. for the pattern "foo[[bar:.*]]baz", VariableDefs will map "bar" to - /// 1. + /// Note: uses std::map rather than StringMap to be able to get the key when + /// iterating over values. std::map VariableDefs; /// Pointer to the class instance shared by all patterns holding a table with @@ -165,22 +262,21 @@ static bool parseVariable(StringRef Str, bool &IsPseudo, unsigned &TrailIdx); /// Parse a numeric expression involving pseudo variable \p Name with the /// string corresponding to the operation being performed in \p Trailer. - /// Return whether parsing failed in which case errors are reported on \p SM. - bool parseExpression(StringRef Name, StringRef Trailer, - const SourceMgr &SM) const; + /// Return the class representing the numeric expression or nullptr if + /// parsing fails in which case errors are reported on \p SM. + FileCheckNumExpr *parseNumericExpression(StringRef Name, StringRef Trailer, + const SourceMgr &SM) const; bool ParsePattern(StringRef PatternStr, StringRef Prefix, SourceMgr &SM, unsigned LineNumber, const FileCheckRequest &Req); size_t match(StringRef Buffer, size_t &MatchLen) const; - /// Print value of successful substitutions or name of undefined pattern - /// variables preventing such a successful substitution. - void printVariableUses(const SourceMgr &SM, StringRef Buffer, - SMRange MatchRange = None) const; + /// Print value of successful substitutions or name of undefined pattern or + /// numeric variables preventing such a successful substitution. + void printSubsts(const SourceMgr &SM, StringRef Buffer, + SMRange MatchRange = None) const; void printFuzzyMatch(const SourceMgr &SM, StringRef Buffer, std::vector *Diags) const; - bool hasVariable() const { - return !(VariableUses.empty() && VariableDefs.empty()); - } + bool hasVariable() const { return !(Substs.empty() && VariableDefs.empty()); } Check::FileCheckType getCheckTy() const { return CheckTy; } @@ -190,7 +286,6 @@ bool AddRegExToRegEx(StringRef RS, unsigned &CurParen, SourceMgr &SM); void AddBackrefToRegEx(unsigned BackrefNum); unsigned computeMatchDistance(StringRef Buffer) const; - void evaluateExpression(StringRef Expr, std::string &Value) const; size_t FindRegexVarEnd(StringRef Str, SourceMgr &SM); }; diff --git a/llvm/lib/Support/FileCheck.cpp b/llvm/lib/Support/FileCheck.cpp --- a/llvm/lib/Support/FileCheck.cpp +++ b/llvm/lib/Support/FileCheck.cpp @@ -24,6 +24,31 @@ using namespace llvm; +llvm::Optional FileCheckPatternSubst::getSubstitute() const { + if (IsNumExpr) { + return utostr(NumExpr->getValue()); + } else { + // Look up the value and escape it so that we can put it into the + // regex. + llvm::Optional VarVal = Context->getPatternVarValue(SubstStr); + if (!VarVal) + return llvm::None; + return Regex::escape(*VarVal); + } +} + +StringRef FileCheckPatternSubst::getUndefVarName() const { + // Parsing guarantees only @LINE is ever referenced and it is not undefined + // by ClearLocalVars. + if (IsNumExpr) + return StringRef(); + + if (!Context->getPatternVarValue(SubstStr)) + return SubstStr; + + return StringRef(); +} + bool FileCheckPattern::isValidVarNameStart(char C) { return C == '_' || isalpha(C); } @@ -56,6 +81,9 @@ return false; } +// Parsing helper function that strips all leading whitespace from S. +static inline void skipWhitespace(StringRef &S) { S = S.ltrim(" \t"); } + // Parsing helper function that strips the first character in S and returns it. static char next(StringRef &S) { char C = S.front(); @@ -63,49 +91,58 @@ return C; } -bool FileCheckPattern::parseExpression(StringRef Name, StringRef Trailer, - const SourceMgr &SM) const { +FileCheckNumExpr * +FileCheckPattern::parseNumericExpression(StringRef Name, StringRef Trailer, + const SourceMgr &SM) const { if (!Name.equals("@LINE")) { SM.PrintMessage(SMLoc::getFromPointer(Name.data()), SourceMgr::DK_Error, - "invalid pseudo variable '" + Name + "'"); - return true; + "invalid pseudo numeric variable '" + Name + "'"); + return nullptr; } // Check if this is a supported operation and select function to perform it. + skipWhitespace(Trailer); if (Trailer.empty()) - return false; + return Context->registerNumExpr(LineNumber); SMLoc OpLoc = SMLoc::getFromPointer(Trailer.data()); char Operator = next(Trailer); - switch (Operator) { - case '+': - case '-': - break; - default: - SM.PrintMessage(OpLoc, SourceMgr::DK_Error, - Twine("unsupported numeric operation '") + Twine(Operator) + - "'"); - return true; - } // Parse right operand. + skipWhitespace(Trailer); if (Trailer.empty()) { SM.PrintMessage(SMLoc::getFromPointer(Trailer.data()), SourceMgr::DK_Error, "missing operand in numeric expression '" + Trailer + "'"); - return true; + return nullptr; } uint64_t Offset; if (Trailer.consumeInteger(10, Offset)) { SM.PrintMessage(SMLoc::getFromPointer(Trailer.data()), SourceMgr::DK_Error, "invalid offset in numeric expression '" + Trailer + "'"); - return true; + return nullptr; } + skipWhitespace(Trailer); if (!Trailer.empty()) { SM.PrintMessage(SMLoc::getFromPointer(Trailer.data()), SourceMgr::DK_Error, "unexpected characters at end of numeric expression '" + Trailer + "'"); - return true; + return nullptr; } - return false; + + uint64_t Value; + switch (Operator) { + case '+': + Value = LineNumber + Offset; + break; + case '-': + Value = LineNumber - Offset; + break; + default: + SM.PrintMessage(OpLoc, SourceMgr::DK_Error, + Twine("unsupported numeric operation '") + Twine(Operator) + + "'"); + return nullptr; + } + return Context->registerNumExpr(Value); } /// Parses the given string into the Pattern. @@ -195,33 +232,43 @@ continue; } - // Named RegEx matches. These are of two forms: [[foo:.*]] which matches .* - // (or some other regex) and assigns it to the FileCheck variable 'foo'. The - // second form is [[foo]] which is a reference to foo. The variable name - // itself must be of the form "[a-zA-Z_][0-9a-zA-Z_]*", otherwise we reject - // it. This is to catch some common errors. + // Pattern variable and numeric expressions matches. Pattern variables + // come in two forms: [[foo:.*]] which matches .* (or some other regex) and + // assigns it to the FileCheck variable 'foo'. The second form is [[foo]] + // which is a substitution of foo's value. Variable names themselves must + // be of the form "[a-zA-Z_][0-9a-zA-Z_]*", otherwise we reject it. This is + // to catch some common errors. Numeric expressions only have the + // substitution mode. Numeric variable names have the same restriction as + // their pattern variable counterpart. if (PatternStr.startswith("[[")) { StringRef MatchStr = PatternStr.substr(2); + bool IsNumExpr = MatchStr.consume_front("#"); + const char *RefTypeStr = + IsNumExpr ? "numeric expression" : "pattern variable"; // Find the closing bracket pair ending the match. End is going to be an // offset relative to the beginning of the match string. size_t End = FindRegexVarEnd(MatchStr, SM); if (End == StringRef::npos) { - SM.PrintMessage(SMLoc::getFromPointer(PatternStr.data()), - SourceMgr::DK_Error, - "invalid named regex reference, no ]] found"); + SM.PrintMessage( + SMLoc::getFromPointer(PatternStr.data()), SourceMgr::DK_Error, + std::string("Invalid ") + RefTypeStr + " reference, no ]] found"); return true; } MatchStr = MatchStr.substr(0, End); - PatternStr = PatternStr.substr(End + 4); + PatternStr = PatternStr.substr(End + 4 + (int)IsNumExpr); size_t VarEndIdx = MatchStr.find(":"); - size_t SpacePos = MatchStr.substr(0, VarEndIdx).find_first_of(" \t"); - if (SpacePos != StringRef::npos) { - SM.PrintMessage(SMLoc::getFromPointer(MatchStr.data() + SpacePos), - SourceMgr::DK_Error, "unexpected whitespace"); - return true; + if (IsNumExpr) + skipWhitespace(MatchStr); + else { + size_t SpacePos = MatchStr.substr(0, VarEndIdx).find_first_of(" \t"); + if (SpacePos != StringRef::npos) { + SM.PrintMessage(SMLoc::getFromPointer(MatchStr.data() + SpacePos), + SourceMgr::DK_Error, "unexpected whitespace"); + return true; + } } // Get the regex name (e.g. "foo") and verify it is well formed. @@ -229,10 +276,13 @@ unsigned TrailIdx; if (parseVariable(MatchStr, IsPseudo, TrailIdx)) { SM.PrintMessage(SMLoc::getFromPointer(MatchStr.data()), - SourceMgr::DK_Error, "invalid name in named regex"); + SourceMgr::DK_Error, "invalid variable name"); return true; } + unsigned SubstInsertIdx = RegExStr.size(); + FileCheckNumExpr *NumExpr; + StringRef Name = MatchStr.substr(0, TrailIdx); StringRef Trailer = MatchStr.substr(TrailIdx); bool IsVarDef = (VarEndIdx != StringRef::npos); @@ -240,30 +290,39 @@ if (IsVarDef && (IsPseudo || !Trailer.consume_front(":"))) { SM.PrintMessage(SMLoc::getFromPointer(MatchStr.data()), SourceMgr::DK_Error, - "invalid name in named regex definition"); + "invalid name in pattern variable definition"); return true; } if (!IsVarDef && IsPseudo) { - if (parseExpression(Name, Trailer, SM)) + NumExpr = parseNumericExpression(Name, Trailer, SM); + if (NumExpr == nullptr) return true; + IsNumExpr = true; } // Handle [[foo]]. if (!IsVarDef) { - // Handle variables that were defined earlier on the same line by - // emitting a backreference. - if (VariableDefs.find(Name) != VariableDefs.end()) { - unsigned VarParenNum = VariableDefs[Name]; - if (VarParenNum < 1 || VarParenNum > 9) { + // Handle use of pattern variables that were defined earlier on the + // same line by emitting a backreference. + if (!IsNumExpr && VariableDefs.find(Name) != VariableDefs.end()) { + unsigned CaptureParen = VariableDefs[Name]; + if (CaptureParen < 1 || CaptureParen > 9) { SM.PrintMessage(SMLoc::getFromPointer(Name.data()), SourceMgr::DK_Error, "Can't back-reference more than 9 variables"); return true; } - AddBackrefToRegEx(VarParenNum); + AddBackrefToRegEx(CaptureParen); } else { - VariableUses.push_back(std::make_pair(MatchStr, RegExStr.size())); + // Handle use of pattern variables ([[]]) defined in previous + // CHECK pattern or use of a numeric expression. + FileCheckPatternSubst Subst = + IsNumExpr + ? FileCheckPatternSubst(Context, MatchStr, NumExpr, + SubstInsertIdx) + : FileCheckPatternSubst(Context, MatchStr, SubstInsertIdx); + Substs.push_back(Subst); } continue; } @@ -316,19 +375,6 @@ RegExStr += Backref; } -/// Evaluates expression and stores the result to \p Value. -void FileCheckPattern::evaluateExpression(StringRef Expr, - std::string &Value) const { - Expr = Expr.substr(StringRef("@LINE").size()); - int Offset = 0; - if (!Expr.empty()) { - if (Expr[0] == '+') - Expr = Expr.substr(1); - Expr.getAsInteger(10, Offset); - } - Value = llvm::itostr(LineNumber + Offset); -} - /// Matches the pattern string against the input buffer \p Buffer /// /// This returns the position that is matched or npos if there is no match. If @@ -336,8 +382,8 @@ /// MatchLen. /// /// The GlobalVariableTable StringMap in the FileCheckPatternContext class -/// instance provides the current values of FileCheck variables and is updated -/// if this match defines new values. +/// instance provides the current values of FileCheck pattern variables and is +/// updated if this match defines new values. size_t FileCheckPattern::match(StringRef Buffer, size_t &MatchLen) const { // If this is the EOF pattern, match it immediately. if (CheckTy == Check::CheckEOF) { @@ -357,30 +403,23 @@ // actual value. StringRef RegExToMatch = RegExStr; std::string TmpStr; - if (!VariableUses.empty()) { + if (!Substs.empty()) { TmpStr = RegExStr; unsigned InsertOffset = 0; - for (const auto &VariableUse : VariableUses) { - std::string Value; - - if (VariableUse.first[0] == '@') { - evaluateExpression(VariableUse.first, Value); - } else { - llvm::Optional ValueRef = - Context->getVarValue(VariableUse.first); - // If the variable is undefined, return an error. - if (!ValueRef) - return StringRef::npos; - - // Look up the value and escape it so that we can put it into the regex. - Value += Regex::escape(*ValueRef); - } + // Substitute all pattern variables and numeric expressions whose value is + // known just now. Use of pattern variables defined on the same line are + // handled by back-references. + for (const auto &Subst : Substs) { + // Substitute and check for failure (e.g. use of undefined variable). + llvm::Optional Value = Subst.getSubstitute(); + if (!Value) + return StringRef::npos; // Plop it into the regex at the adjusted offset. - TmpStr.insert(TmpStr.begin() + VariableUse.second + InsertOffset, - Value.begin(), Value.end()); - InsertOffset += Value.size(); + TmpStr.insert(TmpStr.begin() + Subst.getIndex() + InsertOffset, + Value->begin(), Value->end()); + InsertOffset += Value->size(); } // Match the newly constructed regex. @@ -395,7 +434,7 @@ assert(!MatchInfo.empty() && "Didn't get any match"); StringRef FullMatch = MatchInfo[0]; - // If this defines any variables, remember their values. + // If this defines any pattern variables, remember their values. for (const auto &VariableDef : VariableDefs) { assert(VariableDef.second < MatchInfo.size() && "Internal paren error"); Context->GlobalVariableTable[VariableDef.first] = @@ -430,33 +469,33 @@ return BufferPrefix.edit_distance(ExampleString); } -void FileCheckPattern::printVariableUses(const SourceMgr &SM, StringRef Buffer, - SMRange MatchRange) const { - // If this was a regular expression using variables, print the current - // variable values. - if (!VariableUses.empty()) { - for (const auto &VariableUse : VariableUses) { +void FileCheckPattern::printSubsts(const SourceMgr &SM, StringRef Buffer, + SMRange MatchRange) const { + // Print info what we know about substitutions. This covers both uses of + // pattern variables and numeric subsitutions. + if (!Substs.empty()) { + for (const auto &Subst : Substs) { SmallString<256> Msg; raw_svector_ostream OS(Msg); - StringRef Var = VariableUse.first; - if (Var[0] == '@') { - std::string Value; - evaluateExpression(Var, Value); - OS << "with expression \""; - OS.write_escaped(Var) << "\" equal to \""; - OS.write_escaped(Value) << "\""; + bool IsNumExpr = Subst.isNumExpr(); + llvm::Optional MatchedValue = Subst.getSubstitute(); + + // Substitution failed or is not known at match time, print undefined + // variable it uses. + if (!MatchedValue) { + StringRef UndefVarName = Subst.getUndefVarName(); + if (UndefVarName.empty()) + continue; + OS << "uses undefined variable \""; + OS.write_escaped(UndefVarName) << "\""; } else { - llvm::Optional VarValue = Context->getVarValue(Var); - - // Check for undefined variable references. - if (!VarValue) { - OS << "uses undefined variable \""; - OS.write_escaped(Var) << "\""; - } else { + // Substitution succeeded. Print substituted value. + if (IsNumExpr) + OS << "with numeric expression \""; + else OS << "with variable \""; - OS.write_escaped(Var) << "\" equal to \""; - OS.write_escaped(*VarValue) << "\""; - } + OS.write_escaped(Subst.getSubstString()) << "\" equal to \""; + OS.write_escaped(*MatchedValue) << "\""; } if (MatchRange.isValid()) @@ -535,7 +574,7 @@ } llvm::Optional -FileCheckPatternContext::getVarValue(StringRef VarName) { +FileCheckPatternContext::getPatternVarValue(StringRef VarName) { auto VarIter = GlobalVariableTable.find(VarName); if (VarIter == GlobalVariableTable.end()) return llvm::None; @@ -543,6 +582,12 @@ return VarIter->second; } +template +FileCheckNumExpr *FileCheckPatternContext::registerNumExpr(Types... Args) { + NumExprs.emplace_back(new FileCheckNumExpr(Args...)); + return NumExprs.back().get(); +} + /// Finds the closing sequence of a regex variable usage or definition. /// /// \p Str has to point in the beginning of the definition (right after the @@ -999,7 +1044,7 @@ Loc, ExpectedMatch ? SourceMgr::DK_Remark : SourceMgr::DK_Error, Message); SM.PrintMessage(MatchRange.Start, SourceMgr::DK_Note, "found here", {MatchRange}); - Pat.printVariableUses(SM, Buffer, MatchRange); + Pat.printSubsts(SM, Buffer, MatchRange); } static void PrintMatch(bool ExpectedMatch, const SourceMgr &SM, @@ -1050,7 +1095,7 @@ SM.PrintMessage(SearchRange.Start, SourceMgr::DK_Note, "scanning from here"); // Allow the pattern to print additional information if desired. - Pat.printVariableUses(SM, Buffer); + Pat.printSubsts(SM, Buffer); if (ExpectedMatch) Pat.printFuzzyMatch(SM, Buffer, Diags); diff --git a/llvm/test/FileCheck/line-count.txt b/llvm/test/FileCheck/line-count.txt --- a/llvm/test/FileCheck/line-count.txt +++ b/llvm/test/FileCheck/line-count.txt @@ -23,34 +23,44 @@ 23 arst CHECK: [[@LINE]] {{a}}rst 24 25 BAD1: [[@LINE:cant-have-regex]] -26 ERR1: line-count.txt:[[@LINE-1]]:12: error: invalid name in named regex definition +26 ERR1: line-count.txt:[[#@LINE-1]]:12: error: invalid name in pattern variable definition 27 28 BAD2: [[ @LINE]] -29 ERR2: line-count.txt:[[@LINE-1]]:12: error: unexpected whitespace +29 ERR2: line-count.txt:[[#@LINE-1]]:12: error: unexpected whitespace 30 31 BAD3: [[@LINE ]] -32 ERR3: line-count.txt:[[@LINE-1]]:17: error: unexpected whitespace +32 ERR3: line-count.txt:[[#@LINE-1]]:17: error: unexpected whitespace 33 34 BAD4: [[ @LINE-1]] -35 ERR4: line-count.txt:[[@LINE-1]]:12: error: unexpected whitespace +35 ERR4: line-count.txt:[[#@LINE-1]]:12: error: unexpected whitespace 36 37 BAD5: [[@LINE -1]] -38 ERR5: line-count.txt:[[@LINE-1]]:17: error: unexpected whitespace +38 ERR5: line-count.txt:[[#@LINE-1]]:17: error: unexpected whitespace 39 40 BAD6: [[@LINE- 1]] -41 ERR6: line-count.txt:[[@LINE-1]]:18: error: unexpected whitespace +41 ERR6: line-count.txt:[[#@LINE-1]]:18: error: unexpected whitespace 42 43 BAD7: [[@LINE-1 ]] -44 ERR7: line-count.txt:[[@LINE-1]]:19: error: unexpected whitespace +44 ERR7: line-count.txt:[[#@LINE-1]]:19: error: unexpected whitespace 45 46 BAD8: [[@LIN]] -47 ERR8: line-count.txt:[[@LINE-1]]:12: error: invalid pseudo variable '@LIN' +47 ERR8: line-count.txt:[[#@LINE-1]]:12: error: invalid pseudo numeric variable '@LIN' 48 49 BAD9: [[@LINE*2]] -50 ERR9: line-count.txt:[[@LINE-1]]:17: error: unsupported numeric operation '*' +50 ERR9: line-count.txt:[[#@LINE-1]]:17: error: unsupported numeric operation '*' 51 52 BAD10: [[@LINE-x]] -53 ERR10: line-count.txt:[[@LINE-1]]:19: error: invalid offset in numeric expression 'x' +53 ERR10: line-count.txt:[[#@LINE-1]]:19: error: invalid offset in numeric expression 'x' 54 55 BAD11: [[@LINE-1x]] -56 ERR11: line-count.txt:[[@LINE-1]]:19: error: unexpected characters at end of numeric expression 'x' +56 ERR11: line-count.txt:[[#@LINE-1]]:19: error: unexpected characters at end of numeric expression 'x' +57 +58 CHECK: [[#@LINE]] CHECK +59 CHECK: [[# @LINE]] CHECK +60 CHECK: [[# @LINE ]] CHECK +61 +62 CHECK: [[#@LINE-1]] +63 CHECK: [[# @LINE-1]] CHECK +64 CHECK: [[# @LINE -1]] CHECK +65 CHECK: [[# @LINE - 1]] CHECK +66 CHECK: [[# @LINE - 1 ]] CHECK diff --git a/llvm/unittests/Support/FileCheckTest.cpp b/llvm/unittests/Support/FileCheckTest.cpp --- a/llvm/unittests/Support/FileCheckTest.cpp +++ b/llvm/unittests/Support/FileCheckTest.cpp @@ -26,28 +26,28 @@ // Check defined variables are present and undefined is absent. StringRef LocalVarStr = "LocalVar"; StringRef UnknownVarStr = "UnknownVar"; - llvm::Optional LocalVar = Cxt.getVarValue(LocalVarStr); - llvm::Optional UnknownVar = Cxt.getVarValue(UnknownVarStr); + llvm::Optional LocalVar = Cxt.getPatternVarValue(LocalVarStr); + llvm::Optional UnknownVar = Cxt.getPatternVarValue(UnknownVarStr); EXPECT_TRUE(LocalVar); EXPECT_EQ(*LocalVar, "FOO"); EXPECT_FALSE(UnknownVar); // Clear local variables and check they become absent. Cxt.clearLocalVars(); - LocalVar = Cxt.getVarValue(LocalVarStr); + LocalVar = Cxt.getPatternVarValue(LocalVarStr); EXPECT_FALSE(LocalVar); // Redefine global variables and check variables are defined again. GlobalDefines.emplace_back(std::string("$GlobalVar=BAR")); Cxt.defineCmdlineVariables(GlobalDefines, SM); StringRef GlobalVarStr = "$GlobalVar"; - llvm::Optional GlobalVar = Cxt.getVarValue(GlobalVarStr); + llvm::Optional GlobalVar = Cxt.getPatternVarValue(GlobalVarStr); EXPECT_TRUE(GlobalVar); EXPECT_EQ(*GlobalVar, "BAR"); // Clear local variables and check global variables remain defined. Cxt.clearLocalVars(); - GlobalVar = Cxt.getVarValue(GlobalVarStr); + GlobalVar = Cxt.getPatternVarValue(GlobalVarStr); EXPECT_TRUE(GlobalVar); } @@ -142,7 +142,7 @@ SM.AddNewSourceBuffer(std::move(Buffer), SMLoc()); StringRef VarNameRef = NameTrailerRef.substr(0, VarName.size()); StringRef TrailerRef = NameTrailerRef.substr(VarName.size()); - return P.parseExpression(VarNameRef, TrailerRef, SM); + return P.parseNumericExpression(VarNameRef, TrailerRef, SM) == nullptr; } }; @@ -182,4 +182,49 @@ Trailer = "+5x"; EXPECT_TRUE(Tester.parse_expect(VarName, Trailer)); } + +TEST_F(FileCheckTest, Substitute) { + SourceMgr SM; + FileCheckPatternContext Context; + std::vector GlobalDefines; + GlobalDefines.emplace_back(std::string("FOO=BAR")); + Context.defineCmdlineVariables(GlobalDefines, SM); + + FileCheckPatternSubst Subst = FileCheckPatternSubst(&Context, "VAR404", 42); + EXPECT_FALSE(Subst.getSubstitute()); + + FileCheckNumExpr NumExpr = FileCheckNumExpr(42); + Subst = FileCheckPatternSubst(&Context, "@LINE", &NumExpr, 12); + llvm::Optional Value = Subst.getSubstitute(); + EXPECT_TRUE(Value); + EXPECT_EQ("42", *Value); + + FileCheckPattern P = FileCheckPattern(Check::CheckPlain, &Context); + Subst = FileCheckPatternSubst(&Context, "FOO", 42); + Value = Subst.getSubstitute(); + EXPECT_TRUE(Value); + EXPECT_EQ("BAR", *Value); +} + +TEST_F(FileCheckTest, UndefVars) { + SourceMgr SM; + FileCheckPatternContext Context; + std::vector GlobalDefines; + GlobalDefines.emplace_back(std::string("FOO=BAR")); + Context.defineCmdlineVariables(GlobalDefines, SM); + + FileCheckPatternSubst Subst = FileCheckPatternSubst(&Context, "VAR404", 42); + StringRef UndefVar = Subst.getUndefVarName(); + EXPECT_EQ("VAR404", UndefVar); + + FileCheckNumExpr NumExpr = FileCheckNumExpr(42); + Subst = FileCheckPatternSubst(&Context, "@LINE", &NumExpr, 12); + UndefVar = Subst.getUndefVarName(); + EXPECT_EQ("", UndefVar); + + Subst = FileCheckPatternSubst(&Context, "FOO", 42); + UndefVar = Subst.getUndefVarName(); + EXPECT_EQ("", UndefVar); +} + } // namespace