From 9c7acf780cf751f44c917f1246ea214fa2e43ebf Mon Sep 17 00:00:00 2001 From: Mislav Bradac Date: Fri, 24 Mar 2017 17:01:06 +0100 Subject: [PATCH] Implement expression evaluation Reviewers: buda Reviewed By: buda Differential Revision: https://phabricator.memgraph.io/D179 --- src/query/backend/cpp/typed_value.cpp | 17 ++ src/query/backend/cpp/typed_value.hpp | 15 +- .../frontend/ast/cypher_main_visitor.cpp | 3 + src/query/frontend/interpret/interpret.hpp | 43 +++- tests/unit/interpreter.cpp | 217 ++++++++++++++++++ tests/unit/typed_value.cpp | 12 + 6 files changed, 299 insertions(+), 8 deletions(-) diff --git a/src/query/backend/cpp/typed_value.cpp b/src/query/backend/cpp/typed_value.cpp index 833852dc0..65ffe19ad 100644 --- a/src/query/backend/cpp/typed_value.cpp +++ b/src/query/backend/cpp/typed_value.cpp @@ -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(); + case TypedValue::Type::Double: + return +a.Value(); + 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() == 0LL) + throw TypedValueException("Division by zero"); return a.Value() / b.Value(); } } @@ -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() == 0LL) throw TypedValueException("Mod with zero"); return a.Value() % b.Value(); } } diff --git a/src/query/backend/cpp/typed_value.hpp b/src/query/backend/cpp/typed_value.hpp index 331e1c3f0..37baea8b2 100644 --- a/src/query/backend/cpp/typed_value.hpp +++ b/src/query/backend/cpp/typed_value.hpp @@ -26,11 +26,11 @@ typedef traversal_template::Path Path; * TypedValue::Type. Each such type corresponds to exactly one C++ type. */ class TypedValue : public TotalOrdering { -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 T &Value(); - template const T &Value() const; + template + T &Value(); + template + 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); diff --git a/src/query/frontend/ast/cypher_main_visitor.cpp b/src/query/frontend/ast/cypher_main_visitor.cpp index 18902a124..e811c5929 100644 --- a/src/query/frontend/ast/cypher_main_visitor.cpp +++ b/src/query/frontend/ast/cypher_main_visitor.cpp @@ -404,6 +404,9 @@ antlrcpp::Any CypherMainVisitor::visitExpression8( std::vector 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; diff --git a/src/query/frontend/interpret/interpret.hpp b/src/query/frontend/interpret/interpret.hpp index 9d6fb6bba..5413a7a4b 100644 --- a/src/query/frontend/interpret/interpret.hpp +++ b/src/query/frontend/interpret/interpret.hpp @@ -1,7 +1,7 @@ #pragma once -#include #include +#include #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_); } diff --git a/tests/unit/interpreter.cpp b/tests/unit/interpreter.cpp index 13033df8b..11ffae881 100644 --- a/tests/unit/interpreter.cpp +++ b/tests/unit/interpreter.cpp @@ -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(storage.Create(true), + storage.Create(false)); + op->Accept(eval.eval); + ASSERT_EQ(eval.eval.PopBack().Value(), true); + op = storage.Create(storage.Create(true), + storage.Create(true)); + op->Accept(eval.eval); + ASSERT_EQ(eval.eval.PopBack().Value(), true); +} + +TEST(ExpressionEvaluator, XorOperator) { + AstTreeStorage storage; + NoContextExpressionEvaluator eval; + auto *op = storage.Create(storage.Create(true), + storage.Create(false)); + op->Accept(eval.eval); + ASSERT_EQ(eval.eval.PopBack().Value(), true); + op = storage.Create(storage.Create(true), + storage.Create(true)); + op->Accept(eval.eval); + ASSERT_EQ(eval.eval.PopBack().Value(), false); +} + +TEST(ExpressionEvaluator, AndOperator) { + AstTreeStorage storage; + NoContextExpressionEvaluator eval; + auto *op = storage.Create(storage.Create(true), + storage.Create(true)); + op->Accept(eval.eval); + ASSERT_EQ(eval.eval.PopBack().Value(), true); + op = storage.Create(storage.Create(false), + storage.Create(true)); + op->Accept(eval.eval); + ASSERT_EQ(eval.eval.PopBack().Value(), false); +} + +TEST(ExpressionEvaluator, AdditionOperator) { + AstTreeStorage storage; + NoContextExpressionEvaluator eval; + auto *op = storage.Create(storage.Create(2), + storage.Create(3)); + op->Accept(eval.eval); + ASSERT_EQ(eval.eval.PopBack().Value(), 5); +} + +TEST(ExpressionEvaluator, SubtractionOperator) { + AstTreeStorage storage; + NoContextExpressionEvaluator eval; + auto *op = storage.Create(storage.Create(2), + storage.Create(3)); + op->Accept(eval.eval); + ASSERT_EQ(eval.eval.PopBack().Value(), -1); +} + +TEST(ExpressionEvaluator, MultiplicationOperator) { + AstTreeStorage storage; + NoContextExpressionEvaluator eval; + auto *op = storage.Create(storage.Create(2), + storage.Create(3)); + op->Accept(eval.eval); + ASSERT_EQ(eval.eval.PopBack().Value(), 6); +} + +TEST(ExpressionEvaluator, DivisionOperator) { + AstTreeStorage storage; + NoContextExpressionEvaluator eval; + auto *op = storage.Create(storage.Create(50), + storage.Create(10)); + op->Accept(eval.eval); + ASSERT_EQ(eval.eval.PopBack().Value(), 5); +} + +TEST(ExpressionEvaluator, ModOperator) { + AstTreeStorage storage; + NoContextExpressionEvaluator eval; + auto *op = storage.Create(storage.Create(65), + storage.Create(10)); + op->Accept(eval.eval); + ASSERT_EQ(eval.eval.PopBack().Value(), 5); +} + +TEST(ExpressionEvaluator, EqualOperator) { + AstTreeStorage storage; + NoContextExpressionEvaluator eval; + auto *op = storage.Create(storage.Create(10), + storage.Create(15)); + op->Accept(eval.eval); + ASSERT_EQ(eval.eval.PopBack().Value(), false); + op = storage.Create(storage.Create(15), + storage.Create(15)); + op->Accept(eval.eval); + ASSERT_EQ(eval.eval.PopBack().Value(), true); + op = storage.Create(storage.Create(20), + storage.Create(15)); + op->Accept(eval.eval); + ASSERT_EQ(eval.eval.PopBack().Value(), false); +} + +TEST(ExpressionEvaluator, NotEqualOperator) { + AstTreeStorage storage; + NoContextExpressionEvaluator eval; + auto *op = storage.Create(storage.Create(10), + storage.Create(15)); + op->Accept(eval.eval); + ASSERT_EQ(eval.eval.PopBack().Value(), true); + op = storage.Create(storage.Create(15), + storage.Create(15)); + op->Accept(eval.eval); + ASSERT_EQ(eval.eval.PopBack().Value(), false); + op = storage.Create(storage.Create(20), + storage.Create(15)); + op->Accept(eval.eval); + ASSERT_EQ(eval.eval.PopBack().Value(), true); +} + +TEST(ExpressionEvaluator, LessOperator) { + AstTreeStorage storage; + NoContextExpressionEvaluator eval; + auto *op = storage.Create(storage.Create(10), + storage.Create(15)); + op->Accept(eval.eval); + ASSERT_EQ(eval.eval.PopBack().Value(), true); + op = storage.Create(storage.Create(15), + storage.Create(15)); + op->Accept(eval.eval); + ASSERT_EQ(eval.eval.PopBack().Value(), false); + op = storage.Create(storage.Create(20), + storage.Create(15)); + op->Accept(eval.eval); + ASSERT_EQ(eval.eval.PopBack().Value(), false); +} + +TEST(ExpressionEvaluator, GreaterOperator) { + AstTreeStorage storage; + NoContextExpressionEvaluator eval; + auto *op = storage.Create(storage.Create(10), + storage.Create(15)); + op->Accept(eval.eval); + ASSERT_EQ(eval.eval.PopBack().Value(), false); + op = storage.Create(storage.Create(15), + storage.Create(15)); + op->Accept(eval.eval); + ASSERT_EQ(eval.eval.PopBack().Value(), false); + op = storage.Create(storage.Create(20), + storage.Create(15)); + op->Accept(eval.eval); + ASSERT_EQ(eval.eval.PopBack().Value(), true); +} + +TEST(ExpressionEvaluator, LessEqualOperator) { + AstTreeStorage storage; + NoContextExpressionEvaluator eval; + auto *op = storage.Create(storage.Create(10), + storage.Create(15)); + op->Accept(eval.eval); + ASSERT_EQ(eval.eval.PopBack().Value(), true); + op = storage.Create(storage.Create(15), + storage.Create(15)); + op->Accept(eval.eval); + ASSERT_EQ(eval.eval.PopBack().Value(), true); + op = storage.Create(storage.Create(20), + storage.Create(15)); + op->Accept(eval.eval); + ASSERT_EQ(eval.eval.PopBack().Value(), false); +} + +TEST(ExpressionEvaluator, GreaterEqualOperator) { + AstTreeStorage storage; + NoContextExpressionEvaluator eval; + auto *op = storage.Create(storage.Create(10), + storage.Create(15)); + op->Accept(eval.eval); + ASSERT_EQ(eval.eval.PopBack().Value(), false); + op = storage.Create(storage.Create(15), + storage.Create(15)); + op->Accept(eval.eval); + ASSERT_EQ(eval.eval.PopBack().Value(), true); + op = storage.Create(storage.Create(20), + storage.Create(15)); + op->Accept(eval.eval); + ASSERT_EQ(eval.eval.PopBack().Value(), true); +} + +TEST(ExpressionEvaluator, NotOperator) { + AstTreeStorage storage; + NoContextExpressionEvaluator eval; + auto *op = storage.Create(storage.Create(false)); + op->Accept(eval.eval); + ASSERT_EQ(eval.eval.PopBack().Value(), true); +} + +TEST(ExpressionEvaluator, UnaryPlusOperator) { + AstTreeStorage storage; + NoContextExpressionEvaluator eval; + auto *op = storage.Create(storage.Create(5)); + op->Accept(eval.eval); + ASSERT_EQ(eval.eval.PopBack().Value(), 5); +} + +TEST(ExpressionEvaluator, UnaryMinusOperator) { + AstTreeStorage storage; + NoContextExpressionEvaluator eval; + auto *op = storage.Create(storage.Create(5)); + op->Accept(eval.eval); + ASSERT_EQ(eval.eval.PopBack().Value(), -5); +} diff --git a/tests/unit/typed_value.cpp b/tests/unit/typed_value.cpp index b9a728edd..bf8db5d37 100644 --- a/tests/unit/typed_value.cpp +++ b/tests/unit/typed_value.cpp @@ -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()), 2); + EXPECT_FLOAT_EQ((+TypedValue(2.0).Value()), 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));