From 8fd2270370244f0e93b4fd9ac4e13473f3cd7dd7 Mon Sep 17 00:00:00 2001 From: Paul Walker Date: Thu, 14 May 2020 10:32:58 +0000 Subject: [PATCH] [FileCheck] Add function call support to numerical expressions. 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: () Where is a predefined string literal, currently limited to one of add, max, min and sub. 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 | 27 ++++-- llvm/lib/Support/FileCheck.cpp | 131 ++++++++++++++++++++++++++--- llvm/lib/Support/FileCheckImpl.h | 24 +++++- llvm/test/FileCheck/numeric-expression.txt | 81 ++++++++++++++++++ llvm/unittests/Support/FileCheckTest.cpp | 96 ++++++++++++++++++++- 5 files changed, 338 insertions(+), 21 deletions(-) diff --git a/llvm/docs/CommandGuide/FileCheck.rst b/llvm/docs/CommandGuide/FileCheck.rst index b2e3dfc..8199258 100644 --- a/llvm/docs/CommandGuide/FileCheck.rst +++ b/llvm/docs/CommandGuide/FileCheck.rst @@ -686,12 +686,27 @@ The syntax of a numeric substitution is ``[[#%,]]`` 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 ``()`` 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 index a1a37c9..4a797ea 100644 --- a/llvm/lib/Support/FileCheck.cpp +++ b/llvm/lib/Support/FileCheck.cpp @@ -230,6 +230,34 @@ Expected llvm::operator-(const ExpressionValue &LeftOperand, } } +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 @@ 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> Pattern::parseNumericOperand( // 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 @@ Pattern::parseBinop(StringRef Expr, StringRef &RemainingExpr, 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 @@ Expected> 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( diff --git a/llvm/lib/Support/FileCheckImpl.h b/llvm/lib/Support/FileCheckImpl.h index 6998df3..d3bc32b 100644 --- a/llvm/lib/Support/FileCheckImpl.h +++ b/llvm/lib/Support/FileCheckImpl.h @@ -152,6 +152,10 @@ Expected operator+(const ExpressionValue &Lhs, 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 @@ 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> 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 index d5b4db7..aca8b9d3 100644 --- a/llvm/test/FileCheck/numeric-expression.txt +++ b/llvm/test/FileCheck/numeric-expression.txt @@ -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: {{^}} ^{{$}} diff --git a/llvm/unittests/Support/FileCheckTest.cpp b/llvm/unittests/Support/FileCheckTest.cpp index c549991..6cdb5f1 100644 --- a/llvm/unittests/Support/FileCheckTest.cpp +++ b/llvm/unittests/Support/FileCheckTest.cpp @@ -812,7 +812,7 @@ private: 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 @@ 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; -- 2.7.4