[llvm] r359741 - FileCheck [4/12]: Introduce @LINE numeric expressions

Thomas Preud'homme via llvm-commits llvm-commits at lists.llvm.org
Wed May 1 17:04:38 PDT 2019


Author: thopre
Date: Wed May  1 17:04:38 2019
New Revision: 359741

URL: http://llvm.org/viewvc/llvm-project?rev=359741&view=rev
Log:
FileCheck [4/12]: Introduce @LINE numeric expressions

Summary:
This patch is part of a patch series to add support for FileCheck
numeric expressions. This specific patch introduces the @LINE numeric
expressions.

This commit introduces a new syntax to express a relation a numeric
value in the input text must have with the line number of a given CHECK
pattern: [[#<@LINE numeric expression>]]. Further commits build on that
to express relations between several numeric values in the input text.
To help with naming, regular variables are renamed into pattern
variables and old @LINE expression syntax is referred to as legacy
numeric expression.

Compared to existing @LINE expressions, this new syntax allow arbitrary
spacing between the component of the expression. It offers otherwise the
same functionality but the commit serves to introduce some of the data
structure needed to support more general numeric expressions.

Copyright:
    - Linaro (changes up to diff 183612 of revision D55940)
    - GraphCore (changes in later versions of revision D55940 and
                 in new revision created off D55940)

Reviewers: jhenderson, chandlerc, jdenny, probinson, grimar, arichardson, rnk

Subscribers: hiraditya, llvm-commits, probinson, dblaikie, grimar, arichardson, tra, rnk, kristina, hfinkel, rogfer01, JonChesterfield

Tags: #llvm

Differential Revision: https://reviews.llvm.org/D60384

Modified:
    llvm/trunk/docs/CommandGuide/FileCheck.rst
    llvm/trunk/include/llvm/Support/FileCheck.h
    llvm/trunk/lib/Support/FileCheck.cpp
    llvm/trunk/test/FileCheck/line-count.txt
    llvm/trunk/unittests/Support/FileCheckTest.cpp

Modified: llvm/trunk/docs/CommandGuide/FileCheck.rst
URL: http://llvm.org/viewvc/llvm-project/llvm/trunk/docs/CommandGuide/FileCheck.rst?rev=359741&r1=359740&r2=359741&view=diff
==============================================================================
--- llvm/trunk/docs/CommandGuide/FileCheck.rst (original)
+++ llvm/trunk/docs/CommandGuide/FileCheck.rst Wed May  1 17:04:38 2019
@@ -102,8 +102,8 @@ and from the command line.
 
 .. option:: -D<VAR=VALUE>
 
-  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 like you would in C.  In the rare
 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` supports pattern expressions that allow pattern variables
+to be defined and substituted into patterns.  Here is a simple example:
 
 .. code-block:: llvm
 
@@ -560,31 +560,38 @@ CHECK-LABEL block. Global variables are
 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 that 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+<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).
+To support this case, FileCheck allows using ``[[#@LINE]]``,
+``[[#@LINE+<offset>]]`` and ``[[#@LINE-<offset>]]`` numeric expressions in
+patterns, with an arbitrary number of spaces between each element 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+<offset>]]`` and
+``[[@LINE-<offset>]]`` without any spaces inside the brackets and where
+``offset`` is an integer.
+
 Matching Newline Characters
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~
 

Modified: llvm/trunk/include/llvm/Support/FileCheck.h
URL: http://llvm.org/viewvc/llvm-project/llvm/trunk/include/llvm/Support/FileCheck.h?rev=359741&r1=359740&r2=359741&view=diff
==============================================================================
--- llvm/trunk/include/llvm/Support/FileCheck.h (original)
+++ llvm/trunk/include/llvm/Support/FileCheck.h Wed May  1 17:04:38 2019
@@ -36,9 +36,87 @@ struct FileCheckRequest {
   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 None 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<std::string> 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 @@ class FileCheckPatternContext {
 
 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<StringRef> 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<std::unique_ptr<FileCheckNumExpr>> NumExprs;
+
 public:
-  /// Return the value of variable \p VarName or None if no such variable has
-  /// been defined.
-  llvm::Optional<StringRef> getVarValue(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.
+  /// Return the value of pattern variable \p VarName or None if no such
+  /// variable has been defined.
+  llvm::Optional<StringRef> getPatternVarValue(StringRef VarName);
+
+  /// 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<std::string> &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 <class... Types> FileCheckNumExpr *makeNumExpr(Types... Args);
 };
 
 class FileCheckPattern {
@@ -123,16 +211,25 @@ class FileCheckPattern {
   /// 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<std::pair<StringRef, unsigned>> VariableUses;
+  /// 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<FileCheckPatternSubstitution> Substitutions;
 
-  /// Maps definitions of variables to their parenthesized capture numbers.
+  /// 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<StringRef, unsigned> VariableDefs;
 
   /// Pointer to the class instance shared by all patterns holding a table with
@@ -165,21 +262,22 @@ public:
   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<FileCheckDiag> *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 @@ private:
   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);
 };
 

Modified: llvm/trunk/lib/Support/FileCheck.cpp
URL: http://llvm.org/viewvc/llvm-project/llvm/trunk/lib/Support/FileCheck.cpp?rev=359741&r1=359740&r2=359741&view=diff
==============================================================================
--- llvm/trunk/lib/Support/FileCheck.cpp (original)
+++ llvm/trunk/lib/Support/FileCheck.cpp Wed May  1 17:04:38 2019
@@ -24,6 +24,31 @@
 
 using namespace llvm;
 
+llvm::Optional<std::string> 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<StringRef> 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 @@ bool FileCheckPattern::parseVariable(Str
   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 @@ static char popFront(StringRef &S) {
   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 @@ bool FileCheckPattern::ParsePattern(Stri
       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 expression 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. Pattern
+    // variables must satisfy the regular expression "[a-zA-Z_][0-9a-zA-Z_]*"
+    // to be valid, as this helps catch some common errors. Numeric expressions
+    // only support the @LINE pseudo numeric variable.
     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 @@ bool FileCheckPattern::ParsePattern(Stri
       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 @@ bool FileCheckPattern::ParsePattern(Stri
       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 ([[<var>]]) 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 @@ void FileCheckPattern::AddBackrefToRegEx
   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 @@ void FileCheckPattern::evaluateExpressio
 /// 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 @@ size_t FileCheckPattern::match(StringRef
   // 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<StringRef> 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<std::string> 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 @@ size_t FileCheckPattern::match(StringRef
   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 @@ unsigned FileCheckPattern::computeMatchD
   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) << "\"";
-      } else {
-        llvm::Optional<StringRef> VarValue = Context->getVarValue(Var);
+      bool IsNumExpr = Substitution.isNumExpr();
+      llvm::Optional<std::string> MatchedValue = Substitution.getResult();
 
-        // Check for undefined variable references.
-        if (!VarValue) {
-          OS << "uses undefined variable \"";
-          OS.write_escaped(Var) << "\"";
-        } else {
+      // 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 {
+        // 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 @@ void FileCheckPattern::printFuzzyMatch(
 }
 
 llvm::Optional<StringRef>
-FileCheckPatternContext::getVarValue(StringRef VarName) {
+FileCheckPatternContext::getPatternVarValue(StringRef VarName) {
   auto VarIter = GlobalVariableTable.find(VarName);
   if (VarIter == GlobalVariableTable.end())
     return llvm::None;
@@ -542,6 +584,12 @@ FileCheckPatternContext::getVarValue(Str
   return VarIter->second;
 }
 
+template <class... Types>
+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 @@ static void PrintMatch(bool ExpectedMatc
       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 @@ static void PrintNoMatch(bool ExpectedMa
   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);

Modified: llvm/trunk/test/FileCheck/line-count.txt
URL: http://llvm.org/viewvc/llvm-project/llvm/trunk/test/FileCheck/line-count.txt?rev=359741&r1=359740&r2=359741&view=diff
==============================================================================
--- llvm/trunk/test/FileCheck/line-count.txt (original)
+++ llvm/trunk/test/FileCheck/line-count.txt Wed May  1 17:04:38 2019
@@ -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

Modified: llvm/trunk/unittests/Support/FileCheckTest.cpp
URL: http://llvm.org/viewvc/llvm-project/llvm/trunk/unittests/Support/FileCheckTest.cpp?rev=359741&r1=359740&r2=359741&view=diff
==============================================================================
--- llvm/trunk/unittests/Support/FileCheckTest.cpp (original)
+++ llvm/trunk/unittests/Support/FileCheckTest.cpp Wed May  1 17:04:38 2019
@@ -105,7 +105,7 @@ public:
     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 @@ TEST_F(FileCheckTest, ParseExpr) {
   EXPECT_TRUE(Tester.parseExpect(VarName, Trailer));
 }
 
+TEST_F(FileCheckTest, Substitution) {
+  SourceMgr SM;
+  FileCheckPatternContext Context;
+  std::vector<std::string> 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<std::string> 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<std::string> 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<std::string> GlobalDefines;
@@ -176,9 +222,9 @@ TEST_F(FileCheckTest, FileCheckContext)
   StringRef LocalVarStr = "LocalVar";
   StringRef EmptyVarStr = "EmptyVar";
   StringRef UnknownVarStr = "UnknownVar";
-  llvm::Optional<StringRef> LocalVar = Cxt.getVarValue(LocalVarStr);
-  llvm::Optional<StringRef> EmptyVar = Cxt.getVarValue(EmptyVarStr);
-  llvm::Optional<StringRef> UnknownVar = Cxt.getVarValue(UnknownVarStr);
+  llvm::Optional<StringRef> LocalVar = Cxt.getPatternVarValue(LocalVarStr);
+  llvm::Optional<StringRef> EmptyVar = Cxt.getPatternVarValue(EmptyVarStr);
+  llvm::Optional<StringRef> UnknownVar = Cxt.getPatternVarValue(UnknownVarStr);
   EXPECT_TRUE(LocalVar);
   EXPECT_EQ(*LocalVar, "FOO");
   EXPECT_TRUE(EmptyVar);
@@ -187,9 +233,9 @@ TEST_F(FileCheckTest, FileCheckContext)
 
   // 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 @@ TEST_F(FileCheckTest, FileCheckContext)
   GotError = Cxt.defineCmdlineVariables(GlobalDefines, SM);
   EXPECT_FALSE(GotError);
   StringRef GlobalVarStr = "$GlobalVar";
-  llvm::Optional<StringRef> GlobalVar = Cxt.getVarValue(GlobalVarStr);
+  llvm::Optional<StringRef> 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




More information about the llvm-commits mailing list