Files
spark/src/lib/expression/ExpressionEvaluator.cpp
T
John Colvin cf9e3a97fd cleanup: Get rid of BTS_NO_COPY macro
It's cleaner and more precise to specify copy ctrs/ops as deleted
2024-03-28 17:15:55 +00:00

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