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,10 +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 ``VAR`` to ```` that can be used - in ``CHECK:`` lines. + Sets a filecheck numeric variable ``VAR`` 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 . .. option:: -version @@ -585,16 +587,12 @@ would match ``mov r5, 42`` and set ``REG`` to the value ``5``. The syntax to check a numeric expression constraint is -``[[#]]`` where: +``[[#]]`` where: -*```` is the name of a numeric variable defined on a previous line. - -*```` is an optional numeric operation to perform on the value of -````. Currently supported numeric operations are ``+`` and ``-``. - -*```` is the immediate value that constitutes the second operand of -the numeric operation . It must be present if ```` is present, -absent otherwise. +``>`` is a numeric expression whose operands are either numeric + variables previously defined or integer literals. Currently supported + numeric operations are ``+`` and ``-``. A single numeric variable or + integer literal is also accepted. For example: 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 @@ -40,29 +40,57 @@ // Numeric expression handling code. //===----------------------------------------------------------------------===// -class FileCheckASTBinop; +/// Base class representing the AST of a given numeric expression. +class FileCheckNumExprAST { +public: + virtual ~FileCheckNumExprAST() = default; + + /// Evaluate the value of the expression represented by this AST. + virtual llvm::Optional eval() const = 0; + + /// Append names of undefined variables used in the expression represented by + /// this AST. Must be overriden in any subclass representing an expression + /// that can contain a variable. + virtual void + appendUndefVarNames(std::vector &UndefVarNames) const {} +}; + +/// Class representing a literal in the AST of a numeric expression. +class FileCheckNumExprLiteral : public FileCheckNumExprAST { +private: + /// Actual value of the literal. + uint64_t Value; -/// Class representing a numeric expression. +public: + /// Constructor for an unsigned literal. + FileCheckNumExprLiteral(uint64_t Val) : Value(Val) {} + + /// Evaluate the value of the expression represented by this AST. Therefore, + /// return the literal's value. + llvm::Optional eval() const { return Value; } +}; + +/// Class representing a numeric expression and its matching format. class FileCheckNumExpr { private: /// Pointer to AST of the numeric expression. - std::shared_ptr AST; + std::shared_ptr AST; public: /// Generic constructor for a numeric expression whose equality constraint is /// represented by \p AST. - FileCheckNumExpr(std::shared_ptr AST) : AST(AST) {} + FileCheckNumExpr(std::shared_ptr AST) : AST(AST) {} /// Return pointer to AST of the numeric expression. Pointer is guaranteed to /// be valid as long as this object is. - FileCheckASTBinop *getAST() const { return AST.get(); } + FileCheckNumExprAST *getAST() const { return AST.get(); } }; /// Class representing a numeric variable with a given value in the AST of a /// numeric expression. Each definition of a variable gets its own instance of /// this class, variable uses share the same instance as the respective /// definition. -class FileCheckNumExprVar { +class FileCheckNumExprVar : public FileCheckNumExprAST { private: /// Name of the numeric variable. StringRef Name; @@ -90,8 +118,12 @@ /// Return name of that numeric variable. StringRef getName() const { return Name; } - /// Return value of this numeric variable. - llvm::Optional getValue() const; + /// Evaluate the value of the expression represented by this AST. Therefore, + /// return this variable's value. + llvm::Optional eval() const; + + /// Append numeric variable's name to UndefVarNames if undefined. + void appendUndefVarNames(std::vector &UndefVarNames) const; /// Set value of this numeric variable if not defined. Return whether /// variable was already defined. @@ -110,30 +142,31 @@ /// Class representing a single binary operation in the AST of a numeric /// expression. -class FileCheckASTBinop { +class FileCheckASTBinop : public FileCheckNumExprAST { private: /// Left operand. - std::shared_ptr Opl; + std::shared_ptr Opl; /// Right operand. - uint64_t Opr; + std::shared_ptr Opr; /// Pointer to function that can evaluate this binary operation. binop_eval_t EvalBinop; public: FileCheckASTBinop(binop_eval_t EvalBinop, - std::shared_ptr OperandLeft, - uint64_t OperandRight) + std::shared_ptr OperandLeft, + std::shared_ptr OperandRight) : Opl(OperandLeft), Opr(OperandRight), EvalBinop(EvalBinop) {} /// Evaluate the value of the binary operation represented by this AST. Uses - /// EvalBinop to perform the binary operation. + /// EvalBinop to perform the binary operation on the values of recursively + /// evaluating the left and right operands. llvm::Optional eval() const; - /// Return the name of the undefined variable used in this substitution if - /// any. - llvm::Optional getUndefVarName() const; + /// Append names of undefined variables used in any of the operands of this + /// binary operation. + void appendUndefVarNames(std::vector &UndefVarNames) const; }; class FileCheckPatternContext; @@ -191,9 +224,9 @@ /// its definition matched. llvm::Optional getSubstitute() const; - /// Return the name of the undefined variable used in this substitution if - /// any. - llvm::Optional getUndefVarName() const; + /// Return in \p UndefVarNames the name of the undefined variables used in + /// this substitution if any. + void getUndefVarNames(std::vector &UndefVarNames) const; }; //===----------------------------------------------------------------------===// @@ -362,11 +395,11 @@ static bool parseVariable(StringRef Str, bool &IsPseudo, unsigned &TrailIdx); bool parseNumericVariable(StringRef &Expr, StringRef &Name, bool IsDefinition, - const SourceMgr &SM) const; + bool AcceptFail, const SourceMgr &SM) const; std::shared_ptr parseNumericExpression(StringRef Expr, std::shared_ptr &NumVarDef, - const SourceMgr &SM) const; + bool Legacy, const SourceMgr &SM) const; bool ParsePattern(StringRef PatternStr, StringRef Prefix, SourceMgr &SM, const FileCheckRequest &Req); size_t match(StringRef Buffer, size_t &MatchLen, const SourceMgr &SM) const; @@ -388,8 +421,13 @@ size_t FindRegexVarEnd(StringRef Str, SourceMgr &SM); /// Numeric expression parsing helpers. - std::shared_ptr - ParseFileCheckBinop(StringRef &Expr, const SourceMgr &SM) const; + enum AllowedOperand { LegacyVar, LegacyLiteral, Any }; + std::shared_ptr + parseNumericOperand(StringRef &Expr, enum AllowedOperand AO, + const SourceMgr &SM) const; + std::shared_ptr + parseFileCheckBinop(StringRef &Expr, std::shared_ptr Opl, + bool Legacy, const SourceMgr &SM) const; }; //===----------------------------------------------------------------------===// 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,13 +24,21 @@ using namespace llvm; -/// Return value of this numeric variable. -llvm::Optional FileCheckNumExprVar::getValue() const { +/// Evaluate the value of the expression represented by this AST. Therefore, +/// return this variable's value. +llvm::Optional FileCheckNumExprVar::eval() const { if (!Defined) return llvm::None; return Value; } +/// Append numeric variable's name to UndefVarNames if undefined. +void FileCheckNumExprVar::appendUndefVarNames( + std::vector &UndefVarNames) const { + if (!Defined) + UndefVarNames.emplace_back(Name); +} + /// Set value of this numeric variable if not defined. Return whether the /// variable was already defined. bool FileCheckNumExprVar::setValue(uint64_t Value) { @@ -54,18 +62,24 @@ /// EvalBinop to perform the binary operation on the values of recursively /// evaluating the left and right operands. llvm::Optional FileCheckASTBinop::eval() const { - llvm::Optional OptOpl = Opl->getValue(); - // Variable has been undefined. + llvm::Optional OptOpl = Opl->eval(); + llvm::Optional OptOpr = Opr->eval(); + + // Uses undefined variable. if (!OptOpl.hasValue()) return llvm::None; - return EvalBinop(OptOpl.getValue(), Opr); + if (!OptOpr.hasValue()) + return llvm::None; + + return EvalBinop(OptOpl.getValue(), OptOpr.getValue()); } -/// Return the name of the undefined variable used in this substitution if any. -llvm::Optional FileCheckASTBinop::getUndefVarName() const { - if (!Opl->getValue().hasValue()) - return Opl->getName(); - return llvm::None; +/// Append names of undefined variables used in any of the operands of this +/// binary operation. +void FileCheckASTBinop::appendUndefVarNames( + std::vector &UndefVarNames) const { + Opl->appendUndefVarNames(UndefVarNames); + Opr->appendUndefVarNames(UndefVarNames); } /// Return the result of the substitution represented by this class instance or @@ -92,17 +106,18 @@ } } -/// Return the name of the undefined variable used in this substitution if any. -llvm::Optional FileCheckPatternSubst::getUndefVarName() const { +/// Return in \p UndefVarNames the name of the undefined variables used in +/// this substitution if any. +void FileCheckPatternSubst::getUndefVarNames( + std::vector &UndefVarNames) const { + UndefVarNames.clear(); if (IsNumExpr) // Although use of undefined numeric variable is tested at parse time, // numeric variable can get undefined later by ClearLocalVariables. - return NumExpr->getAST()->getUndefVarName(); + NumExpr->getAST()->appendUndefVarNames(UndefVarNames); else { if (!Context->getPatternVarValue(SubstStr).hasValue()) - return SubstStr; - - return llvm::None; + UndefVarNames.emplace_back(SubstStr); } } @@ -157,17 +172,19 @@ /// Parse \p Expr for use or definition (if \p IsDefinition is true) of a /// numeric variable. Return whether parsing fails in which case errors are -/// reported on \p SM. Otherwise, the name of the numeric variable is set in -/// \p Name. +/// reported on \p SM, unless \p AcceptFail is true and the error is in +/// parsing the variable name. Otherwise, the name of the numeric variable is +/// set in \p Name. bool FileCheckPattern::parseNumericVariable(StringRef &Expr, StringRef &Name, - bool IsDefinition, + bool IsDefinition, bool AcceptFail, const SourceMgr &SM) const { bool IsPseudo; unsigned TrailIdx; if (parseVariable(Expr, IsPseudo, TrailIdx)) { - SM.PrintMessage(SMLoc::getFromPointer(Expr.data()), SourceMgr::DK_Error, - "Invalid variable name"); + if (!AcceptFail) + SM.PrintMessage(SMLoc::getFromPointer(Expr.data()), SourceMgr::DK_Error, + "Invalid variable name"); return true; } Name = Expr.substr(0, TrailIdx); @@ -222,29 +239,54 @@ } } +/// Parse \p Expr for use of a numeric operand. Accept both literal values and +/// numeric variables, depending on the value of \p AllowedOperandFlag. Return +/// 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 +FileCheckPattern::parseNumericOperand(StringRef &Expr, enum AllowedOperand AO, + const SourceMgr &SM) const { + + // Try to parse as a numeric variable use. + if (AO == LegacyVar || AO == Any) { + StringRef Name; + // Error reporting done in ParseNumericVariable. + if (!parseNumericVariable(Expr, Name, false /*IsDefinition*/, + true /*AcceptFail*/, SM)) { + std::shared_ptr NumVar = + Context->GlobalNumericVariableTable.find(Name)->second; + return NumVar; + } + } + + // Otherwise, parse it as a literal. + if (AO != LegacyLiteral && AO != Any) + return nullptr; + uint64_t LiteralValue; + if (Expr.consumeInteger(10, LiteralValue)) + return nullptr; + return std::make_shared(LiteralValue); +} + static uint64_t doAdd(uint64_t Opl, uint64_t Opr) { return Opl + Opr; } static uint64_t doSub(uint64_t Opl, uint64_t Opr) { return Opl - Opr; } -/// Parse \p Expr for a binary operation. -/// Return the class representing that 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 -FileCheckPattern::ParseFileCheckBinop(StringRef &Expr, - const SourceMgr &SM) const { - StringRef Name; - if (parseNumericVariable(Expr, Name, false, SM)) - // Error reporting done in parseNumericVariable. - return nullptr; - std::shared_ptr Opl = - Context->GlobalNumericVariableTable.find(Name)->second; +/// Parse \p Expr for a binary operation. The left operand of this binary +/// operation is given in \p Opl and \p Legacy indicates whether we are parsing +/// a legacy numeric expression. Return the class representing this 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 +FileCheckPattern::parseFileCheckBinop(StringRef &Expr, + std::shared_ptr Opl, + bool Legacy, const SourceMgr &SM) const { + skipWhitespace(Expr); + if (Expr.empty()) + return Opl; // Check if this is a supported operation and selection function to perform // it. - skipWhitespace(Expr); - if (Expr.empty()) - return std::make_shared(doAdd, Opl, 0); SMLoc oploc = SMLoc::getFromPointer(Expr.data()); char Operator = next(Expr); binop_eval_t EvalBinop; @@ -269,10 +311,12 @@ "Missing operand in numeric expression"); return nullptr; } - uint64_t Opr; - if (Expr.consumeInteger(10, Opr)) { + // Second operand in legacy numeric expression is a literal. + enum AllowedOperand AO = Legacy ? LegacyLiteral : Any; + std::shared_ptr Opr = parseNumericOperand(Expr, AO, SM); + if (Opr == nullptr) { SM.PrintMessage(SMLoc::getFromPointer(Expr.data()), SourceMgr::DK_Error, - "Invalid offset in numeric expression '" + Expr + "'"); + "Invalid operand format '" + Expr + "'"); return nullptr; } skipWhitespace(Expr); @@ -286,14 +330,16 @@ return std::make_shared(EvalBinop, Opl, Opr); } -/// Parse \p Expr for a numeric expression. Return the class representing the -/// AST of numeric expression or nullptr if parsing fails in which case errors -/// are reported on \p SM. Set \p NumVarDef to the pointer to the class -/// representing the variable defined to this numeric expression if any. +/// Parse \p Expr for a numeric expression. Parameter \p Legacy indicates +/// whether Expr should be a legacy numeric expression. Return the class +/// representing the AST of numeric expression or nullptr if parsing fails +/// in which case errors are reported on \p SM. Set \p NumVarDef to the +/// pointer to the class representing the variable defined to this numeric +/// expression if any. std::shared_ptr FileCheckPattern::parseNumericExpression( StringRef Expr, std::shared_ptr &NumVarDef, - const SourceMgr &SM) const { - std::shared_ptr NumExprAST; + bool Legacy, const SourceMgr &SM) const { + std::shared_ptr NumExprAST; // Parse numeric variable definition. NumVarDef.reset(); @@ -304,7 +350,8 @@ skipWhitespace(DefExpr); StringRef Name; - if (parseNumericVariable(DefExpr, Name, true /*IsDefinition*/, SM)) + if (parseNumericVariable(DefExpr, Name, true /*IsDefinition*/, + false /*AcceptFail*/, SM)) // Invalid variable definition. Error reporting done in parsing function. return nullptr; @@ -327,9 +374,24 @@ } else { // Parse numeric expression itself. skipWhitespace(Expr); - NumExprAST = ParseFileCheckBinop(Expr, SM); - if (NumExprAST == nullptr) + // First operand in legacy numeric expression is the @LINE pseudo variable. + enum AllowedOperand AO = Legacy ? LegacyVar : Any; + NumExprAST = parseNumericOperand(Expr, AO, SM); + while (NumExprAST != nullptr && !Expr.empty()) { + NumExprAST = parseFileCheckBinop(Expr, NumExprAST, Legacy, SM); + // Legacy numeric expressions only allow 2 operands. + if (Legacy && !Expr.empty()) { + SM.PrintMessage(SMLoc::getFromPointer(Expr.data()), SourceMgr::DK_Error, + "Unexpected characters at end of numeric expression '" + + Expr + "'"); + return nullptr; + } + } + if (NumExprAST == nullptr) { + SM.PrintMessage(SMLoc::getFromPointer(Expr.data()), SourceMgr::DK_Error, + "Invalid operand format '" + Expr + "'"); return nullptr; + } } return std::make_shared(NumExprAST); @@ -456,6 +518,7 @@ PatternStr = PatternStr.substr(End + 4 + (int)IsNumExpr); bool IsVarDef; + bool Legacy = false; StringRef DefName; StringRef SubstStr; StringRef MatchRegexp; @@ -485,7 +548,7 @@ StringRef Trailer = MatchStr.substr(TrailIdx); size_t DefSepIdx = Trailer.find(":"); IsVarDef = (DefSepIdx != StringRef::npos); - IsNumExpr = IsPseudo; + Legacy = IsNumExpr = IsPseudo; if (IsVarDef) { if ((IsPseudo || !Trailer.consume_front(":"))) { @@ -512,7 +575,7 @@ // Parse numeric expression. if (IsNumExpr) { - NumExpr = parseNumericExpression(MatchStr, NumVarDef, SM); + NumExpr = parseNumericExpression(MatchStr, NumVarDef, Legacy, SM); if (NumExpr == nullptr) return true; IsNumExpr = true; @@ -746,14 +809,16 @@ llvm::Optional OptMatchedValue = Subst.getSubstitute(); // Substitution failed or is not known at match time, print undefined - // variable it uses. + // variables it uses. if (!OptMatchedValue.hasValue()) { - llvm::Optional OptUndefVarName = Subst.getUndefVarName(); - if (!OptUndefVarName.hasValue()) + std::vector UndefVarNames; + Subst.getUndefVarNames(UndefVarNames); + if (UndefVarNames.empty()) continue; - StringRef UndefVarName = OptUndefVarName.getValue(); - OS << "uses undefined variable \""; - OS.write_escaped(UndefVarName) << "\""; + for (auto UndefVarName : UndefVarNames) { + OS << "uses undefined variable \""; + OS.write_escaped(UndefVarName) << "\""; + } } else { // Substitution succeeded. Print substituted value. std::string MatchedValue = OptMatchedValue.getValue(); @@ -1779,7 +1844,7 @@ StringRef CmdlineName; SMLoc CmdlineNameLoc = SMLoc::getFromPointer(Expr.data()); if (P.parseNumericVariable(Expr, CmdlineName, true /*IsDefinition*/, - SM) || + false /*AcceptFail*/, SM) || !Expr.empty()) { SM.PrintMessage(CmdlineNameLoc, SourceMgr::DK_Error, "Invalid variable name"); diff --git a/llvm/test/FileCheck/line-count.txt b/llvm/test/FileCheck/line-count.txt --- a/llvm/test/FileCheck/line-count.txt +++ b/llvm/test/FileCheck/line-count.txt @@ -50,7 +50,7 @@ 50 ERR9: line-count.txt:[[#@LINE-1]]:17: error: Unsupported numeric operation '*' 51 52 BAD10: [[@LINE-x]] -53 ERR10: line-count.txt:[[#@LINE-1]]:19: error: Invalid offset in numeric expression 'x' +53 ERR10: line-count.txt:[[#@LINE-1]]:19: error: Invalid operand format 'x' 54 55 BAD11: [[@LINE-1x]] 56 ERR11: line-count.txt:[[#@LINE-1]]:19: error: Unexpected characters at end of numeric expression 'x'