431 lines
14 KiB
C++
431 lines
14 KiB
C++
#include "ExpressionEvaluator.h"
|
|
#include "BooleanGrammar.h"
|
|
#include "IPropertyEvaluator.h"
|
|
#include "ParserExceptions.h"
|
|
|
|
#include <boost/algorithm/string.hpp> // iequals
|
|
#include <boost/regex.hpp>
|
|
#include <boost/spirit/include/qi.hpp>
|
|
#include <boost/spirit/include/support_utree.hpp>
|
|
#include <sstream>
|
|
|
|
namespace qi = boost::spirit::qi;
|
|
namespace sp = boost::spirit;
|
|
using namespace std;
|
|
|
|
namespace bts {
|
|
namespace expression {
|
|
|
|
class ExpressionEvaluatorImpl {
|
|
public:
|
|
ExpressionEvaluatorImpl(std::shared_ptr<IPropertyEvaluator> property_eval);
|
|
ExpressionEvaluatorImpl(const ExpressionEvaluatorImpl &) = delete;
|
|
ExpressionEvaluatorImpl &operator=(const ExpressionEvaluatorImpl &) = delete;
|
|
~ExpressionEvaluatorImpl();
|
|
|
|
bool eval(const std::string &expression) const;
|
|
bool check_valid(
|
|
const std::string &expression, std::string *failed_property) const;
|
|
void pre_parse(const string &expression, sp::utree &parse_result) const;
|
|
void pre_parse(
|
|
const std::vector<std::string> &expressions,
|
|
std::vector<boost::spirit::utree> &asts) const;
|
|
bool eval_ast(const boost::spirit::utree &ast) const;
|
|
Value eval_to_value(const std::string &expression) const;
|
|
|
|
private:
|
|
|
|
bool parse(
|
|
std::string::const_iterator &begin,
|
|
const std::string::const_iterator &end,
|
|
boost::spirit::utree &result) const;
|
|
bool check_properties(
|
|
const boost::spirit::utree &node, std::string *failed_property) const;
|
|
Value eval_node(const boost::spirit::utree &node) const;
|
|
|
|
inline bool op_and(boost::spirit::utree::const_iterator &args) const;
|
|
inline bool op_or(boost::spirit::utree::const_iterator &args) const;
|
|
inline bool op_in(boost::spirit::utree::const_iterator &args) const;
|
|
inline bool op_gt(boost::spirit::utree::const_iterator &args) const;
|
|
inline bool op_gte(boost::spirit::utree::const_iterator &args) const;
|
|
inline bool op_lt(boost::spirit::utree::const_iterator &args) const;
|
|
inline bool op_lte(boost::spirit::utree::const_iterator &args) const;
|
|
inline bool op_eq(boost::spirit::utree::const_iterator args) const;
|
|
inline bool op_neq(boost::spirit::utree::const_iterator &args) const;
|
|
inline bool op_match(boost::spirit::utree::const_iterator &args) const;
|
|
|
|
inline bool op_not(const boost::spirit::utree &operand) const;
|
|
inline Value op_list(const boost::spirit::utree &operands) const;
|
|
inline Value op_prop(const boost::spirit::utree &operand) const;
|
|
|
|
const std::shared_ptr<IPropertyEvaluator> _property_eval;
|
|
|
|
mutable RecursionGuard<std::string> _recursion_guard;
|
|
};
|
|
|
|
ExpressionEvaluatorImpl::ExpressionEvaluatorImpl(
|
|
std::shared_ptr<IPropertyEvaluator> property_eval)
|
|
: _property_eval(property_eval), _recursion_guard() {}
|
|
|
|
ExpressionEvaluatorImpl::~ExpressionEvaluatorImpl() {}
|
|
|
|
bool ExpressionEvaluatorImpl::eval(const std::string &expression) const {
|
|
boost::spirit::utree parse_result;
|
|
pre_parse(expression, parse_result);
|
|
return eval_ast(parse_result);
|
|
}
|
|
|
|
Value ExpressionEvaluatorImpl::eval_to_value(
|
|
const std::string &expression) const {
|
|
boost::spirit::utree parse_result;
|
|
pre_parse(expression, parse_result);
|
|
return eval_node(parse_result);
|
|
}
|
|
|
|
void ExpressionEvaluatorImpl::pre_parse(
|
|
const std::string &expression, boost::spirit::utree &parse_result) const {
|
|
std::string::const_iterator begin = expression.begin();
|
|
std::string::const_iterator end = expression.end();
|
|
bool result = parse(begin, end, parse_result);
|
|
if (!result) {
|
|
size_t pos = begin - expression.begin();
|
|
throw ParseFailedException(expression, pos);
|
|
}
|
|
// std::cout << std::endl << parse_result;
|
|
}
|
|
|
|
void ExpressionEvaluatorImpl::pre_parse(
|
|
const std::vector<std::string> &expressions,
|
|
std::vector<boost::spirit::utree> &asts) const {
|
|
std::vector<std::string>::const_iterator i;
|
|
asts.reserve(expressions.size());
|
|
for (i = expressions.begin(); i != expressions.end(); ++i) {
|
|
boost::spirit::utree parse_result;
|
|
pre_parse(*i, parse_result);
|
|
asts.push_back(parse_result);
|
|
}
|
|
}
|
|
|
|
bool ExpressionEvaluatorImpl::eval_ast(const boost::spirit::utree &ast) const {
|
|
return value_to_bool(eval_node(ast));
|
|
}
|
|
|
|
bool ExpressionEvaluatorImpl::check_valid(
|
|
const std::string &expression, std::string *failed_property) const {
|
|
sp::utree parse_result;
|
|
std::string::const_iterator begin = expression.begin();
|
|
std::string::const_iterator end = expression.end();
|
|
bool result = parse(begin, end, parse_result);
|
|
if (result) {
|
|
result = check_properties(parse_result, failed_property);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
bool ExpressionEvaluatorImpl::parse(
|
|
std::string::const_iterator &begin,
|
|
const std::string::const_iterator &end,
|
|
sp::utree &ast) const {
|
|
// We reference a grammar defined elsewhere for compile time performance
|
|
bool result = qi::phrase_parse(
|
|
begin, end, boolean_string_grammar, qi::ascii::space, ast);
|
|
return result && (begin == end);
|
|
}
|
|
|
|
bool ExpressionEvaluatorImpl::check_properties(
|
|
const boost::spirit::utree &node, std::string *failed_property) const {
|
|
bool result = true;
|
|
|
|
// a list is either a list [a,b,c] or (operator, arg1, arg2,...)
|
|
if (node.which() == sp::utree_type::list_type) {
|
|
boost::spirit::utree::const_iterator iter = node.begin();
|
|
boost::spirit::utree::const_iterator end = node.end();
|
|
|
|
const sp::utree &operator_node = node.front();
|
|
|
|
if (operator_node.which() == sp::utree_type::symbol_type) {
|
|
std::string op = get_symbol(operator_node);
|
|
++iter; // advanced past operator
|
|
|
|
if (op == "property") {
|
|
std::string symbol = get_symbol(*iter);
|
|
if (!_property_eval->get_value(symbol, nullptr)) {
|
|
if (failed_property != nullptr) {
|
|
*failed_property = symbol;
|
|
}
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
for (; result && iter != end; ++iter) {
|
|
result = check_properties(*iter, failed_property);
|
|
}
|
|
} else if (node.which() == sp::utree_type::invalid_type) {
|
|
result = false;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
void check_args(
|
|
const std::string &op, const sp::utree &node, size_t got, size_t expected) {
|
|
if (got != expected) {
|
|
std::ostringstream s_exp;
|
|
s_exp << node;
|
|
throw InsuficientArgumentsException(op, s_exp.str(), expected);
|
|
}
|
|
}
|
|
|
|
Value ExpressionEvaluatorImpl::eval_node(const sp::utree &node) const {
|
|
if (node.which() != sp::utree_type::list_type || node.size() == 1) {
|
|
return value_from_utree(node);
|
|
}
|
|
|
|
sp::utree::const_iterator iter = node.begin();
|
|
const sp::utree &operator_node = *iter;
|
|
++iter; // advance past operator
|
|
|
|
if (operator_node.which() != sp::utree_type::symbol_type)
|
|
throw std::invalid_argument(
|
|
"Operator expression must begin with operator symbol");
|
|
|
|
std::string op = get_symbol(operator_node);
|
|
|
|
if (op == "property") {
|
|
check_args(op, node, node.size(), 2);
|
|
return op_prop(*iter);
|
|
} else if (op == "==") {
|
|
check_args(op, node, node.size(), 3);
|
|
return op_eq(iter);
|
|
} else if (op == "~=") {
|
|
check_args(op, node, node.size(), 3);
|
|
return op_match(iter);
|
|
} else if (op == "!") {
|
|
check_args(op, node, node.size(), 2);
|
|
return op_not(*iter);
|
|
} else if (op == "list") {
|
|
return op_list(node);
|
|
} else if (op == "and") {
|
|
check_args(op, node, node.size(), 3);
|
|
return op_and(iter);
|
|
} else if (op == "or") {
|
|
check_args(op, node, node.size(), 3);
|
|
return op_or(iter);
|
|
} else if (op == "in") {
|
|
check_args(op, node, node.size(), 3);
|
|
return op_in(iter);
|
|
} else if (op == ">") {
|
|
check_args(op, node, node.size(), 3);
|
|
return op_gt(iter);
|
|
} else if (op == ">=") {
|
|
check_args(op, node, node.size(), 3);
|
|
return op_gte(iter);
|
|
} else if (op == "<") {
|
|
check_args(op, node, node.size(), 3);
|
|
return op_lt(iter);
|
|
} else if (op == "<=") {
|
|
check_args(op, node, node.size(), 3);
|
|
return op_lte(iter);
|
|
} else if (op == "!=") {
|
|
check_args(op, node, node.size(), 3);
|
|
return op_neq(iter);
|
|
}
|
|
|
|
std::string exception_msg = "Unknown operator: " + op;
|
|
throw ParserException(exception_msg);
|
|
}
|
|
|
|
bool ExpressionEvaluatorImpl::op_and(
|
|
boost::spirit::utree::const_iterator &args) const {
|
|
// short circuit rhs
|
|
bool left = value_to_bool(eval_node(*args));
|
|
args++;
|
|
return left && value_to_bool(eval_node(*args));
|
|
}
|
|
|
|
bool ExpressionEvaluatorImpl::op_or(
|
|
boost::spirit::utree::const_iterator &args) const {
|
|
// short circuit rhs
|
|
bool left = value_to_bool(eval_node(*args));
|
|
return left || value_to_bool(eval_node(*++args));
|
|
}
|
|
|
|
struct InsensitiveStringCompare {
|
|
InsensitiveStringCompare(const std::string &val) : value(val) {}
|
|
const std::string &value;
|
|
|
|
bool operator()(const std::string &val) const {
|
|
return boost::iequals(value, val);
|
|
}
|
|
|
|
bool operator()(const Value val) const {
|
|
if (val.which() != ValueType::anything) {
|
|
return boost::iequals(value, expression::value_to_string(val));
|
|
}
|
|
return false;
|
|
}
|
|
};
|
|
|
|
bool ExpressionEvaluatorImpl::op_in(
|
|
boost::spirit::utree::const_iterator &args) const {
|
|
Value left = eval_node(*args);
|
|
Value right = eval_node(*++args);
|
|
|
|
std::vector<Value> vector;
|
|
if (right.which() == ValueType::list) {
|
|
vector = boost::get<std::vector<Value>>(right);
|
|
if (left.which() == ValueType::string) {
|
|
return std::find_if(
|
|
vector.begin(), vector.end(),
|
|
InsensitiveStringCompare(boost::get<string>(left))) !=
|
|
vector.end();
|
|
} else {
|
|
return std::find(vector.begin(), vector.end(), left) != vector.end();
|
|
}
|
|
} else if (right.which() == ValueType::string) {
|
|
string r = boost::get<string>(right);
|
|
string l = boost::get<string>(left);
|
|
boost::algorithm::to_lower(r);
|
|
boost::algorithm::to_lower(l);
|
|
return r.find(l) != std::string::npos;
|
|
} else {
|
|
throw std::invalid_argument(
|
|
"Right hand argument to 'in' operator must be a list");
|
|
}
|
|
}
|
|
|
|
bool ExpressionEvaluatorImpl::op_gt(
|
|
boost::spirit::utree::const_iterator &args) const {
|
|
Value left = eval_node(*args);
|
|
Value right = eval_node(*++args);
|
|
return right < left;
|
|
}
|
|
|
|
bool ExpressionEvaluatorImpl::op_gte(
|
|
boost::spirit::utree::const_iterator &args) const {
|
|
Value left = eval_node(*args);
|
|
Value right = eval_node(*++args);
|
|
return right == left || right < left;
|
|
}
|
|
|
|
bool ExpressionEvaluatorImpl::op_lt(
|
|
boost::spirit::utree::const_iterator &args) const {
|
|
Value left = eval_node(*args);
|
|
Value right = eval_node(*++args);
|
|
return left < right;
|
|
}
|
|
|
|
bool ExpressionEvaluatorImpl::op_lte(
|
|
boost::spirit::utree::const_iterator &args) const {
|
|
Value left = eval_node(*args);
|
|
Value right = eval_node(*++args);
|
|
return left == right || left < right;
|
|
}
|
|
|
|
bool ExpressionEvaluatorImpl::op_eq(
|
|
boost::spirit::utree::const_iterator args) const {
|
|
Value left = eval_node(*args);
|
|
args++;
|
|
Value right = eval_node(*args);
|
|
if (left.which() == ValueType::string &&
|
|
right.which() == ValueType::string) {
|
|
return boost::iequals(
|
|
boost::get<std::string>(left), boost::get<std::string>(right));
|
|
}
|
|
return left == right;
|
|
}
|
|
|
|
bool ExpressionEvaluatorImpl::op_neq(
|
|
boost::spirit::utree::const_iterator &args) const {
|
|
return !op_eq(args);
|
|
}
|
|
|
|
inline bool ExpressionEvaluatorImpl::op_match(
|
|
boost::spirit::utree::const_iterator &args) const {
|
|
Value left = eval_node(*args);
|
|
args++;
|
|
Value right = eval_node(*args);
|
|
|
|
return ::boost::regex_search(
|
|
boost::get<std::string>(left),
|
|
boost::regex(boost::get<std::string>(right)));
|
|
}
|
|
|
|
bool ExpressionEvaluatorImpl::op_not(
|
|
const boost::spirit::utree &operand) const {
|
|
return !value_to_bool(eval_node(operand));
|
|
}
|
|
|
|
Value ExpressionEvaluatorImpl::op_list(
|
|
const boost::spirit::utree &operands) const {
|
|
boost::spirit::utree::const_iterator iter = operands.begin(),
|
|
end = operands.end();
|
|
++iter; // advance over "list" operator
|
|
|
|
std::vector<Value> list_values;
|
|
list_values.reserve(operands.size() - 1);
|
|
|
|
for (; iter != end; ++iter) {
|
|
list_values.push_back(eval_node(*iter));
|
|
}
|
|
|
|
return Value(list_values);
|
|
}
|
|
|
|
Value ExpressionEvaluatorImpl::op_prop(const sp::utree &node) const {
|
|
Value result;
|
|
std::string symbol = get_symbol(node);
|
|
|
|
if (_recursion_guard.check(symbol)) {
|
|
throw RecursivePropertyGetException(symbol, _recursion_guard.get());
|
|
}
|
|
GuardFrame<std::string> frame;
|
|
_recursion_guard.push(symbol, &frame);
|
|
|
|
if (!_property_eval->get_value(symbol, &result)) {
|
|
throw InvalidPropertyException(symbol);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
//
|
|
// Outter class method definitions
|
|
//
|
|
|
|
ExpressionEvaluator::ExpressionEvaluator(
|
|
std::shared_ptr<IPropertyEvaluator> property_eval)
|
|
: _impl(new ExpressionEvaluatorImpl(property_eval)) {}
|
|
|
|
ExpressionEvaluator::~ExpressionEvaluator() {}
|
|
|
|
bool ExpressionEvaluator::eval(const std::string &expression) const {
|
|
return _impl->eval(expression);
|
|
}
|
|
|
|
Value ExpressionEvaluator::eval_to_value(const std::string &expression) const {
|
|
return _impl->eval_to_value(expression);
|
|
}
|
|
|
|
bool ExpressionEvaluator::check_valid(
|
|
const std::string &expression, std::string *failed_property) const {
|
|
return _impl->check_valid(expression, failed_property);
|
|
}
|
|
|
|
void ExpressionEvaluator::pre_parse(
|
|
const std::string &expression, boost::spirit::utree &ast) const {
|
|
return _impl->pre_parse(expression, ast);
|
|
}
|
|
void ExpressionEvaluator::pre_parse(
|
|
const std::vector<std::string> &expressions,
|
|
std::vector<boost::spirit::utree> &asts) const {
|
|
return _impl->pre_parse(expressions, asts);
|
|
}
|
|
bool ExpressionEvaluator::eval_ast(const boost::spirit::utree &ast) const {
|
|
return _impl->eval_ast(ast);
|
|
}
|
|
|
|
} // namespace expression
|
|
} // namespace bts
|