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 @@ -694,6 +694,8 @@ 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. + There is currently no support for operator precendence, but parentheses can + be used to change the evaluation order. 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 @@ -273,6 +273,13 @@ Expected> Pattern::parseNumericOperand( StringRef &Expr, AllowedOperand AO, Optional LineNumber, FileCheckPatternContext *Context, const SourceMgr &SM) { + if (Expr.startswith("(")) { + if (AO != AllowedOperand::Any) + return ErrorDiagnostic::get( + SM, Expr, "parenthesized expression not permitted here"); + return parseParenExpr(Expr, LineNumber, Context, SM); + } + if (AO == AllowedOperand::LineVar || AO == AllowedOperand::Any) { // Try to parse as a numeric variable use. Expected ParseVarResult = @@ -300,6 +307,38 @@ "invalid operand format '" + Expr + "'"); } +Expected> +Pattern::parseParenExpr(StringRef &Expr, Optional LineNumber, + FileCheckPatternContext *Context, const SourceMgr &SM) { + Expr = Expr.ltrim(SpaceChars); + assert(Expr.startswith("(")); + + // Parse right operand. + Expr.consume_front("("); + Expr = Expr.ltrim(SpaceChars); + if (Expr.empty()) + return ErrorDiagnostic::get(SM, Expr, "missing operand in expression"); + + // Note: parseNumericOperand handles nested opening parentheses. + Expected> SubExprResult = + parseNumericOperand(Expr, AllowedOperand::Any, LineNumber, Context, SM); + Expr = Expr.ltrim(SpaceChars); + while (SubExprResult && !Expr.empty() && !Expr.startswith(")")) { + StringRef OrigExpr = Expr; + SubExprResult = parseBinop(OrigExpr, Expr, std::move(*SubExprResult), false, + LineNumber, Context, SM); + Expr = Expr.ltrim(SpaceChars); + } + if (!SubExprResult) + return SubExprResult; + + if (!Expr.consume_front(")")) { + return ErrorDiagnostic::get(SM, Expr, + "missing ')' at end of nested expression"); + } + return SubExprResult; +} + static uint64_t add(uint64_t LeftOp, uint64_t RightOp) { return LeftOp + RightOp; } 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 @@ -665,7 +665,8 @@ /// \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. + /// otherwise. If \p Expr starts with a "(" this function will attempt to + /// parse a parenthesized expression. static Expected> parseNumericOperand(StringRef &Expr, AllowedOperand AO, Optional LineNumber, @@ -684,6 +685,16 @@ std::unique_ptr LeftOp, bool IsLegacyLineExpr, Optional LineNumber, FileCheckPatternContext *Context, const SourceMgr &SM); + + /// Parses a parenthesized expression inside \p Expr at line \p LineNumber, or + /// before input is parsed if \p LineNumber is None. \p Expr must start with + /// a '('. Accepts both literal values and numeric variables. 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> + parseParenExpr(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 @@ -164,6 +164,25 @@ EMPTY NUM EXPR // CHECK-LABEL: EMPTY NUM EXPR foo 104 bar // CHECK-NEXT: {{^}}foo [[#]] bar + +; Numeric expressions using parentheses. +RUN: %ProtectFileCheckOutput \ +RUN: not FileCheck -D#NUMVAR=10 --check-prefix PAREN-OP \ +RUN: --input-file %s %s 2>&1 \ +RUN: | FileCheck --strict-whitespace --check-prefix PAREN-OP-MSG %s + +PAREN EXPRESSIONS +(NUMVAR+2)-1: 11 +NUMVAR+(2-1): 11 +NUMVAR+(2-1: 11 +PAREN-OP-LABEL: PAREN EXPRESSIONS +PAREN-OP-NEXT: (NUMVAR+2)-1: [[#(NUMVAR+2)-1]] +PAREN-OP-NEXT: NUMVAR+(2-1): [[#NUMVAR+(2-1)]] +PAREN-OP-NEXT: NUMVAR+(2-1: [[#NUMVAR+(2-1]] +PAREN-OP-MSG: numeric-expression.txt:[[#@LINE-1]]:43: error: missing ')' at end of nested expression +PAREN-OP-MSG-NEXT: {{P}}AREN-OP-NEXT: NUMVAR+(2-1: {{\[\[#NUMVAR\+\(2\-1]\]}} +PAREN-OP-MSG-NEXT: {{^}} ^{{$}} + ; Numeric expression using undefined variables. RUN: %ProtectFileCheckOutput \ RUN: not FileCheck --check-prefix UNDEF-USE --input-file %s %s 2>&1 \ 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,46 @@ "implicit format conflict between 'FOO' (%u) and " "'VAR_LOWER_HEX' (%x), need an explicit format specifier", Tester.parseSubst("FOO+VAR_LOWER_HEX").takeError()); + + // Simple parenthesized expressions: + EXPECT_THAT_EXPECTED(Tester.parseSubst("(1)"), Succeeded()); + EXPECT_THAT_EXPECTED(Tester.parseSubst("(1+1)"), Succeeded()); + EXPECT_THAT_EXPECTED(Tester.parseSubst("(1)+1"), Succeeded()); + EXPECT_THAT_EXPECTED(Tester.parseSubst("((1)+1)"), Succeeded()); + EXPECT_THAT_EXPECTED(Tester.parseSubst("((1)+X)"), Succeeded()); + EXPECT_THAT_EXPECTED(Tester.parseSubst("((X)+Y)"), Succeeded()); + + expectDiagnosticError("missing operand in expression", + Tester.parseSubst("(").takeError()); + expectDiagnosticError("missing ')' at end of nested expression", + Tester.parseSubst("(1").takeError()); + expectDiagnosticError("missing operand in expression", + Tester.parseSubst("(1+").takeError()); + expectDiagnosticError("missing ')' at end of nested expression", + Tester.parseSubst("(1+1").takeError()); + expectDiagnosticError("missing ')' at end of nested expression", + Tester.parseSubst("((1+2+3").takeError()); + expectDiagnosticError("missing ')' at end of nested expression", + Tester.parseSubst("((1+2)+3").takeError()); + + // Test missing operation between operands: + expectDiagnosticError("unsupported operation '('", + Tester.parseSubst("(1)(2)").takeError()); + expectDiagnosticError("unsupported operation '('", + Tester.parseSubst("2(X)").takeError()); + + // Test more closing than opening parentheses. The diagnostic messages are + // not ideal, but for now simply check that we reject invalid input. + expectDiagnosticError("invalid operand format ')'", + Tester.parseSubst(")").takeError()); + expectDiagnosticError("unsupported operation ')'", + Tester.parseSubst("1)").takeError()); + expectDiagnosticError("unsupported operation ')'", + Tester.parseSubst("(1+2))").takeError()); + expectDiagnosticError("unsupported operation ')'", + Tester.parseSubst("(2))").takeError()); + expectDiagnosticError("unsupported operation ')'", + Tester.parseSubst("(1))(").takeError()); } TEST_F(FileCheckTest, ParsePattern) { @@ -844,6 +884,52 @@ Succeeded()); } +TEST_F(FileCheckTest, MatchParen) { + PatternTester Tester; + // Check simple parenthesized expressions + Tester.initNextPattern(); + ASSERT_FALSE(Tester.parsePattern("[[#NUMVAR:]]")); + expectNotFoundError(Tester.match("FAIL").takeError()); + expectNotFoundError(Tester.match("").takeError()); + EXPECT_THAT_EXPECTED(Tester.match("18"), Succeeded()); + + Tester.initNextPattern(); + ASSERT_FALSE(Tester.parsePattern("[[#NUMVAR + (2 + 2)]]")); + expectNotFoundError(Tester.match("21").takeError()); + EXPECT_THAT_EXPECTED(Tester.match("22"), Succeeded()); + Tester.initNextPattern(); + ASSERT_FALSE(Tester.parsePattern("[[#NUMVAR + (2)]]")); + EXPECT_THAT_EXPECTED(Tester.match("20"), Succeeded()); + Tester.initNextPattern(); + ASSERT_FALSE(Tester.parsePattern("[[#NUMVAR+(2)]]")); + EXPECT_THAT_EXPECTED(Tester.match("20"), Succeeded()); + Tester.initNextPattern(); + ASSERT_FALSE(Tester.parsePattern("[[#NUMVAR+(NUMVAR)]]")); + EXPECT_THAT_EXPECTED(Tester.match("36"), Succeeded()); + + // Check nested parenthesized expressions: + Tester.initNextPattern(); + ASSERT_FALSE(Tester.parsePattern("[[#NUMVAR+(2+(2))]]")); + EXPECT_THAT_EXPECTED(Tester.match("22"), Succeeded()); + Tester.initNextPattern(); + ASSERT_FALSE(Tester.parsePattern("[[#NUMVAR+(2+(NUMVAR))]]")); + EXPECT_THAT_EXPECTED(Tester.match("38"), Succeeded()); + Tester.initNextPattern(); + ASSERT_FALSE(Tester.parsePattern("[[#NUMVAR+((((NUMVAR))))]]")); + EXPECT_THAT_EXPECTED(Tester.match("36"), Succeeded()); + Tester.initNextPattern(); + ASSERT_FALSE(Tester.parsePattern("[[#NUMVAR+((((NUMVAR)))-1)-1]]")); + EXPECT_THAT_EXPECTED(Tester.match("34"), Succeeded()); + + // Parentheses can also be the first character after the '#': + Tester.initNextPattern(); + ASSERT_FALSE(Tester.parsePattern("[[#(NUMVAR)]]")); + EXPECT_THAT_EXPECTED(Tester.match("18"), Succeeded()); + Tester.initNextPattern(); + ASSERT_FALSE(Tester.parsePattern("[[#(NUMVAR+2)]]")); + EXPECT_THAT_EXPECTED(Tester.match("20"), Succeeded()); +} + TEST_F(FileCheckTest, Substitution) { SourceMgr SM; FileCheckPatternContext Context;