Back to home page

Project CMSSW displayed by LXR

 
 

    


File indexing completed on 2024-04-06 12:12:49

0001 /** \class edm::PathStatusFilter
0002 
0003 Allows one to filter based on the results of Paths.
0004 One configures the filter with a text string that
0005 is a logical expression of path names. When the
0006 referenced Paths complete, the PathStatus product
0007 for each Path is retrieved from the Event. If the
0008 PathStatus holds the value "Pass", then the corresponding
0009 operand in the logical expression evaluates to "true",
0010 otherwise it evaluates to false. The overall logical
0011 expression is then evaluated and its result is the
0012 filter return value.
0013 
0014 The logical expression syntax is very intuitive. The
0015 operands are path names. The allowed operators in order
0016 of precedence are "not", "and", and "or". Parentheses
0017 can be used. The syntax requires operators and pathnames
0018 be separated by at least one space or parenthesis.
0019 Extra space between operators, path names, or parentheses
0020 is ignored. A path name cannot be the same as an
0021 operator name. For example, a valid expression would be:
0022 
0023   path1 and not (path2 or not path3)
0024 
0025 Note that this works only for Paths in the current process.
0026 It does not work for EndPaths or Paths from prior processes.
0027 
0028 \author W. David Dagenhart, created 31 July, 2017
0029 
0030 */
0031 
0032 #include "DataFormats/Common/interface/PathStatus.h"
0033 #include "FWCore/Framework/interface/ConsumesCollector.h"
0034 #include "FWCore/Framework/interface/Event.h"
0035 #include "FWCore/Framework/interface/global/EDFilter.h"
0036 #include "FWCore/Framework/interface/MakerMacros.h"
0037 #include "FWCore/MessageLogger/interface/MessageLogger.h"
0038 #include "FWCore/ParameterSet/interface/ConfigurationDescriptions.h"
0039 #include "FWCore/ParameterSet/interface/ParameterSet.h"
0040 #include "FWCore/ParameterSet/interface/ParameterSetDescription.h"
0041 #include "FWCore/Utilities/interface/EDGetToken.h"
0042 #include "FWCore/Utilities/interface/Exception.h"
0043 #include "FWCore/Utilities/interface/InputTag.h"
0044 #include "FWCore/Utilities/interface/propagate_const.h"
0045 
0046 #include <boost/spirit/include/qi.hpp>
0047 
0048 #include <functional>
0049 #include <memory>
0050 #include <ostream>
0051 #include <string>
0052 #include <sstream>
0053 #include <vector>
0054 #include <utility>
0055 
0056 namespace qi = boost::spirit::qi;
0057 namespace ascii = boost::spirit::ascii;
0058 
0059 namespace edm {
0060 
0061   class EventSetup;
0062   class StreamID;
0063 
0064   namespace pathStatusExpression {
0065     class Evaluator;
0066   }
0067 
0068   class PathStatusFilter : public global::EDFilter<> {
0069   public:
0070     explicit PathStatusFilter(ParameterSet const&);
0071     static void fillDescriptions(ConfigurationDescriptions&);
0072     bool filter(StreamID, Event&, EventSetup const&) const final;
0073 
0074   private:
0075     edm::propagate_const<std::unique_ptr<pathStatusExpression::Evaluator>> evaluator_;
0076     bool verbose_;
0077   };
0078 
0079   namespace pathStatusExpression {
0080 
0081     class Evaluator {
0082     public:
0083       virtual ~Evaluator() {}
0084 
0085       enum EvaluatorType { Name, Not, And, Or, BeginParen };
0086       virtual EvaluatorType type() const = 0;
0087 
0088       virtual void setLeft(std::unique_ptr<Evaluator>&&) {}
0089       virtual void setRight(std::unique_ptr<Evaluator>&&) {}
0090 
0091       virtual void print(std::ostream& out, unsigned int indentation) const {}
0092       virtual void init(ConsumesCollector&) {}
0093       virtual bool evaluate(Event const& event) const { return true; };
0094     };
0095 
0096     class Operand : public Evaluator {
0097     public:
0098       Operand(std::vector<char> const& pathName) : pathName_(pathName.begin(), pathName.end()) {}
0099 
0100       EvaluatorType type() const override { return Name; }
0101 
0102       void print(std::ostream& out, unsigned int indentation) const override {
0103         out << std::string(indentation, ' ') << pathName_ << "\n";
0104       }
0105 
0106       void init(ConsumesCollector& iC) override { token_ = iC.consumes<PathStatus>(InputTag(pathName_)); }
0107 
0108       bool evaluate(Event const& event) const override { return event.get(token_).accept(); }
0109 
0110     private:
0111       std::string pathName_;
0112       EDGetTokenT<PathStatus> token_;
0113     };
0114 
0115     class NotOperator : public Evaluator {
0116     public:
0117       EvaluatorType type() const override { return Not; }
0118 
0119       void setLeft(std::unique_ptr<Evaluator>&& v) override { operand_ = std::move(v); }
0120 
0121       void print(std::ostream& out, unsigned int indentation) const override {
0122         out << std::string(indentation, ' ') << "not\n";
0123         operand_->print(out, indentation + 4);
0124       }
0125 
0126       void init(ConsumesCollector& iC) override { operand_->init(iC); }
0127 
0128       bool evaluate(Event const& event) const override { return !operand_->evaluate(event); }
0129 
0130     private:
0131       edm::propagate_const<std::unique_ptr<Evaluator>> operand_;
0132     };
0133 
0134     template <typename T>
0135     class BinaryOperator : public Evaluator {
0136     public:
0137       EvaluatorType type() const override;
0138 
0139       void setLeft(std::unique_ptr<Evaluator>&& v) override { left_ = std::move(v); }
0140       void setRight(std::unique_ptr<Evaluator>&& v) override { right_ = std::move(v); }
0141 
0142       void print(std::ostream& out, unsigned int indentation) const override;
0143 
0144       void init(ConsumesCollector& iC) override {
0145         left_->init(iC);
0146         right_->init(iC);
0147       }
0148 
0149       bool evaluate(Event const& event) const override {
0150         T op;
0151         return op(left_->evaluate(event), right_->evaluate(event));
0152       }
0153 
0154     private:
0155       edm::propagate_const<std::unique_ptr<Evaluator>> left_;
0156       edm::propagate_const<std::unique_ptr<Evaluator>> right_;
0157     };
0158 
0159     template <>
0160     inline Evaluator::EvaluatorType BinaryOperator<std::logical_and<bool>>::type() const {
0161       return And;
0162     }
0163 
0164     template <>
0165     inline Evaluator::EvaluatorType BinaryOperator<std::logical_or<bool>>::type() const {
0166       return Or;
0167     }
0168 
0169     template <>
0170     void BinaryOperator<std::logical_and<bool>>::print(std::ostream& out, unsigned int indentation) const {
0171       out << std::string(indentation, ' ') << "and\n";
0172       left_->print(out, indentation + 4);
0173       right_->print(out, indentation + 4);
0174     }
0175     template <>
0176     void BinaryOperator<std::logical_or<bool>>::print(std::ostream& out, unsigned int indentation) const {
0177       out << std::string(indentation, ' ') << "or\n";
0178       left_->print(out, indentation + 4);
0179       right_->print(out, indentation + 4);
0180     }
0181 
0182     using AndOperator = BinaryOperator<std::logical_and<bool>>;
0183     using OrOperator = BinaryOperator<std::logical_or<bool>>;
0184 
0185     class BeginParenthesis : public Evaluator {
0186     public:
0187       EvaluatorType type() const override { return BeginParen; }
0188     };
0189 
0190     // This class exists to properly handle the precedence of the
0191     // operators and also handle the order of operations specified
0192     // by parentheses. (search for shunting yard algorithm on the
0193     // internet for a description of this algorithm)
0194     class ShuntingYardAlgorithm {
0195     public:
0196       void addPathName(std::vector<char> const& s) { operandStack.push_back(std::make_unique<Operand>(s)); }
0197 
0198       void addOperatorNot() {
0199         if (operatorStack.empty() || operatorStack.back()->type() != Evaluator::Not) {
0200           operatorStack.push_back(std::make_unique<NotOperator>());
0201         } else {
0202           // Two Not operations in a row cancel and are the same as no operation at all.
0203           operatorStack.pop_back();
0204         }
0205       }
0206 
0207       void moveBinaryOperator() {
0208         std::unique_ptr<Evaluator>& backEvaluator = operatorStack.back();
0209         backEvaluator->setRight(std::move(operandStack.back()));
0210         operandStack.pop_back();
0211         backEvaluator->setLeft(std::move(operandStack.back()));
0212         operandStack.pop_back();
0213         operandStack.push_back(std::move(backEvaluator));
0214         operatorStack.pop_back();
0215       }
0216 
0217       void moveNotOperator() {
0218         std::unique_ptr<Evaluator>& backEvaluator = operatorStack.back();
0219         backEvaluator->setLeft(std::move(operandStack.back()));
0220         operandStack.pop_back();
0221         operandStack.push_back(std::move(backEvaluator));
0222         operatorStack.pop_back();
0223       }
0224 
0225       void addOperatorAnd() {
0226         while (!operatorStack.empty()) {
0227           std::unique_ptr<Evaluator>& backEvaluator = operatorStack.back();
0228           if (backEvaluator->type() == Evaluator::And) {
0229             moveBinaryOperator();
0230           } else if (backEvaluator->type() == Evaluator::Not) {
0231             moveNotOperator();
0232           } else {
0233             break;
0234           }
0235         }
0236         operatorStack.push_back(std::make_unique<AndOperator>());
0237       }
0238 
0239       void addOperatorOr() {
0240         while (!operatorStack.empty()) {
0241           std::unique_ptr<Evaluator>& backEvaluator = operatorStack.back();
0242           if (backEvaluator->type() == Evaluator::And || backEvaluator->type() == Evaluator::Or) {
0243             moveBinaryOperator();
0244           } else if (backEvaluator->type() == Evaluator::Not) {
0245             moveNotOperator();
0246           } else {
0247             break;
0248           }
0249         }
0250         operatorStack.push_back(std::make_unique<OrOperator>());
0251       }
0252 
0253       void addBeginParenthesis() { operatorStack.push_back(std::make_unique<BeginParenthesis>()); }
0254 
0255       void addEndParenthesis() {
0256         while (!operatorStack.empty()) {
0257           std::unique_ptr<Evaluator>& backEvaluator = operatorStack.back();
0258           if (backEvaluator->type() == Evaluator::BeginParen) {
0259             operatorStack.pop_back();
0260             break;
0261           }
0262           if (backEvaluator->type() == Evaluator::And || backEvaluator->type() == Evaluator::Or) {
0263             moveBinaryOperator();
0264           } else if (backEvaluator->type() == Evaluator::Not) {
0265             moveNotOperator();
0266           }
0267         }
0268       }
0269 
0270       std::unique_ptr<Evaluator> finish() {
0271         while (!operatorStack.empty()) {
0272           std::unique_ptr<Evaluator>& backEvaluator = operatorStack.back();
0273           // Just a sanity check. The grammar defined for the boost Spirit parser
0274           // should catch any errors of this type before we get here.
0275           if (backEvaluator->type() == Evaluator::BeginParen) {
0276             throw cms::Exception("LogicError")
0277                 << "Should be impossible to get this error. Contact a Framework developer";
0278           }
0279           if (backEvaluator->type() == Evaluator::And || backEvaluator->type() == Evaluator::Or) {
0280             moveBinaryOperator();
0281           } else if (backEvaluator->type() == Evaluator::Not) {
0282             moveNotOperator();
0283           }
0284         }
0285         // Just a sanity check. The grammar defined for the boost Spirit parser
0286         // should catch any errors of this type before we get here.
0287         if (!operatorStack.empty() || operandStack.size() != 1U) {
0288           throw cms::Exception("LogicError") << "Should be impossible to get this error. Contact a Framework developer";
0289         }
0290         std::unique_ptr<Evaluator> temp = std::move(operandStack.back());
0291         operandStack.pop_back();
0292         return temp;
0293       }
0294 
0295     private:
0296       std::vector<std::unique_ptr<Evaluator>> operandStack;
0297       std::vector<std::unique_ptr<Evaluator>> operatorStack;
0298     };
0299 
0300     // Use boost Spirit to parse the logical expression character string
0301     template <typename Iterator>
0302     class Grammar : public qi::grammar<Iterator, ascii::space_type> {
0303     public:
0304       Grammar(ShuntingYardAlgorithm* algorithm) : Grammar::base_type(expression), algorithm_(algorithm) {
0305         // setup functors that call into shunting algorithm while parsing the logical expression
0306         auto addPathName = std::bind(&ShuntingYardAlgorithm::addPathName, algorithm_, std::placeholders::_1);
0307         auto addOperatorNot = std::bind(&ShuntingYardAlgorithm::addOperatorNot, algorithm_);
0308         auto addOperatorAnd = std::bind(&ShuntingYardAlgorithm::addOperatorAnd, algorithm_);
0309         auto addOperatorOr = std::bind(&ShuntingYardAlgorithm::addOperatorOr, algorithm_);
0310         auto addBeginParenthesis = std::bind(&ShuntingYardAlgorithm::addBeginParenthesis, algorithm_);
0311         auto addEndParenthesis = std::bind(&ShuntingYardAlgorithm::addEndParenthesis, algorithm_);
0312 
0313         // Define the syntax allowed in the logical expressions
0314         pathName = !unaryOperator >> !binaryOperatorTest >> (+qi::char_("a-zA-Z0-9_"))[addPathName];
0315         binaryOperand = (qi::lit('(')[addBeginParenthesis] >> expression >> qi::lit(')')[addEndParenthesis]) |
0316                         (unaryOperator[addOperatorNot] >> binaryOperand) | pathName;
0317         afterOperator = ascii::space | &qi::lit('(') | &qi::eoi;
0318         unaryOperator = qi::lit("not") >> afterOperator;
0319         // The only difference in the next two is that one calls a functor and the other does not
0320         binaryOperatorTest = (qi::lit("and") >> afterOperator) | (qi::lit("or") >> afterOperator);
0321         binaryOperator =
0322             (qi::lit("and") >> afterOperator)[addOperatorAnd] | (qi::lit("or") >> afterOperator)[addOperatorOr];
0323         expression = binaryOperand % binaryOperator;
0324       }
0325 
0326     private:
0327       qi::rule<Iterator> pathName;
0328       qi::rule<Iterator, ascii::space_type> binaryOperand;
0329       qi::rule<Iterator> afterOperator;
0330       qi::rule<Iterator> unaryOperator;
0331       qi::rule<Iterator> binaryOperatorTest;
0332       qi::rule<Iterator> binaryOperator;
0333       qi::rule<Iterator, ascii::space_type> expression;
0334 
0335       ShuntingYardAlgorithm* algorithm_;
0336     };
0337   }  // namespace pathStatusExpression
0338 
0339   PathStatusFilter::PathStatusFilter(ParameterSet const& pset)
0340       : evaluator_(nullptr), verbose_(pset.getUntrackedParameter<bool>("verbose")) {
0341     std::string const logicalExpression = pset.getParameter<std::string>("logicalExpression");
0342     if (verbose_) {
0343       LogAbsolute("PathStatusFilter") << "PathStatusFilter logicalExpression = " << logicalExpression;
0344     }
0345 
0346     if (logicalExpression.empty()) {
0347       return;
0348     }
0349 
0350     pathStatusExpression::ShuntingYardAlgorithm shuntingYardAlgorithm;
0351 
0352     pathStatusExpression::Grammar<std::string::const_iterator> grammar(&shuntingYardAlgorithm);
0353 
0354     auto it = logicalExpression.cbegin();
0355     if (!qi::phrase_parse(it, logicalExpression.cend(), grammar, ascii::space) || (it != logicalExpression.cend())) {
0356       throw cms::Exception("Configuration") << "Syntax error in logical expression. Here is an example of how\n"
0357                                             << "the syntax should look:\n"
0358                                             << "    \"path1 and not (path2 or not path3)\"\n"
0359                                             << "The expression must contain alternating appearances of operands\n"
0360                                             << "which are path names and binary operators which can be \'and\'\n"
0361                                             << "or \'or\', with a path name at the beginning and end. There\n"
0362                                             << "must be at least one path name. In addition to the alternating\n"
0363                                             << "path names and binary operators, the unary operator \'not\' can\n"
0364                                             << "be inserted before a path name or a begin parenthesis.\n"
0365                                             << "Parentheses are allowed. Parentheses must come in matching pairs.\n"
0366                                             << "Matching begin and end parentheses must contain a complete and\n"
0367                                             << "syntactically correct logical expression. There must be at least\n"
0368                                             << "one space or parenthesis between operators and path names. Extra\n"
0369                                             << "space is ignored and OK. Path names can only contain upper and\n"
0370                                             << "lower case letters, numbers, and underscores. A path name cannot\n"
0371                                             << "be the same as an operator name.\n";
0372     }
0373 
0374     evaluator_ = shuntingYardAlgorithm.finish();
0375     if (verbose_) {
0376       std::stringstream out;
0377       evaluator_->print(out, 0);
0378       LogAbsolute("PathStatusFilter") << out.str();
0379     }
0380     ConsumesCollector iC(consumesCollector());
0381     evaluator_->init(iC);
0382   }
0383 
0384   void PathStatusFilter::fillDescriptions(ConfigurationDescriptions& descriptions) {
0385     ParameterSetDescription desc;
0386     desc.add<std::string>("logicalExpression", std::string())
0387         ->setComment(
0388             "Operands are path names. Operators in precedence order "
0389             "\'not\', \'and\', and \'or\'. Parentheses allowed.");
0390     desc.addUntracked<bool>("verbose", false)->setComment("For debugging only");
0391     descriptions.add("pathStatusFilter", desc);
0392   }
0393 
0394   bool PathStatusFilter::filter(StreamID, Event& event, EventSetup const&) const {
0395     if (evaluator_ == nullptr) {
0396       return true;
0397     }
0398     return evaluator_->evaluate(event);
0399   }
0400 }  // namespace edm
0401 
0402 using edm::PathStatusFilter;
0403 DEFINE_FWK_MODULE(PathStatusFilter);