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 @@ -105,12 +105,12 @@ 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 - ``FileCheck Numeric Variables and Expressions`` for details on the format - and meaning of ````. + ```` that can be used in ``CHECK:`` lines. See section + ``FileCheck Numeric Variables and Expressions`` for details on supported + numeric expressions. .. option:: -version @@ -619,11 +619,19 @@ due to ``7`` being unequal to ``5 + 1``. +A numeric variable can also be defined to the result of a numeric expression, +in which case the numeric expression constraint 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. + The ``--enable-var-scope`` option has the same effect on numeric variables as on string variables. Important note: In its current implementation, a numeric expression cannot use -a numeric variable defined on the same line. +a numeric variable with a non-empty numeric expression constraint defined on +the same line. FileCheck Pseudo Numeric Variables ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/llvm/include/llvm/Support/FileCheck.h b/llvm/include/llvm/Support/FileCheck.h --- a/llvm/include/llvm/Support/FileCheck.h +++ b/llvm/include/llvm/Support/FileCheck.h @@ -80,6 +80,12 @@ /// Name of the numeric variable. StringRef Name; + /// Pointer to numeric expression defining this numeric variable. Null for + /// pseudo variable whose value is known at parse time (e.g. @LINE pseudo + /// variable) or cleared local variable. If numeric expression is empty + /// NumExpr points to a FileCheckNumExpr with a null AST. + std::shared_ptr NumExprAST; + /// Value of numeric variable, if defined, or None otherwise. Optional Value; @@ -88,21 +94,28 @@ size_t DefLineNumber; public: - /// Constructor for a variable \p Name defined at line \p DefLineNumber. - FileCheckNumericVariable(size_t DefLineNumber, StringRef Name) - : Name(Name), DefLineNumber(DefLineNumber) {} + /// Constructor for a variable \p Name defined at line \p DefLineNumber to + /// the numeric expression represented by NumExpr. + FileCheckNumericVariable(size_t DefLineNumber, StringRef Name, + std::shared_ptr NumExprAST) + : Name(Name), NumExprAST(NumExprAST), DefLineNumber(DefLineNumber) {} /// Constructor for numeric variable \p Name with a known \p Value at parse /// time (e.g. the @LINE numeric variable). FileCheckNumericVariable(StringRef Name, uint64_t Value) - : Name(Name), Value(Value), DefLineNumber(0) {} + : Name(Name), NumExprAST(nullptr), Value(Value), DefLineNumber(0) {} /// \returns name of that numeric variable. StringRef getName() const { return Name; } /// Evaluates and returns the value of the expression represented by this - /// AST. Therefore, \returns this variable's value. - Optional eval() const { return Value; } + /// AST. Therefore, \returns this variable's value or the value of its + /// associated numeric expression, if any. + Optional eval() const; + + /// \returns whether this variable's value is known at match time, when + /// performing the substitutions. + bool isMatchTimeKnown() const; /// Appends numeric variable's name to UndefVarNames if undefined. void appendUndefVarNames(std::vector &UndefVarNames) const; @@ -429,26 +442,20 @@ /// \p IsPseudo to indicate if it is a pseudo variable, sets \p Name to the /// parsed variable name and strips \p Str from the variable name. static bool parseVariable(StringRef &Str, StringRef &Name, bool &IsPseudo); - /// Parses \p Expr for the definition of a numeric variable, returning an - /// error if \p Context already holds a string variable with the same name. - /// \returns whether parsing fails, in which case errors are reported on - /// \p SM. Otherwise, sets \p Name to the name of the parsed numeric - /// variable. - static bool parseNumericVariableDefinition(StringRef &Expr, StringRef &Name, - FileCheckPatternContext *Context, - const SourceMgr &SM); - /// Parses \p Expr for a numeric substitution block. Parameter \p Legacy - /// indicates whether Expr should be a legacy numeric substitution block. - /// Sets \p NumExprAST to point to the class instance representing the - /// numeric expression whose value must be substituted. Also sets + /// Parses \p Expr for a numeric substitution block at line \p LineNumber, + /// where 0 indicates the command-line. Parameter \p Legacy indicates whether + /// \p Expr should be a legacy numeric substitution block and \p Context + /// points to the class instance holding the live string and numeric + /// variables. Sets \p NumExprAST to point to the class instance representing + /// the numeric expression whose value must be substituted. Also sets /// \p DefinedNumericVariable to point to the class representing the numeric /// variable defined in this numeric substitution block, or nullptr if this /// block does not defined any variable. \returns whether parsing fails, in /// which case errors are reported on \p SM. - bool parseNumericSubstitutionBlock( + static bool parseNumericSubstitutionBlock( StringRef Expr, std::shared_ptr &NumExprAST, FileCheckNumericVariable *&DefinedNumericVariable, bool Legacy, - const SourceMgr &SM) const; + size_t LineNumber, FileCheckPatternContext *Context, const SourceMgr &SM); /// Parses the pattern in \p PatternStr and initializes this FileCheckPattern /// instance accordingly. /// @@ -507,22 +514,47 @@ std::shared_ptr parseNumericVariableUse(StringRef &Expr, const SourceMgr &SM) const; enum AllowedOperand { LegacyVar, LegacyLiteral, Any }; - /// Parses \p Expr for use of a numeric operand. Accepts both literal values - /// and numeric variables, depending on the value of \p AllowedOperandFlag. - /// \returns the class representing that operand in the AST of the numeric - /// expression or nullptr if parsing fails in which case errors are reported - /// on \p SM. - std::shared_ptr + /// Parses \p Expr for the definition of a numeric variable, returning an + /// error if \p Context already holds a string variable with the same name. + /// \returns whether parsing fails in which case errors are reported on + /// \p SM. Otherwise, sets \p Name to the name of the parsed numeric + /// variable. + static bool parseNumericVariableDefinition(StringRef &Expr, StringRef &Name, + FileCheckPatternContext *Context, + const SourceMgr &SM); + /// Parses \p Expr for the use of a numeric variable at line \p LineNumber, + /// where 0 indicates the command-line. Parameter \p Context points to the + /// class instance holding the live string and numeric variables. \returns + /// the pointer to the class instance representing that variable if + /// successful, or nullptr otherwise, which case errors are reported on + /// \p SM. + static std::shared_ptr + parseNumericVariableUse(StringRef &Expr, size_t LineNumber, + FileCheckPatternContext *Context, + const SourceMgr &SM); + /// Parses \p Expr for use of a numeric operand at line \p LineNumber, where + /// 0 indicates the command-line. Accepts both literal values and numeric + /// variables, depending on the value of \p AllowedOperandFlag. 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 numeric expression or nullptr if parsing fails in which case + /// errors are reported on \p SM. + static std::shared_ptr parseNumericOperand(StringRef &Expr, enum AllowedOperand AO, - const SourceMgr &SM) const; - /// Parses \p Expr for a binary operation. The left operand of this binary - /// operation is given in \p LeftOp and \p Legacy indicates whether we are - /// parsing a legacy numeric expression. \returns the class representing the - /// binary operation in the AST of the numeric expression, or nullptr if - /// parsing fails, in which case errors are reported on \p SM. - std::shared_ptr + size_t LineNumber, FileCheckPatternContext *Context, + const SourceMgr &SM); + /// Parses \p Expr for a binary operation at line \p LineNumber, where 0 + /// indicates the command-line. The left operand of this binary operation is + /// given in \p LeftOp and \p Legacy indicates whether we are parsing a + /// legacy numeric expression. Parameter \p Context points to the class + /// instance holding the live string and numeric variables. \returns the + /// class representing the binary operation in the AST of the numeric + /// expression, or nullptr if parsing fails, in which case errors are + /// reported on \p SM. + static std::shared_ptr parseBinop(StringRef &Expr, std::shared_ptr LeftOp, - bool Legacy, const SourceMgr &SM) const; + bool Legacy, size_t LineNumber, FileCheckPatternContext *Context, + const SourceMgr &SM); }; //===----------------------------------------------------------------------===// 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 @@ -24,6 +24,23 @@ using namespace llvm; +Optional FileCheckNumericVariable::eval() const { + if (Value) + return Value; + + if (NumExprAST == nullptr) + return None; + + return NumExprAST->eval(); +} + +bool FileCheckNumericVariable::isMatchTimeKnown() const { + if (Value) + return true; + + return NumExprAST != nullptr; +} + void FileCheckNumericVariable::appendUndefVarNames( std::vector &UndefVarNames) const { if (!Value) @@ -33,6 +50,11 @@ bool FileCheckNumericVariable::setValue(uint64_t NewValue) { if (Value) return true; + if (NumExprAST != nullptr) { + Optional EvaluatedValue = NumExprAST->eval(); + if (!EvaluatedValue || *EvaluatedValue != NewValue) + return true; + } Value = NewValue; return false; } @@ -41,6 +63,7 @@ if (!Value) return true; Value = None; + NumExprAST = nullptr; return false; } @@ -163,8 +186,9 @@ } std::shared_ptr -FileCheckPattern::parseNumericVariableUse(StringRef &Expr, - const SourceMgr &SM) const { +FileCheckPattern::parseNumericVariableUse(StringRef &Expr, size_t LineNumber, + FileCheckPatternContext *Context, + const SourceMgr &SM) { bool IsPseudo; StringRef Name; if (parseVariable(Expr, Name, IsPseudo)) @@ -192,25 +216,26 @@ std::shared_ptr NumericVariable = VarTableIter->second; - if (!IsPseudo && NumericVariable->getDefLineNumber() == LineNumber) { + if (!IsPseudo && NumericVariable->getDefLineNumber() == LineNumber && + !NumericVariable->isMatchTimeKnown()) { SM.PrintMessage(SMLoc::getFromPointer(Name.data()), SourceMgr::DK_Error, "numeric variable '" + Name + - "' defined on the same line as used"); + "' defined from input on the same line as used"); return nullptr; } return NumericVariable; } -std::shared_ptr -FileCheckPattern::parseNumericOperand(StringRef &Expr, enum AllowedOperand AO, - const SourceMgr &SM) const { +std::shared_ptr FileCheckPattern::parseNumericOperand( + StringRef &Expr, enum AllowedOperand AO, size_t LineNumber, + FileCheckPatternContext *Context, const SourceMgr &SM) { // Try to parse as a numeric variable use. if (AO == LegacyVar || AO == Any) { // Error reporting done in ParseNumericVariableUse(). std::shared_ptr NumericVariable = - parseNumericVariableUse(Expr, SM); + parseNumericVariableUse(Expr, LineNumber, Context, SM); if (NumericVariable) return NumericVariable; } @@ -232,10 +257,9 @@ return LeftOp - RightOp; } -std::shared_ptr -FileCheckPattern::parseBinop(StringRef &Expr, - std::shared_ptr LeftOp, - bool Legacy, const SourceMgr &SM) const { +std::shared_ptr FileCheckPattern::parseBinop( + StringRef &Expr, std::shared_ptr LeftOp, bool Legacy, + size_t LineNumber, FileCheckPatternContext *Context, const SourceMgr &SM) { Expr = Expr.ltrim(SpaceChars); if (Expr.empty()) return LeftOp; @@ -269,7 +293,7 @@ // Second operand in legacy numeric expression is a literal. enum AllowedOperand AO = Legacy ? LegacyLiteral : Any; std::shared_ptr RightOp = - parseNumericOperand(Expr, AO, SM); + parseNumericOperand(Expr, AO, LineNumber, Context, SM); if (RightOp == nullptr) { SM.PrintMessage(SMLoc::getFromPointer(Expr.data()), SourceMgr::DK_Error, "invalid operand format '" + Expr + "'"); @@ -282,46 +306,26 @@ bool FileCheckPattern::parseNumericSubstitutionBlock( StringRef Expr, std::shared_ptr &NumExprAST, FileCheckNumericVariable *&DefinedNumericVariable, bool Legacy, - const SourceMgr &SM) const { - // Parse the numeric variable definition. + size_t LineNumber, FileCheckPatternContext *Context, const SourceMgr &SM) { + StringRef DefExpr = StringRef(); + DefinedNumericVariable = nullptr; + // Save variable definition expression if any. size_t DefEnd = Expr.find(':'); if (DefEnd != StringRef::npos) { - StringRef DefExpr = Expr.substr(0, DefEnd); - StringRef UseExpr = Expr = Expr.substr(DefEnd + 1); - - DefExpr = DefExpr.ltrim(SpaceChars); - StringRef Name; - if (parseNumericVariableDefinition(DefExpr, Name, Context, SM)) { - // Invalid variable definition. Error reporting done in parsing function. - return true; - } - - DefExpr = DefExpr.ltrim(SpaceChars); - if (!DefExpr.empty()) { - SM.PrintMessage(SMLoc::getFromPointer(DefExpr.data()), - SourceMgr::DK_Error, - "invalid numeric variable definition"); - return true; - } - UseExpr = UseExpr.ltrim(SpaceChars); - if (!UseExpr.empty()) { - SM.PrintMessage( - SMLoc::getFromPointer(UseExpr.data()), SourceMgr::DK_Error, - "unexpected string after variable definition: '" + UseExpr + "'"); - return true; - } + DefExpr = Expr.substr(0, DefEnd); + Expr = Expr.substr(DefEnd + 1); + } - DefinedNumericVariable = - new FileCheckNumericVariable(this->LineNumber, Name); - } else { - // Parse the numeric expression itself. - Expr = Expr.ltrim(SpaceChars); + // Parse the numeric expression itself. + Expr = Expr.ltrim(SpaceChars); + if (!Expr.empty()) { // First operand in legacy numeric expression is the @LINE pseudo variable. enum AllowedOperand AO = Legacy ? LegacyVar : Any; - NumExprAST = parseNumericOperand(Expr, AO, SM); + NumExprAST = parseNumericOperand(Expr, AO, LineNumber, Context, SM); while (NumExprAST != nullptr && !Expr.empty()) { - NumExprAST = parseBinop(Expr, NumExprAST, Legacy, SM); + NumExprAST = + parseBinop(Expr, NumExprAST, Legacy, LineNumber, Context, SM); // Legacy numeric expressions only allow 2 operands. if (Legacy && !Expr.empty()) { SM.PrintMessage(SMLoc::getFromPointer(Expr.data()), SourceMgr::DK_Error, @@ -337,6 +341,27 @@ } } + // Parse the numeric variable definition. + if (DefEnd != StringRef::npos) { + DefExpr = DefExpr.ltrim(SpaceChars); + StringRef Name; + if (parseNumericVariableDefinition(DefExpr, Name, Context, SM)) { + // Invalid variable definition. Error reporting done in parsing function. + return true; + } + + DefExpr = DefExpr.ltrim(SpaceChars); + if (!DefExpr.empty()) { + SM.PrintMessage(SMLoc::getFromPointer(DefExpr.data()), + SourceMgr::DK_Error, + "invalid numeric variable definition"); + return true; + } + + DefinedNumericVariable = + new FileCheckNumericVariable(LineNumber, Name, NumExprAST); + } + return false; } @@ -427,14 +452,16 @@ continue; } - // String and numeric substitution blocks. String substitution blocks come + // String and numeric substitution blocks. Pattern substitution blocks come // in two forms: [[foo:.*]] and [[foo]]. The former matches .* (or some // other regex) and assigns it to the string variable 'foo'. The latter - // substitutes foo's value. Numeric substitution blocks work the same way - // as string ones, but start with a '#' sign after the double brackets. - // Both string and numeric variable names must satisfy the regular - // expression "[a-zA-Z_][0-9a-zA-Z_]*" to be valid, as this helps catch - // some common errors. + // substitutes foo's value. Numeric substitution blocks recognize the same + // form as string ones, but start with a '#' sign after the double + // brackets. They also accept a combined form which sets a numeric variable + // to the evaluation of a numeric expression. Both string and numeric + // variable names must satisfy the regular expression + // "[a-zA-Z_][0-9a-zA-Z_]*" to be valid, as this helps catch some common + // errors. if (PatternStr.startswith("[[")) { StringRef UnparsedPatternStr = PatternStr.substr(2); // Find the closing bracket pair ending the match. End is going to be an @@ -455,10 +482,11 @@ PatternStr = UnparsedPatternStr.substr(End + 2); bool IsDefinition = false; + bool SubstNeeded = false; bool Legacy = false; StringRef DefName; StringRef SubstStr; - StringRef MatchRegexp; + StringRef MatchRegexp = StringRef(); size_t SubstInsertIdx = RegExStr.size(); // Parse string variable or legacy numeric expression. @@ -482,6 +510,7 @@ } IsDefinition = (VarEndIdx != StringRef::npos); + SubstNeeded = !IsDefinition; if (IsDefinition) { if ((IsPseudo || !MatchStr.consume_front(":"))) { SM.PrintMessage(SMLoc::getFromPointer(Name.data()), @@ -515,18 +544,56 @@ FileCheckNumericVariable *DefinedNumericVariable; if (IsNumBlock) { if (parseNumericSubstitutionBlock(MatchStr, NumExprAST, - DefinedNumericVariable, Legacy, SM)) + DefinedNumericVariable, Legacy, + LineNumber, Context, SM)) return true; - if (DefinedNumericVariable) { - IsDefinition = true; + IsDefinition = DefinedNumericVariable != nullptr; + SubstNeeded = NumExprAST != nullptr; + if (IsDefinition) DefName = DefinedNumericVariable->getName(); - MatchRegexp = StringRef("[0-9]+"); - } else + if (SubstNeeded) SubstStr = MatchStr; + else + MatchRegexp = StringRef("[0-9]+"); } + // Handle variable definition: [[:(...)]] and [[#(...):(...)]]. + if (IsDefinition) { + RegExStr += '('; + ++SubstInsertIdx; + + if (IsNumBlock) { + struct FileCheckNumExprMatch NumExprDef = {DefinedNumericVariable, + CurParen}; + NumericVariableDefs[DefName] = NumExprDef; + // This store is done here rather than in match() to allow + // parseNumericVariableUse() to get the pointer to the class instance + // of the right variable definition corresponding to a given numeric + // variable use. + Context->GlobalNumericVariableTable[DefName] = + std::shared_ptr(DefinedNumericVariable); + } else { + VariableDefs[DefName] = CurParen; + // Mark string variable as defined to detect collisions between + // string and numeric variables in parseNumericVariableUse() and + // DefineCmdlineVariables() when the latter is created later than the + // former. We cannot reuse GlobalVariableTable for this by populating + // it with an empty string since we would then lose the ability to + // detect the use of an undefined variable in match(). + Context->DefinedVariableTable[DefName] = true; + } + + ++CurParen; + } + + if (!MatchRegexp.empty() && AddRegExToRegEx(MatchRegexp, CurParen, SM)) + return true; + + if (IsDefinition) + RegExStr += ')'; + // Handle substitutions: [[foo]] and [[#]]. - if (!IsDefinition) { + if (SubstNeeded) { // Handle substitution of string variables that were defined earlier on // the same line by emitting a backreference. Numeric expressions do // not support substituting a numeric variable defined on the same @@ -550,37 +617,7 @@ : Context->makeStringSubstitution(SubstStr, SubstInsertIdx); Substitutions.push_back(Substitution); } - continue; - } - - // Handle variable definitions: [[:(...)]] and - // [[#(...):(...)]]. - if (IsNumBlock) { - FileCheckNumExprMatch NumExprDef = {DefinedNumericVariable, CurParen}; - NumericVariableDefs[DefName] = NumExprDef; - // This store is done here rather than in match() to allow - // parseNumericVariableUse() to get the pointer to the class instance - // of the right variable definition corresponding to a given numeric - // variable use. - Context->GlobalNumericVariableTable[DefName] = - std::shared_ptr(DefinedNumericVariable); - } else { - VariableDefs[DefName] = CurParen; - // Mark the string variable as defined to detect collisions between - // string and numeric variables in parseNumericVariableUse() and - // DefineCmdlineVariables() when the latter is created later than the - // former. We cannot reuse GlobalVariableTable for this by populating - // it with an empty string since we would then lose the ability to - // detect the use of an undefined variable in match(). - Context->DefinedVariableTable[DefName] = true; } - RegExStr += '('; - ++CurParen; - - if (AddRegExToRegEx(MatchRegexp, CurParen, SM)) - return true; - - RegExStr += ')'; } // Handle fixed string matches. @@ -1734,9 +1771,37 @@ std::string CmdlineDefsDiag; StringRef Prefix1 = "Global define #"; StringRef Prefix2 = ": "; - for (StringRef CmdlineDef : CmdlineDefines) - CmdlineDefsDiag += - (Prefix1 + Twine(++I) + Prefix2 + CmdlineDef + "\n").str(); + StringRef Suffix1 = " (parsed as: [["; + StringRef Suffix3 = "]])"; + SmallVector, 4> CmdlineDefsIndices; + for (StringRef CmdlineDef : CmdlineDefines) { + StringRef DefNo = Twine(++I).str(); + size_t EqIdx = CmdlineDef.find('='); + if (EqIdx == StringRef::npos) { + outs() << "Missing equal sign in global definition '" << CmdlineDef + << "'"; + CmdlineDefsIndices.push_back(std::make_pair(0, 0)); + ErrorFound = true; + continue; + } + // Numeric variable definition. + if (CmdlineDef[0] == '#') { + // Append a copy command-line definition adapted to use the same format + // as in the input file to be able to reuse ParseNumericExpression. + CmdlineDefsDiag += + (Prefix1 + DefNo + Prefix2 + CmdlineDef + Suffix1).str(); + std::string Suffix2 = CmdlineDef; + Suffix2[EqIdx] = ':'; + CmdlineDefsIndices.push_back( + std::make_pair(CmdlineDefsDiag.size(), Suffix2.size())); + CmdlineDefsDiag += (Suffix2 + Suffix3 + "\n").str(); + } else { + CmdlineDefsDiag += (Prefix1 + DefNo + Prefix2).str(); + CmdlineDefsIndices.push_back( + std::make_pair(CmdlineDefsDiag.size(), CmdlineDef.size())); + CmdlineDefsDiag += (CmdlineDef + "\n").str(); + } + } // Create a buffer with fake command line content in order to display // parsing diagnostic with location information and point to the @@ -1746,66 +1811,39 @@ StringRef CmdlineDefsDiagRef = CmdLineDefsDiagBuffer->getBuffer(); SM.AddNewSourceBuffer(std::move(CmdLineDefsDiagBuffer), SMLoc()); - SmallVector CmdlineDefsDiagVec; - CmdlineDefsDiagRef.split(CmdlineDefsDiagVec, '\n', -1 /*MaxSplit*/, - false /*KeepEmpty*/); - for (StringRef CmdlineDefDiag : CmdlineDefsDiagVec) { - unsigned DefStart = CmdlineDefDiag.find(Prefix2) + Prefix2.size(); - StringRef CmdlineDef = CmdlineDefDiag.substr(DefStart); - size_t EqIdx = CmdlineDef.find('='); - if (EqIdx == StringRef::npos) { - SM.PrintMessage(SMLoc::getFromPointer(CmdlineDef.data()), - SourceMgr::DK_Error, - "Missing equal sign in global definition"); - ErrorFound = true; + for (std::pair CmdlineDefIndices : CmdlineDefsIndices) { + StringRef CmdlineDef = CmdlineDefsDiagRef.substr(CmdlineDefIndices.first, + CmdlineDefIndices.second); + // Error already handled in previous loop, skip this. + if (CmdlineDef.empty()) continue; - } // Numeric variable definition. if (CmdlineDef[0] == '#') { - StringRef CmdlineName = CmdlineDef.substr(1, EqIdx - 1); - StringRef VarName; - SMLoc CmdlineNameLoc = SMLoc::getFromPointer(CmdlineName.data()); - bool ParseError = FileCheckPattern::parseNumericVariableDefinition( - CmdlineName, VarName, this, SM); - // Check that CmdlineName starts with a valid numeric variable to be - // defined and that it is not followed that anything. That second check - // detects cases like "FOO+2" in a "FOO+2=10" definition. - if (ParseError || !CmdlineName.empty()) { - if (!ParseError) - SM.PrintMessage(CmdlineNameLoc, SourceMgr::DK_Error, - "invalid variable name"); - ErrorFound = true; - continue; - } - - // Detect collisions between string and numeric variables when the latter - // is created later than the former. - if (DefinedVariableTable.find(VarName) != DefinedVariableTable.end()) { - SM.PrintMessage( - SMLoc::getFromPointer(VarName.data()), SourceMgr::DK_Error, - "string variable with name '" + VarName + "' already exists"); + // Now parse to both check syntax is correct and create the necessary + // class instance. + StringRef CmdlineDefExpr = CmdlineDef.substr(1); + std::shared_ptr NumExprAST; + FileCheckNumericVariable *DefinedNumericVariable; + if (FileCheckPattern::parseNumericSubstitutionBlock( + CmdlineDefExpr, NumExprAST, DefinedNumericVariable, false, 0, + this, SM)) { ErrorFound = true; continue; } - - StringRef CmdlineVal = CmdlineDef.substr(EqIdx + 1); - uint64_t Val; - if (CmdlineVal.getAsInteger(10, Val)) { - SM.PrintMessage(SMLoc::getFromPointer(CmdlineVal.data()), + Optional Value = NumExprAST->eval(); + if (!Value) { + SM.PrintMessage(SMLoc::getFromPointer(CmdlineDefExpr.data()), SourceMgr::DK_Error, - "invalid value in numeric variable definition '" + - CmdlineVal + "'"); + "unable to represent numeric value"); ErrorFound = true; continue; } - auto DefinedNumericVariable = - std::make_shared(0, VarName); - DefinedNumericVariable->setValue(Val); + DefinedNumericVariable->setValue(*Value); // Record this variable definition. GlobalNumericVariableTable[DefinedNumericVariable->getName()] = - DefinedNumericVariable; + std::shared_ptr(DefinedNumericVariable); } else { // String variable definition. std::pair CmdlineNameVal = CmdlineDef.split('='); diff --git a/llvm/test/FileCheck/numeric-defines-diagnostics.txt b/llvm/test/FileCheck/numeric-defines-diagnostics.txt --- a/llvm/test/FileCheck/numeric-defines-diagnostics.txt +++ b/llvm/test/FileCheck/numeric-defines-diagnostics.txt @@ -4,30 +4,22 @@ RUN: not FileCheck -D#10VALUE=10 --input-file %s %s 2>&1 \ RUN: | FileCheck %s --strict-whitespace --check-prefix NUMERRCLIFMT -NUMERRCLIFMT: Global defines:1:20: error: invalid variable name -NUMERRCLIFMT-NEXT: Global define #1: #10VALUE=10 -NUMERRCLIFMT-NEXT: {{^ \^$}} +NUMERRCLIFMT: Global defines:1:46: error: invalid variable name +NUMERRCLIFMT-NEXT: Global define #1: #10VALUE=10 (parsed as: {{\[\[#10VALUE:10\]\]}}) +NUMERRCLIFMT-NEXT: {{^ \^$}} ; Invalid definition of pseudo variable. RUN: not FileCheck -D#@VALUE=10 --input-file %s %s 2>&1 \ RUN: | FileCheck %s --strict-whitespace --check-prefix NUMERRCLIPSEUDO -NUMERRCLIPSEUDO: Global defines:1:20: error: definition of pseudo numeric variable unsupported -NUMERRCLIPSEUDO-NEXT: Global define #1: #@VALUE=10 -NUMERRCLIPSEUDO-NEXT: {{^ \^$}} +NUMERRCLIPSEUDO: Global defines:1:45: error: definition of pseudo numeric variable unsupported +NUMERRCLIPSEUDO-NEXT: Global define #1: #@VALUE=10 (parsed as: {{\[\[#@VALUE:10\]\]}}) +NUMERRCLIPSEUDO-NEXT: {{^ \^$}} ; Invalid definition of an expression. RUN: not FileCheck -D#VALUE+2=10 --input-file %s %s 2>&1 \ RUN: | FileCheck %s --strict-whitespace --check-prefix NUMERRCLITRAIL -NUMERRCLITRAIL: Global defines:1:20: error: invalid variable name -NUMERRCLITRAIL-NEXT: Global define #1: #VALUE+2=10 -NUMERRCLITRAIL-NEXT: {{^ \^$}} - -; Invalid value: numeric expression instead of literal. -RUN: not FileCheck -D#VALUE1=3 -D#VALUE2='VALUE1 + 2' --input-file %s %s 2>&1 \ -RUN: | FileCheck %s --strict-whitespace --check-prefix NUMERRCLIEXPR - -NUMERRCLIEXPR: Global defines:2:27: error: invalid value in numeric variable definition 'VALUE1 + 2' -NUMERRCLIEXPR-NEXT: Global define #2: #VALUE2=VALUE1 + 2 -NUMERRCLIEXPR-NEXT: {{^ \^$}} +NUMERRCLITRAIL: Global defines:1:51: error: invalid numeric variable definition +NUMERRCLITRAIL-NEXT: Global define #1: #VALUE+2=10 (parsed as: {{\[\[#VALUE\+2:10\]\]}}) +NUMERRCLITRAIL-NEXT: {{^ \^$}} diff --git a/llvm/test/FileCheck/numeric-defines.txt b/llvm/test/FileCheck/numeric-defines.txt --- a/llvm/test/FileCheck/numeric-defines.txt +++ b/llvm/test/FileCheck/numeric-defines.txt @@ -1,22 +1,22 @@ ; 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#NUMVAL=12 --check-prefix CHECKNUM --input-file %s %s -RUN: not FileCheck -D#NUMVAL=8 --check-prefix CHECKNUM --input-file %s %s 2>&1 \ -RUN: | FileCheck %s --strict-whitespace --check-prefix NUMERRMSG -RUN: not FileCheck -D#NUMVAL=12 --check-prefix NUMNOT --input-file %s %s 2>&1 \ -RUN: | FileCheck %s --strict-whitespace --check-prefix NOT-NUMERRMSG -RUN: FileCheck -D#NUMVAL=8 --check-prefixes NUMNOT --input-file %s %s +RUN: FileCheck -D#NUMVAL1=8 -D#NUMVAL2='NUMVAL1 + 4' -check-prefix CHECKNUM -input-file %s %s +RUN: not FileCheck -D#NUMVAL2=8 -check-prefix CHECKNUM -input-file %s %s 2>&1 \ +RUN: | FileCheck %s --strict-whitespace -check-prefix NUMERRMSG +RUN: not FileCheck -D#NUMVAL2=12 -check-prefix NUMNOT -input-file %s %s 2>&1 \ +RUN: | FileCheck %s --strict-whitespace -check-prefix NOT-NUMERRMSG +RUN: FileCheck -D#NUMVAL2=8 -check-prefixes NUMNOT -input-file %s %s -Numeric value = 12 -CHECKNUM: Numeric value = [[#NUMVAL]] -NUMNOT-NOT: Numeric value = [[#NUMVAL]] +Numeric value #2 = 12 +CHECKNUM: Numeric value #2 = [[#NUMVAL2]] +NUMNOT-NOT: Numeric value #2 = [[#NUMVAL2]] NUMERRMSG: defines.txt:[[#@LINE-3]]:11: error: CHECKNUM: expected string not found in input NUMERRMSG: defines.txt:1:1: note: scanning from here -NUMERRMSG: defines.txt:1:1: note: with "NUMVAL" equal to "8" +NUMERRMSG: defines.txt:1:1: note: with "NUMVAL2" equal to "8" NUMERRMSG: defines.txt:[[#@LINE-7]]:1: note: possible intended match here NOT-NUMERRMSG: defines.txt:[[#@LINE-7]]:13: error: {{NUMNOT}}-NOT: excluded string found in input NOT-NUMERRMSG: defines.txt:[[#@LINE-10]]:1: note: found here -NOT-NUMERRMSG: defines.txt:[[#@LINE-11]]:1: note: with "NUMVAL" equal to "12" +NOT-NUMERRMSG: defines.txt:[[#@LINE-11]]:1: note: with "NUMVAL2" equal to "12" 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 @@ -68,6 +68,20 @@ CHECK-LABEL: USE UNSIGNED IMM CHECK-NEXT: [[#VAR1+9223372036854775808]] +; Numeric expression using a variable defined from a numeric expression. +DEF EXPR GOOD MATCH +42 +41 43 +; CHECK-LABEL: DEF EXPR GOOD MATCH +; CHECK-NEXT: [[# VAR42:VAR1+31]] +; CHECK-NEXT: [[# VAR41: VAR42-1]] [[# VAR41 + 2]] + +; Empty numeric expression. +EMPTY NUM EXPR +foo 104 bar +; CHECK-LABEL: EMPTY NUM EXPR +; CHECK-NEXT: foo [[#]] bar + ; Numeric expression using undefined variable. RUN: not FileCheck --check-prefix UNDEF-USE --input-file %s %s 2>&1 \ RUN: | FileCheck --strict-whitespace --check-prefix UNDEF-USE-MSG %s @@ -125,6 +139,17 @@ INPUT-NUM-CONFLICT: numeric-expression.txt:[[#@LINE-7]]:22: error: string variable with name 'STRVAR' already exists INPUT-NUM-CONFLICT-NEXT: CONFLICT4: redef2 {{\[\[#STRVAR:\]\]}} INPUT-NUM-CONFLICT-NEXT: {{^ \^$}} -CLI-NUM-CONFLICT: Global defines:2:20: error: string variable with name 'STRVAR' already exists -CLI-NUM-CONFLICT-NEXT: Global define #2: #STRVAR=42 -CLI-NUM-CONFLICT-NEXT: {{^ \^$}} +CLI-NUM-CONFLICT: Global defines:2:45: error: string variable with name 'STRVAR' already exists +CLI-NUM-CONFLICT-NEXT: Global define #2: #STRVAR=42 (parsed as: {{\[\[#STRVAR:42\]\]}}) +CLI-NUM-CONFLICT-NEXT: {{^ \^$}} + +; Verify that when variable is set to an expression the expression is still +; checked. +RUN: not FileCheck -check-prefix DEF-EXPR-FAIL -input-file %s %s + +DEF EXPR WRONG MATCH +20 +43 +DEF-EXPR-FAIL-LABEL: DEF EXPR WRONG MATCH +DEF-EXPR-FAIL-NEXT: [[# VAR20:]] +DEF-EXPR-FAIL-NEXT: [[# VAR42: VAR20+22]] 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 @@ -28,52 +28,74 @@ EXPECT_TRUE(UndefVarNames.empty()); } +uint64_t doAdd(uint64_t OpL, uint64_t OpR) { return OpL + OpR; } + TEST_F(FileCheckTest, NumericVariable) { - // Undefined variable: eval and clearValue fails, appendUndefVarNames returns - // the variable and setValue works. - FileCheckNumericVariable FooVar = FileCheckNumericVariable(1, "FOO"); - EXPECT_EQ("FOO", FooVar.getName()); - llvm::Optional Value = FooVar.eval(); + // Undefined variable: isMatchTimeKnown returns false, eval and clearValue + // fail, appendUndefVarNames returns the variable and setValue works. + auto FooVar = std::make_shared(1, "FOO", nullptr); + EXPECT_EQ("FOO", FooVar->getName()); + EXPECT_FALSE(FooVar->isMatchTimeKnown()); + llvm::Optional Value = FooVar->eval(); EXPECT_FALSE(Value); - EXPECT_TRUE(FooVar.clearValue()); + EXPECT_TRUE(FooVar->clearValue()); std::vector UndefVarNames; EXPECT_TRUE(UndefVarNames.empty()); - FooVar.appendUndefVarNames(UndefVarNames); + FooVar->appendUndefVarNames(UndefVarNames); EXPECT_EQ(1U, UndefVarNames.size()); EXPECT_EQ("FOO", UndefVarNames[0]); - EXPECT_FALSE(FooVar.setValue(42)); + EXPECT_FALSE(FooVar->setValue(42)); - // Defined variable: eval returns value set, setValue fails and - // appendUndefVarNames is a nop. - Value = FooVar.eval(); + // Defined variable: isMatchTimeKnown returns true, eval returns value set, + // setValue fails and appendUndefVarNames is a nop. + EXPECT_TRUE(FooVar->isMatchTimeKnown()); + Value = FooVar->eval(); EXPECT_TRUE(Value); EXPECT_EQ(42U, *Value); - EXPECT_TRUE(FooVar.setValue(43)); - Value = FooVar.eval(); + EXPECT_TRUE(FooVar->setValue(43)); + Value = FooVar->eval(); EXPECT_TRUE(Value); EXPECT_EQ(42U, *Value); UndefVarNames.clear(); EXPECT_TRUE(UndefVarNames.empty()); - FooVar.appendUndefVarNames(UndefVarNames); + FooVar->appendUndefVarNames(UndefVarNames); EXPECT_TRUE(UndefVarNames.empty()); + // Undefined variable set from numeric expression: isMatchTimeKnown returns + // true, eval returns value of expression, and setValue succeeds. + auto One = std::make_shared(1); + auto Binop = std::make_shared(doAdd, FooVar, One); + FileCheckNumericVariable FoobarVar = + FileCheckNumericVariable(2, "FOOBAR", Binop); + EXPECT_TRUE(FoobarVar.isMatchTimeKnown()); + Value = FoobarVar.eval(); + EXPECT_TRUE(Value); + EXPECT_EQ(43U, *Value); + EXPECT_FALSE(FoobarVar.setValue(43)); + Value = FoobarVar.eval(); + EXPECT_TRUE(Value); + EXPECT_EQ(43U, *Value); + // Clearing variable: eval fails and clearValue again fails. // appendUndefVarNames returns the variable again. - EXPECT_FALSE(FooVar.clearValue()); - Value = FooVar.eval(); + EXPECT_FALSE(FooVar->clearValue()); + EXPECT_FALSE(FoobarVar.clearValue()); + Value = FooVar->eval(); + EXPECT_FALSE(Value); + Value = FoobarVar.eval(); EXPECT_FALSE(Value); - EXPECT_TRUE(FooVar.clearValue()); + EXPECT_TRUE(FooVar->clearValue()); EXPECT_TRUE(UndefVarNames.empty()); - FooVar.appendUndefVarNames(UndefVarNames); + FooVar->appendUndefVarNames(UndefVarNames); EXPECT_EQ(1U, UndefVarNames.size()); EXPECT_EQ("FOO", UndefVarNames[0]); } -uint64_t doAdd(uint64_t OpL, uint64_t OpR) { return OpL + OpR; } - TEST_F(FileCheckTest, Binop) { - auto FooVar = std::make_shared("FOO", 42); - auto BarVar = std::make_shared("BAR", 18); + auto FooVar = std::make_shared(1, "FOO", nullptr); + FooVar->setValue(42); + auto BarVar = std::make_shared(2, "BAR", nullptr); + BarVar->setValue(18); FileCheckASTBinop Binop = FileCheckASTBinop(doAdd, FooVar, BarVar); // Defined variable: eval returns right value, no undefined variable @@ -219,19 +241,13 @@ P = FileCheckPattern(Check::CheckPlain, &Context, LineNumber++); } - bool parseNumVarDefExpect(StringRef Expr) { - StringRef ExprBufferRef = bufferize(SM, Expr); - StringRef Name; - return FileCheckPattern::parseNumericVariableDefinition(ExprBufferRef, Name, - &Context, SM); - } - bool parseSubstExpect(StringRef Expr) { StringRef ExprBufferRef = bufferize(SM, Expr); std::shared_ptr NumExprAST; FileCheckNumericVariable *DefinedNumericVariable; - return P.parseNumericSubstitutionBlock(ExprBufferRef, NumExprAST, - DefinedNumericVariable, false, SM); + return FileCheckPattern::parseNumericSubstitutionBlock( + ExprBufferRef, NumExprAST, DefinedNumericVariable, false, + LineNumber - 1, &Context, SM); } bool parsePatternExpect(StringRef Pattern) { @@ -246,19 +262,6 @@ } }; -TEST_F(FileCheckTest, ParseNumericVariableDefinition) { - PatternTester Tester; - - // Invalid definition of pseudo. - EXPECT_TRUE(Tester.parseNumVarDefExpect("@LINE")); - - // Conflict with pattern variable. - EXPECT_TRUE(Tester.parseNumVarDefExpect("BAR")); - - // Defined variable. - EXPECT_FALSE(Tester.parseNumVarDefExpect("FOO")); -} - TEST_F(FileCheckTest, ParseExpr) { PatternTester Tester; @@ -269,17 +272,18 @@ EXPECT_TRUE(Tester.parseSubstExpect("@FOO:")); EXPECT_TRUE(Tester.parseSubstExpect("@LINE:")); + // Conflict with pattern variable. + EXPECT_TRUE(Tester.parseSubstExpect("BAR:")); + // Garbage after name of variable being defined. EXPECT_TRUE(Tester.parseSubstExpect("VAR GARBAGE:")); - // Variable defined to numeric expression. - EXPECT_TRUE(Tester.parseSubstExpect("VAR1: FOO")); - // Acceptable variable definition. EXPECT_FALSE(Tester.parseSubstExpect("VAR1:")); EXPECT_FALSE(Tester.parseSubstExpect(" VAR2:")); EXPECT_FALSE(Tester.parseSubstExpect("VAR3 :")); EXPECT_FALSE(Tester.parseSubstExpect("VAR3: ")); + EXPECT_FALSE(Tester.parsePatternExpect("[[#FOOBAR: FOO+1]]")); // Numeric expression. @@ -311,6 +315,7 @@ // Valid expression. EXPECT_FALSE(Tester.parseSubstExpect("@LINE+5")); EXPECT_FALSE(Tester.parseSubstExpect("FOO+4")); + EXPECT_FALSE(Tester.parseSubstExpect("FOOBAR")); Tester.initNextPattern(); EXPECT_FALSE(Tester.parsePatternExpect("[[#FOO+FOO]]")); EXPECT_FALSE(Tester.parsePatternExpect("[[#FOO+3-FOO]]")); @@ -342,7 +347,6 @@ EXPECT_TRUE(Tester.parsePatternExpect("[[#42INVALID]]")); EXPECT_TRUE(Tester.parsePatternExpect("[[#@FOO]]")); EXPECT_TRUE(Tester.parsePatternExpect("[[#@LINE/2]]")); - EXPECT_TRUE(Tester.parsePatternExpect("[[#YUP:@LINE]]")); // Valid numeric expressions and numeric variable definition. EXPECT_FALSE(Tester.parsePatternExpect("[[#FOO]]")); @@ -386,8 +390,11 @@ // Substitution of defined pseudo and non-pseudo numeric variable returns the // right value, getUndefVarNames does not return any variable. - auto LineVar = std::make_shared("@LINE", 42); - auto NVar = std::make_shared("N", 10); + auto LineVar = + std::make_shared(1, "@LINE", nullptr); + auto NVar = std::make_shared(1, "N", nullptr); + LineVar->setValue(42); + NVar->setValue(10); FileCheckNumericSubstitution SubstitutionLine = FileCheckNumericSubstitution(&Context, "@LINE", LineVar, 12); FileCheckNumericSubstitution SubstitutionN = @@ -541,7 +548,7 @@ FileCheckNumericVariable *DefinedNumericVariable; EXPECT_FALSE(P.parseNumericSubstitutionBlock(LocalNumVarRef, NumExprAST, DefinedNumericVariable, - false /*Legacy*/, SM)); + false /*Legacy*/, 1, &Cxt, SM)); EXPECT_TRUE(LocalVar); EXPECT_EQ(*LocalVar, "FOO"); llvm::Optional NumExprVal = NumExprAST->eval(); @@ -565,7 +572,7 @@ P = FileCheckPattern(Check::CheckPlain, &Cxt, 2); EXPECT_TRUE(P.parseNumericSubstitutionBlock(LocalNumVarRef, NumExprAST, DefinedNumericVariable, - false /*Legacy*/, SM)); + false /*Legacy*/, 2, &Cxt, SM)); EmptyVar = Cxt.getPatternVarValue(EmptyVarStr); EXPECT_FALSE(EmptyVar); @@ -582,7 +589,7 @@ P = FileCheckPattern(Check::CheckPlain, &Cxt, 3); EXPECT_FALSE(P.parseNumericSubstitutionBlock(GlobalNumVarRef, NumExprAST, DefinedNumericVariable, - false /*Legacy*/, SM)); + false /*Legacy*/, 3, &Cxt, SM)); NumExprVal = NumExprAST->eval(); EXPECT_TRUE(NumExprVal); EXPECT_EQ(*NumExprVal, 36U); @@ -594,7 +601,7 @@ P = FileCheckPattern(Check::CheckPlain, &Cxt, 4); EXPECT_FALSE(P.parseNumericSubstitutionBlock(GlobalNumVarRef, NumExprAST, DefinedNumericVariable, - false /*Legacy*/, SM)); + false /*Legacy*/, 4, &Cxt, SM)); NumExprVal = NumExprAST->eval(); EXPECT_TRUE(NumExprVal); EXPECT_EQ(*NumExprVal, 36U);