#pragma once //FIXME: move some of code into .cpp to avoid this include? #include "JsonNode.h" namespace LogicalExpressionDetail { /// class that defines required types for logical expressions template class ExpressionBase { public: /// Possible logical operations, mostly needed to create different types for boost::variant enum EOperations { ANY_OF, ALL_OF, NONE_OF }; template class Element; typedef Element OperatorAny; typedef Element OperatorAll; typedef Element OperatorNone; typedef ContainedClass Value; /// Variant that contains all possible elements from logical expression typedef boost::variant< OperatorAll, OperatorAny, OperatorNone, Value > Variant; /// Variant element, contains list of expressions to which operation "tag" should be applied template class Element { public: Element() {} Element(std::vector expressions): expressions(expressions) {} std::vector expressions; bool operator == (const Element & other) const { return expressions == other.expressions; } template void serialize(Handler & h, const int version) { h & expressions; } }; }; /// Visitor to test result (true/false) of the expression template class TestVisitor : public boost::static_visitor { typedef ExpressionBase Base; std::function classTest; size_t countPassed(const std::vector & element) const { return boost::range::count_if(element, [&](const typename Base::Variant & expr) { return boost::apply_visitor(*this, expr); }); } public: TestVisitor(std::function classTest): classTest(classTest) {} bool operator()(const typename Base::OperatorAny & element) const { return countPassed(element.expressions) != 0; } bool operator()(const typename Base::OperatorAll & element) const { return countPassed(element.expressions) == element.expressions.size(); } bool operator()(const typename Base::OperatorNone & element) const { return countPassed(element.expressions) == 0; } bool operator()(const typename Base::Value & element) const { return classTest(element); } }; /// visitor that is trying to generates candidates that must be fulfilled /// to complete this expression template class CandidatesVisitor : public boost::static_visitor > { typedef ExpressionBase Base; typedef std::vector TValueList; TestVisitor classTest; public: CandidatesVisitor(std::function classTest): classTest(classTest) {} TValueList operator()(const typename Base::OperatorAny & element) const { TValueList ret; if (!classTest(element)) { for (auto & elem : element.expressions) boost::range::copy(boost::apply_visitor(*this, elem), std::back_inserter(ret)); } return ret; } TValueList operator()(const typename Base::OperatorAll & element) const { TValueList ret; if (!classTest(element)) { for (auto & elem : element.expressions) boost::range::copy(boost::apply_visitor(*this, elem), std::back_inserter(ret)); } return ret; } TValueList operator()(const typename Base::OperatorNone & element) const { return TValueList(); //TODO. Implementing this one is not straightforward, if ever possible } TValueList operator()(const typename Base::Value & element) const { if (classTest(element)) return TValueList(); else return TValueList(1, element); } }; /// Simple foreach visitor template class ForEachVisitor : public boost::static_visitor::Variant> { typedef ExpressionBase Base; std::function visitor; public: ForEachVisitor(std::function visitor): visitor(visitor) {} typename Base::Variant operator()(const typename Base::Value & element) const { return visitor(element); } template typename Base::Variant operator()(Type element) const { for (auto & entry : element.expressions) entry = boost::apply_visitor(*this, entry); return element; } }; /// Minimizing visitor that removes all redundant elements from variant (e.g. AllOf inside another AllOf can be merged safely) template class MinimizingVisitor : public boost::static_visitor::Variant> { typedef ExpressionBase Base; public: typename Base::Variant operator()(const typename Base::Value & element) const { return element; } template typename Base::Variant operator()(const Type & element) const { Type ret; for (auto & entryRO : element.expressions) { auto entry = boost::apply_visitor(*this, entryRO); try { // copy entries from child of this type auto sublist = boost::get(entry).expressions; std::move(sublist.begin(), sublist.end(), std::back_inserter(ret.expressions)); } catch (boost::bad_get &) { // different type (e.g. allOf vs oneOf) just copy ret.expressions.push_back(entry); } } for ( auto it = ret.expressions.begin(); it != ret.expressions.end();) { if (std::find(ret.expressions.begin(), it, *it) != it) it = ret.expressions.erase(it); // erase duplicate else it++; // goto next } return ret; } }; /// Json parser for expressions template class Reader { typedef ExpressionBase Base; std::function classParser; typename Base::Variant readExpression(const JsonNode & node) { assert(!node.Vector().empty()); std::string type = node.Vector()[0].String(); if (type == "anyOf") return typename Base::OperatorAny(readVector(node)); if (type == "allOf") return typename Base::OperatorAll(readVector(node)); if (type == "noneOf") return typename Base::OperatorNone(readVector(node)); return classParser(node); } std::vector readVector(const JsonNode & node) { std::vector ret; ret.reserve(node.Vector().size()-1); for (size_t i=1; i < node.Vector().size(); i++) ret.push_back(readExpression(node.Vector()[i])); return ret; } public: Reader(std::function classParser): classParser(classParser) {} typename Base::Variant operator ()(const JsonNode & node) { return readExpression(node); } }; /// Serializes expression in JSON format. Part of map format. template class Writer : public boost::static_visitor { typedef ExpressionBase Base; std::function classPrinter; JsonNode printExpressionList(std::string name, const std::vector & element) const { JsonNode ret; ret.Vector().resize(1); ret.Vector().back().String() = name; for (auto & expr : element) ret.Vector().push_back(boost::apply_visitor(*this, expr)); return ret; } public: Writer(std::function classPrinter): classPrinter(classPrinter) {} JsonNode operator()(const typename Base::OperatorAny & element) const { return printExpressionList("anyOf", element.expressions); } JsonNode operator()(const typename Base::OperatorAll & element) const { return printExpressionList("allOf", element.expressions); } JsonNode operator()(const typename Base::OperatorNone & element) const { return printExpressionList("noneOf", element.expressions); } JsonNode operator()(const typename Base::Value & element) const { return classPrinter(element); } }; std::string DLL_LINKAGE getTextForOperator(std::string operation); /// Prints expression in human-readable format template class Printer : public boost::static_visitor { typedef ExpressionBase Base; std::function classPrinter; std::unique_ptr> statusTest; mutable std::string prefix; template std::string formatString(std::string toFormat, const Operator & expr) const { // highlight not fulfilled expressions, if pretty formatting is on if (statusTest && !(*statusTest)(expr)) return "{" + toFormat + "}"; return toFormat; } std::string printExpressionList(const std::vector & element) const { std::string ret; prefix.push_back('\t'); for (auto & expr : element) ret += prefix + boost::apply_visitor(*this, expr) + "\n"; prefix.pop_back(); return ret; } public: Printer(std::function classPrinter): classPrinter(classPrinter) {} Printer(std::function classPrinter, std::function toBool): classPrinter(classPrinter), statusTest(new TestVisitor(toBool)) {} std::string operator()(const typename Base::OperatorAny & element) const { return formatString(getTextForOperator("anyOf"), element) + "\n" + printExpressionList(element.expressions); } std::string operator()(const typename Base::OperatorAll & element) const { return formatString(getTextForOperator("allOf"), element) + "\n" + printExpressionList(element.expressions); } std::string operator()(const typename Base::OperatorNone & element) const { return formatString(getTextForOperator("noneOf"), element) + "\n" + printExpressionList(element.expressions); } std::string operator()(const typename Base::Value & element) const { return formatString(classPrinter(element), element); } }; } /// /// Class for evaluation of logical expressions generated in runtime /// template class LogicalExpression { typedef LogicalExpressionDetail::ExpressionBase Base; public: /// Type of values used in expressions, same as ContainedClass typedef typename Base::Value Value; /// Operators for use in expressions, all include vectors with operands typedef typename Base::OperatorAny OperatorAny; typedef typename Base::OperatorAll OperatorAll; typedef typename Base::OperatorNone OperatorNone; /// one expression entry typedef typename Base::Variant Variant; private: Variant data; public: /// Base constructor LogicalExpression() {} /// Constructor from variant or (implicitly) from Operator* types LogicalExpression(const Variant & data): data(data) { } /// Constructor that receives JsonNode as input and function that can parse Value instances LogicalExpression(const JsonNode & input, std::function parser) { LogicalExpressionDetail::Reader reader(parser); LogicalExpression expr(reader(input)); std::swap(data, expr.data); } Variant get() const { return data; } /// Simple visitor that visits all entries in expression Variant morph(std::function morpher) const { LogicalExpressionDetail::ForEachVisitor visitor(morpher); return boost::apply_visitor(visitor, data); } /// Minimizes expression, removing any redundant elements void minimize() { LogicalExpressionDetail::MinimizingVisitor visitor; data = boost::apply_visitor(visitor, data); } /// calculates if expression evaluates to "true". /// Note: empty expressions always return true bool test(std::function toBool) const { LogicalExpressionDetail::TestVisitor testVisitor(toBool); return boost::apply_visitor(testVisitor, data); } /// generates list of candidates that can be fulfilled by caller (like AI) std::vector getFulfillmentCandidates(std::function toBool) const { LogicalExpressionDetail::CandidatesVisitor candidateVisitor(toBool); return boost::apply_visitor(candidateVisitor, data); } /// Converts expression in human-readable form /// Second version will try to do some pretty printing using H3 text formatting "{}" /// to indicate fulfilled components of an expression std::string toString(std::function toStr) const { LogicalExpressionDetail::Printer printVisitor(toStr); return boost::apply_visitor(printVisitor, data); } std::string toString(std::function toStr, std::function toBool) const { LogicalExpressionDetail::Printer printVisitor(toStr, toBool); return boost::apply_visitor(printVisitor, data); } JsonNode toJson(std::function toJson) const { LogicalExpressionDetail::Writer writeVisitor(toJson); return boost::apply_visitor(writeVisitor, data); } template void serialize(Handler & h, const int version) { h & data; } };