1
0
mirror of https://github.com/vcmi/vcmi.git synced 2024-12-22 22:13:35 +02:00
vcmi/lib/LogicalExpression.h
2023-04-30 13:35:54 +03:00

624 lines
18 KiB
C++

/*
* 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
//FIXME: move some of code into .cpp to avoid this include?
#include "JsonNode.h"
VCMI_LIB_NAMESPACE_BEGIN
namespace LogicalExpressionDetail
{
/// class that defines required types for logical expressions
template<typename ContainedClass>
class ExpressionBase
{
public:
/// Possible logical operations, mostly needed to create different types for std::variant
enum EOperations
{
ANY_OF,
ALL_OF,
NONE_OF
};
template<EOperations tag> class Element;
using OperatorAny = Element<ANY_OF>;
using OperatorAll = Element<ALL_OF>;
using OperatorNone = Element<NONE_OF>;
using Value = ContainedClass;
/// Variant that contains all possible elements from logical expression
using Variant = std::variant<OperatorAll, OperatorAny, OperatorNone, Value>;
/// Variant element, contains list of expressions to which operation "tag" should be applied
template<EOperations tag>
class Element
{
public:
Element() {}
Element(std::vector<Variant> expressions):
expressions(expressions)
{}
std::vector<Variant> expressions;
bool operator == (const Element & other) const
{
return expressions == other.expressions;
}
template <typename Handler>
void serialize(Handler & h, const int version)
{
h & expressions;
}
};
};
/// Visitor to test result (true/false) of the expression
template<typename ContainedClass>
class TestVisitor
{
using Base = ExpressionBase<ContainedClass>;
std::function<bool(const typename Base::Value &)> classTest;
size_t countPassed(const std::vector<typename Base::Variant> & element) const
{
return boost::range::count_if(element, [&](const typename Base::Variant & expr)
{
return std::visit(*this, expr);
});
}
public:
TestVisitor(std::function<bool (const typename Base::Value &)> 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 <typename ContainedClass>
class SatisfiabilityVisitor;
template <typename ContainedClass>
class FalsifiabilityVisitor;
template<typename ContainedClass>
class PossibilityVisitor
{
using Base = ExpressionBase<ContainedClass>;
protected:
std::function<bool(const typename Base::Value &)> satisfiabilityTest;
std::function<bool(const typename Base::Value &)> falsifiabilityTest;
SatisfiabilityVisitor<ContainedClass> *satisfiabilityVisitor;
FalsifiabilityVisitor<ContainedClass> *falsifiabilityVisitor;
size_t countSatisfiable(const std::vector<typename Base::Variant> & element) const
{
return boost::range::count_if(element, [&](const typename Base::Variant & expr)
{
return std::visit(*satisfiabilityVisitor, expr);
});
}
size_t countFalsifiable(const std::vector<typename Base::Variant> & element) const
{
return boost::range::count_if(element, [&](const typename Base::Variant & expr)
{
return std::visit(*falsifiabilityVisitor, expr);
});
}
public:
PossibilityVisitor(std::function<bool (const typename Base::Value &)> satisfiabilityTest,
std::function<bool (const typename Base::Value &)> falsifiabilityTest):
satisfiabilityTest(satisfiabilityTest),
falsifiabilityTest(falsifiabilityTest),
satisfiabilityVisitor(nullptr),
falsifiabilityVisitor(nullptr)
{}
void setSatisfiabilityVisitor(SatisfiabilityVisitor<ContainedClass> *satisfiabilityVisitor)
{
this->satisfiabilityVisitor = satisfiabilityVisitor;
}
void setFalsifiabilityVisitor(FalsifiabilityVisitor<ContainedClass> *falsifiabilityVisitor)
{
this->falsifiabilityVisitor = falsifiabilityVisitor;
}
};
/// Visitor to test whether expression's value can be true
template <typename ContainedClass>
class SatisfiabilityVisitor : public PossibilityVisitor<ContainedClass>
{
using Base = ExpressionBase<ContainedClass>;
public:
SatisfiabilityVisitor(std::function<bool (const typename Base::Value &)> satisfiabilityTest,
std::function<bool (const typename Base::Value &)> falsifiabilityTest):
PossibilityVisitor<ContainedClass>(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 <typename ContainedClass>
class FalsifiabilityVisitor : public PossibilityVisitor<ContainedClass>
{
using Base = ExpressionBase<ContainedClass>;
public:
FalsifiabilityVisitor(std::function<bool (const typename Base::Value &)> satisfiabilityTest,
std::function<bool (const typename Base::Value &)> falsifiabilityTest):
PossibilityVisitor<ContainedClass>(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<typename ContainedClass>
class CandidatesVisitor
{
using Base = ExpressionBase<ContainedClass>;
using TValueList = std::vector<typename Base::Value>;
TestVisitor<ContainedClass> classTest;
public:
CandidatesVisitor(std::function<bool(const typename Base::Value &)> 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<typename ContainedClass>
class ForEachVisitor
{
using Base = ExpressionBase<ContainedClass>;
std::function<typename Base::Variant(const typename Base::Value &)> visitor;
public:
ForEachVisitor(std::function<typename Base::Variant(const typename Base::Value &)> visitor):
visitor(visitor)
{}
typename Base::Variant operator()(const typename Base::Value & element) const
{
return visitor(element);
}
template <typename Type>
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<typename ContainedClass>
class MinimizingVisitor
{
using Base = ExpressionBase<ContainedClass>;
public:
typename Base::Variant operator()(const typename Base::Value & element) const
{
return element;
}
template <typename Type>
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<Type>(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 <typename ContainedClass>
class Reader
{
using Base = ExpressionBase<ContainedClass>;
std::function<typename Base::Value(const JsonNode &)> 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<typename Base::Variant> readVector(const JsonNode & node)
{
std::vector<typename Base::Variant> 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<typename Base::Value(const JsonNode &)> classParser):
classParser(classParser)
{}
typename Base::Variant operator ()(const JsonNode & node)
{
return readExpression(node);
}
};
/// Serializes expression in JSON format. Part of map format.
template<typename ContainedClass>
class Writer
{
using Base = ExpressionBase<ContainedClass>;
std::function<JsonNode(const typename Base::Value &)> classPrinter;
JsonNode printExpressionList(std::string name, const std::vector<typename Base::Variant> & 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<JsonNode(const typename Base::Value &)> 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<typename ContainedClass>
class Printer
{
using Base = ExpressionBase<ContainedClass>;
std::function<std::string(const typename Base::Value &)> classPrinter;
std::unique_ptr<TestVisitor<ContainedClass>> statusTest;
mutable std::string prefix;
template<typename Operator>
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<typename Base::Variant> & 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<std::string(const typename Base::Value &)> classPrinter):
classPrinter(classPrinter)
{}
Printer(std::function<std::string(const typename Base::Value &)> classPrinter, std::function<bool(const typename Base::Value &)> toBool):
classPrinter(classPrinter),
statusTest(new TestVisitor<ContainedClass>(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<typename ContainedClass>
class LogicalExpression
{
using Base = LogicalExpressionDetail::ExpressionBase<ContainedClass>;
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<Value(const JsonNode &)> parser)
{
LogicalExpressionDetail::Reader<Value> 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<Variant(const Value &)> morpher) const
{
LogicalExpressionDetail::ForEachVisitor<Value> visitor(morpher);
return std::visit(visitor, data);
}
/// Minimizes expression, removing any redundant elements
void minimize()
{
LogicalExpressionDetail::MinimizingVisitor<Value> visitor;
data = std::visit(visitor, data);
}
/// calculates if expression evaluates to "true".
/// Note: empty expressions always return true
bool test(std::function<bool(const Value &)> toBool) const
{
LogicalExpressionDetail::TestVisitor<Value> testVisitor(toBool);
return std::visit(testVisitor, data);
}
/// calculates if expression can evaluate to "true".
bool satisfiable(std::function<bool(const Value &)> satisfiabilityTest, std::function<bool(const Value &)> falsifiabilityTest) const
{
LogicalExpressionDetail::SatisfiabilityVisitor<Value> satisfiabilityVisitor(satisfiabilityTest, falsifiabilityTest);
LogicalExpressionDetail::FalsifiabilityVisitor<Value> 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<bool(const Value &)> satisfiabilityTest, std::function<bool(const Value &)> falsifiabilityTest) const
{
LogicalExpressionDetail::SatisfiabilityVisitor<Value> satisfiabilityVisitor(satisfiabilityTest);
LogicalExpressionDetail::FalsifiabilityVisitor<Value> 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<Value> getFulfillmentCandidates(std::function<bool(const Value &)> toBool) const
{
LogicalExpressionDetail::CandidatesVisitor<Value> 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<std::string(const Value &)> toStr) const
{
LogicalExpressionDetail::Printer<Value> printVisitor(toStr);
return std::visit(printVisitor, data);
}
std::string toString(std::function<std::string(const Value &)> toStr, std::function<bool(const Value &)> toBool) const
{
LogicalExpressionDetail::Printer<Value> printVisitor(toStr, toBool);
return std::visit(printVisitor, data);
}
JsonNode toJson(std::function<JsonNode(const Value &)> toJson) const
{
LogicalExpressionDetail::Writer<Value> writeVisitor(toJson);
return std::visit(writeVisitor, data);
}
template <typename Handler>
void serialize(Handler & h, const int version)
{
h & data;
}
};
VCMI_LIB_NAMESPACE_END