From 8e96697c7df688e33a49b7883d20c4563eca0306 Mon Sep 17 00:00:00 2001 From: Thomas Preud'homme Date: Tue, 5 Mar 2019 23:20:29 +0000 Subject: [PATCH] FileCheck [9/12]: Add support for matching formats Summary: This patch is part of a patch series to add support for FileCheck numeric expressions. This specific patch adds support for selecting a matching format to match a numeric value against (ie. decimal, hex lower case letters or hex upper case letters). This commit allows to select what format a numeric value should be matched against. The following formats are supported: decimal value, lower case hex value and upper case hex value. Matching formats impact both the format of numeric value to be matched as well as the format of accepted numbers in a definition with empty numeric expression constraint. Default for absence of format is decimal value unless the numeric expression constraint is non null and use a variable in which case the format is the one used to define that variable. Conclict of format in case of several variable being used is diagnosed and forces the user to select a matching format explicitely. This commit also enables immediates in numeric expressions to be in any radix known to StringRef's GetAsInteger method, except for legacy numeric expressions (ie [[@LINE+]] which only support decimal immediates. Copyright: - Linaro (changes up to diff 183612 of revision D55940) - GraphCore (changes in later versions of revision D55940 and in new revision created off D55940) Reviewers: jhenderson, chandlerc, jdenny, probinson, grimar, arichardson Reviewed By: jhenderson, arichardson Subscribers: daltenty, MaskRay, hiraditya, llvm-commits, probinson, dblaikie, grimar, arichardson, kristina, hfinkel, rogfer01, JonChesterfield Tags: #llvm Differential Revision: https://reviews.llvm.org/D60389 --- llvm/docs/CommandGuide/FileCheck.rst | 71 +++- llvm/lib/Support/FileCheck.cpp | 197 +++++++-- llvm/lib/Support/FileCheckImpl.h | 168 ++++++-- llvm/test/FileCheck/line-count.txt | 23 +- .../test/FileCheck/numeric-defines-diagnostics.txt | 16 +- llvm/test/FileCheck/numeric-defines.txt | 81 +++- llvm/test/FileCheck/numeric-expression.txt | 215 ++++++++-- llvm/test/FileCheck/string-defines-diagnostics.txt | 8 +- llvm/unittests/Support/FileCheckTest.cpp | 438 ++++++++++++++++++--- 9 files changed, 1009 insertions(+), 208 deletions(-) diff --git a/llvm/docs/CommandGuide/FileCheck.rst b/llvm/docs/CommandGuide/FileCheck.rst index 0072c9c..49db571 100644 --- a/llvm/docs/CommandGuide/FileCheck.rst +++ b/llvm/docs/CommandGuide/FileCheck.rst @@ -112,10 +112,11 @@ and from the command line. Sets a filecheck pattern variable ``VAR`` with value ``VALUE`` that can be used in ``CHECK:`` lines. -.. option:: -D#= +.. option:: -D#,= - Sets a filecheck numeric variable ``NUMVAR`` to the result of evaluating - ```` that can be used in ``CHECK:`` lines. See section + Sets a filecheck numeric variable ``NUMVAR`` of matching format ``FMT`` to + the result of evaluating ```` that can be used in + ``CHECK:`` lines. See section ``FileCheck Numeric Variables and Expressions`` for details on supported numeric expressions. @@ -588,27 +589,44 @@ numeric expression constraint based on those variables via a numeric substitution. This allows ``CHECK:`` directives to verify a numeric relation between two numbers, such as the need for consecutive registers to be used. -The syntax to define a numeric variable is ``[[#:]]`` where -```` is the name of the numeric variable to define to the matching -value. +The syntax to define a numeric variable is ``[[#%,:]]`` where: + +* ``%`` is an optional scanf-style matching format specifier to + indicate what number format to match (e.g. hex number). Currently accepted + format specifiers are ``%u``, ``%x`` and ``%X``. If absent, the format + specifier defaults to ``%u``. + +* ```` is the name of the numeric variable to define to the matching + value. For example: .. code-block:: llvm - ; CHECK: mov r[[#REG:]], 42 + ; CHECK: mov r[[#REG:]], 0x[[#%X,IMM:]] + +would match ``mov r5, 0xF0F0`` and set ``REG`` to the value ``5`` and ``IMM`` +to the value ``0xF0F0``. -would match ``mov r5, 42`` and set ``REG`` to the value ``5``. +The syntax of a numeric substitution is ``[[#%,]]`` where: -The syntax of a numeric substitution is ``[[#]]`` where ```` is an -expression. An expression is recursively defined as: +* ``%`` is the same matching format specifier as for defining numeric + variables but acting as a printf-style format to indicate how a numeric + expression value should be matched against. If absent, the format specifier + is inferred from the matching format of the numeric variable(s) used by the + expression constraint if any, and defaults to ``%u`` if no numeric variable + is used. In case of conflict between matching formats of several numeric + variables the format specifier is mandatory. -* a numeric operand, or -* an expression followed by an operator and a numeric operand. +* ```` is an expression. An expression is in turn recursively defined + as: -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. + * 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. The supported operators are ``+`` and ``-``. Spaces are accepted + before, after and between any of these elements. For example: @@ -616,6 +634,7 @@ For example: ; CHECK: load r[[#REG:]], [r0] ; CHECK: load r[[#REG+1]], [r1] + ; CHECK: Loading from 0x[[#%x,ADDR:] to 0x[[#ADDR + 7]] The above example would match the text: @@ -623,6 +642,7 @@ The above example would match the text: load r5, [r0] load r6, [r1] + Loading from 0xa0463440 to 0xa0463447 but would not match the text: @@ -630,8 +650,10 @@ but would not match the text: load r5, [r0] load r7, [r1] + Loading from 0xa0463440 to 0xa0463443 -due to ``7`` being unequal to ``5 + 1``. +Due to ``7`` being unequal to ``5 + 1`` and ``a0463443`` being unequal to +``a0463440 + 7``. The syntax also supports an empty expression, equivalent to writing {{[0-9]+}}, for cases where the input must contain a numeric value but the value itself @@ -646,8 +668,21 @@ to check that a value is synthesized rather than moved around. A numeric variable can also be defined to the result of a numeric expression, in which case the numeric expression is checked and if verified the variable is assigned to the value. The unified syntax for both defining numeric variables -and checking a numeric expression is thus ``[[#: ]]`` with each -element as described previously. +and checking a numeric expression is thus ``[[#%,: ]]`` +with each element as described previously. One can use this syntax to make a +testcase more self-describing by using variables instead of values: + +.. code-block:: gas + + ; CHECK: mov r[[#REG_OFFSET:]], 0x[[#%X,FIELD_OFFSET:12]] + ; CHECK-NEXT: load r[[#]], [r[[#REG_BASE:]], r[[#REG_OFFSET]]] + +which would match: + +.. code-block:: gas + + mov r4, 0xC + load r6, [r5, r4] The ``--enable-var-scope`` option has the same effect on numeric variables as on string variables. diff --git a/llvm/lib/Support/FileCheck.cpp b/llvm/lib/Support/FileCheck.cpp index 2261ecc..d6351e8 100644 --- a/llvm/lib/Support/FileCheck.cpp +++ b/llvm/lib/Support/FileCheck.cpp @@ -25,6 +25,47 @@ using namespace llvm; +Expected ExpressionFormat::getWildcardRegex() const { + switch (Value) { + case Kind::Unsigned: + return StringRef("[0-9]+"); + case Kind::HexUpper: + return StringRef("[0-9A-F]+"); + case Kind::HexLower: + return StringRef("[0-9a-f]+"); + default: + return createStringError(std::errc::invalid_argument, + "trying to match value with invalid format"); + } +} + +Expected +ExpressionFormat::getMatchingString(uint64_t IntegerValue) const { + switch (Value) { + case Kind::Unsigned: + return utostr(IntegerValue); + case Kind::HexUpper: + return utohexstr(IntegerValue, /*LowerCase=*/false); + case Kind::HexLower: + return utohexstr(IntegerValue, /*LowerCase=*/true); + default: + return createStringError(std::errc::invalid_argument, + "trying to match value with invalid format"); + } +} + +Expected +ExpressionFormat::valueFromStringRepr(StringRef StrVal, + const SourceMgr &SM) const { + bool Hex = Value == Kind::HexUpper || Value == Kind::HexLower; + uint64_t IntegerValue; + if (StrVal.getAsInteger(Hex ? 16 : 10, IntegerValue)) + return ErrorDiagnostic::get(SM, StrVal, + "unable to represent numeric value"); + + return IntegerValue; +} + Expected NumericVariableUse::eval() const { Optional Value = Variable->getValue(); if (Value) @@ -51,11 +92,28 @@ Expected BinaryOperation::eval() const { return EvalBinop(*LeftOp, *RightOp); } +ExpressionFormat BinaryOperation::getImplicitFormat() const { + ExpressionFormat LeftFormat = LeftOperand->getImplicitFormat(); + ExpressionFormat RightFormat = RightOperand->getImplicitFormat(); + + ExpressionFormat Format = + LeftFormat != ExpressionFormat::Kind::NoFormat ? LeftFormat : RightFormat; + if (LeftFormat != ExpressionFormat::Kind::NoFormat && + RightFormat != ExpressionFormat::Kind::NoFormat && + LeftFormat != RightFormat) + Format = ExpressionFormat(ExpressionFormat::Kind::Conflict); + + return Format; +} + Expected NumericSubstitution::getResult() const { - Expected EvaluatedValue = ExpressionASTPointer->eval(); + assert(ExpressionPointer->getAST() != nullptr && + "Substituting empty expression"); + Expected EvaluatedValue = ExpressionPointer->getAST()->eval(); if (!EvaluatedValue) return EvaluatedValue.takeError(); - return utostr(*EvaluatedValue); + ExpressionFormat Format = ExpressionPointer->getFormat(); + return Format.getMatchingString(*EvaluatedValue); } Expected StringSubstitution::getResult() const { @@ -113,7 +171,8 @@ char NotFoundError::ID = 0; Expected Pattern::parseNumericVariableDefinition( StringRef &Expr, FileCheckPatternContext *Context, - Optional LineNumber, const SourceMgr &SM) { + Optional LineNumber, ExpressionFormat ImplicitFormat, + const SourceMgr &SM) { Expected ParseVarResult = parseVariable(Expr, SM); if (!ParseVarResult) return ParseVarResult.takeError(); @@ -137,10 +196,14 @@ Expected Pattern::parseNumericVariableDefinition( NumericVariable *DefinedNumericVariable; auto VarTableIter = Context->GlobalNumericVariableTable.find(Name); - if (VarTableIter != Context->GlobalNumericVariableTable.end()) + if (VarTableIter != Context->GlobalNumericVariableTable.end()) { DefinedNumericVariable = VarTableIter->second; - else - DefinedNumericVariable = Context->makeNumericVariable(Name, LineNumber); + if (DefinedNumericVariable->getImplicitFormat() != ImplicitFormat) + return ErrorDiagnostic::get( + SM, Expr, "format different from previous variable definition"); + } else + DefinedNumericVariable = + Context->makeNumericVariable(Name, ImplicitFormat, LineNumber); return DefinedNumericVariable; } @@ -165,7 +228,8 @@ Expected> Pattern::parseNumericVariableUse( if (VarTableIter != Context->GlobalNumericVariableTable.end()) NumericVariable = VarTableIter->second; else { - NumericVariable = Context->makeNumericVariable(Name); + NumericVariable = Context->makeNumericVariable( + Name, ExpressionFormat(ExpressionFormat::Kind::Unsigned)); Context->GlobalNumericVariableTable[Name] = NumericVariable; } @@ -198,7 +262,8 @@ Expected> Pattern::parseNumericOperand( // Otherwise, parse it as a literal. uint64_t LiteralValue; - if (!Expr.consumeInteger(/*Radix=*/10, LiteralValue)) + if (!Expr.consumeInteger((AO == AllowedOperand::LegacyLiteral) ? 10 : 0, + LiteralValue)) return std::make_unique(LiteralValue); return ErrorDiagnostic::get(SM, Expr, @@ -244,7 +309,7 @@ Pattern::parseBinop(StringRef &Expr, std::unique_ptr LeftOp, return ErrorDiagnostic::get(SM, Expr, "missing operand in expression"); // The second operand in a legacy @LINE expression is always a literal. AllowedOperand AO = - IsLegacyLineExpr ? AllowedOperand::Literal : AllowedOperand::Any; + IsLegacyLineExpr ? AllowedOperand::LegacyLiteral : AllowedOperand::Any; Expected> RightOpResult = parseNumericOperand(Expr, AO, LineNumber, Context, SM); if (!RightOpResult) @@ -255,13 +320,47 @@ Pattern::parseBinop(StringRef &Expr, std::unique_ptr LeftOp, std::move(*RightOpResult)); } -Expected> Pattern::parseNumericSubstitutionBlock( +Expected> Pattern::parseNumericSubstitutionBlock( StringRef Expr, Optional &DefinedNumericVariable, bool IsLegacyLineExpr, Optional LineNumber, FileCheckPatternContext *Context, const SourceMgr &SM) { std::unique_ptr ExpressionASTPointer = nullptr; StringRef DefExpr = StringRef(); DefinedNumericVariable = None; + ExpressionFormat ExplicitFormat = ExpressionFormat(); + + // Parse format specifier. + size_t FormatSpecEnd = Expr.find(','); + if (FormatSpecEnd != StringRef::npos) { + Expr = Expr.ltrim(SpaceChars); + if (!Expr.consume_front("%")) + return ErrorDiagnostic::get( + SM, Expr, "invalid matching format specification in expression"); + + // Check for unknown matching format specifier and set matching format in + // class instance representing this expression. + SMLoc fmtloc = SMLoc::getFromPointer(Expr.data()); + switch (popFront(Expr)) { + case 'u': + ExplicitFormat = ExpressionFormat(ExpressionFormat::Kind::Unsigned); + break; + case 'x': + ExplicitFormat = ExpressionFormat(ExpressionFormat::Kind::HexLower); + break; + case 'X': + ExplicitFormat = ExpressionFormat(ExpressionFormat::Kind::HexUpper); + break; + default: + return ErrorDiagnostic::get(SM, fmtloc, + "invalid format specifier in expression"); + } + + Expr = Expr.ltrim(SpaceChars); + if (!Expr.consume_front(",")) + return ErrorDiagnostic::get( + SM, Expr, "invalid matching format specification in expression"); + } + // Save variable definition expression if any. size_t DefEnd = Expr.find(':'); if (DefEnd != StringRef::npos) { @@ -271,6 +370,7 @@ Expected> Pattern::parseNumericSubstitutionBlock( // Parse the expression itself. Expr = Expr.ltrim(SpaceChars); + StringRef UseExpr = Expr; if (!Expr.empty()) { // The first operand in a legacy @LINE expression is always the @LINE // pseudo variable. @@ -288,22 +388,44 @@ Expected> Pattern::parseNumericSubstitutionBlock( "unexpected characters at end of expression '" + Expr + "'"); } if (!ParseResult) - return ParseResult; + return ParseResult.takeError(); ExpressionASTPointer = std::move(*ParseResult); } + // Select format of the expression, i.e. (i) its explicit format, if any, + // otherwise (ii) its implicit format, if any, otherwise (iii) the default + // format (unsigned). Error out in case of conflicting implicit format + // without explicit format. + ExpressionFormat Format, + ImplicitFormat = ExpressionASTPointer + ? ExpressionASTPointer->getImplicitFormat() + : ExpressionFormat(ExpressionFormat::Kind::NoFormat); + if (bool(ExplicitFormat)) + Format = ExplicitFormat; + else if (ImplicitFormat == ExpressionFormat::Kind::Conflict) + return ErrorDiagnostic::get( + SM, UseExpr, + "variables with conflicting format specifier: need an explicit one"); + else if (bool(ImplicitFormat)) + Format = ImplicitFormat; + else + Format = ExpressionFormat(ExpressionFormat::Kind::Unsigned); + + std::unique_ptr ExpressionPointer = + std::make_unique(std::move(ExpressionASTPointer), Format); + // Parse the numeric variable definition. if (DefEnd != StringRef::npos) { DefExpr = DefExpr.ltrim(SpaceChars); - Expected ParseResult = - parseNumericVariableDefinition(DefExpr, Context, LineNumber, SM); + Expected ParseResult = parseNumericVariableDefinition( + DefExpr, Context, LineNumber, ExpressionPointer->getFormat(), SM); if (!ParseResult) return ParseResult.takeError(); DefinedNumericVariable = *ParseResult; } - return std::move(ExpressionASTPointer); + return std::move(ExpressionPointer); } bool Pattern::parsePattern(StringRef PatternStr, StringRef Prefix, @@ -476,10 +598,10 @@ bool Pattern::parsePattern(StringRef PatternStr, StringRef Prefix, } // Parse numeric substitution block. - std::unique_ptr ExpressionASTPointer; + std::unique_ptr ExpressionPointer; Optional DefinedNumericVariable; if (IsNumBlock) { - Expected> ParseResult = + Expected> ParseResult = parseNumericSubstitutionBlock(MatchStr, DefinedNumericVariable, IsLegacyLineExpr, LineNumber, Context, SM); @@ -487,16 +609,18 @@ bool Pattern::parsePattern(StringRef PatternStr, StringRef Prefix, logAllUnhandledErrors(ParseResult.takeError(), errs()); return true; } - ExpressionASTPointer = std::move(*ParseResult); - SubstNeeded = ExpressionASTPointer != nullptr; + ExpressionPointer = std::move(*ParseResult); + SubstNeeded = ExpressionPointer->getAST() != nullptr; if (DefinedNumericVariable) { IsDefinition = true; DefName = (*DefinedNumericVariable)->getName(); } if (SubstNeeded) SubstStr = MatchStr; - else - MatchRegexp = "[0-9]+"; + else { + ExpressionFormat Format = ExpressionPointer->getFormat(); + MatchRegexp = cantFail(Format.getWildcardRegex()); + } } // Handle variable definition: [[:(...)]] and [[#(...):(...)]]. @@ -554,8 +678,7 @@ bool Pattern::parsePattern(StringRef PatternStr, StringRef Prefix, Substitution *Substitution = IsNumBlock ? Context->makeNumericSubstitution( - SubstStr, std::move(ExpressionASTPointer), - SubstInsertIdx) + SubstStr, std::move(ExpressionPointer), SubstInsertIdx) : Context->makeStringSubstitution(SubstStr, SubstInsertIdx); Substitutions.push_back(Substitution); } @@ -676,11 +799,11 @@ Expected Pattern::match(StringRef Buffer, size_t &MatchLen, NumericVariableMatch.DefinedNumericVariable; StringRef MatchedValue = MatchInfo[CaptureParenGroup]; - uint64_t Val; - if (MatchedValue.getAsInteger(10, Val)) - return ErrorDiagnostic::get(SM, MatchedValue, - "Unable to represent numeric value"); - DefinedNumericVariable->setValue(Val); + ExpressionFormat Format = DefinedNumericVariable->getImplicitFormat(); + Expected Value = Format.valueFromStringRepr(MatchedValue, SM); + if (!Value) + return Value.takeError(); + DefinedNumericVariable->setValue(*Value); } // Like CHECK-NEXT, CHECK-EMPTY's match range is considered to start after @@ -837,10 +960,10 @@ FileCheckPatternContext::makeStringSubstitution(StringRef VarName, } Substitution *FileCheckPatternContext::makeNumericSubstitution( - StringRef ExpressionStr, - std::unique_ptr ExpressionASTPointer, size_t InsertIdx) { + StringRef ExpressionStr, std::unique_ptr Expression, + size_t InsertIdx) { Substitutions.push_back(std::make_unique( - this, ExpressionStr, std::move(ExpressionASTPointer), InsertIdx)); + this, ExpressionStr, std::move(Expression), InsertIdx)); return Substitutions.back().get(); } @@ -1104,7 +1227,8 @@ FindFirstMatchingPrefix(Regex &PrefixRE, StringRef &Buffer, void FileCheckPatternContext::createLineVariable() { assert(!LineVariable && "@LINE pseudo numeric variable already created"); StringRef LineName = "@LINE"; - LineVariable = makeNumericVariable(LineName); + LineVariable = makeNumericVariable( + LineName, ExpressionFormat(ExpressionFormat::Kind::Unsigned)); GlobalNumericVariableTable[LineName] = LineVariable; } @@ -1815,20 +1939,19 @@ Error FileCheckPatternContext::defineCmdlineVariables( // to create the necessary class instance. StringRef CmdlineDefExpr = CmdlineDef.substr(1); Optional DefinedNumericVariable; - Expected> ExpressionASTResult = + Expected> ExpressionResult = Pattern::parseNumericSubstitutionBlock( CmdlineDefExpr, DefinedNumericVariable, false, None, this, SM); - if (!ExpressionASTResult) { - Errs = joinErrors(std::move(Errs), ExpressionASTResult.takeError()); + if (!ExpressionResult) { + Errs = joinErrors(std::move(Errs), ExpressionResult.takeError()); continue; } - std::unique_ptr ExpressionASTPointer = - std::move(*ExpressionASTResult); + std::unique_ptr Expression = std::move(*ExpressionResult); // Now evaluate the expression whose value this variable should be set // to, since the expression of a command-line variable definition should // only use variables defined earlier on the command-line. If not, this // is an error and we report it. - Expected Value = ExpressionASTPointer->eval(); + Expected Value = Expression->getAST()->eval(); if (!Value) { Errs = joinErrors(std::move(Errs), Value.takeError()); continue; diff --git a/llvm/lib/Support/FileCheckImpl.h b/llvm/lib/Support/FileCheckImpl.h index dc07d22..a75530c 100644 --- a/llvm/lib/Support/FileCheckImpl.h +++ b/llvm/lib/Support/FileCheckImpl.h @@ -30,6 +30,68 @@ namespace llvm { // Numeric substitution handling code. //===----------------------------------------------------------------------===// +/// Type representing the format an expression value should be textualized into +/// for matching. Used to represent both explicit format specifiers as well as +/// implicit format from using numeric variables. +struct ExpressionFormat { + enum class Kind { + /// Denote absence of format. Used for implicit format of literals and + /// empty expressions. + NoFormat, + /// Used when there are several conflicting implicit formats in an + /// expression. + Conflict, + /// Value is an unsigned integer and should be printed as a decimal number. + Unsigned, + /// Value should be printed as an uppercase hex number. + HexUpper, + /// Value should be printed as a lowercase hex number. + HexLower + }; + +private: + Kind Value; + +public: + /// Evaluates a format to true if it can be used in a match. + explicit operator bool() const { + return Value != Kind::NoFormat && Value != Kind::Conflict; + } + + /// Define format equality: formats are equal if neither is NoFormat and + /// their kinds are the same. + bool operator==(const ExpressionFormat &Other) const { + return Value != Kind::NoFormat && Value == Other.Value; + } + + bool operator!=(const ExpressionFormat &other) const { + return !(*this == other); + } + + bool operator==(Kind OtherValue) const { return Value == OtherValue; } + + bool operator!=(Kind OtherValue) const { return !(*this == OtherValue); } + + ExpressionFormat() : Value(Kind::NoFormat){}; + explicit ExpressionFormat(Kind Value) : Value(Value){}; + + /// \returns a wildcard regular expression StringRef that matches any value + /// in the format represented by this instance, or an error if the format is + /// NoFormat or Conflict. + Expected getWildcardRegex() const; + + /// \returns the string representation of \p Value in the format represented + /// by this instance, or an error if the format is NoFormat or Conflict. + Expected getMatchingString(uint64_t Value) const; + + /// \returns the value corresponding to string representation \p StrVal + /// according to the matching format represented by this instance or an error + /// with diagnostic against \p SM if \p StrVal does not correspond to a valid + /// and representable value. + Expected valueFromStringRepr(StringRef StrVal, + const SourceMgr &SM) const; +}; + /// Base class representing the AST of a given expression. class ExpressionAST { public: @@ -38,6 +100,13 @@ public: /// Evaluates and \returns the value of the expression represented by this /// AST or an error if evaluation fails. virtual Expected eval() const = 0; + + /// \returns either the implicit format of this AST, FormatConflict if + /// implicit formats of the AST's components conflict, or NoFormat if the AST + /// has no implicit format (e.g. AST is made up of a single literal). + virtual ExpressionFormat getImplicitFormat() const { + return ExpressionFormat(); + } }; /// Class representing an unsigned literal in the AST of an expression. @@ -78,12 +147,38 @@ public: } }; +/// Class representing an expression and its matching format. +class Expression { +private: + /// Pointer to AST of the expression. + std::unique_ptr AST; + + /// Format to use (e.g. hex upper case letters) when matching the value. + ExpressionFormat Format; + +public: + /// Generic constructor for an expression represented by the given \p AST and + /// whose matching format is \p Format. + Expression(std::unique_ptr AST, ExpressionFormat Format) + : AST(std::move(AST)), Format(Format) {} + + /// \returns pointer to AST of the expression. Pointer is guaranteed to be + /// valid as long as this object is. + ExpressionAST *getAST() const { return AST.get(); } + + ExpressionFormat getFormat() const { return Format; } +}; + /// Class representing a numeric variable and its associated current value. class NumericVariable { private: /// Name of the numeric variable. StringRef Name; + /// Format to use for expressions using this variable without an explicit + /// format. + ExpressionFormat ImplicitFormat; + /// Value of numeric variable, if defined, or None otherwise. Optional Value; @@ -93,15 +188,20 @@ private: Optional DefLineNumber; public: - /// Constructor for a variable \p Name defined at line \p DefLineNumber or - /// defined before input is parsed if \p DefLineNumber is None. - explicit NumericVariable(StringRef Name, + /// Constructor for a variable \p Name with implicit format \p ImplicitFormat + /// defined at line \p DefLineNumber or defined before input is parsed if + /// \p DefLineNumber is None. + explicit NumericVariable(StringRef Name, ExpressionFormat ImplicitFormat, Optional DefLineNumber = None) - : Name(Name), DefLineNumber(DefLineNumber) {} + : Name(Name), ImplicitFormat(ImplicitFormat), + DefLineNumber(DefLineNumber) {} /// \returns name of this numeric variable. StringRef getName() const { return Name; } + /// \returns implicit format of this numeric variable. + ExpressionFormat getImplicitFormat() const { return ImplicitFormat; } + /// \returns this variable's value. Optional getValue() const { return Value; } @@ -133,6 +233,11 @@ public: /// \returns the value of the variable referenced by this instance. Expected eval() const override; + + /// \returns implicit format of this numeric variable. + ExpressionFormat getImplicitFormat() const override { + return Variable->getImplicitFormat(); + } }; /// Type of functions evaluating a given binary operation. @@ -163,6 +268,11 @@ public: /// \returns the expression value or an error if an undefined numeric /// variable is used in one of the operands. Expected eval() const override; + + /// \returns the implicit format of this AST, if any, a format conflict if + /// the implicit formats of the AST's components conflict, or no format if + /// the AST has no implicit format (e.g. AST is made of a single literal). + ExpressionFormat getImplicitFormat() const override; }; class FileCheckPatternContext; @@ -218,14 +328,14 @@ class NumericSubstitution : public Substitution { private: /// Pointer to the class representing the expression whose value is to be /// substituted. - std::unique_ptr ExpressionASTPointer; + std::unique_ptr ExpressionPointer; public: - NumericSubstitution(FileCheckPatternContext *Context, StringRef Expr, - std::unique_ptr ExprAST, size_t InsertIdx) - : Substitution(Context, Expr, InsertIdx) { - ExpressionASTPointer = std::move(ExprAST); - } + NumericSubstitution(FileCheckPatternContext *Context, StringRef ExpressionStr, + std::unique_ptr ExpressionPointer, + size_t InsertIdx) + : Substitution(Context, ExpressionStr, InsertIdx), + ExpressionPointer(std::move(ExpressionPointer)) {} /// \returns a string containing the result of evaluating the expression in /// this substitution, or an error if evaluation failed. @@ -270,6 +380,10 @@ private: /// automatically free them once they are guaranteed to no longer be used. std::vector> NumericVariables; + /// Vector holding pointers to all parsed expressions. Used to automatically + /// free the expressions once they are guaranteed to no longer be used. + std::vector> Expressions; + /// Vector holding pointers to all substitutions. Used to automatically free /// them once they are guaranteed to no longer be used. std::vector> Substitutions; @@ -307,10 +421,9 @@ private: /// Makes a new numeric substitution and registers it for destruction when /// the context is destroyed. - Substitution * - makeNumericSubstitution(StringRef ExpressionStr, - std::unique_ptr ExpressionAST, - size_t InsertIdx); + Substitution *makeNumericSubstitution(StringRef ExpressionStr, + std::unique_ptr Expression, + size_t InsertIdx); }; /// Class to represent an error holding a diagnostic with location information @@ -388,12 +501,12 @@ class Pattern { std::map VariableDefs; /// Structure representing the definition of a numeric variable in a pattern. - /// It holds the pointer to the class representing the numeric variable whose - /// value is being defined and the number of the parenthesis group in - /// RegExStr to capture that value. + /// It holds the pointer to the class instance holding the value and matching + /// format of the numeric variable whose value is being defined and the + /// number of the parenthesis group in RegExStr to capture that value. struct NumericVariableMatch { - /// Pointer to class representing the numeric variable whose value is being - /// defined. + /// Pointer to class instance holding the value and matching format of the + /// numeric variable being defined. NumericVariable *DefinedNumericVariable; /// Number of the parenthesis group in RegExStr that captures the value of @@ -457,12 +570,12 @@ public: /// \p IsLegacyLineExpr indicates whether \p Expr should be a legacy @LINE /// expression and \p Context points to the class instance holding the live /// string and numeric variables. \returns a pointer to the class instance - /// representing the AST of the expression whose value must be substitued, or - /// an error holding a diagnostic against \p SM if parsing fails. If - /// substitution was successful, sets \p DefinedNumericVariable to point to - /// the class representing the numeric variable defined in this numeric - /// substitution block, or None if this block does not define any variable. - static Expected> parseNumericSubstitutionBlock( + /// representing the expression whose value must be substitued, or an error + /// holding a diagnostic against \p SM if parsing fails. If substitution was + /// successful, sets \p DefinedNumericVariable to point to the class + /// representing the numeric variable defined in this numeric substitution + /// block, or None if this block does not define any variable. + static Expected> parseNumericSubstitutionBlock( StringRef Expr, Optional &DefinedNumericVariable, bool IsLegacyLineExpr, Optional LineNumber, FileCheckPatternContext *Context, const SourceMgr &SM); @@ -526,7 +639,8 @@ private: /// should defining such a variable be invalid. static Expected parseNumericVariableDefinition( StringRef &Expr, FileCheckPatternContext *Context, - Optional LineNumber, const SourceMgr &SM); + Optional LineNumber, ExpressionFormat ImplicitFormat, + const SourceMgr &SM); /// Parses \p Name as a (pseudo if \p IsPseudo is true) numeric variable use /// 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 @@ -536,7 +650,7 @@ private: static Expected> parseNumericVariableUse( StringRef Name, bool IsPseudo, Optional LineNumber, FileCheckPatternContext *Context, const SourceMgr &SM); - enum class AllowedOperand { LineVar, Literal, Any }; + 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 diff --git a/llvm/test/FileCheck/line-count.txt b/llvm/test/FileCheck/line-count.txt index 5d885d8..0f98a2a 100644 --- a/llvm/test/FileCheck/line-count.txt +++ b/llvm/test/FileCheck/line-count.txt @@ -62,12 +62,19 @@ 62 BAD12: [[#@LINE-1]] NOT HERE 63 ERR12: note: with "@LINE-1" equal to "61" 64 -65 CHECK: [[#@LINE]] CHECK -66 CHECK: [[# @LINE]] CHECK -67 CHECK: [[# @LINE ]] CHECK +; RUN: %ProtectFileCheckOutput \ +; RUN: not FileCheck --check-prefix BAD13 --input-file %s %s 2>&1 \ +; RUN: | FileCheck --check-prefix ERR13 %s 68 -69 CHECK: [[#@LINE-1]] -70 CHECK: [[# @LINE-1]] CHECK -71 CHECK: [[# @LINE -1]] CHECK -72 CHECK: [[# @LINE - 1]] CHECK -73 CHECK: [[# @LINE - 1 ]] CHECK +69 BAD13: [[@LINE-0xA]] +70 ERR13: line-count.txt:[[#@LINE-1]]:20: error: unexpected characters at end of expression 'xA' +71 +72 CHECK: [[#@LINE]] CHECK +73 CHECK: [[# @LINE]] CHECK +74 CHECK: [[# @LINE ]] CHECK +75 +76 CHECK: [[#@LINE-1]] +77 CHECK: [[# @LINE-1]] CHECK +78 CHECK: [[# @LINE -1]] CHECK +79 CHECK: [[# @LINE - 1]] CHECK +80 CHECK: [[# @LINE - 1 ]] CHECK diff --git a/llvm/test/FileCheck/numeric-defines-diagnostics.txt b/llvm/test/FileCheck/numeric-defines-diagnostics.txt index ae4a501..da8c9cc 100644 --- a/llvm/test/FileCheck/numeric-defines-diagnostics.txt +++ b/llvm/test/FileCheck/numeric-defines-diagnostics.txt @@ -3,11 +3,11 @@ ; Invalid variable name: starts with a digit. RUN: %ProtectFileCheckOutput \ RUN: not FileCheck -D#10VALUE=10 --input-file %s %s 2>&1 \ -RUN: | FileCheck %s --strict-whitespace --match-full-lines --check-prefix NUMERRCLIFMT +RUN: | FileCheck %s --strict-whitespace --match-full-lines --check-prefix NUMERRCLINAME -NUMERRCLIFMT:Global defines:1:46: error: invalid variable name -NUMERRCLIFMT-NEXT:Global define #1: #10VALUE=10 (parsed as: {{\[\[#10VALUE:10\]\]}}) - NUMERRCLIFMT-NEXT: ^ +NUMERRCLINAME:Global defines:1:46: error: invalid variable name +NUMERRCLINAME-NEXT:Global define #1: #10VALUE=10 (parsed as: {{\[\[#10VALUE:10\]\]}}) + NUMERRCLINAME-NEXT: ^ ; Invalid definition of pseudo variable. RUN: %ProtectFileCheckOutput \ @@ -26,3 +26,11 @@ RUN: | FileCheck %s --strict-whitespace --match-full-lines --check-prefix NUME NUMERRCLITRAIL:Global defines:1:51: error: unexpected characters after numeric variable name NUMERRCLITRAIL-NEXT:Global define #1: #VALUE+2=10 (parsed as: {{\[\[#VALUE\+2:10\]\]}}) NUMERRCLITRAIL-NEXT: ^ + +; Invalid format for variable. +RUN: not FileCheck -D#,VALUE=10 --input-file %s %s 2>&1 \ +RUN: | FileCheck %s --strict-whitespace --match-full-lines --check-prefix NUMERRCLIFMT + +NUMERRCLIFMT:Global defines:1:45: error: invalid matching format specification in expression +NUMERRCLIFMT-NEXT:Global define #1: #,VALUE=10 (parsed as: {{\[\[#,VALUE:10\]\]}}) + NUMERRCLIFMT-NEXT: ^ diff --git a/llvm/test/FileCheck/numeric-defines.txt b/llvm/test/FileCheck/numeric-defines.txt index 2cc9f55..1bd56b7 100644 --- a/llvm/test/FileCheck/numeric-defines.txt +++ b/llvm/test/FileCheck/numeric-defines.txt @@ -1,31 +1,33 @@ ; Test functionality of -D# option: numeric variables are defined to the right ; value and CHECK directives using them match as expected given the value set. -RUN: FileCheck -D#NUMVAL1=8 -D#NUMVAL2='NUMVAL1 + 4' -check-prefixes CHECKNUM1,CHECKNUM2 -input-file %s %s +; Tests with default format specifier. +RUN: FileCheck -D#NUMVAL1=8 -D#NUMVAL2='NUMVAL1 + 4' --check-prefixes CHECKNUM1,CHECKNUM2 --input-file %s %s RUN: %ProtectFileCheckOutput \ -RUN: not FileCheck -D#NUMVAL1=7 -D#NUMVAL2=12 -check-prefix CHECKNUM1 -input-file %s %s 2>&1 \ -RUN: | FileCheck %s --strict-whitespace -check-prefix NUMERRMSG1 +RUN: not FileCheck -D#NUMVAL1=7 -D#NUMVAL2=12 --check-prefix CHECKNUM1 --input-file %s %s 2>&1 \ +RUN: | FileCheck %s --strict-whitespace --check-prefix NUMERRMSG1 RUN: %ProtectFileCheckOutput \ -RUN: not FileCheck -D#NUMVAL1=8 -D#NUMVAL2=8 -check-prefix CHECKNUM2 -input-file %s %s 2>&1 \ -RUN: | FileCheck %s --strict-whitespace -check-prefix NUMERRMSG2 +RUN: not FileCheck -D#NUMVAL1=8 -D#NUMVAL2=8 --check-prefix CHECKNUM2 --input-file %s %s 2>&1 \ +RUN: | FileCheck %s --strict-whitespace --check-prefix NUMERRMSG2 RUN: %ProtectFileCheckOutput \ -RUN: not FileCheck -D#NUMVAL1=8 -D#NUMVAL2=8 -check-prefix NUMNOT -input-file %s %s 2>&1 \ -RUN: | FileCheck %s --strict-whitespace -check-prefixes NOT-NUMERRMSG1 +RUN: not FileCheck -D#NUMVAL1=8 -D#NUMVAL2=8 --check-prefix NUMNOT1 --input-file %s %s 2>&1 \ +RUN: | FileCheck %s --strict-whitespace --check-prefix NOT-NUMERRMSG1 RUN: %ProtectFileCheckOutput \ -RUN: not FileCheck -D#NUMVAL1=7 -D#NUMVAL2=12 -check-prefix NUMNOT -input-file %s %s 2>&1 \ -RUN: | FileCheck %s --strict-whitespace -check-prefixes NOT-NUMERRMSG2 -RUN: FileCheck -D#NUMVAL1=7 -D#NUMVAL2=8 -check-prefixes NUMNOT -input-file %s %s +RUN: not FileCheck -D#NUMVAL1=7 -D#NUMVAL2=12 --check-prefix NUMNOT1 --input-file %s %s 2>&1 \ +RUN: | FileCheck %s --strict-whitespace --check-prefix NOT-NUMERRMSG2 + +RUN: FileCheck -D#NUMVAL1=7 -D#NUMVAL2=8 --check-prefix NUMNOT1 --input-file %s %s Numeric value #1 = 8 Numeric value #2 = 12 CHECKNUM1: Numeric value #1 = [[#NUMVAL1]] CHECKNUM2: Numeric value #2 = [[#NUMVAL2]] -NUMNOT-NOT: Numeric value #1 = [[#NUMVAL1]] -NUMNOT-NOT: Numeric value #2 = [[#NUMVAL2]] +NUMNOT1-NOT: Numeric value #1 = [[#NUMVAL1]] +NUMNOT1-NOT: Numeric value #2 = [[#NUMVAL2]] NUMERRMSG1: defines.txt:[[#@LINE-5]]:12: error: CHECKNUM1: expected string not found in input NUMERRMSG1: defines.txt:1:1: note: scanning from here @@ -37,10 +39,61 @@ NUMERRMSG2: defines.txt:1:1: note: scanning from here NUMERRMSG2: defines.txt:1:1: note: with "NUMVAL2" equal to "8" NUMERRMSG2: defines.txt:[[#@LINE-14]]:1: note: possible intended match here -NOT-NUMERRMSG1: defines.txt:[[#@LINE-13]]:13: error: {{NUMNOT}}-NOT: excluded string found in input +NOT-NUMERRMSG1: defines.txt:[[#@LINE-13]]:14: error: {{NUMNOT1}}-NOT: excluded string found in input NOT-NUMERRMSG1: defines.txt:[[#@LINE-18]]:1: note: found here NOT-NUMERRMSG1: defines.txt:[[#@LINE-19]]:1: note: with "NUMVAL1" equal to "8" -NOT-NUMERRMSG2: defines.txt:[[#@LINE-16]]:13: error: {{NUMNOT}}-NOT: excluded string found in input +NOT-NUMERRMSG2: defines.txt:[[#@LINE-16]]:14: error: {{NUMNOT1}}-NOT: excluded string found in input NOT-NUMERRMSG2: defines.txt:[[#@LINE-21]]:1: note: found here NOT-NUMERRMSG2: defines.txt:[[#@LINE-22]]:1: note: with "NUMVAL2" equal to "12" + +; Tests with explicit format specifiers. +RUN: FileCheck -D#%X,NUMVAL3=8 -D#%X,NUMVAL4='NUMVAL3 + 4' --check-prefixes CHECKNUM3,CHECKNUM4 --input-file %s %s + +RUN: %ProtectFileCheckOutput \ +RUN: not FileCheck -D#%X,NUMVAL3=7 -D#%X,NUMVAL4=12 --check-prefix CHECKNUM3 --input-file %s %s 2>&1 \ +RUN: | FileCheck %s --strict-whitespace --check-prefix NUMERRMSG3 + +RUN: %ProtectFileCheckOutput \ +RUN: not FileCheck -D#%X,NUMVAL3=8 -D#%X,NUMVAL4=8 --check-prefix CHECKNUM4 --input-file %s %s 2>&1 \ +RUN: | FileCheck %s --strict-whitespace --check-prefix NUMERRMSG4 + +RUN: %ProtectFileCheckOutput \ +RUN: not FileCheck -D#%X,NUMVAL3=8 -D#%X,NUMVAL4=8 --check-prefix NUMNOT2 --input-file %s %s 2>&1 \ +RUN: | FileCheck %s --strict-whitespace --check-prefix NOT-NUMERRMSG3 + +RUN: %ProtectFileCheckOutput \ +RUN: not FileCheck -D#%X,NUMVAL3=7 -D#%X,NUMVAL4=12 --check-prefix NUMNOT2 --input-file %s %s 2>&1 \ +RUN: | FileCheck %s --strict-whitespace --check-prefix NOT-NUMERRMSG4 + +RUN: FileCheck -D#%X,NUMVAL3=7 -D#%X,NUMVAL4=8 --check-prefix NUMNOT2 --input-file %s %s + +; Test with explicit and default format specifiers. +RUN: FileCheck -D#NUMVAL3=8 -D#%X,NUMVAL4='NUMVAL3 + 4' --check-prefixes CHECKNUM3,CHECKNUM4 --input-file %s %s +; Test with explicit and implicit format specifiers. +RUN: FileCheck -D#%X,NUMVAL3=8 -D#NUMVAL4='NUMVAL3 + 4' --check-prefixes CHECKNUM3,CHECKNUM4 --input-file %s %s + +Numeric value #3 = 8 +Numeric value #4 = C +CHECKNUM3: Numeric value #3 = [[#NUMVAL3]] +CHECKNUM4: Numeric value #4 = [[#NUMVAL4]] +NUMNOT2-NOT: Numeric value #3 = [[#NUMVAL3]] +NUMNOT2-NOT: Numeric value #4 = [[#NUMVAL4]] + +NUMERRMSG3: defines.txt:[[#@LINE-5]]:12: error: CHECKNUM3: expected string not found in input +NUMERRMSG3: defines.txt:1:1: note: scanning from here +NUMERRMSG3: defines.txt:1:1: note: with "NUMVAL3" equal to "7" +NUMERRMSG3: defines.txt:[[#@LINE-10]]:1: note: possible intended match here + +NUMERRMSG4: defines.txt:[[#@LINE-9]]:12: error: CHECKNUM4: expected string not found in input +NUMERRMSG4: defines.txt:1:1: note: scanning from here +NUMERRMSG4: defines.txt:1:1: note: with "NUMVAL4" equal to "8" +NUMERRMSG4: defines.txt:[[#@LINE-14]]:1: note: possible intended match here + +NOT-NUMERRMSG3: defines.txt:[[#@LINE-13]]:14: error: {{NUMNOT2}}-NOT: excluded string found in input +NOT-NUMERRMSG3: defines.txt:[[#@LINE-18]]:1: note: found here +NOT-NUMERRMSG3: defines.txt:[[#@LINE-19]]:1: note: with "NUMVAL3" equal to "8" + +NOT-NUMERRMSG4: defines.txt:[[#@LINE-16]]:14: error: {{NUMNOT2}}-NOT: excluded string found in input +NOT-NUMERRMSG4: defines.txt:[[#@LINE-21]]:1: note: found here +NOT-NUMERRMSG4: defines.txt:[[#@LINE-22]]:1: note: with "NUMVAL4" equal to "C" diff --git a/llvm/test/FileCheck/numeric-expression.txt b/llvm/test/FileCheck/numeric-expression.txt index 01cd281..14e3782 100644 --- a/llvm/test/FileCheck/numeric-expression.txt +++ b/llvm/test/FileCheck/numeric-expression.txt @@ -2,18 +2,19 @@ RUN: FileCheck --input-file %s %s ; We use CHECK-NEXT directives to force a match on all lines with digits. -; Numeric variable definition without spaces. -DEF NO SPC +; Numeric variable definition with default matching format without spaces. +DEF DEFAULT FMT NO SPC 10 -CHECK-LABEL: DEF NO SPC +CHECK-LABEL: DEF DEFAULT FMT NO SPC CHECK-NEXT: [[#VAR1:]] -; Numeric variable definition with different spacing. -DEF SPC +; Numeric variable definition with default matching format with different +; spacing. +DEF DEFAULT FMT SPC 11 11 11 -CHECK-LABEL: DEF SPC +CHECK-LABEL: DEF DEFAULT FMT SPC CHECK-NEXT: [[# VAR1a:]] CHECK-NEXT: [[# VAR1b :]] CHECK-NEXT: [[# VAR1c : ]] @@ -24,63 +25,187 @@ REDEF NO SPC CHECK-LABEL: REDEF CHECK-NEXT: [[#VAR1:]] -; Numeric expressions using variables defined on other lines without spaces. -USE NO SPC +; Numeric variable definition with explicit matching format. +DEF FMT +c +D +CHECK-LABEL: DEF FMT +CHECK-NEXT: [[#%x,VAR2:]] +CHECK-NEXT: [[#%X,VAR3:]] + +; Numeric variable definition with explicit matching format with different +; spacing. +DEF FMT SPC +c +CHECK-LABEL: DEF FMT SPC +CHECK-NEXT: [[# %x , VAR2a : ]] + +; Numeric variable definition with unsupported matching format. +RUN: not FileCheck --check-prefixes ERR,INVALID-FMT-SPEC1 --input-file %s %s 2>&1 \ +RUN: | FileCheck --check-prefix INVALID-FMT-SPEC-MSG1 --strict-whitespace %s +RUN: not FileCheck --check-prefixes ERR,INVALID-FMT-SPEC2 --input-file %s %s 2>&1 \ +RUN: | FileCheck --check-prefix INVALID-FMT-SPEC-MSG2 --strict-whitespace %s + +DEF INVALID FMT +INVVAR1=a +INVVAR2=11 +ERR-LABEL: DEF INVALID FMT +INVALID-FMT-SPEC1-NEXT: INVVAR1=[[#%c,INVVAR1:]] +INVALID-FMT-SPEC2-NEXT: INVVAR2=[[#%hhd,INVVAR2:]] +INVALID-FMT-SPEC-MSG1: numeric-expression.txt:[[#@LINE-2]]:37: error: invalid format specifier in expression +INVALID-FMT-SPEC-MSG1-NEXT: {{I}}NVALID-FMT-SPEC1-NEXT: INVVAR1={{\[\[#%c,INVVAR1:\]\]}} +INVALID-FMT-SPEC-MSG1-NEXT: {{^}} ^{{$}} +INVALID-FMT-SPEC-MSG2: numeric-expression.txt:[[#@LINE-4]]:37: error: invalid format specifier in expression +INVALID-FMT-SPEC-MSG2-NEXT: {{I}}NVALID-FMT-SPEC2-NEXT: INVVAR2={{\[\[#%hhd,INVVAR2:\]\]}} +INVALID-FMT-SPEC-MSG2-NEXT: {{^}} ^{{$}} + +; Numeric expressions in explicit matching format and default matching rule using +; variables defined on other lines without spaces. +USE DEF FMT IMPL MATCH 11 12 10 +c +d +b +1a +D +E +C +1B 11 11 11 -CHECK-LABEL: USE -CHECK-NEXT: [[#VAR1]] -CHECK-NEXT: [[#VAR1+1]] -CHECK-NEXT: [[#VAR1-1]] -CHECK-NEXT: [[#VAR1a]] -CHECK-NEXT: [[#VAR1b]] -CHECK-NEXT: [[#VAR1c]] +c +c +c +c +c +CHECK-LABEL: USE DEF FMT IMPL MATCH +CHECK-NEXT: [[#%u,VAR1]] +CHECK-NEXT: [[#%u,VAR1+1]] +CHECK-NEXT: [[#%u,VAR1-1]] +CHECK-NEXT: [[#%x,VAR2]] +CHECK-NEXT: [[#%x,VAR2+1]] +CHECK-NEXT: [[#%x,VAR2-1]] +CHECK-NEXT: [[#%x,VAR2+14]] +CHECK-NEXT: [[#%X,VAR3]] +CHECK-NEXT: [[#%X,VAR3+1]] +CHECK-NEXT: [[#%X,VAR3-1]] +CHECK-NEXT: [[#%X,VAR3+14]] +CHECK-NEXT: [[#%u,VAR1a]] +CHECK-NEXT: [[#%u,VAR1b]] +CHECK-NEXT: [[#%u,VAR1c]] +CHECK-NEXT: [[#%x,VAR2a]] -; Numeric expressions using variables defined on other lines with different -; spacing. -USE SPC +; Numeric expressions in explicit matching format and default matching rule using +; variables defined on other lines with different spacing. +USE EXPL FMT IMPL MATCH SPC 11 11 +11 +12 12 12 12 12 +12 +10 +10 10 10 10 10 -CHECK-LABEL: USE SPC -CHECK-NEXT: [[# VAR1]] -CHECK-NEXT: [[# VAR1 ]] -CHECK-NEXT: [[# VAR1+1]] -CHECK-NEXT: [[# VAR1 +1]] -CHECK-NEXT: [[# VAR1 + 1]] -CHECK-NEXT: [[# VAR1 + 1 ]] -CHECK-NEXT: [[# VAR1-1]] -CHECK-NEXT: [[# VAR1 -1]] -CHECK-NEXT: [[# VAR1 - 1]] -CHECK-NEXT: [[# VAR1 - 1 ]] +CHECK-LABEL: USE EXPL FMT IMPL MATCH SPC +CHECK-NEXT: [[#%u, VAR1]] +CHECK-NEXT: [[# %u, VAR1]] +CHECK-NEXT: [[# %u, VAR1 ]] +CHECK-NEXT: [[#%u, VAR1+1]] +CHECK-NEXT: [[# %u, VAR1+1]] +CHECK-NEXT: [[# %u , VAR1+1]] +CHECK-NEXT: [[# %u , VAR1 +1]] +CHECK-NEXT: [[# %u , VAR1 + 1]] +CHECK-NEXT: [[# %u , VAR1 + 1 ]] +CHECK-NEXT: [[#%u, VAR1-1]] +CHECK-NEXT: [[# %u, VAR1-1]] +CHECK-NEXT: [[# %u , VAR1-1]] +CHECK-NEXT: [[# %u , VAR1 -1]] +CHECK-NEXT: [[# %u , VAR1 - 1]] +CHECK-NEXT: [[# %u , VAR1 - 1 ]] + +; Numeric expressions in implicit matching format and default matching rule using +; variables defined on other lines. +USE IMPL FMT IMPL MATCH +11 +12 +10 +c +d +b +1a +D +E +C +1B +CHECK-LABEL: USE IMPL FMT IMPL MATCH +CHECK-NEXT: [[#VAR1]] +CHECK-NEXT: [[#VAR1+1]] +CHECK-NEXT: [[#VAR1-1]] +CHECK-NEXT: [[#VAR2]] +CHECK-NEXT: [[#VAR2+1]] +CHECK-NEXT: [[#VAR2-1]] +CHECK-NEXT: [[#VAR2+14]] +CHECK-NEXT: [[#VAR3]] +CHECK-NEXT: [[#VAR3+1]] +CHECK-NEXT: [[#VAR3-1]] +CHECK-NEXT: [[#VAR3+14]] ; Numeric expressions using variables defined on other lines and an immediate ; interpreted as an unsigned value. ; Note: 9223372036854775819 = 0x8000000000000000 + 11 -; 9223372036854775808 = 0x8000000000000000 -USE UNSIGNED IMM +USE IMPL FMT IMPL MATCH UNSIGNED IMM 9223372036854775819 -CHECK-LABEL: USE UNSIGNED IMM -CHECK-NEXT: [[#VAR1+9223372036854775808]] +CHECK-LABEL: USE IMPL FMT IMPL MATCH UNSIGNED IMM +CHECK-NEXT: [[#VAR1+0x8000000000000000]] + +; Numeric expressions with matching format overriding the implicit format of +; variables defined on other lines. +USE CONV FMT IMPL MATCH +b +B +12 +13 +CHECK-LABEL: USE CONV FMT IMPL MATCH +CHECK-NEXT: [[# %x, VAR1]] +CHECK-NEXT: [[# %X, VAR1]] +CHECK-NEXT: [[# %u, VAR2]] +CHECK-NEXT: [[# %u, VAR3]] + +; Conflicting implicit format. +RUN: not FileCheck --check-prefixes CHECK,FMT-CONFLICT --input-file %s %s 2>&1 \ +RUN: | FileCheck --strict-whitespace --check-prefix FMT-CONFLICT-MSG %s + +VAR USE IMPL FMT CONFLICT +23 +FMT-CONFLICT-LABEL: VAR USE IMPL FMT CONFLICT +FMT-CONFLICT-NEXT: [[#VAR1 + VAR2]] +FMT-CONFLICT-MSG: numeric-expression.txt:[[#@LINE-1]]:23: error: variables with conflicting format specifier: need an explicit one +FMT-CONFLICT-MSG-NEXT: {{F}}MT-CONFLICT-NEXT: {{\[\[#VAR1 \+ VAR2\]\]}} +FMT-CONFLICT-MSG-NEXT: {{^ \^$}} + +; Explicitly specified format can override conflicting implicit formats. +VAR USE IMPL OVERRIDE FMT CONFLICT +23 +CHECK-LABEL: VAR USE IMPL OVERRIDE FMT CONFLICT +CHECK-NEXT: [[# %u, VAR1 + VAR2]] ; Numeric expressions using more than one variable defined on other lines. USE MULTI VAR 31 42 CHECK-LABEL: USE MULTI VAR -CHECK-NEXT: [[#VAR2:]] -CHECK-NEXT: [[#VAR1+VAR2]] +CHECK-NEXT: [[#VAR4:]] +CHECK-NEXT: [[#VAR1+VAR4]] ; Numeric expression using a variable defined from a numeric expression. DEF EXPR GOOD MATCH @@ -93,8 +218,8 @@ DEF EXPR GOOD MATCH ; Empty numeric expression. EMPTY NUM EXPR foo 104 bar -; CHECK-LABEL: EMPTY NUM EXPR -; CHECK-NEXT: foo [[#]] bar +CHECK-LABEL: EMPTY NUM EXPR +CHECK-NEXT: foo [[#]] bar ; Numeric expression using undefined variables. RUN: %ProtectFileCheckOutput \ @@ -187,7 +312,7 @@ BIG VALUE NUMVAR: 10000000000000000000000 BIGVAL-LABEL: BIG VALUE BIGVAL-NEXT: NUMVAR: [[#NUMVAR:]] -BIGVAL-MSG: numeric-expression.txt:[[#@LINE-3]]:9: error: Unable to represent numeric value +BIGVAL-MSG: numeric-expression.txt:[[#@LINE-3]]:9: error: unable to represent numeric value BIGVAL-MSG-NEXT: {{N}}UMVAR: 10000000000000000000000 BIGVAL-MSG-NEXT: {{^}} ^{{$}} @@ -232,3 +357,17 @@ SAME-LINE-USE2-NEXT: [[#VAR2:VAR1+1]] [[#VAR2+1]] SAME-LINE-USE-MSG2: numeric-expression.txt:[[#@LINE-1]]:42: error: numeric variable 'VAR2' defined earlier in the same CHECK directive SAME-LINE-USE-MSG2-NEXT: {{S}}AME-LINE-USE2-NEXT: {{\[\[#VAR2:VAR1\+1\]\] \[\[#VAR2\+1\]\]}} SAME-LINE-USE-MSG2-NEXT: {{^}} ^{{$}} + +; Invalid change of format in variable redefinition. +RUN: not FileCheck --check-prefix REDEF-NEW-FMT --input-file %s %s 2>&1 \ +RUN: | FileCheck --strict-whitespace --check-prefix REDEF-NEW-FMT-MSG %s + +VAR REDEF FMT CHANGE +22 +DC +REDEF-NEW-FMT-LABEL: VAR REDEF FMT CHANGE +REDEF-NEW-FMT-NEXT: [[#VAR1:]] +REDEF-NEW-FMT-NEXT: [[#%X,VAR1:]] +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: {{^ \^$}} diff --git a/llvm/test/FileCheck/string-defines-diagnostics.txt b/llvm/test/FileCheck/string-defines-diagnostics.txt index b975c38..2c88b66 100644 --- a/llvm/test/FileCheck/string-defines-diagnostics.txt +++ b/llvm/test/FileCheck/string-defines-diagnostics.txt @@ -27,11 +27,11 @@ ERRCLIVAR2: Missing variable name in command-line definition '-D=' ; Invalid variable name: starts with a digit. RUN: %ProtectFileCheckOutput \ RUN: not FileCheck -D10VALUE=10 --input-file %s %s 2>&1 \ -RUN: | FileCheck %s --strict-whitespace --match-full-lines --check-prefix ERRCLIFMT +RUN: | FileCheck %s --strict-whitespace --match-full-lines --check-prefix ERRCLINAME -ERRCLIFMT:Global defines:1:19: error: invalid variable name -ERRCLIFMT-NEXT:Global define #1: 10VALUE=10 -ERRCLIFMT-NEXT: ^ +ERRCLINAME:Global defines:1:19: error: invalid variable name +ERRCLINAME-NEXT:Global define #1: 10VALUE=10 +ERRCLINAME-NEXT: ^ ; Invalid definition of pseudo variable. RUN: %ProtectFileCheckOutput \ diff --git a/llvm/unittests/Support/FileCheckTest.cpp b/llvm/unittests/Support/FileCheckTest.cpp index ebe2362..a329d8c 100644 --- a/llvm/unittests/Support/FileCheckTest.cpp +++ b/llvm/unittests/Support/FileCheckTest.cpp @@ -8,8 +8,10 @@ #include "llvm/Support/FileCheck.h" #include "../lib/Support/FileCheckImpl.h" +#include "llvm/Support/Regex.h" #include "llvm/Testing/Support/Error.h" #include "gtest/gtest.h" +#include #include using namespace llvm; @@ -18,12 +20,216 @@ namespace { class FileCheckTest : public ::testing::Test {}; +static StringRef bufferize(SourceMgr &SM, StringRef Str) { + std::unique_ptr Buffer = + MemoryBuffer::getMemBufferCopy(Str, "TestBuffer"); + StringRef StrBufferRef = Buffer->getBuffer(); + SM.AddNewSourceBuffer(std::move(Buffer), SMLoc()); + return StrBufferRef; +} + +template +static void expectError(StringRef ExpectedMsg, Error Err) { + bool ErrorHandled = false; + EXPECT_THAT_ERROR(handleErrors(std::move(Err), + [&](const ErrorT &E) { + EXPECT_NE( + E.message().find(ExpectedMsg.str()), + std::string::npos); + ErrorHandled = true; + }), + Succeeded()); + EXPECT_TRUE(ErrorHandled); +} + +static void expectDiagnosticError(StringRef ExpectedMsg, Error Err) { + expectError(ExpectedMsg, std::move(Err)); +} + +struct ExpressionFormatParameterisedFixture + : public ::testing::TestWithParam< + std::tuple> { + void SetUp() { std::tie(Kind, AllowHex, AllowUpperHex) = GetParam(); } + + ExpressionFormat::Kind Kind; + bool AllowHex; + bool AllowUpperHex; +}; + +TEST_P(ExpressionFormatParameterisedFixture, Format) { + SourceMgr SM; + ExpressionFormat Format(Kind); + + Expected WildcardPattern = Format.getWildcardRegex(); + ASSERT_THAT_EXPECTED(WildcardPattern, Succeeded()); + Regex WildcardRegex(*WildcardPattern); + ASSERT_TRUE(WildcardRegex.isValid()); + // Does not match empty string. + EXPECT_FALSE(WildcardRegex.match("")); + // Matches all decimal digits and matches several of them. + SmallVector Matches; + StringRef DecimalDigits = "0123456789"; + ASSERT_TRUE(WildcardRegex.match(DecimalDigits, &Matches)); + EXPECT_EQ(Matches[0], DecimalDigits); + // Check non digits or digits with wrong casing are not matched. + if (AllowHex) { + StringRef HexOnlyDigits[] = {"abcdef", "ABCDEF"}; + StringRef AcceptedHexOnlyDigits = + AllowUpperHex ? HexOnlyDigits[1] : HexOnlyDigits[0]; + StringRef RefusedHexOnlyDigits = + AllowUpperHex ? HexOnlyDigits[0] : HexOnlyDigits[1]; + ASSERT_TRUE(WildcardRegex.match(AcceptedHexOnlyDigits, &Matches)); + EXPECT_EQ(Matches[0], AcceptedHexOnlyDigits); + EXPECT_FALSE(WildcardRegex.match(RefusedHexOnlyDigits)); + + EXPECT_FALSE(WildcardRegex.match("g")); + EXPECT_FALSE(WildcardRegex.match("G")); + } else { + EXPECT_FALSE(WildcardRegex.match("a")); + EXPECT_FALSE(WildcardRegex.match("A")); + } + + Expected MatchingString = Format.getMatchingString(0U); + ASSERT_THAT_EXPECTED(MatchingString, Succeeded()); + EXPECT_EQ(*MatchingString, "0"); + MatchingString = Format.getMatchingString(9U); + ASSERT_THAT_EXPECTED(MatchingString, Succeeded()); + EXPECT_EQ(*MatchingString, "9"); + Expected TenMatchingString = Format.getMatchingString(10U); + ASSERT_THAT_EXPECTED(TenMatchingString, Succeeded()); + Expected FifteenMatchingString = Format.getMatchingString(15U); + ASSERT_THAT_EXPECTED(FifteenMatchingString, Succeeded()); + StringRef ExpectedTenMatchingString, ExpectedFifteenMatchingString; + if (AllowHex) { + if (AllowUpperHex) { + ExpectedTenMatchingString = "A"; + ExpectedFifteenMatchingString = "F"; + } else { + ExpectedTenMatchingString = "a"; + ExpectedFifteenMatchingString = "f"; + } + } else { + ExpectedTenMatchingString = "10"; + ExpectedFifteenMatchingString = "15"; + } + EXPECT_EQ(*TenMatchingString, ExpectedTenMatchingString); + EXPECT_EQ(*FifteenMatchingString, ExpectedFifteenMatchingString); + + StringRef BufferizedValidValueStr = bufferize(SM, "0"); + Expected Val = + Format.valueFromStringRepr(BufferizedValidValueStr, SM); + ASSERT_THAT_EXPECTED(Val, Succeeded()); + EXPECT_EQ(*Val, 0U); + BufferizedValidValueStr = bufferize(SM, "9"); + Val = Format.valueFromStringRepr(BufferizedValidValueStr, SM); + ASSERT_THAT_EXPECTED(Val, Succeeded()); + EXPECT_EQ(*Val, 9U); + StringRef BufferizedTenStr, BufferizedInvalidTenStr, BufferizedFifteenStr; + StringRef TenStr, FifteenStr, InvalidTenStr; + if (AllowHex) { + if (AllowUpperHex) { + TenStr = "A"; + FifteenStr = "F"; + InvalidTenStr = "a"; + } else { + TenStr = "a"; + FifteenStr = "f"; + InvalidTenStr = "A"; + } + } else { + TenStr = "10"; + FifteenStr = "15"; + InvalidTenStr = "A"; + } + BufferizedTenStr = bufferize(SM, TenStr); + Val = Format.valueFromStringRepr(BufferizedTenStr, SM); + ASSERT_THAT_EXPECTED(Val, Succeeded()); + EXPECT_EQ(*Val, 10U); + BufferizedFifteenStr = bufferize(SM, FifteenStr); + Val = Format.valueFromStringRepr(BufferizedFifteenStr, SM); + ASSERT_THAT_EXPECTED(Val, Succeeded()); + EXPECT_EQ(*Val, 15U); + // Wrong casing is not tested because valueFromStringRepr() relies on + // StringRef's getAsInteger() which does not allow to restrict casing. + BufferizedInvalidTenStr = bufferize(SM, InvalidTenStr); + expectDiagnosticError( + "unable to represent numeric value", + Format.valueFromStringRepr(bufferize(SM, "G"), SM).takeError()); + + // Check boolean operator. + EXPECT_TRUE(bool(Format)); +} + +INSTANTIATE_TEST_CASE_P( + AllowedExplicitExpressionFormat, ExpressionFormatParameterisedFixture, + ::testing::Values( + std::make_tuple(ExpressionFormat::Kind::Unsigned, /*AllowHex=*/false, + /*AllowUpperHex=*/false), + std::make_tuple(ExpressionFormat::Kind::HexLower, /*AllowHex=*/true, + /*AllowUpperHex=*/false), + std::make_tuple(ExpressionFormat::Kind::HexUpper, /*AllowHex=*/true, + /*AllowUpperHex=*/true)), ); + +TEST_F(FileCheckTest, NoFormatProperties) { + ExpressionFormat NoFormat(ExpressionFormat::Kind::NoFormat); + expectError("trying to match value with invalid format", + NoFormat.getWildcardRegex().takeError()); + expectError("trying to match value with invalid format", + NoFormat.getMatchingString(18).takeError()); + EXPECT_FALSE(bool(NoFormat)); +} + +TEST_F(FileCheckTest, ConflictFormatProperties) { + ExpressionFormat ConflictFormat(ExpressionFormat::Kind::Conflict); + expectError("trying to match value with invalid format", + ConflictFormat.getWildcardRegex().takeError()); + expectError("trying to match value with invalid format", + ConflictFormat.getMatchingString(18).takeError()); + EXPECT_FALSE(bool(ConflictFormat)); +} + +TEST_F(FileCheckTest, FormatEqualityOperators) { + ExpressionFormat UnsignedFormat(ExpressionFormat::Kind::Unsigned); + ExpressionFormat UnsignedFormat2(ExpressionFormat::Kind::Unsigned); + EXPECT_TRUE(UnsignedFormat == UnsignedFormat2); + EXPECT_FALSE(UnsignedFormat != UnsignedFormat2); + + ExpressionFormat HexLowerFormat(ExpressionFormat::Kind::HexLower); + EXPECT_FALSE(UnsignedFormat == HexLowerFormat); + EXPECT_TRUE(UnsignedFormat != HexLowerFormat); + + ExpressionFormat NoFormat(ExpressionFormat::Kind::NoFormat); + ExpressionFormat NoFormat2(ExpressionFormat::Kind::NoFormat); + EXPECT_FALSE(NoFormat == NoFormat2); + EXPECT_TRUE(NoFormat != NoFormat2); + + ExpressionFormat ConflictFormat(ExpressionFormat::Kind::Conflict); + ExpressionFormat ConflictFormat2(ExpressionFormat::Kind::Conflict); + EXPECT_TRUE(ConflictFormat == ConflictFormat2); + EXPECT_FALSE(ConflictFormat != ConflictFormat2); +} + +TEST_F(FileCheckTest, FormatKindEqualityOperators) { + ExpressionFormat UnsignedFormat(ExpressionFormat::Kind::Unsigned); + EXPECT_TRUE(UnsignedFormat == ExpressionFormat::Kind::Unsigned); + EXPECT_FALSE(UnsignedFormat != ExpressionFormat::Kind::Unsigned); + EXPECT_FALSE(UnsignedFormat == ExpressionFormat::Kind::HexLower); + EXPECT_TRUE(UnsignedFormat != ExpressionFormat::Kind::HexLower); + ExpressionFormat ConflictFormat(ExpressionFormat::Kind::Conflict); + EXPECT_TRUE(ConflictFormat == ExpressionFormat::Kind::Conflict); + EXPECT_FALSE(ConflictFormat != ExpressionFormat::Kind::Conflict); + ExpressionFormat NoFormat(ExpressionFormat::Kind::NoFormat); + EXPECT_TRUE(NoFormat == ExpressionFormat::Kind::NoFormat); + EXPECT_FALSE(NoFormat != ExpressionFormat::Kind::NoFormat); +} + TEST_F(FileCheckTest, Literal) { // Eval returns the literal's value. ExpressionLiteral Ten(10); Expected Value = Ten.eval(); ASSERT_THAT_EXPECTED(Value, Succeeded()); EXPECT_EQ(10U, *Value); + EXPECT_EQ(Ten.getImplicitFormat(), ExpressionFormat::Kind::NoFormat); // Max value can be correctly represented. ExpressionLiteral Max(std::numeric_limits::max()); @@ -43,6 +249,16 @@ static std::string toString(const std::unordered_set &Set) { return Str; } +TEST_F(FileCheckTest, Expression) { + std::unique_ptr Ten = + std::make_unique(10); + ExpressionLiteral *TenPtr = Ten.get(); + Expression Expr(std::move(Ten), + ExpressionFormat(ExpressionFormat::Kind::HexLower)); + EXPECT_EQ(Expr.getAST(), TenPtr); + EXPECT_EQ(Expr.getFormat(), ExpressionFormat::Kind::HexLower); +} + static void expectUndefErrors(std::unordered_set ExpectedUndefVarNames, Error Err) { @@ -60,9 +276,12 @@ uint64_t doAdd(uint64_t OpL, uint64_t OpR) { return OpL + OpR; } TEST_F(FileCheckTest, NumericVariable) { // Undefined variable: getValue and eval fail, error returned by eval holds // the name of the undefined variable. - NumericVariable FooVar("FOO", 1); + NumericVariable FooVar("FOO", + ExpressionFormat(ExpressionFormat::Kind::Unsigned), 1); EXPECT_EQ("FOO", FooVar.getName()); + EXPECT_EQ(FooVar.getImplicitFormat(), ExpressionFormat::Kind::Unsigned); NumericVariableUse FooVarUse("FOO", &FooVar); + EXPECT_EQ(FooVarUse.getImplicitFormat(), ExpressionFormat::Kind::Unsigned); EXPECT_FALSE(FooVar.getValue()); Expected EvalResult = FooVarUse.eval(); expectUndefErrors({"FOO"}, EvalResult.takeError()); @@ -86,20 +305,24 @@ TEST_F(FileCheckTest, NumericVariable) { } TEST_F(FileCheckTest, Binop) { - NumericVariable FooVar("FOO", 1); + NumericVariable FooVar("FOO", + ExpressionFormat(ExpressionFormat::Kind::Unsigned), 1); FooVar.setValue(42); std::unique_ptr FooVarUse = std::make_unique("FOO", &FooVar); - NumericVariable BarVar("BAR", 2); + NumericVariable BarVar("BAR", + ExpressionFormat(ExpressionFormat::Kind::Unsigned), 2); BarVar.setValue(18); std::unique_ptr BarVarUse = std::make_unique("BAR", &BarVar); BinaryOperation Binop(doAdd, std::move(FooVarUse), std::move(BarVarUse)); - // Defined variable: eval returns right value. + // Defined variables: eval returns right value; implicit format is as + // expected. Expected Value = Binop.eval(); ASSERT_THAT_EXPECTED(Value, Succeeded()); EXPECT_EQ(60U, *Value); + EXPECT_EQ(Binop.getImplicitFormat(), ExpressionFormat::Kind::Unsigned); // 1 undefined variable: eval fails, error contains name of undefined // variable. @@ -112,6 +335,26 @@ TEST_F(FileCheckTest, Binop) { BarVar.clearValue(); Value = Binop.eval(); expectUndefErrors({"FOO", "BAR"}, Value.takeError()); + + // Literal + Variable has format of variable. + FooVarUse = std::make_unique("FOO", &FooVar); + std::unique_ptr Eighteen = + std::make_unique(18); + Binop = BinaryOperation(doAdd, std::move(FooVarUse), std::move(Eighteen)); + EXPECT_EQ(Binop.getImplicitFormat(), ExpressionFormat::Kind::Unsigned); + FooVarUse = std::make_unique("FOO", &FooVar); + Eighteen = std::make_unique(18); + Binop = BinaryOperation(doAdd, std::move(Eighteen), std::move(FooVarUse)); + EXPECT_EQ(Binop.getImplicitFormat(), ExpressionFormat::Kind::Unsigned); + + // Variables with different implicit format conflict. + NumericVariable BazVar("BAZ", + ExpressionFormat(ExpressionFormat::Kind::HexLower), 3); + FooVarUse = std::make_unique("BAZ", &FooVar); + std::unique_ptr BazVarUse = + std::make_unique("BAZ", &BazVar); + Binop = BinaryOperation(doAdd, std::move(FooVarUse), std::move(BazVarUse)); + EXPECT_EQ(Binop.getImplicitFormat(), ExpressionFormat::Kind::Conflict); } TEST_F(FileCheckTest, ValidVarNameStart) { @@ -126,32 +369,6 @@ TEST_F(FileCheckTest, ValidVarNameStart) { EXPECT_FALSE(Pattern::isValidVarNameStart(':')); } -static StringRef bufferize(SourceMgr &SM, StringRef Str) { - std::unique_ptr Buffer = - MemoryBuffer::getMemBufferCopy(Str, "TestBuffer"); - StringRef StrBufferRef = Buffer->getBuffer(); - SM.AddNewSourceBuffer(std::move(Buffer), SMLoc()); - return StrBufferRef; -} - -template -static void expectError(StringRef ExpectedMsg, Error Err) { - bool ErrorHandled = false; - EXPECT_THAT_ERROR(handleErrors(std::move(Err), - [&](const ErrorT &E) { - EXPECT_NE( - E.message().find(ExpectedMsg.str()), - std::string::npos); - ErrorHandled = true; - }), - Succeeded()); - EXPECT_TRUE(ErrorHandled); -} - -static void expectDiagnosticError(StringRef ExpectedMsg, Error Err) { - expectError(ExpectedMsg, std::move(Err)); -} - TEST_F(FileCheckTest, ParseVar) { SourceMgr SM; StringRef OrigVarName = bufferize(SM, "GoodVar42"); @@ -257,7 +474,7 @@ public: size_t getLineNumber() const { return LineNumber; } - Expected> + Expected> parseSubst(StringRef Expr, bool IsLegacyLineExpr = false) { StringRef ExprBufferRef = bufferize(SM, Expr); Optional DefinedNumericVariable; @@ -295,13 +512,36 @@ TEST_F(FileCheckTest, ParseNumericSubstitutionBlock) { expectDiagnosticError("unexpected characters after numeric variable name", Tester.parseSubst("VAR GARBAGE:").takeError()); + // Change of format. + expectDiagnosticError("format different from previous variable definition", + Tester.parseSubst("%X,FOO:").takeError()); + + // Invalid format. + expectDiagnosticError("invalid matching format specification in expression", + Tester.parseSubst("X,VAR1:").takeError()); + expectDiagnosticError("invalid format specifier in expression", + Tester.parseSubst("%F,VAR1:").takeError()); + expectDiagnosticError("invalid matching format specification in expression", + Tester.parseSubst("%X a,VAR1:").takeError()); + + // Acceptable variable definition. EXPECT_THAT_EXPECTED(Tester.parseSubst("VAR1:"), Succeeded()); EXPECT_THAT_EXPECTED(Tester.parseSubst(" VAR2:"), Succeeded()); EXPECT_THAT_EXPECTED(Tester.parseSubst("VAR3 :"), Succeeded()); EXPECT_THAT_EXPECTED(Tester.parseSubst("VAR3: "), Succeeded()); + + // Acceptable variable definition with format specifier. Use parsePattern for + // variables whose definition needs to be visible for later checks. + EXPECT_FALSE(Tester.parsePattern("[[#%u, VAR_UNSIGNED:]]")); + EXPECT_FALSE(Tester.parsePattern("[[#%x, VAR_LOWER_HEX:]]")); + EXPECT_THAT_EXPECTED(Tester.parseSubst("%X, VAR_UPPER_HEX:"), Succeeded()); + + // Acceptable variable definition from a numeric expression. EXPECT_THAT_EXPECTED(Tester.parseSubst("FOOBAR: FOO+1"), Succeeded()); - // Numeric expression. + // Numeric expression. Switch to next line to make above valid definition + // available in expressions. + Tester.initNextPattern(); // Invalid variable name. expectDiagnosticError("invalid operand format '%VAR'", @@ -342,8 +582,17 @@ TEST_F(FileCheckTest, ParseNumericSubstitutionBlock) { // Valid single operand expression. EXPECT_THAT_EXPECTED(Tester.parseSubst("FOO"), Succeeded()); + // Invalid format. + expectDiagnosticError("invalid matching format specification in expression", + Tester.parseSubst("X,FOO:").takeError()); + expectDiagnosticError("invalid format specifier in expression", + Tester.parseSubst("%F,FOO").takeError()); + expectDiagnosticError("invalid matching format specification in expression", + Tester.parseSubst("%X a,FOO").takeError()); + // Valid expression with 2 or more operands. EXPECT_THAT_EXPECTED(Tester.parseSubst("FOO+3"), Succeeded()); + EXPECT_THAT_EXPECTED(Tester.parseSubst("FOO+0xC"), Succeeded()); EXPECT_THAT_EXPECTED(Tester.parseSubst("FOO-3+FOO"), Succeeded()); expectDiagnosticError("unsupported operation '/'", @@ -367,6 +616,16 @@ TEST_F(FileCheckTest, ParseNumericSubstitutionBlock) { "invalid variable name", Tester.parseSubst("2", /*IsLegacyNumExpr=*/true).takeError()); + // Invalid hex literal in legacy @LINE expression. + expectDiagnosticError( + "unexpected characters at end of expression 'xC'", + Tester.parseSubst("@LINE+0xC", /*LegacyLineExpr=*/true).takeError()); + + // Valid expression with format specifier. + EXPECT_THAT_EXPECTED(Tester.parseSubst("%u, FOO"), Succeeded()); + EXPECT_THAT_EXPECTED(Tester.parseSubst("%x, FOO"), Succeeded()); + EXPECT_THAT_EXPECTED(Tester.parseSubst("%X, FOO"), Succeeded()); + // Valid legacy @LINE expression. EXPECT_THAT_EXPECTED(Tester.parseSubst("@LINE+2", /*IsLegacyNumExpr=*/true), Succeeded()); @@ -378,6 +637,18 @@ TEST_F(FileCheckTest, ParseNumericSubstitutionBlock) { expectDiagnosticError( "unexpected characters at end of expression '+2'", Tester.parseSubst("@LINE+2+2", /*IsLegacyNumExpr=*/true).takeError()); + + // Valid expression with several variables when their implicit formats do not + // conflict. + EXPECT_THAT_EXPECTED(Tester.parseSubst("FOO+VAR_UNSIGNED"), Succeeded()); + + // Valid implicit format conflict in presence of explicit formats. + EXPECT_THAT_EXPECTED(Tester.parseSubst("%X,FOO+VAR_LOWER_HEX"), Succeeded()); + + // Implicit format conflict. + expectDiagnosticError( + "variables with conflicting format specifier: need an explicit one", + Tester.parseSubst("FOO+VAR_LOWER_HEX").takeError()); } TEST_F(FileCheckTest, ParsePattern) { @@ -407,6 +678,12 @@ TEST_F(FileCheckTest, ParsePattern) { // Valid numeric substitution. EXPECT_FALSE(Tester.parsePattern("[[#FOO]]")); + + // Valid legacy @LINE expression. + EXPECT_FALSE(Tester.parsePattern("[[@LINE+2]]")); + + // Invalid legacy @LINE expression with non decimal literal. + EXPECT_TRUE(Tester.parsePattern("[[@LINE+0x3]]")); } TEST_F(FileCheckTest, Match) { @@ -417,12 +694,44 @@ TEST_F(FileCheckTest, Match) { expectNotFoundError(Tester.match("FAIL").takeError()); EXPECT_THAT_EXPECTED(Tester.match("18"), Succeeded()); - // Check matching a definition only matches a number. + // Check matching a definition only matches a number with the right format. 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(); + Tester.parsePattern("[[#%u,NUMVAR_UNSIGNED:]]"); + expectNotFoundError(Tester.match("C").takeError()); + EXPECT_THAT_EXPECTED(Tester.match("20"), Succeeded()); + Tester.initNextPattern(); + Tester.parsePattern("[[#%x,NUMVAR_LOWER_HEX:]]"); + expectNotFoundError(Tester.match("g").takeError()); + expectNotFoundError(Tester.match("C").takeError()); + EXPECT_THAT_EXPECTED(Tester.match("c"), Succeeded()); + Tester.initNextPattern(); + Tester.parsePattern("[[#%X,NUMVAR_UPPER_HEX:]]"); + expectNotFoundError(Tester.match("H").takeError()); + expectNotFoundError(Tester.match("b").takeError()); + EXPECT_THAT_EXPECTED(Tester.match("B"), Succeeded()); + + // Check matching expressions with no explicit format matches the values in + // the right format. + Tester.initNextPattern(); + Tester.parsePattern("[[#NUMVAR_UNSIGNED-5]]"); + expectNotFoundError(Tester.match("f").takeError()); + expectNotFoundError(Tester.match("F").takeError()); + EXPECT_THAT_EXPECTED(Tester.match("15"), Succeeded()); + Tester.initNextPattern(); + Tester.parsePattern("[[#NUMVAR_LOWER_HEX+1]]"); + expectNotFoundError(Tester.match("13").takeError()); + expectNotFoundError(Tester.match("D").takeError()); + EXPECT_THAT_EXPECTED(Tester.match("d"), Succeeded()); + Tester.initNextPattern(); + Tester.parsePattern("[[#NUMVAR_UPPER_HEX+1]]"); + expectNotFoundError(Tester.match("12").takeError()); + expectNotFoundError(Tester.match("c").takeError()); + EXPECT_THAT_EXPECTED(Tester.match("C"), Succeeded()); // Check matching an undefined variable returns a NotFound error. Tester.initNextPattern(); @@ -441,7 +750,7 @@ TEST_F(FileCheckTest, Match) { expectNotFoundError(Tester.match("18 21").takeError()); EXPECT_THAT_EXPECTED(Tester.match("18 20"), Succeeded()); - // Check matching a numeric expression using @LINE after match failure uses + // Check matching a numeric expression using @LINE after a match failure uses // the correct value for @LINE. Tester.initNextPattern(); ASSERT_FALSE(Tester.parsePattern("[[#@LINE]]")); @@ -476,14 +785,17 @@ TEST_F(FileCheckTest, Substitution) { // Numeric substitution blocks constituted of defined numeric variables are // substituted for the variable's value. - NumericVariable NVar("N", 1); + NumericVariable NVar("N", ExpressionFormat(ExpressionFormat::Kind::Unsigned), + 1); NVar.setValue(10); auto NVarUse = std::make_unique("N", &NVar); - NumericSubstitution SubstitutionN(&Context, "N", std::move(NVarUse), + auto ExpressionN = std::make_unique( + std::move(NVarUse), ExpressionFormat(ExpressionFormat::Kind::HexUpper)); + NumericSubstitution SubstitutionN(&Context, "N", std::move(ExpressionN), /*InsertIdx=*/30); SubstValue = SubstitutionN.getResult(); ASSERT_THAT_EXPECTED(SubstValue, Succeeded()); - EXPECT_EQ("10", *SubstValue); + EXPECT_EQ("A", *SubstValue); // Substitution of an undefined numeric variable fails, error holds name of // undefined variable. @@ -565,7 +877,8 @@ TEST_F(FileCheckTest, FileCheckContext) { GlobalDefines.emplace_back(std::string("LocalVar=FOO")); GlobalDefines.emplace_back(std::string("EmptyVar=")); GlobalDefines.emplace_back(std::string("#LocalNumVar1=18")); - GlobalDefines.emplace_back(std::string("#LocalNumVar2=LocalNumVar1+2")); + GlobalDefines.emplace_back(std::string("#%x,LocalNumVar2=LocalNumVar1+2")); + GlobalDefines.emplace_back(std::string("#LocalNumVar3=0xc")); ASSERT_THAT_ERROR(Cxt.defineCmdlineVariables(GlobalDefines, SM), Succeeded()); // Create @LINE pseudo numeric variable and check it is present by matching @@ -588,12 +901,13 @@ TEST_F(FileCheckTest, FileCheckContext) { StringRef LocalVarStr = "LocalVar"; StringRef LocalNumVar1Ref = bufferize(SM, "LocalNumVar1"); StringRef LocalNumVar2Ref = bufferize(SM, "LocalNumVar2"); + StringRef LocalNumVar3Ref = bufferize(SM, "LocalNumVar3"); StringRef EmptyVarStr = "EmptyVar"; StringRef UnknownVarStr = "UnknownVar"; Expected LocalVar = Cxt.getPatternVarValue(LocalVarStr); P = Pattern(Check::CheckPlain, &Cxt, ++LineNumber); Optional DefinedNumericVariable; - Expected> ExpressionASTPointer = + Expected> ExpressionPointer = P.parseNumericSubstitutionBlock(LocalNumVar1Ref, DefinedNumericVariable, /*IsLegacyLineExpr=*/false, LineNumber, &Cxt, SM); @@ -601,17 +915,25 @@ TEST_F(FileCheckTest, FileCheckContext) { EXPECT_EQ(*LocalVar, "FOO"); Expected EmptyVar = Cxt.getPatternVarValue(EmptyVarStr); Expected UnknownVar = Cxt.getPatternVarValue(UnknownVarStr); - ASSERT_THAT_EXPECTED(ExpressionASTPointer, Succeeded()); - Expected ExpressionVal = (*ExpressionASTPointer)->eval(); + ASSERT_THAT_EXPECTED(ExpressionPointer, Succeeded()); + Expected ExpressionVal = (*ExpressionPointer)->getAST()->eval(); ASSERT_THAT_EXPECTED(ExpressionVal, Succeeded()); EXPECT_EQ(*ExpressionVal, 18U); - ExpressionASTPointer = P.parseNumericSubstitutionBlock( + ExpressionPointer = P.parseNumericSubstitutionBlock( LocalNumVar2Ref, DefinedNumericVariable, /*IsLegacyLineExpr=*/false, LineNumber, &Cxt, SM); - ASSERT_THAT_EXPECTED(ExpressionASTPointer, Succeeded()); - ExpressionVal = (*ExpressionASTPointer)->eval(); + ASSERT_THAT_EXPECTED(ExpressionPointer, Succeeded()); + ExpressionVal = (*ExpressionPointer)->getAST()->eval(); ASSERT_THAT_EXPECTED(ExpressionVal, Succeeded()); EXPECT_EQ(*ExpressionVal, 20U); + ExpressionPointer = + P.parseNumericSubstitutionBlock(LocalNumVar3Ref, DefinedNumericVariable, + /*IsLegacyLineExpr=*/false, + LineNumber, &Cxt, SM); + ASSERT_THAT_EXPECTED(ExpressionPointer, Succeeded()); + ExpressionVal = (*ExpressionPointer)->getAST()->eval(); + ASSERT_THAT_EXPECTED(ExpressionVal, Succeeded()); + EXPECT_EQ(*ExpressionVal, 12U); ASSERT_THAT_EXPECTED(EmptyVar, Succeeded()); EXPECT_EQ(*EmptyVar, ""); expectUndefErrors({UnknownVarStr}, UnknownVar.takeError()); @@ -624,20 +946,20 @@ TEST_F(FileCheckTest, FileCheckContext) { // local variables, if it was created before. This is important because local // variable clearing due to --enable-var-scope happens after numeric // expressions are linked to the numeric variables they use. - expectUndefErrors({"LocalNumVar2"}, - (*ExpressionASTPointer)->eval().takeError()); + expectUndefErrors({"LocalNumVar3"}, + (*ExpressionPointer)->getAST()->eval().takeError()); P = Pattern(Check::CheckPlain, &Cxt, ++LineNumber); - ExpressionASTPointer = P.parseNumericSubstitutionBlock( + ExpressionPointer = P.parseNumericSubstitutionBlock( LocalNumVar1Ref, DefinedNumericVariable, /*IsLegacyLineExpr=*/false, LineNumber, &Cxt, SM); - ASSERT_THAT_EXPECTED(ExpressionASTPointer, Succeeded()); - ExpressionVal = (*ExpressionASTPointer)->eval(); + ASSERT_THAT_EXPECTED(ExpressionPointer, Succeeded()); + ExpressionVal = (*ExpressionPointer)->getAST()->eval(); expectUndefErrors({"LocalNumVar1"}, ExpressionVal.takeError()); - ExpressionASTPointer = P.parseNumericSubstitutionBlock( + ExpressionPointer = P.parseNumericSubstitutionBlock( LocalNumVar2Ref, DefinedNumericVariable, /*IsLegacyLineExpr=*/false, LineNumber, &Cxt, SM); - ASSERT_THAT_EXPECTED(ExpressionASTPointer, Succeeded()); - ExpressionVal = (*ExpressionASTPointer)->eval(); + ASSERT_THAT_EXPECTED(ExpressionPointer, Succeeded()); + ExpressionVal = (*ExpressionPointer)->getAST()->eval(); expectUndefErrors({"LocalNumVar2"}, ExpressionVal.takeError()); EmptyVar = Cxt.getPatternVarValue(EmptyVarStr); expectUndefErrors({"EmptyVar"}, EmptyVar.takeError()); @@ -655,11 +977,11 @@ TEST_F(FileCheckTest, FileCheckContext) { ASSERT_THAT_EXPECTED(GlobalVar, Succeeded()); EXPECT_EQ(*GlobalVar, "BAR"); P = Pattern(Check::CheckPlain, &Cxt, ++LineNumber); - ExpressionASTPointer = P.parseNumericSubstitutionBlock( + ExpressionPointer = P.parseNumericSubstitutionBlock( GlobalNumVarRef, DefinedNumericVariable, /*IsLegacyLineExpr=*/false, LineNumber, &Cxt, SM); - ASSERT_THAT_EXPECTED(ExpressionASTPointer, Succeeded()); - ExpressionVal = (*ExpressionASTPointer)->eval(); + ASSERT_THAT_EXPECTED(ExpressionPointer, Succeeded()); + ExpressionVal = (*ExpressionPointer)->getAST()->eval(); ASSERT_THAT_EXPECTED(ExpressionVal, Succeeded()); EXPECT_EQ(*ExpressionVal, 36U); @@ -667,11 +989,11 @@ TEST_F(FileCheckTest, FileCheckContext) { Cxt.clearLocalVars(); EXPECT_THAT_EXPECTED(Cxt.getPatternVarValue(GlobalVarStr), Succeeded()); P = Pattern(Check::CheckPlain, &Cxt, ++LineNumber); - ExpressionASTPointer = P.parseNumericSubstitutionBlock( + ExpressionPointer = P.parseNumericSubstitutionBlock( GlobalNumVarRef, DefinedNumericVariable, /*IsLegacyLineExpr=*/false, LineNumber, &Cxt, SM); - ASSERT_THAT_EXPECTED(ExpressionASTPointer, Succeeded()); - ExpressionVal = (*ExpressionASTPointer)->eval(); + ASSERT_THAT_EXPECTED(ExpressionPointer, Succeeded()); + ExpressionVal = (*ExpressionPointer)->getAST()->eval(); ASSERT_THAT_EXPECTED(ExpressionVal, Succeeded()); EXPECT_EQ(*ExpressionVal, 36U); } -- 2.7.4