[FileCheck] Add function call support to numerical expressions.
authorPaul Walker <paul.walker@arm.com>
Thu, 14 May 2020 10:32:58 +0000 (10:32 +0000)
committerPaul Walker <paul.walker@arm.com>
Wed, 10 Jun 2020 09:42:00 +0000 (09:42 +0000)
This patch extends numerical expressions to allow calls to
predefined functions. These calls can be combined with the
existing numerical operators, which includes nesting calls.

The call syntax is:

  <func>(<args>)

Where <func> is a predefined string literal, currently limited to
one of add, max, min and sub. <arg> is a comma seperated list of
numerical expressions.

Subscribers: arichardson, hiraditya, thopre, llvm-commits

Tags: #llvm

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

llvm/docs/CommandGuide/FileCheck.rst
llvm/lib/Support/FileCheck.cpp
llvm/lib/Support/FileCheckImpl.h
llvm/test/FileCheck/numeric-expression.txt
llvm/unittests/Support/FileCheckTest.cpp

index b2e3dfc..8199258 100644 (file)
@@ -686,12 +686,27 @@ The syntax of a numeric substitution is ``[[#%<fmtspec>,<expr>]]`` where:
   * a numeric operand, or
   * an expression followed by an operator and a numeric operand.
 
-  A numeric operand is a previously defined numeric variable, or an integer
-  literal and have a 64-bit precision. The supported operators are ``+`` and
-  ``-``. Spaces are accepted before, after and between any of these elements.
-  Overflow and underflow are rejected. There is currently no support for
-  operator precendence, but parentheses can be used to change the evaluation
-  order.
+  A numeric operand is a previously defined numeric variable, an integer
+  literal, or a function. Spaces are accepted before, after and between any of
+  these elements. Numeric operands have 64-bit precision. Overflow and underflow
+  are rejected. There is no support for operator precendence, but parentheses
+  can be used to change the evaluation order.
+
+The supported operators are:
+
+  * ``+`` - Returns the sum of its two operands.
+  * ``-`` - Returns the difference of its two operands.
+
+The syntax of a function call is ``<name>(<arguments>)`` where:
+
+* ``name`` is a predefined string literal. Accepted values are:
+
+  * add - Returns the sum of its two operands.
+  * max - Returns the largest of its two operands.
+  * min - Returns the smallest of its two operands.
+  * sub - Returns the difference of its two operands.
+
+* ``<arguments>`` is a comma seperated list of expressions.
 
 For example:
 
index a1a37c9..4a797ea 100644 (file)
@@ -230,6 +230,34 @@ Expected<ExpressionValue> llvm::operator-(const ExpressionValue &LeftOperand,
   }
 }
 
+Expected<ExpressionValue> llvm::max(const ExpressionValue &LeftOperand,
+                                    const ExpressionValue &RightOperand) {
+  if (LeftOperand.isNegative() && RightOperand.isNegative()) {
+    int64_t LeftValue = cantFail(LeftOperand.getSignedValue());
+    int64_t RightValue = cantFail(RightOperand.getSignedValue());
+    return ExpressionValue(std::max(LeftValue, RightValue));
+  }
+
+  if (!LeftOperand.isNegative() && !RightOperand.isNegative()) {
+    uint64_t LeftValue = cantFail(LeftOperand.getUnsignedValue());
+    uint64_t RightValue = cantFail(RightOperand.getUnsignedValue());
+    return ExpressionValue(std::max(LeftValue, RightValue));
+  }
+
+  if (LeftOperand.isNegative())
+    return RightOperand;
+
+  return LeftOperand;
+}
+
+Expected<ExpressionValue> llvm::min(const ExpressionValue &LeftOperand,
+                                    const ExpressionValue &RightOperand) {
+  if (cantFail(max(LeftOperand, RightOperand)) == LeftOperand)
+    return RightOperand;
+
+  return LeftOperand;
+}
+
 Expected<ExpressionValue> NumericVariableUse::eval() const {
   Optional<ExpressionValue> Value = Variable->getValue();
   if (Value)
@@ -309,23 +337,20 @@ Pattern::parseVariable(StringRef &Str, const SourceMgr &SM) {
   if (Str.empty())
     return ErrorDiagnostic::get(SM, Str, "empty variable name");
 
-  bool ParsedOneChar = false;
-  unsigned I = 0;
+  size_t I = 0;
   bool IsPseudo = Str[0] == '@';
 
   // Global vars start with '$'.
   if (Str[0] == '$' || IsPseudo)
     ++I;
 
-  for (unsigned E = Str.size(); I != E; ++I) {
-    if (!ParsedOneChar && !isValidVarNameStart(Str[I]))
-      return ErrorDiagnostic::get(SM, Str, "invalid variable name");
+  if (!isValidVarNameStart(Str[I++]))
+    return ErrorDiagnostic::get(SM, Str, "invalid variable name");
 
+  for (size_t E = Str.size(); I != E; ++I)
     // Variable names are composed of alphanumeric characters and underscores.
     if (Str[I] != '_' && !isAlnum(Str[I]))
       break;
-    ParsedOneChar = true;
-  }
 
   StringRef Name = Str.take_front(I);
   Str = Str.substr(I);
@@ -436,10 +461,22 @@ Expected<std::unique_ptr<ExpressionAST>> Pattern::parseNumericOperand(
     // Try to parse as a numeric variable use.
     Expected<Pattern::VariableProperties> ParseVarResult =
         parseVariable(Expr, SM);
-    if (ParseVarResult)
+    if (ParseVarResult) {
+      // Try to parse a function call.
+      if (Expr.ltrim(SpaceChars).startswith("(")) {
+        if (AO != AllowedOperand::Any)
+          return ErrorDiagnostic::get(SM, ParseVarResult->Name,
+                                      "unexpected function call");
+
+        return parseCallExpr(Expr, ParseVarResult->Name, LineNumber, Context,
+                             SM);
+      }
+
       return parseNumericVariableUse(ParseVarResult->Name,
                                      ParseVarResult->IsPseudo, LineNumber,
                                      Context, SM);
+    }
+
     if (AO == AllowedOperand::LineVar)
       return ParseVarResult.takeError();
     // Ignore the error and retry parsing as a literal.
@@ -540,6 +577,79 @@ Pattern::parseBinop(StringRef Expr, StringRef &RemainingExpr,
                                            std::move(*RightOpResult));
 }
 
+Expected<std::unique_ptr<ExpressionAST>>
+Pattern::parseCallExpr(StringRef &Expr, StringRef FuncName,
+                       Optional<size_t> LineNumber,
+                       FileCheckPatternContext *Context, const SourceMgr &SM) {
+  Expr = Expr.ltrim(SpaceChars);
+  assert(Expr.startswith("("));
+
+  auto OptFunc = StringSwitch<Optional<binop_eval_t>>(FuncName)
+                     .Case("add", operator+)
+                     .Case("max", max)
+                     .Case("min", min)
+                     .Case("sub", operator-)
+                     .Default(None);
+
+  if (!OptFunc)
+    return ErrorDiagnostic::get(
+        SM, FuncName, Twine("call to undefined function '") + FuncName + "'");
+
+  Expr.consume_front("(");
+  Expr = Expr.ltrim(SpaceChars);
+
+  // Parse call arguments, which are comma separated.
+  SmallVector<std::unique_ptr<ExpressionAST>, 4> Args;
+  while (!Expr.empty() && !Expr.startswith(")")) {
+    if (Expr.startswith(","))
+      return ErrorDiagnostic::get(SM, Expr, "missing argument");
+
+    // Parse the argument, which is an arbitary expression.
+    StringRef OuterBinOpExpr = Expr;
+    Expected<std::unique_ptr<ExpressionAST>> Arg =
+        parseNumericOperand(Expr, AllowedOperand::Any, LineNumber, Context, SM);
+    while (Arg && !Expr.empty()) {
+      Expr = Expr.ltrim(SpaceChars);
+      // Have we reached an argument terminator?
+      if (Expr.startswith(",") || Expr.startswith(")"))
+        break;
+
+      // Arg = Arg <op> <expr>
+      Arg = parseBinop(OuterBinOpExpr, Expr, std::move(*Arg), false, LineNumber,
+                       Context, SM);
+    }
+
+    // Prefer an expression error over a generic invalid argument message.
+    if (!Arg)
+      return Arg.takeError();
+    Args.push_back(std::move(*Arg));
+
+    // Have we parsed all available arguments?
+    Expr = Expr.ltrim(SpaceChars);
+    if (!Expr.consume_front(","))
+      break;
+
+    Expr = Expr.ltrim(SpaceChars);
+    if (Expr.startswith(")"))
+      return ErrorDiagnostic::get(SM, Expr, "missing argument");
+  }
+
+  if (!Expr.consume_front(")"))
+    return ErrorDiagnostic::get(SM, Expr,
+                                "missing ')' at end of call expression");
+
+  const unsigned NumArgs = Args.size();
+  if (NumArgs == 2)
+    return std::make_unique<BinaryOperation>(Expr, *OptFunc, std::move(Args[0]),
+                                             std::move(Args[1]));
+
+  // TODO: Support more than binop_eval_t.
+  return ErrorDiagnostic::get(SM, FuncName,
+                              Twine("function '") + FuncName +
+                                  Twine("' takes 2 arguments but ") +
+                                  Twine(NumArgs) + " given");
+}
+
 Expected<std::unique_ptr<Expression>> Pattern::parseNumericSubstitutionBlock(
     StringRef Expr, Optional<NumericVariable *> &DefinedNumericVariable,
     bool IsLegacyLineExpr, Optional<size_t> LineNumber,
@@ -549,9 +659,10 @@ Expected<std::unique_ptr<Expression>> Pattern::parseNumericSubstitutionBlock(
   DefinedNumericVariable = None;
   ExpressionFormat ExplicitFormat = ExpressionFormat();
 
-  // Parse format specifier.
+  // Parse format specifier (NOTE: ',' is also an argument seperator).
   size_t FormatSpecEnd = Expr.find(',');
-  if (FormatSpecEnd != StringRef::npos) {
+  size_t FunctionStart = Expr.find('(');
+  if (FormatSpecEnd != StringRef::npos && FormatSpecEnd < FunctionStart) {
     Expr = Expr.ltrim(SpaceChars);
     if (!Expr.consume_front("%"))
       return ErrorDiagnostic::get(
index 6998df3..d3bc32b 100644 (file)
@@ -152,6 +152,10 @@ Expected<ExpressionValue> operator+(const ExpressionValue &Lhs,
                                     const ExpressionValue &Rhs);
 Expected<ExpressionValue> operator-(const ExpressionValue &Lhs,
                                     const ExpressionValue &Rhs);
+Expected<ExpressionValue> max(const ExpressionValue &Lhs,
+                              const ExpressionValue &Rhs);
+Expected<ExpressionValue> min(const ExpressionValue &Lhs,
+                              const ExpressionValue &Rhs);
 
 /// Base class representing the AST of a given expression.
 class ExpressionAST {
@@ -722,10 +726,10 @@ private:
       FileCheckPatternContext *Context, const SourceMgr &SM);
   enum class AllowedOperand { LineVar, LegacyLiteral, Any };
   /// Parses \p Expr for use of a numeric operand at line \p LineNumber, or
-  /// before input is parsed if \p LineNumber is None. Accepts both literal
-  /// values and numeric variables, depending on the value of \p AO. Parameter
-  /// \p Context points to the class instance holding the live string and
-  /// numeric variables. \returns the class representing that operand in the
+  /// before input is parsed if \p LineNumber is None. Accepts literal values,
+  /// numeric variables and function calls, depending on the value of \p AO.
+  /// Parameter \p Context points to the class instance holding the live string
+  /// and numeric variables. \returns the class representing that operand in the
   /// AST of the expression or an error holding a diagnostic against \p SM
   /// otherwise. If \p Expr starts with a "(" this function will attempt to
   /// parse a parenthesized expression.
@@ -757,6 +761,18 @@ private:
   static Expected<std::unique_ptr<ExpressionAST>>
   parseParenExpr(StringRef &Expr, Optional<size_t> LineNumber,
                  FileCheckPatternContext *Context, const SourceMgr &SM);
+
+  /// Parses \p Expr for an argument list belonging to a call to function \p
+  /// FuncName at line \p LineNumber, or before input is parsed if \p LineNumber
+  /// is None. Parameter \p FuncLoc is the source location used for diagnostics.
+  /// Parameter \p Context points to the class instance holding the live string
+  /// and numeric variables. \returns the class representing that call in the
+  /// AST of the expression or an error holding a diagnostic against \p SM
+  /// otherwise.
+  static Expected<std::unique_ptr<ExpressionAST>>
+  parseCallExpr(StringRef &Expr, StringRef FuncName,
+                Optional<size_t> LineNumber, FileCheckPatternContext *Context,
+                const SourceMgr &SM);
 };
 
 //===----------------------------------------------------------------------===//
index d5b4db7..aca8b9d 100644 (file)
@@ -55,20 +55,38 @@ USE EXPL FMT IMPL MATCH  // CHECK-LABEL: USE EXPL FMT IMPL MATCH
 11  // CHECK-NEXT: {{^}}[[#%u,UNSI]]
 12  // CHECK-NEXT: {{^}}[[#%u,UNSI+1]]
 10  // CHECK-NEXT: {{^}}[[#%u,UNSI-1]]
+15  // CHECK-NEXT: {{^}}[[#%u,add(UNSI,4)]]
+11  // CHECK-NEXT: {{^}}[[#%u,max(UNSI,7)]]
+99  // CHECK-NEXT: {{^}}[[#%u,max(UNSI,99)]]
+7   // CHECK-NEXT: {{^}}[[#%u,min(UNSI,7)]]
+11  // CHECK-NEXT: {{^}}[[#%u,min(UNSI,99)]]
+8   // CHECK-NEXT: {{^}}[[#%u,sub(UNSI,3)]]
 c   // CHECK-NEXT: {{^}}[[#%x,LHEX]]
 d   // CHECK-NEXT: {{^}}[[#%x,LHEX+1]]
 b   // CHECK-NEXT: {{^}}[[#%x,LHEX-1]]
 1a  // CHECK-NEXT: {{^}}[[#%x,LHEX+0xe]]
 1a  // CHECK-NEXT: {{^}}[[#%x,LHEX+0xE]]
+e   // CHECK-NEXT: {{^}}[[#%x,add(LHEX,2)]]
+ff  // CHECK-NEXT: {{^}}[[#%x,max(LHEX,0xff)]]
+a   // CHECK-NEXT: {{^}}[[#%x,min(LHEX,0xa)]]
+a   // CHECK-NEXT: {{^}}[[#%x,sub(LHEX,2)]]
 D   // CHECK-NEXT: {{^}}[[#%X,UHEX]]
 E   // CHECK-NEXT: {{^}}[[#%X,UHEX+1]]
 C   // CHECK-NEXT: {{^}}[[#%X,UHEX-1]]
 1B  // CHECK-NEXT: {{^}}[[#%X,UHEX+0xe]]
 1B  // CHECK-NEXT: {{^}}[[#%X,UHEX+0xE]]
+F   // CHECK-NEXT: {{^}}[[#%X,add(UHEX,2)]]
+FF  // CHECK-NEXT: {{^}}[[#%X,max(UHEX,0xff)]]
+A   // CHECK-NEXT: {{^}}[[#%X,min(UHEX,0xa)]]
+B   // CHECK-NEXT: {{^}}[[#%X,sub(UHEX,2)]]
 -30 // CHECK-NEXT: {{^}}[[#%d,SIGN]]
 -29 // CHECK-NEXT: {{^}}[[#%d,SIGN+1]]
 -31 // CHECK-NEXT: {{^}}[[#%d,SIGN-1]]
 42  // CHECK-NEXT: {{^}}[[#%d,SIGN+72]]
+-29 // CHECK-NEXT: {{^}}[[#%d,add(SIGN,1)]]
+-17 // CHECK-NEXT: {{^}}[[#%d,max(SIGN,-17)]]
+-30 // CHECK-NEXT: {{^}}[[#%d,min(SIGN,-17)]]
+-31 // CHECK-NEXT: {{^}}[[#%d,sub(SIGN,1)]]
 11  // CHECK-NEXT: {{^}}[[#%u,UNSIa]]
 11  // CHECK-NEXT: {{^}}[[#%u,UNSIb]]
 11  // CHECK-NEXT: {{^}}[[#%u,UNSIc]]
@@ -92,6 +110,15 @@ USE EXPL FMT IMPL MATCH SPC  // CHECK-LABEL: USE EXPL FMT IMPL MATCH SPC
 10  // CHECK-NEXT: {{^}}[[# %u , UNSI -1]]
 10  // CHECK-NEXT: {{^}}[[# %u , UNSI - 1]]
 10  // CHECK-NEXT: {{^}}[[# %u , UNSI - 1 ]]
+13  // CHECK-NEXT: {{^}}[[#%u, add(UNSI,2)]]
+13  // CHECK-NEXT: {{^}}[[# %u, add(UNSI,2)]]
+13  // CHECK-NEXT: {{^}}[[# %u , add(UNSI,2)]]
+13  // CHECK-NEXT: {{^}}[[# %u , add(UNSI, 2)]]
+13  // CHECK-NEXT: {{^}}[[# %u , add( UNSI, 2)]]
+13  // CHECK-NEXT: {{^}}[[# %u , add( UNSI,2)]]
+13  // CHECK-NEXT: {{^}}[[# %u , add(UNSI,2) ]]
+13  // CHECK-NEXT: {{^}}[[# %u , add (UNSI,2)]]
+104 // CHECK-NEXT: {{^}}[[# %u , UNSI + sub( add (100 , UNSI+ 1 ), 20) +1 ]]
 
 ; Numeric expressions in implicit matching format and default matching rule using
 ; variables defined on other lines.
@@ -99,16 +126,22 @@ USE IMPL FMT IMPL MATCH  // CHECK-LABEL: USE IMPL FMT IMPL MATCH
 11  // CHECK-NEXT: {{^}}[[#UNSI]]
 12  // CHECK-NEXT: {{^}}[[#UNSI+1]]
 10  // CHECK-NEXT: {{^}}[[#UNSI-1]]
+99  // CHECK-NEXT: {{^}}[[#max(UNSI,99)]]
+7   // CHECK-NEXT: {{^}}[[#min(UNSI,7)]]
 c   // CHECK-NEXT: {{^}}[[#LHEX]]
 d   // CHECK-NEXT: {{^}}[[#LHEX+1]]
 b   // CHECK-NEXT: {{^}}[[#LHEX-1]]
 1a  // CHECK-NEXT: {{^}}[[#LHEX+0xe]]
 1a  // CHECK-NEXT: {{^}}[[#LHEX+0xE]]
+ff  // CHECK-NEXT: {{^}}[[#max(LHEX,255)]]
+a   // CHECK-NEXT: {{^}}[[#min(LHEX,10)]]
 D   // CHECK-NEXT: {{^}}[[#UHEX]]
 E   // CHECK-NEXT: {{^}}[[#UHEX+1]]
 C   // CHECK-NEXT: {{^}}[[#UHEX-1]]
 1B  // CHECK-NEXT: {{^}}[[#UHEX+0xe]]
 1B  // CHECK-NEXT: {{^}}[[#UHEX+0xE]]
+FF  // CHECK-NEXT: {{^}}[[#max(UHEX,255)]]
+A   // CHECK-NEXT: {{^}}[[#min(UHEX,10)]]
 -30 // CHECK-NEXT: {{^}}[[#SIGN]]
 -29 // CHECK-NEXT: {{^}}[[#SIGN+1]]
 -31 // CHECK-NEXT: {{^}}[[#SIGN-1]]
@@ -367,3 +400,51 @@ UNDERFLOW-NEXT: TINYVAR: [[#%d,TINYVAR:-0x8000000000000000-0x8000000000000000]]
 UNDERFLOW-MSG: numeric-expression.txt:[[#@LINE-1]]:29: error: unable to substitute variable or numeric expression
 UNDERFLOW-MSG-NEXT: {{U}}NDERFLOW-NEXT: TINYVAR: {{\[\[#%d,TINYVAR:-0x8000000000000000-0x8000000000000000\]\]}}
 UNDERFLOW-MSG-NEXT:    {{^}}                            ^{{$}}
+
+RUN: %ProtectFileCheckOutput \
+RUN: not FileCheck -D#NUMVAR=10 --check-prefix CALL-MISSING-CLOSING-BRACKET --input-file %s %s 2>&1 \
+RUN:   | FileCheck --strict-whitespace --check-prefix CALL-MISSING-CLOSING-BRACKET-MSG %s
+
+CALL MISSING CLOSING BRACKET
+30
+CALL-MISSING-CLOSING-BRACKET-LABEL: CALL MISSING CLOSING BRACKET
+CALL-MISSING-CLOSING-BRACKET-NEXT: [[#add(NUMVAR,3]]
+CALL-MISSING-CLOSING-BRACKET-MSG: numeric-expression.txt:[[#@LINE-1]]:51: error: missing ')' at end of call expression
+CALL-MISSING-CLOSING-BRACKET-MSG-NEXT: {{C}}ALL-MISSING-CLOSING-BRACKET-NEXT: {{\[\[#add\(NUMVAR,3\]\]}}
+CALL-MISSING-CLOSING-BRACKET-MSG-NEXT:      {{^}}                                                  ^{{$}}
+
+RUN: %ProtectFileCheckOutput \
+RUN: not FileCheck -D#NUMVAR=10 --check-prefix CALL-MISSING-ARGUMENT --input-file %s %s 2>&1 \
+RUN:   | FileCheck --strict-whitespace --check-prefix CALL-MISSING-ARGUMENT-MSG %s
+
+CALL MISSING ARGUMENT
+30
+CALL-MISSING-ARGUMENT-LABEL: CALL MISSING ARGUMENT
+CALL-MISSING-ARGUMENT-NEXT: [[#add(NUMVAR,)]]
+CALL-MISSING-ARGUMENT-MSG: numeric-expression.txt:[[#@LINE-1]]:43: error: missing argument
+CALL-MISSING-ARGUMENT-MSG-NEXT: {{C}}ALL-MISSING-ARGUMENT-NEXT: {{\[\[#add\(NUMVAR,\)\]\]}}
+CALL-MISSING-ARGUMENT-MSG-NEXT:      {{^}}                                          ^{{$}}
+
+RUN: %ProtectFileCheckOutput \
+RUN: not FileCheck -D#NUMVAR=10 --check-prefix CALL-WRONG-ARGUMENT-COUNT --input-file %s %s 2>&1 \
+RUN:   | FileCheck --strict-whitespace --check-prefix CALL-WRONG-ARGUMENT-COUNT-MSG %s
+
+CALL WRONG ARGUMENT COUNT
+30
+CALL-WRONG-ARGUMENT-COUNT-LABEL: CALL WRONG ARGUMENT COUNT
+CALL-WRONG-ARGUMENT-COUNT-NEXT: [[#add(NUMVAR)]]
+CALL-WRONG-ARGUMENT-COUNT-MSG: numeric-expression.txt:[[#@LINE-1]]:36: error: function 'add' takes 2 arguments but 1 given
+CALL-WRONG-ARGUMENT-COUNT-MSG-NEXT: {{C}}ALL-WRONG-ARGUMENT-COUNT-NEXT: {{\[\[#add\(NUMVAR\)\]\]}}
+CALL-WRONG-ARGUMENT-COUNT-MSG-NEXT:    {{^}}                                   ^{{$}}
+
+RUN: %ProtectFileCheckOutput \
+RUN: not FileCheck -D#NUMVAR=10 --check-prefix CALL-UNDEFINED-FUNCTION --input-file %s %s 2>&1 \
+RUN:   | FileCheck --strict-whitespace --check-prefix CALL-UNDEFINED-FUNCTION-MSG %s
+
+CALL UNDEFINED FUNCTION
+30
+CALL-UNDEFINED-FUNCTION-LABEL: CALL UNDEFINED FUNCTION
+CALL-UNDEFINED-FUNCTION-NEXT: [[#bogus_function(NUMVAR)]]
+CALL-UNDEFINED-FUNCTION-MSG: numeric-expression.txt:[[#@LINE-1]]:34: error: call to undefined function 'bogus_function'
+CALL-UNDEFINED-FUNCTION-MSG-NEXT: {{C}}ALL-UNDEFINED-FUNCTION-NEXT: {{\[\[#bogus_function\(NUMVAR\)\]\]}}
+CALL-UNDEFINED-FUNCTION-MSG-NEXT:    {{^}}                                 ^{{$}}
index c549991..6cdb5f1 100644 (file)
@@ -812,7 +812,7 @@ private:
 
 public:
   PatternTester() {
-    std::vector<StringRef> GlobalDefines = {"#FOO=42", "BAR=BAZ"};
+    std::vector<StringRef> GlobalDefines = {"#FOO=42", "BAR=BAZ", "#add=7"};
     // An ASSERT_FALSE would make more sense but cannot be used in a
     // constructor.
     EXPECT_THAT_ERROR(Context.defineCmdlineVariables(GlobalDefines, SM),
@@ -1056,6 +1056,60 @@ TEST_F(FileCheckTest, ParseNumericSubstitutionBlock) {
                         Tester.parseSubst("(2))").takeError());
   expectDiagnosticError("unsupported operation ')'",
                         Tester.parseSubst("(1))(").takeError());
+
+  // Valid expression with function call.
+  EXPECT_THAT_EXPECTED(Tester.parseSubst("add(FOO,3)"), Succeeded());
+  EXPECT_THAT_EXPECTED(Tester.parseSubst("add (FOO,3)"), Succeeded());
+  // Valid expression with nested function call.
+  EXPECT_THAT_EXPECTED(Tester.parseSubst("add(FOO, min(BAR,10))"), Succeeded());
+  // Valid expression with function call taking expression as argument.
+  EXPECT_THAT_EXPECTED(Tester.parseSubst("add(FOO, (BAR+10) + 3)"),
+                       Succeeded());
+  EXPECT_THAT_EXPECTED(Tester.parseSubst("add(FOO, min (BAR,10) + 3)"),
+                       Succeeded());
+  // Valid expression with variable named the same as a function.
+  EXPECT_THAT_EXPECTED(Tester.parseSubst("add"), Succeeded());
+  EXPECT_THAT_EXPECTED(Tester.parseSubst("add+FOO"), Succeeded());
+  EXPECT_THAT_EXPECTED(Tester.parseSubst("FOO+add"), Succeeded());
+  EXPECT_THAT_EXPECTED(Tester.parseSubst("add(add,add)+add"), Succeeded());
+
+  // Malformed call syntax.
+  expectDiagnosticError("missing ')' at end of call expression",
+                        Tester.parseSubst("add(FOO,(BAR+7)").takeError());
+  expectDiagnosticError("missing ')' at end of call expression",
+                        Tester.parseSubst("add(FOO,min(BAR,7)").takeError());
+  expectDiagnosticError("missing argument",
+                        Tester.parseSubst("add(FOO,)").takeError());
+  expectDiagnosticError("missing argument",
+                        Tester.parseSubst("add(,FOO)").takeError());
+  expectDiagnosticError("missing argument",
+                        Tester.parseSubst("add(FOO,,3)").takeError());
+
+  // Valid call, but to an unknown function.
+  expectDiagnosticError("call to undefined function 'bogus_function'",
+                        Tester.parseSubst("bogus_function(FOO,3)").takeError());
+  expectDiagnosticError("call to undefined function '@add'",
+                        Tester.parseSubst("@add(2,3)").takeError());
+  expectDiagnosticError("call to undefined function '$add'",
+                        Tester.parseSubst("$add(2,3)").takeError());
+  expectDiagnosticError("call to undefined function 'FOO'",
+                        Tester.parseSubst("FOO(2,3)").takeError());
+  expectDiagnosticError("call to undefined function 'FOO'",
+                        Tester.parseSubst("FOO (2,3)").takeError());
+
+  // Valid call, but with incorrect argument count.
+  expectDiagnosticError("function 'add' takes 2 arguments but 1 given",
+                        Tester.parseSubst("add(FOO)").takeError());
+  expectDiagnosticError("function 'add' takes 2 arguments but 3 given",
+                        Tester.parseSubst("add(FOO,3,4)").takeError());
+
+  // Valid call, but not part of a valid expression.
+  expectDiagnosticError("unsupported operation 'a'",
+                        Tester.parseSubst("2add(FOO,2)").takeError());
+  expectDiagnosticError("unsupported operation 'a'",
+                        Tester.parseSubst("FOO add(FOO,2)").takeError());
+  expectDiagnosticError("unsupported operation 'a'",
+                        Tester.parseSubst("add(FOO,2)add(FOO,2)").takeError());
 }
 
 TEST_F(FileCheckTest, ParsePattern) {
@@ -1229,6 +1283,46 @@ TEST_F(FileCheckTest, MatchParen) {
   EXPECT_THAT_EXPECTED(Tester.match("20"), Succeeded());
 }
 
+TEST_F(FileCheckTest, MatchBuiltinFunctions) {
+  PatternTester Tester;
+  // Esnure #NUMVAR has the expected value.
+  Tester.initNextPattern();
+  ASSERT_FALSE(Tester.parsePattern("[[#NUMVAR:]]"));
+  expectNotFoundError(Tester.match("FAIL").takeError());
+  expectNotFoundError(Tester.match("").takeError());
+  EXPECT_THAT_EXPECTED(Tester.match("18"), Succeeded());
+
+  // Check each builtin function generates the expected result.
+  Tester.initNextPattern();
+  ASSERT_FALSE(Tester.parsePattern("[[#add(NUMVAR,13)]]"));
+  EXPECT_THAT_EXPECTED(Tester.match("31"), Succeeded());
+  Tester.initNextPattern();
+  ASSERT_FALSE(Tester.parsePattern("[[#sub(NUMVAR,7)]]"));
+  EXPECT_THAT_EXPECTED(Tester.match("11"), Succeeded());
+  Tester.initNextPattern();
+  ASSERT_FALSE(Tester.parsePattern("[[#max(NUMVAR,5)]]"));
+  EXPECT_THAT_EXPECTED(Tester.match("18"), Succeeded());
+  Tester.initNextPattern();
+  ASSERT_FALSE(Tester.parsePattern("[[#max(NUMVAR,99)]]"));
+  EXPECT_THAT_EXPECTED(Tester.match("99"), Succeeded());
+  Tester.initNextPattern();
+  ASSERT_FALSE(Tester.parsePattern("[[#min(NUMVAR,5)]]"));
+  EXPECT_THAT_EXPECTED(Tester.match("5"), Succeeded());
+  Tester.initNextPattern();
+  ASSERT_FALSE(Tester.parsePattern("[[#min(NUMVAR,99)]]"));
+  EXPECT_THAT_EXPECTED(Tester.match("18"), Succeeded());
+
+  // Check nested function calls.
+  Tester.initNextPattern();
+  ASSERT_FALSE(Tester.parsePattern("[[#add(min(7,2),max(4,10))]]"));
+  EXPECT_THAT_EXPECTED(Tester.match("12"), Succeeded());
+
+  // Check function call that uses a variable of the same name.
+  Tester.initNextPattern();
+  ASSERT_FALSE(Tester.parsePattern("[[#add(add,add)+min (add,3)+add]]"));
+  EXPECT_THAT_EXPECTED(Tester.match("24"), Succeeded());
+}
+
 TEST_F(FileCheckTest, Substitution) {
   SourceMgr SM;
   FileCheckPatternContext Context;