Implement expression evaluation
Reviewers: buda Reviewed By: buda Differential Revision: https://phabricator.memgraph.io/D179
This commit is contained in:
parent
3a07a95f61
commit
9c7acf780c
@ -543,6 +543,20 @@ TypedValue operator-(const TypedValue &a) {
|
||||
}
|
||||
}
|
||||
|
||||
TypedValue operator+(const TypedValue &a) {
|
||||
switch (a.type()) {
|
||||
case TypedValue::Type::Null:
|
||||
return TypedValue::Null;
|
||||
case TypedValue::Type::Int:
|
||||
return +a.Value<int64_t>();
|
||||
case TypedValue::Type::Double:
|
||||
return +a.Value<double>();
|
||||
default:
|
||||
throw TypedValueException("Invalid unary plus operand type (-{})",
|
||||
a.type());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Raises a TypedValueException if the given values do not support arithmetic
|
||||
* operations. If they do, nothing happens.
|
||||
@ -630,6 +644,8 @@ TypedValue operator/(const TypedValue &a, const TypedValue &b) {
|
||||
b.type() == TypedValue::Type::Double) {
|
||||
return ToDouble(a) / ToDouble(b);
|
||||
} else {
|
||||
if (b.Value<int64_t>() == 0LL)
|
||||
throw TypedValueException("Division by zero");
|
||||
return a.Value<int64_t>() / b.Value<int64_t>();
|
||||
}
|
||||
}
|
||||
@ -660,6 +676,7 @@ TypedValue operator%(const TypedValue &a, const TypedValue &b) {
|
||||
b.type() == TypedValue::Type::Double) {
|
||||
return (double)fmod(ToDouble(a), ToDouble(b));
|
||||
} else {
|
||||
if (b.Value<int64_t>() == 0LL) throw TypedValueException("Mod with zero");
|
||||
return a.Value<int64_t>() % b.Value<int64_t>();
|
||||
}
|
||||
}
|
||||
|
@ -26,11 +26,11 @@ typedef traversal_template::Path<VertexAccessor, EdgeAccessor> Path;
|
||||
* TypedValue::Type. Each such type corresponds to exactly one C++ type.
|
||||
*/
|
||||
class TypedValue : public TotalOrdering<TypedValue, TypedValue, TypedValue> {
|
||||
public:
|
||||
public:
|
||||
/** Private default constructor, makes Null */
|
||||
TypedValue() : type_(Type::Null) {}
|
||||
|
||||
public:
|
||||
public:
|
||||
/** A value type. Each type corresponds to exactly one C++ type */
|
||||
enum class Type : unsigned {
|
||||
Null,
|
||||
@ -95,12 +95,14 @@ public:
|
||||
* @tparam T Type to interpret the value as.
|
||||
* @return The value as type T.
|
||||
*/
|
||||
template <typename T> T &Value();
|
||||
template <typename T> const T &Value() const;
|
||||
template <typename T>
|
||||
T &Value();
|
||||
template <typename T>
|
||||
const T &Value() const;
|
||||
|
||||
friend std::ostream &operator<<(std::ostream &stream, const TypedValue &prop);
|
||||
|
||||
private:
|
||||
private:
|
||||
// storage for the value of the property
|
||||
union {
|
||||
bool bool_v;
|
||||
@ -133,7 +135,7 @@ private:
|
||||
* of incompatible Types.
|
||||
*/
|
||||
class TypedValueException : public StacktraceException {
|
||||
public:
|
||||
public:
|
||||
using ::StacktraceException::StacktraceException;
|
||||
};
|
||||
|
||||
@ -145,6 +147,7 @@ TypedValue operator!(const TypedValue &a);
|
||||
|
||||
// arithmetic operators
|
||||
TypedValue operator-(const TypedValue &a);
|
||||
TypedValue operator+(const TypedValue &a);
|
||||
TypedValue operator+(const TypedValue &a, const TypedValue &b);
|
||||
TypedValue operator-(const TypedValue &a, const TypedValue &b);
|
||||
TypedValue operator/(const TypedValue &a, const TypedValue &b);
|
||||
|
@ -404,6 +404,9 @@ antlrcpp::Any CypherMainVisitor::visitExpression8(
|
||||
std::vector<Expression *> comparisons;
|
||||
for (int i = 0; i < (int)operators.size(); ++i) {
|
||||
auto *expr = children[i + 1];
|
||||
// TODO: first_operand should only do lookup if it is only calculated and
|
||||
// not recalculated whole subexpression once again. SymbolGenerator should
|
||||
// generate symbol for every expresion and then lookup would be possible.
|
||||
comparisons.push_back(
|
||||
CreateBinaryOperatorByToken(operators[i], first_operand, expr));
|
||||
first_operand = expr;
|
||||
|
@ -1,7 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
#include <utils/exceptions/not_yet_implemented.hpp>
|
||||
#include <vector>
|
||||
|
||||
#include "query/backend/cpp/typed_value.hpp"
|
||||
#include "query/frontend/ast/ast.hpp"
|
||||
@ -15,7 +15,9 @@ class Frame {
|
||||
Frame(int size) : size_(size), elems_(size_) {}
|
||||
|
||||
auto &operator[](const Symbol &symbol) { return elems_[symbol.position_]; }
|
||||
const auto &operator[](const Symbol &symbol) const { return elems_[symbol.position_]; }
|
||||
const auto &operator[](const Symbol &symbol) const {
|
||||
return elems_[symbol.position_];
|
||||
}
|
||||
|
||||
private:
|
||||
int size_;
|
||||
@ -52,6 +54,41 @@ class ExpressionEvaluator : public TreeVisitorBase {
|
||||
result_stack_.push_back(frame_[symbol_table_[ident]]);
|
||||
}
|
||||
|
||||
#define BINARY_OPERATOR_VISITOR(OP_NODE, CPP_OP) \
|
||||
void PostVisit(OP_NODE &) override { \
|
||||
auto expression2 = PopBack(); \
|
||||
auto expression1 = PopBack(); \
|
||||
result_stack_.push_back(expression1 CPP_OP expression2); \
|
||||
}
|
||||
|
||||
#define UNARY_OPERATOR_VISITOR(OP_NODE, CPP_OP) \
|
||||
void PostVisit(OP_NODE &) override { \
|
||||
auto expression = PopBack(); \
|
||||
result_stack_.push_back(CPP_OP expression); \
|
||||
}
|
||||
|
||||
BINARY_OPERATOR_VISITOR(OrOperator, ||);
|
||||
BINARY_OPERATOR_VISITOR(XorOperator, ^);
|
||||
BINARY_OPERATOR_VISITOR(AndOperator, &&);
|
||||
BINARY_OPERATOR_VISITOR(AdditionOperator, +);
|
||||
BINARY_OPERATOR_VISITOR(SubtractionOperator, -);
|
||||
BINARY_OPERATOR_VISITOR(MultiplicationOperator, *);
|
||||
BINARY_OPERATOR_VISITOR(DivisionOperator, /);
|
||||
BINARY_OPERATOR_VISITOR(ModOperator, %);
|
||||
BINARY_OPERATOR_VISITOR(NotEqualOperator, !=);
|
||||
BINARY_OPERATOR_VISITOR(EqualOperator, ==);
|
||||
BINARY_OPERATOR_VISITOR(LessOperator, <);
|
||||
BINARY_OPERATOR_VISITOR(GreaterOperator, >);
|
||||
BINARY_OPERATOR_VISITOR(LessEqualOperator, <=);
|
||||
BINARY_OPERATOR_VISITOR(GreaterEqualOperator, >=);
|
||||
|
||||
UNARY_OPERATOR_VISITOR(NotOperator, !);
|
||||
UNARY_OPERATOR_VISITOR(UnaryPlusOperator, +);
|
||||
UNARY_OPERATOR_VISITOR(UnaryMinusOperator, -);
|
||||
|
||||
#undef BINARY_OPERATOR_VISITOR
|
||||
#undef UNARY_OPERATOR_VISITOR
|
||||
|
||||
void PostVisit(PropertyLookup &property_lookup) override {
|
||||
auto expression_result = PopBack();
|
||||
switch (expression_result.type()) {
|
||||
@ -78,6 +115,8 @@ class ExpressionEvaluator : public TreeVisitorBase {
|
||||
}
|
||||
|
||||
void Visit(Literal &literal) override {
|
||||
// TODO: no need to evaluate constants, we can write it to frame in one of
|
||||
// the previous phases.
|
||||
result_stack_.push_back(literal.value_);
|
||||
}
|
||||
|
||||
|
@ -699,3 +699,220 @@ TEST(Interpreter, EdgeFilterMultipleTypes) {
|
||||
ResultStreamFaker result = CollectProduce(produce, symbol_table, *dba);
|
||||
EXPECT_EQ(result.GetResults().size(), 2);
|
||||
}
|
||||
|
||||
struct NoContextExpressionEvaluator {
|
||||
NoContextExpressionEvaluator() {}
|
||||
Frame frame{0};
|
||||
SymbolTable symbol_table;
|
||||
ExpressionEvaluator eval{frame, symbol_table};
|
||||
};
|
||||
|
||||
TEST(ExpressionEvaluator, OrOperator) {
|
||||
AstTreeStorage storage;
|
||||
NoContextExpressionEvaluator eval;
|
||||
auto *op = storage.Create<OrOperator>(storage.Create<Literal>(true),
|
||||
storage.Create<Literal>(false));
|
||||
op->Accept(eval.eval);
|
||||
ASSERT_EQ(eval.eval.PopBack().Value<bool>(), true);
|
||||
op = storage.Create<OrOperator>(storage.Create<Literal>(true),
|
||||
storage.Create<Literal>(true));
|
||||
op->Accept(eval.eval);
|
||||
ASSERT_EQ(eval.eval.PopBack().Value<bool>(), true);
|
||||
}
|
||||
|
||||
TEST(ExpressionEvaluator, XorOperator) {
|
||||
AstTreeStorage storage;
|
||||
NoContextExpressionEvaluator eval;
|
||||
auto *op = storage.Create<XorOperator>(storage.Create<Literal>(true),
|
||||
storage.Create<Literal>(false));
|
||||
op->Accept(eval.eval);
|
||||
ASSERT_EQ(eval.eval.PopBack().Value<bool>(), true);
|
||||
op = storage.Create<XorOperator>(storage.Create<Literal>(true),
|
||||
storage.Create<Literal>(true));
|
||||
op->Accept(eval.eval);
|
||||
ASSERT_EQ(eval.eval.PopBack().Value<bool>(), false);
|
||||
}
|
||||
|
||||
TEST(ExpressionEvaluator, AndOperator) {
|
||||
AstTreeStorage storage;
|
||||
NoContextExpressionEvaluator eval;
|
||||
auto *op = storage.Create<AndOperator>(storage.Create<Literal>(true),
|
||||
storage.Create<Literal>(true));
|
||||
op->Accept(eval.eval);
|
||||
ASSERT_EQ(eval.eval.PopBack().Value<bool>(), true);
|
||||
op = storage.Create<AndOperator>(storage.Create<Literal>(false),
|
||||
storage.Create<Literal>(true));
|
||||
op->Accept(eval.eval);
|
||||
ASSERT_EQ(eval.eval.PopBack().Value<bool>(), false);
|
||||
}
|
||||
|
||||
TEST(ExpressionEvaluator, AdditionOperator) {
|
||||
AstTreeStorage storage;
|
||||
NoContextExpressionEvaluator eval;
|
||||
auto *op = storage.Create<AdditionOperator>(storage.Create<Literal>(2),
|
||||
storage.Create<Literal>(3));
|
||||
op->Accept(eval.eval);
|
||||
ASSERT_EQ(eval.eval.PopBack().Value<int64_t>(), 5);
|
||||
}
|
||||
|
||||
TEST(ExpressionEvaluator, SubtractionOperator) {
|
||||
AstTreeStorage storage;
|
||||
NoContextExpressionEvaluator eval;
|
||||
auto *op = storage.Create<SubtractionOperator>(storage.Create<Literal>(2),
|
||||
storage.Create<Literal>(3));
|
||||
op->Accept(eval.eval);
|
||||
ASSERT_EQ(eval.eval.PopBack().Value<int64_t>(), -1);
|
||||
}
|
||||
|
||||
TEST(ExpressionEvaluator, MultiplicationOperator) {
|
||||
AstTreeStorage storage;
|
||||
NoContextExpressionEvaluator eval;
|
||||
auto *op = storage.Create<MultiplicationOperator>(storage.Create<Literal>(2),
|
||||
storage.Create<Literal>(3));
|
||||
op->Accept(eval.eval);
|
||||
ASSERT_EQ(eval.eval.PopBack().Value<int64_t>(), 6);
|
||||
}
|
||||
|
||||
TEST(ExpressionEvaluator, DivisionOperator) {
|
||||
AstTreeStorage storage;
|
||||
NoContextExpressionEvaluator eval;
|
||||
auto *op = storage.Create<DivisionOperator>(storage.Create<Literal>(50),
|
||||
storage.Create<Literal>(10));
|
||||
op->Accept(eval.eval);
|
||||
ASSERT_EQ(eval.eval.PopBack().Value<int64_t>(), 5);
|
||||
}
|
||||
|
||||
TEST(ExpressionEvaluator, ModOperator) {
|
||||
AstTreeStorage storage;
|
||||
NoContextExpressionEvaluator eval;
|
||||
auto *op = storage.Create<ModOperator>(storage.Create<Literal>(65),
|
||||
storage.Create<Literal>(10));
|
||||
op->Accept(eval.eval);
|
||||
ASSERT_EQ(eval.eval.PopBack().Value<int64_t>(), 5);
|
||||
}
|
||||
|
||||
TEST(ExpressionEvaluator, EqualOperator) {
|
||||
AstTreeStorage storage;
|
||||
NoContextExpressionEvaluator eval;
|
||||
auto *op = storage.Create<EqualOperator>(storage.Create<Literal>(10),
|
||||
storage.Create<Literal>(15));
|
||||
op->Accept(eval.eval);
|
||||
ASSERT_EQ(eval.eval.PopBack().Value<bool>(), false);
|
||||
op = storage.Create<EqualOperator>(storage.Create<Literal>(15),
|
||||
storage.Create<Literal>(15));
|
||||
op->Accept(eval.eval);
|
||||
ASSERT_EQ(eval.eval.PopBack().Value<bool>(), true);
|
||||
op = storage.Create<EqualOperator>(storage.Create<Literal>(20),
|
||||
storage.Create<Literal>(15));
|
||||
op->Accept(eval.eval);
|
||||
ASSERT_EQ(eval.eval.PopBack().Value<bool>(), false);
|
||||
}
|
||||
|
||||
TEST(ExpressionEvaluator, NotEqualOperator) {
|
||||
AstTreeStorage storage;
|
||||
NoContextExpressionEvaluator eval;
|
||||
auto *op = storage.Create<NotEqualOperator>(storage.Create<Literal>(10),
|
||||
storage.Create<Literal>(15));
|
||||
op->Accept(eval.eval);
|
||||
ASSERT_EQ(eval.eval.PopBack().Value<bool>(), true);
|
||||
op = storage.Create<NotEqualOperator>(storage.Create<Literal>(15),
|
||||
storage.Create<Literal>(15));
|
||||
op->Accept(eval.eval);
|
||||
ASSERT_EQ(eval.eval.PopBack().Value<bool>(), false);
|
||||
op = storage.Create<NotEqualOperator>(storage.Create<Literal>(20),
|
||||
storage.Create<Literal>(15));
|
||||
op->Accept(eval.eval);
|
||||
ASSERT_EQ(eval.eval.PopBack().Value<bool>(), true);
|
||||
}
|
||||
|
||||
TEST(ExpressionEvaluator, LessOperator) {
|
||||
AstTreeStorage storage;
|
||||
NoContextExpressionEvaluator eval;
|
||||
auto *op = storage.Create<LessOperator>(storage.Create<Literal>(10),
|
||||
storage.Create<Literal>(15));
|
||||
op->Accept(eval.eval);
|
||||
ASSERT_EQ(eval.eval.PopBack().Value<bool>(), true);
|
||||
op = storage.Create<LessOperator>(storage.Create<Literal>(15),
|
||||
storage.Create<Literal>(15));
|
||||
op->Accept(eval.eval);
|
||||
ASSERT_EQ(eval.eval.PopBack().Value<bool>(), false);
|
||||
op = storage.Create<LessOperator>(storage.Create<Literal>(20),
|
||||
storage.Create<Literal>(15));
|
||||
op->Accept(eval.eval);
|
||||
ASSERT_EQ(eval.eval.PopBack().Value<bool>(), false);
|
||||
}
|
||||
|
||||
TEST(ExpressionEvaluator, GreaterOperator) {
|
||||
AstTreeStorage storage;
|
||||
NoContextExpressionEvaluator eval;
|
||||
auto *op = storage.Create<GreaterOperator>(storage.Create<Literal>(10),
|
||||
storage.Create<Literal>(15));
|
||||
op->Accept(eval.eval);
|
||||
ASSERT_EQ(eval.eval.PopBack().Value<bool>(), false);
|
||||
op = storage.Create<GreaterOperator>(storage.Create<Literal>(15),
|
||||
storage.Create<Literal>(15));
|
||||
op->Accept(eval.eval);
|
||||
ASSERT_EQ(eval.eval.PopBack().Value<bool>(), false);
|
||||
op = storage.Create<GreaterOperator>(storage.Create<Literal>(20),
|
||||
storage.Create<Literal>(15));
|
||||
op->Accept(eval.eval);
|
||||
ASSERT_EQ(eval.eval.PopBack().Value<bool>(), true);
|
||||
}
|
||||
|
||||
TEST(ExpressionEvaluator, LessEqualOperator) {
|
||||
AstTreeStorage storage;
|
||||
NoContextExpressionEvaluator eval;
|
||||
auto *op = storage.Create<LessEqualOperator>(storage.Create<Literal>(10),
|
||||
storage.Create<Literal>(15));
|
||||
op->Accept(eval.eval);
|
||||
ASSERT_EQ(eval.eval.PopBack().Value<bool>(), true);
|
||||
op = storage.Create<LessEqualOperator>(storage.Create<Literal>(15),
|
||||
storage.Create<Literal>(15));
|
||||
op->Accept(eval.eval);
|
||||
ASSERT_EQ(eval.eval.PopBack().Value<bool>(), true);
|
||||
op = storage.Create<LessEqualOperator>(storage.Create<Literal>(20),
|
||||
storage.Create<Literal>(15));
|
||||
op->Accept(eval.eval);
|
||||
ASSERT_EQ(eval.eval.PopBack().Value<bool>(), false);
|
||||
}
|
||||
|
||||
TEST(ExpressionEvaluator, GreaterEqualOperator) {
|
||||
AstTreeStorage storage;
|
||||
NoContextExpressionEvaluator eval;
|
||||
auto *op = storage.Create<GreaterEqualOperator>(storage.Create<Literal>(10),
|
||||
storage.Create<Literal>(15));
|
||||
op->Accept(eval.eval);
|
||||
ASSERT_EQ(eval.eval.PopBack().Value<bool>(), false);
|
||||
op = storage.Create<GreaterEqualOperator>(storage.Create<Literal>(15),
|
||||
storage.Create<Literal>(15));
|
||||
op->Accept(eval.eval);
|
||||
ASSERT_EQ(eval.eval.PopBack().Value<bool>(), true);
|
||||
op = storage.Create<GreaterEqualOperator>(storage.Create<Literal>(20),
|
||||
storage.Create<Literal>(15));
|
||||
op->Accept(eval.eval);
|
||||
ASSERT_EQ(eval.eval.PopBack().Value<bool>(), true);
|
||||
}
|
||||
|
||||
TEST(ExpressionEvaluator, NotOperator) {
|
||||
AstTreeStorage storage;
|
||||
NoContextExpressionEvaluator eval;
|
||||
auto *op = storage.Create<NotOperator>(storage.Create<Literal>(false));
|
||||
op->Accept(eval.eval);
|
||||
ASSERT_EQ(eval.eval.PopBack().Value<bool>(), true);
|
||||
}
|
||||
|
||||
TEST(ExpressionEvaluator, UnaryPlusOperator) {
|
||||
AstTreeStorage storage;
|
||||
NoContextExpressionEvaluator eval;
|
||||
auto *op = storage.Create<UnaryPlusOperator>(storage.Create<Literal>(5));
|
||||
op->Accept(eval.eval);
|
||||
ASSERT_EQ(eval.eval.PopBack().Value<int64_t>(), 5);
|
||||
}
|
||||
|
||||
TEST(ExpressionEvaluator, UnaryMinusOperator) {
|
||||
AstTreeStorage storage;
|
||||
NoContextExpressionEvaluator eval;
|
||||
auto *op = storage.Create<UnaryMinusOperator>(storage.Create<Literal>(5));
|
||||
op->Accept(eval.eval);
|
||||
ASSERT_EQ(eval.eval.PopBack().Value<int64_t>(), -5);
|
||||
}
|
||||
|
@ -150,6 +150,16 @@ TEST(TypedValue, UnaryMinus) {
|
||||
EXPECT_THROW(-TypedValue("something"), TypedValueException);
|
||||
}
|
||||
|
||||
TEST(TypedValue, UnaryPlus) {
|
||||
EXPECT_TRUE((+TypedValue::Null).type() == TypedValue::Type::Null);
|
||||
|
||||
EXPECT_PROP_EQ((+TypedValue(2).Value<int64_t>()), 2);
|
||||
EXPECT_FLOAT_EQ((+TypedValue(2.0).Value<double>()), 2.0);
|
||||
|
||||
EXPECT_THROW(+TypedValue(true), TypedValueException);
|
||||
EXPECT_THROW(+TypedValue("something"), TypedValueException);
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs a series of tests on properties of all types. The tests
|
||||
* evaluate how arithmetic operators behave w.r.t. exception throwing
|
||||
@ -236,6 +246,7 @@ TEST(TypedValue, Difference) {
|
||||
TEST(TypedValue, Divison) {
|
||||
ExpectArithmeticThrowsAndNull(
|
||||
false, [](const TypedValue &a, const TypedValue &b) { return a / b; });
|
||||
EXPECT_THROW(TypedValue(1) / TypedValue(0), TypedValueException);
|
||||
|
||||
EXPECT_PROP_EQ(TypedValue(10) / TypedValue(2), TypedValue(5));
|
||||
EXPECT_PROP_EQ(TypedValue(10) / TypedValue(4), TypedValue(2));
|
||||
@ -261,6 +272,7 @@ TEST(TypedValue, Multiplication) {
|
||||
TEST(TypedValue, Modulo) {
|
||||
ExpectArithmeticThrowsAndNull(
|
||||
false, [](const TypedValue &a, const TypedValue &b) { return a % b; });
|
||||
EXPECT_THROW(TypedValue(1) % TypedValue(0), TypedValueException);
|
||||
|
||||
EXPECT_PROP_EQ(TypedValue(10) % TypedValue(2), TypedValue(0));
|
||||
EXPECT_PROP_EQ(TypedValue(10) % TypedValue(4), TypedValue(2));
|
||||
|
Loading…
Reference in New Issue
Block a user