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 @@ -686,12 +686,27 @@ * 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 ``()`` 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. + +* ```` is a comma seperated list of expressions. For example: 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 @@ -230,6 +230,34 @@ } } +Expected 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 llvm::min(const ExpressionValue &LeftOperand, + const ExpressionValue &RightOperand) { + if (cantFail(max(LeftOperand, RightOperand)) == LeftOperand) + return RightOperand; + + return LeftOperand; +} + Expected NumericVariableUse::eval() const { Optional Value = Variable->getValue(); if (Value) @@ -309,23 +337,20 @@ 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 @@ // Try to parse as a numeric variable use. Expected 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 @@ std::move(*RightOpResult)); } +Expected> +Pattern::parseCallExpr(StringRef &Expr, StringRef FuncName, + Optional LineNumber, + FileCheckPatternContext *Context, const SourceMgr &SM) { + Expr = Expr.ltrim(SpaceChars); + assert(Expr.startswith("(")); + + auto OptFunc = StringSwitch>(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, 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> 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 + 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(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> Pattern::parseNumericSubstitutionBlock( StringRef Expr, Optional &DefinedNumericVariable, bool IsLegacyLineExpr, Optional LineNumber, @@ -549,9 +659,10 @@ 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( diff --git a/llvm/lib/Support/FileCheckImpl.h b/llvm/lib/Support/FileCheckImpl.h --- a/llvm/lib/Support/FileCheckImpl.h +++ b/llvm/lib/Support/FileCheckImpl.h @@ -152,6 +152,10 @@ const ExpressionValue &Rhs); Expected operator-(const ExpressionValue &Lhs, const ExpressionValue &Rhs); +Expected max(const ExpressionValue &Lhs, + const ExpressionValue &Rhs); +Expected min(const ExpressionValue &Lhs, + const ExpressionValue &Rhs); /// Base class representing the AST of a given expression. class ExpressionAST { @@ -722,10 +726,10 @@ 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 @@ static Expected> parseParenExpr(StringRef &Expr, Optional 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> + parseCallExpr(StringRef &Expr, StringRef FuncName, + Optional LineNumber, FileCheckPatternContext *Context, + const SourceMgr &SM); }; //===----------------------------------------------------------------------===// diff --git a/llvm/test/FileCheck/numeric-expression.txt b/llvm/test/FileCheck/numeric-expression.txt --- a/llvm/test/FileCheck/numeric-expression.txt +++ b/llvm/test/FileCheck/numeric-expression.txt @@ -55,20 +55,38 @@ 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 @@ 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 @@ 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-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: {{^}} ^{{$}} 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 @@ -812,7 +812,7 @@ public: PatternTester() { - std::vector GlobalDefines = {"#FOO=42", "BAR=BAZ"}; + std::vector 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 @@ 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 @@ 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;