/* * LogicalExpression.h, part of VCMI engine * * Authors: listed in file AUTHORS in main folder * * License: GNU General Public License v2.0 or later * Full text of license available in license.txt file, in main folder * */ #pragma once #include "json/JsonNode.h" VCMI_LIB_NAMESPACE_BEGIN namespace LogicalExpressionDetail { /// class that defines required types for logical expressions template class ExpressionBase { public: /// Possible logical operations, mostly needed to create different types for std::variant enum EOperations { ANY_OF, ALL_OF, NONE_OF }; template class Element; using OperatorAny = Element; using OperatorAll = Element; using OperatorNone = Element; using Value = ContainedClass; /// Variant that contains all possible elements from logical expression using Variant = std::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) { h & expressions; } }; }; /// Visitor to test result (true/false) of the expression template class TestVisitor { using Base = ExpressionBase; std::function classTest; size_t countPassed(const std::vector & element) const { return boost::range::count_if(element, [&](const typename Base::Variant & expr) { return std::visit(*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); } }; template class SatisfiabilityVisitor; template class FalsifiabilityVisitor; template class PossibilityVisitor { using Base = ExpressionBase; protected: std::function satisfiabilityTest; std::function falsifiabilityTest; SatisfiabilityVisitor *satisfiabilityVisitor; FalsifiabilityVisitor *falsifiabilityVisitor; size_t countSatisfiable(const std::vector & element) const { return boost::range::count_if(element, [&](const typename Base::Variant & expr) { return std::visit(*satisfiabilityVisitor, expr); }); } size_t countFalsifiable(const std::vector & element) const { return boost::range::count_if(element, [&](const typename Base::Variant & expr) { return std::visit(*falsifiabilityVisitor, expr); }); } public: PossibilityVisitor(std::function satisfiabilityTest, std::function falsifiabilityTest): satisfiabilityTest(satisfiabilityTest), falsifiabilityTest(falsifiabilityTest), satisfiabilityVisitor(nullptr), falsifiabilityVisitor(nullptr) {} void setSatisfiabilityVisitor(SatisfiabilityVisitor *satisfiabilityVisitor) { this->satisfiabilityVisitor = satisfiabilityVisitor; } void setFalsifiabilityVisitor(FalsifiabilityVisitor *falsifiabilityVisitor) { this->falsifiabilityVisitor = falsifiabilityVisitor; } }; /// Visitor to test whether expression's value can be true template class SatisfiabilityVisitor : public PossibilityVisitor { using Base = ExpressionBase; public: SatisfiabilityVisitor(std::function satisfiabilityTest, std::function falsifiabilityTest): PossibilityVisitor(satisfiabilityTest, falsifiabilityTest) { this->setSatisfiabilityVisitor(this); } bool operator()(const typename Base::OperatorAny & element) const { return this->countSatisfiable(element.expressions) != 0; } bool operator()(const typename Base::OperatorAll & element) const { return this->countSatisfiable(element.expressions) == element.expressions.size(); } bool operator()(const typename Base::OperatorNone & element) const { return this->countFalsifiable(element.expressions) == element.expressions.size(); } bool operator()(const typename Base::Value & element) const { return this->satisfiabilityTest(element); } }; /// Visitor to test whether expression's value can be false template class FalsifiabilityVisitor : public PossibilityVisitor { using Base = ExpressionBase; public: FalsifiabilityVisitor(std::function satisfiabilityTest, std::function falsifiabilityTest): PossibilityVisitor(satisfiabilityTest, falsifiabilityTest) { this->setFalsifiabilityVisitor(this); } bool operator()(const typename Base::OperatorAny & element) const { return this->countFalsifiable(element.expressions) == element.expressions.size(); } bool operator()(const typename Base::OperatorAll & element) const { return this->countFalsifiable(element.expressions) != 0; } bool operator()(const typename Base::OperatorNone & element) const { return this->countSatisfiable(element.expressions) != 0; } bool operator()(const typename Base::Value & element) const { return this->falsifiabilityTest(element); } }; /// visitor that is trying to generates candidates that must be fulfilled /// to complete this expression template class CandidatesVisitor { using Base = ExpressionBase; using TValueList = std::vector; 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(std::visit(*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(std::visit(*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 { using Base = ExpressionBase; 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 = std::visit(*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 { using Base = ExpressionBase; 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 = std::visit(*this, entryRO); try { // copy entries from child of this type auto sublist = std::get(entry).expressions; std::move(sublist.begin(), sublist.end(), std::back_inserter(ret.expressions)); } catch (std::bad_variant_access &) { // 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 { using Base = ExpressionBase; 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 { using Base = ExpressionBase; 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(std::visit(*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(const std::string & operation); /// Prints expression in human-readable format template class Printer { using Base = ExpressionBase; 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 + std::visit(*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 { using Base = LogicalExpressionDetail::ExpressionBase; public: /// Type of values used in expressions, same as ContainedClass using Value = typename Base::Value; /// Operators for use in expressions, all include vectors with operands using OperatorAny = typename Base::OperatorAny; using OperatorAll = typename Base::OperatorAll; using OperatorNone = typename Base::OperatorNone; /// one expression entry using Variant = typename Base::Variant; private: Variant data; public: /// Base constructor LogicalExpression() = default; /// 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 std::visit(visitor, data); } /// Minimizes expression, removing any redundant elements void minimize() { LogicalExpressionDetail::MinimizingVisitor visitor; data = std::visit(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 std::visit(testVisitor, data); } /// calculates if expression can evaluate to "true". bool satisfiable(std::function satisfiabilityTest, std::function falsifiabilityTest) const { LogicalExpressionDetail::SatisfiabilityVisitor satisfiabilityVisitor(satisfiabilityTest, falsifiabilityTest); LogicalExpressionDetail::FalsifiabilityVisitor falsifiabilityVisitor(satisfiabilityTest, falsifiabilityTest); satisfiabilityVisitor.setFalsifiabilityVisitor(&falsifiabilityVisitor); falsifiabilityVisitor.setSatisfiabilityVisitor(&satisfiabilityVisitor); return std::visit(satisfiabilityVisitor, data); } /// calculates if expression can evaluate to "false". bool falsifiable(std::function satisfiabilityTest, std::function falsifiabilityTest) const { LogicalExpressionDetail::SatisfiabilityVisitor satisfiabilityVisitor(satisfiabilityTest); LogicalExpressionDetail::FalsifiabilityVisitor falsifiabilityVisitor(falsifiabilityTest); satisfiabilityVisitor.setFalsifiabilityVisitor(&falsifiabilityVisitor); falsifiabilityVisitor.setFalsifiabilityVisitor(&satisfiabilityVisitor); return std::visit(falsifiabilityVisitor, 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 std::visit(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 std::visit(printVisitor, data); } std::string toString(std::function toStr, std::function toBool) const { LogicalExpressionDetail::Printer printVisitor(toStr, toBool); return std::visit(printVisitor, data); } JsonNode toJson(std::function toJson) const { LogicalExpressionDetail::Writer writeVisitor(toJson); return std::visit(writeVisitor, data); } template void serialize(Handler & h) { h & data; } }; VCMI_LIB_NAMESPACE_END