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 Expressions +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 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` support pattern expressions that 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+]]`` and ``[[#@LINE-]]`` numeric expressions in +patterns, with an arbitrary number of spaces between each elements of the +expression. These expressions expand to the 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-]]`` with no space inside the brackets and where ``offset`` +is an integer. + 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 FileCheckPatternSubstitution { +private: + /// Pointer to a class instance holding 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 FromStr; + + /// If this is a numeric expression substitution, this is the pointer to the + /// class representing that numeric expression. + FileCheckNumExpr *NumExpr = nullptr; + + // Index in RegExStr of where to do the substitution. + size_t InsertIdx; + +public: + /// Constructor for a pattern variable substitution. + FileCheckPatternSubstitution(FileCheckPatternContext *Context, + StringRef VarName, size_t InsertIdx) + : Context(Context), IsNumExpr(false), FromStr(VarName), + InsertIdx(InsertIdx) {} + + /// Constructor for a numeric expression substitution. + FileCheckPatternSubstitution(FileCheckPatternContext *Context, StringRef Expr, + FileCheckNumExpr *NumExpr, size_t InsertIdx) + : Context(Context), IsNumExpr(true), FromStr(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 getFromString() const { return FromStr; } + + /// Return the index where the substitution is to be performed. + size_t 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 getResult() 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 parsed numeric expressions. 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: + /// Make a new numeric expression instance and register it for destruction + /// when the context is destroyed. + template FileCheckNumExpr *makeNumExpr(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 FileCheckPatternSubstitution class to abstract + /// whether it is a pattern variable or a numeric expression. + std::vector Substitutions; + + /// 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,21 +262,22 @@ 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 printSubstitutions(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()); + return !(Substitutions.empty() && VariableDefs.empty()); } Check::FileCheckType getCheckTy() const { return CheckTy; } @@ -190,7 +288,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 FileCheckPatternSubstitution::getResult() 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(FromStr); + if (!VarVal) + return llvm::None; + return Regex::escape(*VarVal); + } +} + +StringRef FileCheckPatternSubstitution::getUndefVarName() const { + // Parsing guarantees only @LINE is ever referenced and it is not undefined + // by ClearLocalVars. + if (IsNumExpr) + return StringRef(); + + if (!Context->getPatternVarValue(FromStr)) + return FromStr; + + return StringRef(); +} + bool FileCheckPattern::isValidVarNameStart(char C) { return C == '_' || isalpha(C); } @@ -55,6 +80,10 @@ return false; } +// StringRef holding all characters considered as horizontal whitespaces by +// FileCheck input canonicalization. +StringRef SpaceChars = " \t"; + // Parsing helper function that strips the first character in S and returns it. static char popFront(StringRef &S) { char C = S.front(); @@ -62,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. + Trailer = Trailer.ltrim(SpaceChars); if (Trailer.empty()) - return false; + return Context->makeNumExpr(LineNumber); SMLoc OpLoc = SMLoc::getFromPointer(Trailer.data()); char Operator = popFront(Trailer); - switch (Operator) { - case '+': - case '-': - break; - default: - SM.PrintMessage(OpLoc, SourceMgr::DK_Error, - Twine("unsupported numeric operation '") + Twine(Operator) + - "'"); - return true; - } // Parse right operand. + Trailer = Trailer.ltrim(SpaceChars); 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; } + Trailer = Trailer.ltrim(SpaceChars); 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->makeNumExpr(Value); } /// Parses the given string into the Pattern. @@ -194,33 +232,45 @@ 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 and numeric expressions matches. Pattern expressions come in two + // forms: [[foo:.*]] and [[foo]]. The former matches .* (or some other + // regex) and assigns it to the FileCheck variable 'foo'. The latter + // substitutes foo's value. Numeric expressions start with a '#' sign after + // the double brackets and only have the substitution form. Both pattern + // and numeric variables must satisfy the regular expression + // "[a-zA-Z_][0-9a-zA-Z_]*" to be valid, as this helps catch some common + // errors. if (PatternStr.startswith("[[")) { - StringRef MatchStr = PatternStr.substr(2); + StringRef UnparsedPatternStr = PatternStr.substr(2); // 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); + size_t End = FindRegexVarEnd(UnparsedPatternStr, SM); + StringRef MatchStr = UnparsedPatternStr.substr(0, End); + bool IsNumExpr = MatchStr.consume_front("#"); + const char *RefTypeStr = + IsNumExpr ? "numeric expression" : "pattern variable"; 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, + Twine("Invalid ") + RefTypeStr + " reference, no ]] found"); return true; } - - MatchStr = MatchStr.substr(0, End); - PatternStr = PatternStr.substr(End + 4); + // Strip the subtitution we are parsing. End points to the start of the + // "]]" closing the expression so account for it in computing the index + // of the first unparsed character. + PatternStr = UnparsedPatternStr.substr(End + 2); 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) + MatchStr = MatchStr.ltrim(SpaceChars); + 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. @@ -228,10 +278,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; } + size_t SubstInsertIdx = RegExStr.size(); + FileCheckNumExpr *NumExpr; + StringRef Name = MatchStr.substr(0, TrailIdx); StringRef Trailer = MatchStr.substr(TrailIdx); bool IsVarDef = (VarEndIdx != StringRef::npos); @@ -239,30 +292,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. + FileCheckPatternSubstitution Substitution = + IsNumExpr ? FileCheckPatternSubstitution(Context, MatchStr, + NumExpr, SubstInsertIdx) + : FileCheckPatternSubstitution(Context, MatchStr, + SubstInsertIdx); + Substitutions.push_back(Substitution); } continue; } @@ -315,19 +377,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 @@ -335,8 +384,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) { @@ -356,30 +405,23 @@ // actual value. StringRef RegExToMatch = RegExStr; std::string TmpStr; - if (!VariableUses.empty()) { + if (!Substitutions.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); - } + size_t InsertOffset = 0; + // 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 &Substitution : Substitutions) { + // Substitute and check for failure (e.g. use of undefined variable). + llvm::Optional Value = Substitution.getResult(); + 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() + Substitution.getIndex() + InsertOffset, + Value->begin(), Value->end()); + InsertOffset += Value->size(); } // Match the newly constructed regex. @@ -394,7 +436,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] = @@ -429,33 +471,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::printSubstitutions(const SourceMgr &SM, StringRef Buffer, + SMRange MatchRange) const { + // Print what we know about substitutions. This covers both uses of pattern + // variables and numeric subsitutions. + if (!Substitutions.empty()) { + for (const auto &Substitution : Substitutions) { 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 = Substitution.isNumExpr(); + llvm::Optional MatchedValue = Substitution.getResult(); + + // Substitution failed or is not known at match time, print the undefined + // variable it uses. + if (!MatchedValue) { + StringRef UndefVarName = Substitution.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(Substitution.getFromString()) << "\" equal to \""; + OS.write_escaped(*MatchedValue) << "\""; } if (MatchRange.isValid()) @@ -534,7 +576,7 @@ } llvm::Optional -FileCheckPatternContext::getVarValue(StringRef VarName) { +FileCheckPatternContext::getPatternVarValue(StringRef VarName) { auto VarIter = GlobalVariableTable.find(VarName); if (VarIter == GlobalVariableTable.end()) return llvm::None; @@ -542,6 +584,12 @@ return VarIter->second; } +template +FileCheckNumExpr *FileCheckPatternContext::makeNumExpr(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 @@ -998,7 +1046,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.printSubstitutions(SM, Buffer, MatchRange); } static void PrintMatch(bool ExpectedMatch, const SourceMgr &SM, @@ -1049,7 +1097,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.printSubstitutions(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 @@ -1,4 +1,4 @@ -; RUN: FileCheck -input-file %s %s +; RUN: FileCheck -input-file %s %s ; RUN: not FileCheck -check-prefix BAD1 -input-file %s %s 2>&1 | FileCheck -check-prefix ERR1 %s ; RUN: not FileCheck -check-prefix BAD2 -input-file %s %s 2>&1 | FileCheck -check-prefix ERR2 %s ; RUN: not FileCheck -check-prefix BAD3 -input-file %s %s 2>&1 | FileCheck -check-prefix ERR3 %s @@ -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 @@ -105,7 +105,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; } }; @@ -146,6 +146,52 @@ EXPECT_TRUE(Tester.parseExpect(VarName, Trailer)); } +TEST_F(FileCheckTest, Substitution) { + SourceMgr SM; + FileCheckPatternContext Context; + std::vector GlobalDefines; + GlobalDefines.emplace_back(std::string("FOO=BAR")); + Context.defineCmdlineVariables(GlobalDefines, SM); + + FileCheckPatternSubstitution Substitution = + FileCheckPatternSubstitution(&Context, "VAR404", 42); + EXPECT_FALSE(Substitution.getResult()); + + FileCheckNumExpr NumExpr = FileCheckNumExpr(42); + Substitution = FileCheckPatternSubstitution(&Context, "@LINE", &NumExpr, 12); + llvm::Optional Value = Substitution.getResult(); + EXPECT_TRUE(Value); + EXPECT_EQ("42", *Value); + + FileCheckPattern P = FileCheckPattern(Check::CheckPlain, &Context); + Substitution = FileCheckPatternSubstitution(&Context, "FOO", 42); + Value = Substitution.getResult(); + 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); + + FileCheckPatternSubstitution Substitution = + FileCheckPatternSubstitution(&Context, "VAR404", 42); + StringRef UndefVar = Substitution.getUndefVarName(); + EXPECT_EQ("VAR404", UndefVar); + + FileCheckNumExpr NumExpr = FileCheckNumExpr(42); + Substitution = FileCheckPatternSubstitution(&Context, "@LINE", &NumExpr, 12); + UndefVar = Substitution.getUndefVarName(); + EXPECT_EQ("", UndefVar); + + Substitution = FileCheckPatternSubstitution(&Context, "FOO", 42); + UndefVar = Substitution.getUndefVarName(); + EXPECT_EQ("", UndefVar); +} + TEST_F(FileCheckTest, FileCheckContext) { FileCheckPatternContext Cxt = FileCheckPatternContext(); std::vector GlobalDefines; @@ -176,9 +222,9 @@ StringRef LocalVarStr = "LocalVar"; StringRef EmptyVarStr = "EmptyVar"; StringRef UnknownVarStr = "UnknownVar"; - llvm::Optional LocalVar = Cxt.getVarValue(LocalVarStr); - llvm::Optional EmptyVar = Cxt.getVarValue(EmptyVarStr); - llvm::Optional UnknownVar = Cxt.getVarValue(UnknownVarStr); + llvm::Optional LocalVar = Cxt.getPatternVarValue(LocalVarStr); + llvm::Optional EmptyVar = Cxt.getPatternVarValue(EmptyVarStr); + llvm::Optional UnknownVar = Cxt.getPatternVarValue(UnknownVarStr); EXPECT_TRUE(LocalVar); EXPECT_EQ(*LocalVar, "FOO"); EXPECT_TRUE(EmptyVar); @@ -187,9 +233,9 @@ // Clear local variables and check they become absent. Cxt.clearLocalVars(); - LocalVar = Cxt.getVarValue(LocalVarStr); + LocalVar = Cxt.getPatternVarValue(LocalVarStr); EXPECT_FALSE(LocalVar); - EmptyVar = Cxt.getVarValue(EmptyVarStr); + EmptyVar = Cxt.getPatternVarValue(EmptyVarStr); EXPECT_FALSE(EmptyVar); // Redefine global variables and check variables are defined again. @@ -197,13 +243,13 @@ GotError = Cxt.defineCmdlineVariables(GlobalDefines, SM); EXPECT_FALSE(GotError); 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); } } // namespace