Implement expression evaluation

Reviewers: buda

Reviewed By: buda

Differential Revision: https://phabricator.memgraph.io/D179
This commit is contained in:
Mislav Bradac 2017-03-24 17:01:06 +01:00
parent 3a07a95f61
commit 9c7acf780c
6 changed files with 299 additions and 8 deletions

View File

@ -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>();
}
}

View File

@ -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);

View File

@ -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;

View File

@ -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_);
}

View File

@ -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);
}

View File

@ -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));