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 @@ -622,12 +622,26 @@ as: * a numeric operand, or - * an expression followed by an operator and a numeric operand. + * a function call, or + * an expression followed by an operator and an expression. A numeric operand is a previously defined numeric variable, or an integer literal. The supported operators are ``+`` and ``-``. Spaces are accepted before, after and between any of these elements. +The syntax of a function call is ``!()`` where: + +* ``name`` is a predefined string literal. Acceptable values are: + + * mul - Return the multiplication of two operands. + * umax - Return the largest of two unsigned operands. + * umin - Return the smallest of two unsigned operands. + + ``!()`` is a precedence operator, which can be used to force the order + of operations when symbolic operators are in use. + +* ```` is a comma seperated list of expressions. + For example: .. code-block:: llvm 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 @@ -185,6 +185,9 @@ // FileCheck input canonicalization. constexpr StringLiteral SpaceChars = " \t"; +// StringRef holding the prefix used to identify the name of a function. +constexpr StringLiteral CallPrefix = "!"; + // Parsing helper function that strips the first character in S and returns it. static char popFront(StringRef &S) { char C = S.front(); @@ -273,6 +276,14 @@ Expected> Pattern::parseNumericOperand( StringRef &Expr, AllowedOperand AO, Optional LineNumber, FileCheckPatternContext *Context, const SourceMgr &SM) { + // Try to parse a function call. + if (Expr.startswith(CallPrefix)) { + if (AO != AllowedOperand::Any) + return ErrorDiagnostic::get(SM, Expr, "unexpected function call"); + + return parseCallExpr(Expr, LineNumber, Context, SM); + } + if (AO == AllowedOperand::LineVar || AO == AllowedOperand::Any) { // Try to parse as a numeric variable use. Expected ParseVarResult = @@ -304,10 +315,22 @@ return LeftOp + RightOp; } +static uint64_t mul(uint64_t LeftOp, uint64_t RightOp) { + return LeftOp * RightOp; +} + static uint64_t sub(uint64_t LeftOp, uint64_t RightOp) { return LeftOp - RightOp; } +static uint64_t umax(uint64_t LeftOp, uint64_t RightOp) { + return std::max(LeftOp, RightOp); +} + +static uint64_t umin(uint64_t LeftOp, uint64_t RightOp) { + return std::min(LeftOp, RightOp); +} + Expected> Pattern::parseBinop(StringRef Expr, StringRef &RemainingExpr, std::unique_ptr LeftOp, @@ -352,6 +375,100 @@ std::move(*RightOpResult)); } +Expected> +Pattern::parseCallExpr(StringRef &Expr, Optional LineNumber, + FileCheckPatternContext *Context, const SourceMgr &SM) { + assert(Expr.startswith(CallPrefix)); + Expr.consume_front(CallPrefix); + + // Record this for diagnostics that should be tied to the function name. + SMLoc OpLoc = SMLoc::getFromPointer(Expr.data()); + + size_t FuncNameEnd = Expr.find('('); + if (FuncNameEnd == StringRef::npos) + return ErrorDiagnostic::get( + SM, Expr, "call expression missing '(' for argument list"); + + StringRef FuncName = Expr.take_front(FuncNameEnd); + auto OptFunc = StringSwitch>(FuncName) + .Case("mul", mul) + .Case("umax", umax) + .Case("umin", umin) + .Default(None); + + // The unnamed function is used to specify operator precedence, e.g !(A+B)*C + bool isPrecedenceOperator = FuncName.empty(); + + if (!OptFunc && !isPrecedenceOperator) + return ErrorDiagnostic::get( + SM, Expr, Twine("call to undefined function '") + FuncName + "'"); + + // Consume function name along with leading '('; + Expr = Expr.drop_front(FuncNameEnd + 1); + Expr = Expr.ltrim(SpaceChars); + + // Parse call arguments, which are comma separated. + SmallVector, 4> Args; + while (!Expr.empty() && !Expr.startswith(")")) { + Expr = Expr.rtrim(SpaceChars); + + // 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(",")) + break; + if (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 (isPrecedenceOperator) { + if (NumArgs != 1) + return ErrorDiagnostic::get( + SM, OpLoc, "precedence operator expects a single argument"); + + return std::move(Args[0]); + } + + 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, OpLoc, + Twine("function '") + FuncName + + Twine("' takes 2 arguments but ") + + Twine(NumArgs) + " given"); +} + Expected> Pattern::parseNumericSubstitutionBlock( StringRef Expr, Optional &DefinedNumericVariable, bool IsLegacyLineExpr, Optional LineNumber, @@ -361,9 +478,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(CallPrefix); + 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 @@ -661,11 +661,11 @@ 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 - /// AST of the expression or an error holding a diagnostic against \p SM - /// otherwise. + /// 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. static Expected> parseNumericOperand(StringRef &Expr, AllowedOperand AO, Optional LineNumber, @@ -684,6 +684,15 @@ std::unique_ptr LeftOp, bool IsLegacyLineExpr, Optional LineNumber, FileCheckPatternContext *Context, const SourceMgr &SM); + + /// Parses \p Expr for a function call at line \p LineNumber, or before input + /// is parsed if \p LineNumber is None. 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, 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 @@ -67,14 +67,25 @@ 11 12 10 +77 +11 +99 +7 +11 c d b 1a +c0 +ff +a D E C 1B +D0 +FF +A 11 11 11 @@ -87,14 +98,25 @@ CHECK-NEXT: [[#%u,VAR1]] CHECK-NEXT: [[#%u,VAR1+1]] CHECK-NEXT: [[#%u,VAR1-1]] +CHECK-NEXT: [[#%u,!mul(VAR1,7)]] +CHECK-NEXT: [[#%u,!umax(VAR1,7)]] +CHECK-NEXT: [[#%u,!umax(VAR1,99)]] +CHECK-NEXT: [[#%u,!umin(VAR1,7)]] +CHECK-NEXT: [[#%u,!umin(VAR1,99)]] CHECK-NEXT: [[#%x,VAR2]] CHECK-NEXT: [[#%x,VAR2+1]] CHECK-NEXT: [[#%x,VAR2-1]] CHECK-NEXT: [[#%x,VAR2+14]] +CHECK-NEXT: [[#%x,!mul(VAR2,16)]] +CHECK-NEXT: [[#%x,!umax(VAR2,255)]] +CHECK-NEXT: [[#%x,!umin(VAR2,10)]] CHECK-NEXT: [[#%X,VAR3]] CHECK-NEXT: [[#%X,VAR3+1]] CHECK-NEXT: [[#%X,VAR3-1]] CHECK-NEXT: [[#%X,VAR3+14]] +CHECK-NEXT: [[#%X,!mul(VAR3,16)]] +CHECK-NEXT: [[#%X,!umax(VAR3,255)]] +CHECK-NEXT: [[#%X,!umin(VAR3,10)]] CHECK-NEXT: [[#%u,VAR1a]] CHECK-NEXT: [[#%u,VAR1b]] CHECK-NEXT: [[#%u,VAR1c]] @@ -118,6 +140,14 @@ 10 10 10 +22 +22 +22 +22 +22 +22 +22 +98 CHECK-LABEL: USE EXPL FMT IMPL MATCH SPC CHECK-NEXT: [[#%u, VAR1]] CHECK-NEXT: [[# %u, VAR1]] @@ -134,6 +164,14 @@ CHECK-NEXT: [[# %u , VAR1 -1]] CHECK-NEXT: [[# %u , VAR1 - 1]] CHECK-NEXT: [[# %u , VAR1 - 1 ]] +CHECK-NEXT: [[#%u, !mul(VAR1,2)]] +CHECK-NEXT: [[# %u, !mul(VAR1,2)]] +CHECK-NEXT: [[# %u , !mul(VAR1,2)]] +CHECK-NEXT: [[# %u , !mul(VAR1, 2)]] +CHECK-NEXT: [[# %u , !mul( VAR1, 2)]] +CHECK-NEXT: [[# %u , !mul( VAR1,2)]] +CHECK-NEXT: [[# %u , !mul(VAR1,2) ]] +CHECK-NEXT: [[# %u , VAR1 + !(!(100-VAR1)- 3) +1 ]] ; Numeric expressions in implicit matching format and default matching rule using ; variables defined on other lines. @@ -141,26 +179,44 @@ 11 12 10 +77 +99 +7 c d b 1a +c0 +ff +a D E C 1B +D0 +FF +A CHECK-LABEL: USE IMPL FMT IMPL MATCH CHECK-NEXT: [[#VAR1]] CHECK-NEXT: [[#VAR1+1]] CHECK-NEXT: [[#VAR1-1]] +CHECK-NEXT: [[#!mul(VAR1,7)]] +CHECK-NEXT: [[#!umax(VAR1,99)]] +CHECK-NEXT: [[#!umin(VAR1,7)]] CHECK-NEXT: [[#VAR2]] CHECK-NEXT: [[#VAR2+1]] CHECK-NEXT: [[#VAR2-1]] CHECK-NEXT: [[#VAR2+14]] +CHECK-NEXT: [[#!mul(VAR2,16)]] +CHECK-NEXT: [[#!umax(VAR2,255)]] +CHECK-NEXT: [[#!umin(VAR2,10)]] CHECK-NEXT: [[#VAR3]] CHECK-NEXT: [[#VAR3+1]] CHECK-NEXT: [[#VAR3-1]] CHECK-NEXT: [[#VAR3+14]] +CHECK-NEXT: [[#!mul(VAR3,16)]] +CHECK-NEXT: [[#!umax(VAR3,255)]] +CHECK-NEXT: [[#!umin(VAR3,10)]] ; Numeric expressions using variables defined on other lines and an immediate ; interpreted as an unsigned value. @@ -386,3 +442,70 @@ REDEF-NEW-FMT-MSG: numeric-expression.txt:[[#@LINE-1]]:31: error: format different from previous variable definition REDEF-NEW-FMT-MSG-NEXT: {{R}}EDEF-NEW-FMT-NEXT: {{\[\[#%X,VAR1:\]\]}} REDEF-NEW-FMT-MSG-NEXT: {{^ \^$}} + +RUN: %ProtectFileCheckOutput \ +RUN: not FileCheck -D#NUMVAR=10 --check-prefix CALL-MISSING-OPENING-BRACKET --input-file %s %s 2>&1 \ +RUN: | FileCheck --strict-whitespace --check-prefix CALL-MISSING-OPENING-BRACKET-MSG %s +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 +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 +RUN: %ProtectFileCheckOutput \ +RUN: not FileCheck -D#NUMVAR=10 --check-prefix CALL-WRONG-ARGUMENT-COUNT1 --input-file %s %s 2>&1 \ +RUN: | FileCheck --strict-whitespace --check-prefix CALL-WRONG-ARGUMENT-COUNT1-MSG %s +RUN: %ProtectFileCheckOutput \ +RUN: not FileCheck -D#NUMVAR=10 --check-prefix CALL-WRONG-ARGUMENT-COUNT2 --input-file %s %s 2>&1 \ +RUN: | FileCheck --strict-whitespace --check-prefix CALL-WRONG-ARGUMENT-COUNT2-MSG %s +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 MISSING OPENING BRACKET +30 +CALL-MISSING-OPENING-BRACKET-LABEL: CALL MISSING OPENING BRACKET +CALL-MISSING-OPENING-BRACKET-NEXT: [[#!mulNUMVAR,3)]] +CALL-MISSING-OPENING-BRACKET-MSG: numeric-expression.txt:[[#@LINE-1]]:40: error: call expression missing '(' for argument list +CALL-MISSING-OPENING-BRACKET-MSG-NEXT: {{C}}ALL-MISSING-OPENING-BRACKET-NEXT: {{\[\[#!mulNUMVAR,3\)\]\]}} +CALL-MISSING-OPENING-BRACKET-MSG-NEXT: {{^}} ^{{$}} + +CALL MISSING CLOSING BRACKET +30 +CALL-MISSING-CLOSING-BRACKET-LABEL: CALL MISSING CLOSING BRACKET +CALL-MISSING-CLOSING-BRACKET-NEXT: [[#!mul(NUMVAR,3]] +CALL-MISSING-CLOSING-BRACKET-MSG: numeric-expression.txt:[[#@LINE-1]]:52: error: missing ')' at end of call expression +CALL-MISSING-CLOSING-BRACKET-MSG-NEXT: {{C}}ALL-MISSING-CLOSING-BRACKET-NEXT: {{\[\[#!mul\(NUMVAR,3\]\]}} +CALL-MISSING-CLOSING-BRACKET-MSG-NEXT: {{^}} ^{{$}} + +CALL MISSING ARGUMENT +30 +CALL-MISSING-ARGUMENT-LABEL: CALL MISSING ARGUMENT +CALL-MISSING-ARGUMENT-NEXT: [[#!mul(NUMVAR,)]] +CALL-MISSING-ARGUMENT-MSG: numeric-expression.txt:[[#@LINE-1]]:44: error: missing argument +CALL-MISSING-ARGUMENT-MSG-NEXT: {{C}}ALL-MISSING-ARGUMENT-NEXT: {{\[\[#!mul\(NUMVAR,\)\]\]}} +CALL-MISSING-ARGUMENT-MSG-NEXT: {{^}} ^{{$}} + +CALL WRONG ARGUMENT COUNT1 +30 +CALL-WRONG-ARGUMENT-COUNT1-LABEL: CALL WRONG ARGUMENT COUNT1 +CALL-WRONG-ARGUMENT-COUNT1-NEXT: [[#!mul(NUMVAR)]] +CALL-WRONG-ARGUMENT-COUNT1-MSG: numeric-expression.txt:[[#@LINE-1]]:38: error: function 'mul' takes 2 arguments but 1 given +CALL-WRONG-ARGUMENT-COUNT1-MSG-NEXT: {{C}}ALL-WRONG-ARGUMENT-COUNT1-NEXT: {{\[\[#!mul\(NUMVAR\)\]\]}} +CALL-WRONG-ARGUMENT-COUNT1-MSG-NEXT: {{^}} ^{{$}} + +CALL WRONG ARGUMENT COUNT2 +30 +CALL-WRONG-ARGUMENT-COUNT2-LABEL: CALL WRONG ARGUMENT COUNT2 +CALL-WRONG-ARGUMENT-COUNT2-NEXT: [[#!(NUMVAR,3)]] +CALL-WRONG-ARGUMENT-COUNT2-MSG: numeric-expression.txt:[[#@LINE-1]]:38: error: precedence operator expects a single argument +CALL-WRONG-ARGUMENT-COUNT2-MSG-NEXT: {{C}}ALL-WRONG-ARGUMENT-COUNT2-NEXT: {{\[\[#!\(NUMVAR,3\)\]\]}} +CALL-WRONG-ARGUMENT-COUNT2-MSG-NEXT: {{^}} ^{{$}} + +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]]:35: 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 @@ -724,6 +724,38 @@ "implicit format conflict between 'FOO' (%u) and " "'VAR_LOWER_HEX' (%x), need an explicit format specifier", Tester.parseSubst("FOO+VAR_LOWER_HEX").takeError()); + + // Valid expression with function call. + EXPECT_THAT_EXPECTED(Tester.parseSubst("!mul(FOO,3)"), Succeeded()); + // Valid expression with nested function call. + EXPECT_THAT_EXPECTED(Tester.parseSubst("!mul(FOO, !umin(BAR,10))"), + Succeeded()); + // Valid expression with function call taking expression as argument. + EXPECT_THAT_EXPECTED(Tester.parseSubst("!mul(FOO, !umin(BAR,10) + 3)"), + Succeeded()); + // Valid expression using precedence operator. + EXPECT_THAT_EXPECTED(Tester.parseSubst("3 + !(FOO - 1 + 7)"), Succeeded()); + + // Malformed call syntax. + expectDiagnosticError("call expression missing '(' for argument list", + Tester.parseSubst("!mulFOO,3)").takeError()); + expectDiagnosticError("missing ')' at end of call expression", + Tester.parseSubst("!mul(FOO,!(3)").takeError()); + expectDiagnosticError("missing argument", + Tester.parseSubst("!mul(FOO,)").takeError()); + + // Valid call, but to an unknown function. + expectDiagnosticError( + "call to undefined function 'bogus_function'", + Tester.parseSubst("!bogus_function(FOO,3)").takeError()); + + // Valid call, but with incorrect argument count. + expectDiagnosticError("precedence operator expects a single argument", + Tester.parseSubst("!(FOO,3)").takeError()); + expectDiagnosticError("function 'mul' takes 2 arguments but 1 given", + Tester.parseSubst("!mul(FOO)").takeError()); + expectDiagnosticError("function 'mul' takes 2 arguments but 3 given", + Tester.parseSubst("!mul(FOO,3,4)").takeError()); } TEST_F(FileCheckTest, ParsePattern) { @@ -844,6 +876,52 @@ 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 the predecence operator. + Tester.initNextPattern(); + ASSERT_FALSE(Tester.parsePattern("[[#!(NUMVAR)]]")); + EXPECT_THAT_EXPECTED(Tester.match("18"), Succeeded()); + Tester.initNextPattern(); + ASSERT_FALSE(Tester.parsePattern("[[#!(NUMVAR+3)]]")); + EXPECT_THAT_EXPECTED(Tester.match("21"), Succeeded()); + Tester.initNextPattern(); + ASSERT_FALSE(Tester.parsePattern("[[#!(NUMVAR+3)-!(2+NUMVAR)]]")); + EXPECT_THAT_EXPECTED(Tester.match("1"), Succeeded()); + Tester.initNextPattern(); + ASSERT_FALSE(Tester.parsePattern("[[#!(!(!(NUMVAR+3-1)))]]")); + EXPECT_THAT_EXPECTED(Tester.match("20"), Succeeded()); + + // Check each builtin function generates the expected result. + Tester.initNextPattern(); + ASSERT_FALSE(Tester.parsePattern("[[#!mul(NUMVAR,3)]]")); + EXPECT_THAT_EXPECTED(Tester.match("54"), Succeeded()); + Tester.initNextPattern(); + ASSERT_FALSE(Tester.parsePattern("[[#!umax(NUMVAR,5)]]")); + EXPECT_THAT_EXPECTED(Tester.match("18"), Succeeded()); + Tester.initNextPattern(); + ASSERT_FALSE(Tester.parsePattern("[[#!umax(NUMVAR,99)]]")); + EXPECT_THAT_EXPECTED(Tester.match("99"), Succeeded()); + Tester.initNextPattern(); + ASSERT_FALSE(Tester.parsePattern("[[#!umin(NUMVAR,5)]]")); + EXPECT_THAT_EXPECTED(Tester.match("5"), Succeeded()); + Tester.initNextPattern(); + ASSERT_FALSE(Tester.parsePattern("[[#!umin(NUMVAR,99)]]")); + EXPECT_THAT_EXPECTED(Tester.match("18"), Succeeded()); + + // Check nested function calls. + Tester.initNextPattern(); + ASSERT_FALSE(Tester.parsePattern("[[#!mul(!umin(7,2),!umax(4,10))]]")); + EXPECT_THAT_EXPECTED(Tester.match("20"), Succeeded()); +} + TEST_F(FileCheckTest, Substitution) { SourceMgr SM; FileCheckPatternContext Context;