diff --git a/CMakeLists.txt b/CMakeLists.txt index 7e0836b..52c239a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -7,7 +7,7 @@ project(expression_tree include(CTest) if (NOT DEFINED CMAKE_CXX_STANDARD) - set(CMAKE_CXX_STANDARD 11) + set(CMAKE_CXX_STANDARD 14) endif() if (NOT CMAKE_BUILD_TYPE OR CMAKE_BUILD_TYPE STREQUAL "") diff --git a/include/attwoodn/expression_tree.hpp b/include/attwoodn/expression_tree.hpp index 00e703e..cafcb31 100644 --- a/include/attwoodn/expression_tree.hpp +++ b/include/attwoodn/expression_tree.hpp @@ -1,11 +1,13 @@ #pragma once +#include + namespace attwoodn::expression_tree { namespace op { template - inline bool less_than(T a, T b) { + inline bool less_than(const T a, const T b) { return a < b; } @@ -16,7 +18,7 @@ namespace attwoodn::expression_tree { } template - inline bool greater_than(T a, T b) { + inline bool greater_than(const T a, const T b) { return a > b; } @@ -27,7 +29,7 @@ namespace attwoodn::expression_tree { } template - inline bool equals(T a, T b) { + inline bool equals(const T a, const T b) { return a == b; } @@ -39,7 +41,7 @@ namespace attwoodn::expression_tree { } template - inline bool not_equals(T a, T b) { + inline bool not_equals(const T a, const T b) { return a != b; } @@ -51,21 +53,313 @@ namespace attwoodn::expression_tree { } } - namespace tree { + enum class boolean_op { + AND, + OR + }; + + namespace node { + + template + class expression_tree_op_node; + + template + class expression_tree_leaf_node; /** - * The base class representing expression tree nodes + * @brief The base class representing all expression tree nodes. */ template - class expression_tree_node_base { + class expression_tree_node { public: - virtual ~expression_tree_node_base() {}; + virtual ~expression_tree_node() {}; + + /** + * @brief Evaluates the given object to determine if it satisfies the expressions defined in this node and all child nodes. + * + * @returns True if the given object satisfied the expression in this node and the expressions of all nodes + * under this node in the expression tree; + * + * False if the given object did not satisfy the expression in this node and the expressions of all + * nodes under this node in the expression tree. + */ virtual bool evaluate(const Obj& obj) = 0; }; - class expression_tree_op_node; - class expression_tree_leaf_node; + /** + * @brief Represents inner boolean operation nodes of the tree. These nodes contain references to a left and right + * child node, as well as the boolean operation to be performed (e.g. left child AND right child, or left child OR right child). + */ + template + class expression_tree_op_node : public expression_tree_node { + public: + using this_type = expression_tree_op_node; + + expression_tree_op_node(boolean_op bool_op) + : bool_op_(bool_op) {} + + expression_tree_op_node(const expression_tree_op_node& other) { + bool_op_ = other.bool_op_; + delete left_; + delete right_; + left_ = new LeftChild(*other.left_); + right_ = new RightChild(*other.right_); + } + + ~expression_tree_op_node() override { + delete left_; + delete right_; + } + + void set_right(RightChild* r) { + delete right_; + right_ = new RightChild(*r); + delete r; + } + + void set_left(LeftChild* l) { + delete left_; + left_ = new LeftChild(*l); + delete l; + } + + bool evaluate(const Obj& obj) override { + if(!left_ || !right_) { + throw std::runtime_error("expression_tree_op_node has a missing child node"); + } + + switch(bool_op_) { + case boolean_op::AND: { + return left_->evaluate(obj) && right_->evaluate(obj); + } + + case boolean_op::OR: { + return left_->evaluate(obj) || right_->evaluate(obj); + } + + default: { + throw std::runtime_error("expression_tree_op_node contained a non-implemented boolean expression"); + } + } + + return false; + } + + /** + * Performs an AND operation with an expression_tree_leaf_node to create a heap-allocated pointer + * to a new expression_tree_op_node. The returned expression_tree_op_node becomes the parent of both this node + * and the other node that was AND'ed with this node. This node becomes the left child. The other node becomes + * the right child. + */ + template>::value>* = nullptr> + expression_tree_op_node* AND (OtherLeafNode* other) { + auto* op_node = new expression_tree_op_node(boolean_op::AND); + op_node->set_left(this); + op_node->set_right(other); + return op_node; + } + + /** + * Performs an OR operation with an expression_tree_leaf_node to create a heap-allocated pointer + * to a new expression_tree_op_node. The returned expression_tree_op_node becomes the parent of both this node + * and the other node that was OR'ed with this node. This node becomes the left child. The other node becomes + * the right child. + */ + template>::value>* = nullptr> + expression_tree_op_node* OR (OtherLeafNode* other) { + auto* op_node = new expression_tree_op_node(boolean_op::OR); + op_node->set_left(this); + op_node->set_right(other); + return op_node; + } + + /** + * Performs an AND operation with another expression_tree_op_node to create a heap-allocated pointer + * to a new expression_tree_op_node. The returned expression_tree_op_node becomes the parent of both this node + * and the other node that was AND'ed with this node. This node becomes the left child. The other node becomes + * the right child. + */ + template>::value>* = nullptr> + expression_tree_op_node* AND (OtherOpNode* other) { + auto* op_node = new expression_tree_op_node(boolean_op::AND); + op_node->set_left(this); + op_node->set_right(other); + return op_node; + } + + /** + * Performs an OR operation with another expression_tree_op_node to create a heap-allocated pointer + * to a new expression_tree_op_node. The returned expression_tree_op_node becomes the parent of both this node + * and the other node that was OR'ed with this node. This node becomes the left child. The other node becomes + * the right child. + */ + template>::value>* = nullptr> + expression_tree_op_node* OR (OtherOpNode* other) { + auto* op_node = new expression_tree_op_node(boolean_op::OR); + op_node->set_left(this); + op_node->set_right(other); + return op_node; + } + + private: + boolean_op bool_op_; + LeftChild* left_ { nullptr }; + RightChild* right_ { nullptr }; + }; + /** + * @brief Represents leaf nodes of the tree. These nodes contain: a reference to a member variable or member function of the + * given Obj type; the requested logical operation to be performed (e.g. equals, greater_than, etc.); and the + * value to compare to the given member variable or member function of an Obj instance. + */ + template + class expression_tree_leaf_node : public expression_tree_node { + public: + using this_type = expression_tree_leaf_node; + + /** + * @brief Constructor that accepts a reference to a member variable of Obj + */ + expression_tree_leaf_node(const CompValue Obj::* obj_mem_var, Op op, CompValue comp_value) + : member_var_(obj_mem_var), + logical_op_(op), + comp_value_(comp_value) {} + + /** + * @brief Constructor that accepts a reference to a const member function of Obj + */ + expression_tree_leaf_node(CompValue (Obj::* obj_mem_func)() const, Op op, CompValue comp_value) + : member_func_(obj_mem_func), + logical_op_(op), + comp_value_(comp_value) {} + + ~expression_tree_leaf_node() override {}; + + bool evaluate(const Obj& obj) override { + if (member_func_ && member_var_) { + throw std::runtime_error("expression_tree_leaf_node has both a member function reference " + + std::string("and member variable reference. Only one is permitted")); + } + + else if (!member_func_ && !member_var_) { + throw std::runtime_error("expression_tree_leaf_node has a nullptr for both member function reference " + + std::string("and member variable reference. At least one is required")); + } + + CompValue actual_value; + + if (member_func_) { + // invoke member function and store the result + actual_value = (obj.*member_func_)(); + } + + else if (member_var_) { + // get value from obj's data member + actual_value = obj.*member_var_; + } + + else return false; + + return logical_op_(actual_value, comp_value_); + } + + /** + * Performs an AND operation with another expression_tree_leaf_node to create a heap-allocated pointer + * to a new expression_tree_op_node. The returned expression_tree_op_node becomes the parent of both this node + * and the other node that was AND'ed with this node. This node becomes the left child. The other node becomes + * the right child. + */ + template>::value>* = nullptr> + expression_tree_op_node* AND (OtherLeafNode* other) { + auto* op_node = new expression_tree_op_node(boolean_op::AND); + op_node->set_left(this); + op_node->set_right(other); + return op_node; + } + + /** + * Performs an OR operation with another expression_tree_leaf_node to create a heap-allocated pointer + * to a new expression_tree_op_node. The returned expression_tree_op_node becomes the parent of both this node + * and the other node that was OR'ed with this node. This node becomes the left child. The other node becomes + * the right child. + */ + template>::value>* = nullptr> + expression_tree_op_node* OR (OtherLeafNode* other) { + auto* op_node = new expression_tree_op_node(boolean_op::OR); + op_node->set_left(this); + op_node->set_right(other); + return op_node; + } + + /** + * Performs an AND operation with an expression_tree_op_node to create a heap-allocated pointer + * to a new expression_tree_op_node. The returned expression_tree_op_node becomes the parent of both this node + * and the other node that was AND'ed with this node. This node becomes the left child. The other node becomes + * the right child. + */ + template>::value>* = nullptr> + expression_tree_op_node* AND (OtherOpNode* other) { + auto* op_node = new expression_tree_op_node(boolean_op::AND); + op_node->set_left(this); + op_node->set_right(other); + return op_node; + } + + /** + * Performs an OR operation with an expression_tree_op_node to create a heap-allocated pointer + * to a new expression_tree_op_node. The returned expression_tree_op_node becomes the parent of both this node + * and the other node that was OR'ed with this node. This node becomes the left child. The other node becomes + * the right child. + */ + template>::value>* = nullptr> + expression_tree_op_node* OR (OtherOpNode* other) { + auto* op_node = new expression_tree_op_node(boolean_op::OR); + op_node->set_left(this); + op_node->set_right(other); + return op_node; + } + + private: + CompValue (Obj::* member_func_)() const = nullptr; + const CompValue Obj::* member_var_ = nullptr; + Op logical_op_; + CompValue comp_value_; + }; + + } + + template struct type_id{typedef T type;}; + + /** + * Makes an expression tree leaf node for comparing pointer-type member variables of a class/struct + */ + template::type, + typename std::enable_if::value, int>::type = 0> + node::expression_tree_leaf_node* make_expr( CompValue Obj::* member_var, Op op, CompValue comp_value ) { + return new node::expression_tree_leaf_node( member_var, op, comp_value ); + } + + /** + * Makes an expression tree leaf node for comparing value-type member variables of a class/struct + */ + template::type> + node::expression_tree_leaf_node* make_expr( const CompValue Obj::* member_var, Op op, CompValue comp_value ) { + return new node::expression_tree_leaf_node( member_var, op, comp_value ); + } + /** + * Makes an expression tree leaf node for comparing the return value from a class/struct's const member function + */ + template::type> + node::expression_tree_leaf_node* make_expr( CompValue (Obj::* member_func)() const, Op op, CompValue comp_value ) { + return new node::expression_tree_leaf_node( member_func, op, comp_value ); } } \ No newline at end of file diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 9ddca6f..b692197 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -1,7 +1,18 @@ if(BUILD_TESTING) - add_executable( expression_tree_test test.cpp ) - add_test( expression_tree_test ${EXECUTABLE_OUTPUT_PATH}/expression_tree_test ) add_executable( operators_test operators.cpp ) + target_link_libraries( operators_test "-fsanitize=address" ) + target_compile_options( operators_test PRIVATE -fsanitize=address ) add_test( operators_test ${EXECUTABLE_OUTPUT_PATH}/operators_test ) + + add_executable( expression_tree_leaf_node_test expression_tree_leaf_node.cpp ) + target_link_libraries( expression_tree_leaf_node_test "-fsanitize=address" ) + target_compile_options( expression_tree_leaf_node_test PRIVATE -fsanitize=address ) + add_test( expression_tree_leaf_node_test ${EXECUTABLE_OUTPUT_PATH}/expression_tree_leaf_node_test ) + + add_executable( expression_tree_op_node_test expression_tree_op_node.cpp ) + target_link_libraries( expression_tree_op_node_test "-fsanitize=address" ) + target_compile_options( expression_tree_op_node_test PRIVATE -fsanitize=address ) + add_test( expression_tree_op_node_test ${EXECUTABLE_OUTPUT_PATH}/expression_tree_op_node_test ) + endif() \ No newline at end of file diff --git a/tests/expression_tree_leaf_node.cpp b/tests/expression_tree_leaf_node.cpp new file mode 100644 index 0000000..147a1b7 --- /dev/null +++ b/tests/expression_tree_leaf_node.cpp @@ -0,0 +1,690 @@ +#include +#include "test_utils.hpp" +#include +#include +#include + +using namespace attwoodn::expression_tree; + +void test_string_evaluation(); +void test_char_ptr_evaluation(); +void test_uint_evaluation(); +void test_const_func_evaluation(); + +int main(int argc, char** argv) { + test_string_evaluation(); + test_char_ptr_evaluation(); + test_uint_evaluation(); + test_const_func_evaluation(); + + return EXIT_SUCCESS; +} + +void test_string_evaluation() { + test_fixture fixture; + + // test const string + { + // test equals + { + auto expr1 = std::unique_ptr>( + make_expr(&test_fixture::some_const_string, &op::equals, std::string("hello world!")) + ); + assert(!expr1->evaluate(fixture)); + + auto expr2 = std::unique_ptr>( + make_expr(&test_fixture::some_const_string, &op::equals, std::string("h")) + ); + assert(!expr2->evaluate(fixture)); + + auto expr3 = std::unique_ptr>( + make_expr(&test_fixture::some_const_string, &op::equals, std::string("t")) + ); + assert(!expr3->evaluate(fixture)); + + auto expr4 = std::unique_ptr>( + make_expr(&test_fixture::some_const_string, &op::equals, std::string("this ")) + ); + assert(!expr4->evaluate(fixture)); + + auto expr5 = std::unique_ptr>( + make_expr(&test_fixture::some_const_string, &op::equals, std::string("this IS 4 T3s7 $tRing ")) + ); + assert(!expr5->evaluate(fixture)); + + auto expr6 = std::unique_ptr>( + make_expr(&test_fixture::some_const_string, &op::equals, std::string("this IS 4 T3s7 $tRing")) + ); + assert(!expr6->evaluate(fixture)); + + auto expr7 = std::unique_ptr>( + make_expr(&test_fixture::some_const_string, &op::equals, std::string("this IS 4 T3s7 $tRing ")) + ); + assert(expr7->evaluate(fixture)); + + auto expr8 = std::unique_ptr>( + make_expr(&test_fixture::some_const_string, &op::equals, fixture.some_const_string) + ); + assert(expr8->evaluate(fixture)); + } + + // test not_equals + { + auto expr1 = std::unique_ptr>( + make_expr(&test_fixture::some_const_string, &op::not_equals, std::string("hello world!")) + ); + assert(expr1->evaluate(fixture)); + + auto expr2 = std::unique_ptr>( + make_expr(&test_fixture::some_const_string, &op::not_equals, std::string("h")) + ); + assert(expr2->evaluate(fixture)); + + auto expr3 = std::unique_ptr>( + make_expr(&test_fixture::some_const_string, &op::not_equals, std::string("t")) + ); + assert(expr3->evaluate(fixture)); + + auto expr4 = std::unique_ptr>( + make_expr(&test_fixture::some_const_string, &op::not_equals, std::string("this ")) + ); + assert(expr4->evaluate(fixture)); + + auto expr5 = std::unique_ptr>( + make_expr(&test_fixture::some_const_string, &op::not_equals, std::string("this IS 4 T3s7 $tRing ")) + ); + assert(expr5->evaluate(fixture)); + + auto expr6 = std::unique_ptr>( + make_expr(&test_fixture::some_const_string, &op::not_equals, std::string("this IS 4 T3s7 $tRing")) + ); + assert(expr6->evaluate(fixture)); + + auto expr7 = std::unique_ptr>( + make_expr(&test_fixture::some_const_string, &op::not_equals, std::string("this IS 4 T3s7 $tRing ")) + ); + assert(!expr7->evaluate(fixture)); + + auto expr8 = std::unique_ptr>( + make_expr(&test_fixture::some_const_string, &op::not_equals, fixture.some_const_string) + ); + assert(!expr8->evaluate(fixture)); + } + + // test less_than + { + auto expr1 = std::unique_ptr>( + make_expr(&test_fixture::some_const_string, &op::less_than, std::string("u")) + ); + assert(expr1->evaluate(fixture)); + + auto expr2 = std::unique_ptr>( + make_expr(&test_fixture::some_const_string, &op::less_than, std::string("z")) + ); + assert(expr2->evaluate(fixture)); + + auto expr3 = std::unique_ptr>( + make_expr(&test_fixture::some_const_string, &op::less_than, std::string("this IS 4 T3s7 $tRing ")) + ); + assert(expr3->evaluate(fixture)); + + auto expr4 = std::unique_ptr>( + make_expr(&test_fixture::some_const_string, &op::less_than, std::string("1234567890")) + ); + assert(!expr4->evaluate(fixture)); + + auto expr5 = std::unique_ptr>( + make_expr(&test_fixture::some_const_string, &op::less_than, std::string("A")) + ); + assert(!expr5->evaluate(fixture)); + + auto expr6 = std::unique_ptr>( + make_expr(&test_fixture::some_const_string, &op::less_than, std::string("this ")) + ); + assert(!expr6->evaluate(fixture)); + + auto expr7 = std::unique_ptr>( + make_expr(&test_fixture::some_const_string, &op::less_than, std::string("stuff")) + ); + assert(!expr7->evaluate(fixture)); + + auto expr8 = std::unique_ptr>( + make_expr(&test_fixture::some_const_string, &op::less_than, std::string("abcdefghijklmnopqrstuvwxyz")) + ); + assert(!expr8->evaluate(fixture)); + + auto expr9 = std::unique_ptr>( + make_expr(&test_fixture::some_const_string, &op::less_than, std::string("this IS 4 T3s7 $tRing ")) + ); + assert(!expr9->evaluate(fixture)); + + auto expr10 = std::unique_ptr>( + make_expr(&test_fixture::some_const_string, &op::less_than, fixture.some_const_string) + ); + assert(!expr10->evaluate(fixture)); + } + + // test greater_than + { + auto expr1 = std::unique_ptr>( + make_expr(&test_fixture::some_const_string, &op::greater_than, std::string("u")) + ); + assert(!expr1->evaluate(fixture)); + + auto expr2 = std::unique_ptr>( + make_expr(&test_fixture::some_const_string, &op::greater_than, std::string("z")) + ); + assert(!expr2->evaluate(fixture)); + + auto expr3 = std::unique_ptr>( + make_expr(&test_fixture::some_const_string, &op::greater_than, std::string("this IS 4 T3s7 $tRing ")) + ); + assert(!expr3->evaluate(fixture)); + + auto expr4 = std::unique_ptr>( + make_expr(&test_fixture::some_const_string, &op::greater_than, std::string("1234567890")) + ); + assert(expr4->evaluate(fixture)); + + auto expr5 = std::unique_ptr>( + make_expr(&test_fixture::some_const_string, &op::greater_than, std::string("A")) + ); + assert(expr5->evaluate(fixture)); + + auto expr6 = std::unique_ptr>( + make_expr(&test_fixture::some_const_string, &op::greater_than, std::string("this ")) + ); + assert(expr6->evaluate(fixture)); + + auto expr7 = std::unique_ptr>( + make_expr(&test_fixture::some_const_string, &op::greater_than, std::string("stuff")) + ); + assert(expr7->evaluate(fixture)); + + auto expr8 = std::unique_ptr>( + make_expr(&test_fixture::some_const_string, &op::greater_than, std::string("abcdefghijklmnopqrstuvwxyz")) + ); + assert(expr8->evaluate(fixture)); + + auto expr9 = std::unique_ptr>( + make_expr(&test_fixture::some_const_string, &op::greater_than, std::string("this IS 4 T3s7 $tRing ")) + ); + assert(!expr9->evaluate(fixture)); + + auto expr10 = std::unique_ptr>( + make_expr(&test_fixture::some_const_string, &op::greater_than, fixture.some_const_string) + ); + assert(!expr10->evaluate(fixture)); + } + } + + // test non-const string + { + // test equals + { + auto expr = std::unique_ptr>( + make_expr(&test_fixture::some_string, &op::equals, std::string("hello world!")) + ); + + fixture.some_string = "hello world!"; + assert(expr->evaluate(fixture)); + + fixture.some_string = "hey, world!"; + assert(!expr->evaluate(fixture)); + + fixture.some_string = "1234"; + assert(!expr->evaluate(fixture)); + + fixture.some_string = "-----"; + assert(!expr->evaluate(fixture)); + + fixture.some_string = ""; + assert(!expr->evaluate(fixture)); + + fixture.some_string = " "; + assert(!expr->evaluate(fixture)); + } + + // test not_equals + { + auto expr = std::unique_ptr>( + make_expr(&test_fixture::some_string, &op::not_equals, std::string("hello world!")) + ); + + fixture.some_string = "hello world!"; + assert(!expr->evaluate(fixture)); + + fixture.some_string = "hey, world!"; + assert(expr->evaluate(fixture)); + + fixture.some_string = "1234"; + assert(expr->evaluate(fixture)); + + fixture.some_string = "-----"; + assert(expr->evaluate(fixture)); + + fixture.some_string = ""; + assert(expr->evaluate(fixture)); + + fixture.some_string = " "; + assert(expr->evaluate(fixture)); + } + + // test less_than + { + auto expr = std::unique_ptr>( + make_expr(&test_fixture::some_string, &op::less_than, std::string("this IS 4 T3s7 $tRing ")) + ); + + fixture.some_string = "u"; + assert(!expr->evaluate(fixture)); + + fixture.some_string = "z"; + assert(!expr->evaluate(fixture)); + + fixture.some_string = "this IS 4 T3s7 $tRing "; + assert(!expr->evaluate(fixture)); + + fixture.some_string = ""; + assert(expr->evaluate(fixture)); + + fixture.some_string = "1234567890"; + assert(expr->evaluate(fixture)); + + fixture.some_string = "A"; + assert(expr->evaluate(fixture)); + + fixture.some_string = "this "; + assert(expr->evaluate(fixture)); + + fixture.some_string = "stuff"; + assert(expr->evaluate(fixture)); + + fixture.some_string = "abcdefghijklmnopqrstuvwxyz"; + assert(expr->evaluate(fixture)); + + fixture.some_string = "this IS 4 T3s7 $tRing "; + assert(!expr->evaluate(fixture)); + } + + // test greater_than + { + auto expr = std::unique_ptr>( + make_expr(&test_fixture::some_string, &op::greater_than, std::string("this IS 4 T3s7 $tRing ")) + ); + + fixture.some_string = "u"; + assert(expr->evaluate(fixture)); + + fixture.some_string = "z"; + assert(expr->evaluate(fixture)); + + fixture.some_string = "this IS 4 T3s7 $tRing "; + assert(expr->evaluate(fixture)); + + fixture.some_string = ""; + assert(!expr->evaluate(fixture)); + + fixture.some_string = "1234567890"; + assert(!expr->evaluate(fixture)); + + fixture.some_string = "A"; + assert(!expr->evaluate(fixture)); + + fixture.some_string = "this "; + assert(!expr->evaluate(fixture)); + + fixture.some_string = "stuff"; + assert(!expr->evaluate(fixture)); + + fixture.some_string = "abcdefghijklmnopqrstuvwxyz"; + assert(!expr->evaluate(fixture)); + + fixture.some_string = "this IS 4 T3s7 $tRing "; + assert(!expr->evaluate(fixture)); + } + } +} + +void test_char_ptr_evaluation() { + char a = 'a'; + char a_too = 'a'; + char b = 'b'; + char b_too = 'b'; + char c = 'c'; + char c_too = 'c'; + + test_fixture fixture; + + // test equals + { + // make an expression : given char* == 'a' + auto expr = std::unique_ptr>( + make_expr(&test_fixture::some_char_ptr, &op::equals, &a) + ); + + fixture.some_char_ptr = &a; + assert(expr->evaluate(fixture)); + + fixture.some_char_ptr = &a_too; + assert(expr->evaluate(fixture)); + + fixture.some_char_ptr = &b; + assert(!expr->evaluate(fixture)); + + fixture.some_char_ptr = &b_too; + assert(!expr->evaluate(fixture)); + + fixture.some_char_ptr = &c; + assert(!expr->evaluate(fixture)); + + fixture.some_char_ptr = &c_too; + assert(!expr->evaluate(fixture)); + } + + // test not_equals + { + // make an expression : given char* != 'a' + auto expr = std::unique_ptr>( + make_expr(&test_fixture::some_char_ptr, &op::not_equals, &a) + ); + + fixture.some_char_ptr = &a; + assert(!expr->evaluate(fixture)); + + fixture.some_char_ptr = &a_too; + assert(!expr->evaluate(fixture)); + + fixture.some_char_ptr = &b; + assert(expr->evaluate(fixture)); + + fixture.some_char_ptr = &b_too; + assert(expr->evaluate(fixture)); + + fixture.some_char_ptr = &c; + assert(expr->evaluate(fixture)); + + fixture.some_char_ptr = &c_too; + assert(expr->evaluate(fixture)); + } + + // test less_than + { + // make an expression : given char* < 'b' + auto expr = std::unique_ptr>( + make_expr(&test_fixture::some_char_ptr, &op::less_than, &b) + ); + + fixture.some_char_ptr = &a; + assert(expr->evaluate(fixture)); + + fixture.some_char_ptr = &a_too; + assert(expr->evaluate(fixture)); + + fixture.some_char_ptr = &b; + assert(!expr->evaluate(fixture)); + + fixture.some_char_ptr = &b_too; + assert(!expr->evaluate(fixture)); + + fixture.some_char_ptr = &c; + assert(!expr->evaluate(fixture)); + + fixture.some_char_ptr = &c_too; + assert(!expr->evaluate(fixture)); + } + + // test greater_than + { + // make an expression : given char* > 'b' + auto expr = std::unique_ptr>( + make_expr(&test_fixture::some_char_ptr, &op::greater_than, &b) + ); + + fixture.some_char_ptr = &a; + assert(!expr->evaluate(fixture)); + + fixture.some_char_ptr = &a_too; + assert(!expr->evaluate(fixture)); + + fixture.some_char_ptr = &b; + assert(!expr->evaluate(fixture)); + + fixture.some_char_ptr = &b_too; + assert(!expr->evaluate(fixture)); + + fixture.some_char_ptr = &c; + assert(expr->evaluate(fixture)); + + fixture.some_char_ptr = &c_too; + assert(expr->evaluate(fixture)); + } +} + +void test_uint_evaluation() { + test_fixture fixture; + + // test equals + { + auto expr = std::unique_ptr>( + make_expr(&test_fixture::some_uint, op::equals, (uint16_t) 9001) + ); + + fixture.some_uint = 0; + assert(!expr->evaluate(fixture)); + + fixture.some_uint = 1; + assert(!expr->evaluate(fixture)); + + fixture.some_uint = -1; + assert(!expr->evaluate(fixture)); + + fixture.some_uint = 9000; + assert(!expr->evaluate(fixture)); + + fixture.some_uint = 9002; + assert(!expr->evaluate(fixture)); + + fixture.some_uint = 9001; + assert(expr->evaluate(fixture)); + } + + // test not_equals + { + auto expr = std::unique_ptr>( + make_expr(&test_fixture::some_uint, op::not_equals, (uint16_t) 9001) + ); + + fixture.some_uint = 0; + assert(expr->evaluate(fixture)); + + fixture.some_uint = 1; + assert(expr->evaluate(fixture)); + + fixture.some_uint = -1; + assert(expr->evaluate(fixture)); + + fixture.some_uint = 9000; + assert(expr->evaluate(fixture)); + + fixture.some_uint = 9002; + assert(expr->evaluate(fixture)); + + fixture.some_uint = 9001; + assert(!expr->evaluate(fixture)); + } + + // test less_than + { + auto expr = std::unique_ptr>( + make_expr(&test_fixture::some_uint, op::less_than, (uint16_t) 9001) + ); + + fixture.some_uint = 0; + assert(expr->evaluate(fixture)); + + fixture.some_uint = 1; + assert(expr->evaluate(fixture)); + + fixture.some_uint = -1; + assert(!expr->evaluate(fixture)); + + fixture.some_uint = 9000; + assert(expr->evaluate(fixture)); + + fixture.some_uint = 9002; + assert(!expr->evaluate(fixture)); + + fixture.some_uint = 9001; + assert(!expr->evaluate(fixture)); + } + + // test greater_than + { + auto expr = std::unique_ptr>( + make_expr(&test_fixture::some_uint, op::greater_than, (uint16_t) 9001) + ); + + fixture.some_uint = 0; + assert(!expr->evaluate(fixture)); + + fixture.some_uint = 1; + assert(!expr->evaluate(fixture)); + + fixture.some_uint = -1; + assert(expr->evaluate(fixture)); + + fixture.some_uint = 9000; + assert(!expr->evaluate(fixture)); + + fixture.some_uint = 9002; + assert(expr->evaluate(fixture)); + + fixture.some_uint = 9001; + assert(!expr->evaluate(fixture)); + } +} + +void test_const_func_evaluation() { + test_fixture fixture; + + // test equals + { + auto expr1 = std::unique_ptr>(make_expr(&test_fixture::is_some_uint_greater_than_zero, &op::equals, true)); + auto expr2 = std::unique_ptr>(make_expr(&test_fixture::is_some_uint_greater_than_zero, &op::equals, false)); + + fixture.some_uint = 0; + assert(!expr1->evaluate(fixture)); + assert(expr2->evaluate(fixture)); + + fixture.some_uint = std::numeric_limits::min(); + assert(!expr1->evaluate(fixture)); + assert(expr2->evaluate(fixture)); + + fixture.some_uint = 1; + assert(expr1->evaluate(fixture)); + assert(!expr2->evaluate(fixture)); + + fixture.some_uint = 500; + assert(expr1->evaluate(fixture)); + assert(!expr2->evaluate(fixture)); + + fixture.some_uint = std::numeric_limits::max(); + assert(expr1->evaluate(fixture)); + assert(!expr2->evaluate(fixture)); + + fixture.some_uint = -1; + assert(expr1->evaluate(fixture)); + assert(!expr2->evaluate(fixture)); + } + + // test not_equals + { + auto expr1 = std::unique_ptr>(make_expr(&test_fixture::is_some_uint_greater_than_zero, &op::not_equals, true)); + auto expr2 = std::unique_ptr>(make_expr(&test_fixture::is_some_uint_greater_than_zero, &op::not_equals, false)); + + fixture.some_uint = 0; + assert(expr1->evaluate(fixture)); + assert(!expr2->evaluate(fixture)); + + fixture.some_uint = std::numeric_limits::min(); + assert(expr1->evaluate(fixture)); + assert(!expr2->evaluate(fixture)); + + fixture.some_uint = 1; + assert(!expr1->evaluate(fixture)); + assert(expr2->evaluate(fixture)); + + fixture.some_uint = 500; + assert(!expr1->evaluate(fixture)); + assert(expr2->evaluate(fixture)); + + fixture.some_uint = std::numeric_limits::max(); + assert(!expr1->evaluate(fixture)); + assert(expr2->evaluate(fixture)); + + fixture.some_uint = -1; + assert(!expr1->evaluate(fixture)); + assert(expr2->evaluate(fixture)); + } + + // test less_than + { + auto expr1 = std::unique_ptr>(make_expr(&test_fixture::is_some_uint_greater_than_zero, &op::less_than, true)); + auto expr2 = std::unique_ptr>(make_expr(&test_fixture::is_some_uint_greater_than_zero, &op::less_than, false)); + + fixture.some_uint = 0; + assert(expr1->evaluate(fixture)); + assert(!expr2->evaluate(fixture)); + + fixture.some_uint = std::numeric_limits::min(); + assert(expr1->evaluate(fixture)); + assert(!expr2->evaluate(fixture)); + + fixture.some_uint = 1; + assert(!expr1->evaluate(fixture)); + assert(!expr2->evaluate(fixture)); + + fixture.some_uint = 500; + assert(!expr1->evaluate(fixture)); + assert(!expr2->evaluate(fixture)); + + fixture.some_uint = std::numeric_limits::max(); + assert(!expr1->evaluate(fixture)); + assert(!expr2->evaluate(fixture)); + + fixture.some_uint = -1; + assert(!expr1->evaluate(fixture)); + assert(!expr2->evaluate(fixture)); + } + + // test greater_than + { + auto expr1 = std::unique_ptr>(make_expr(&test_fixture::is_some_uint_greater_than_zero, &op::greater_than, true)); + auto expr2 = std::unique_ptr>(make_expr(&test_fixture::is_some_uint_greater_than_zero, &op::greater_than, false)); + + fixture.some_uint = 0; + assert(!expr1->evaluate(fixture)); + assert(!expr2->evaluate(fixture)); + + fixture.some_uint = std::numeric_limits::min(); + assert(!expr1->evaluate(fixture)); + assert(!expr2->evaluate(fixture)); + + fixture.some_uint = 1; + assert(!expr1->evaluate(fixture)); + assert(expr2->evaluate(fixture)); + + fixture.some_uint = 500; + assert(!expr1->evaluate(fixture)); + assert(expr2->evaluate(fixture)); + + fixture.some_uint = std::numeric_limits::max(); + assert(!expr1->evaluate(fixture)); + assert(expr2->evaluate(fixture)); + + fixture.some_uint = -1; + assert(!expr1->evaluate(fixture)); + assert(expr2->evaluate(fixture)); + } +} \ No newline at end of file diff --git a/tests/expression_tree_op_node.cpp b/tests/expression_tree_op_node.cpp new file mode 100644 index 0000000..66323e5 --- /dev/null +++ b/tests/expression_tree_op_node.cpp @@ -0,0 +1,142 @@ +#include +#include "test_utils.hpp" +#include +#include +#include +#include + +using namespace attwoodn::expression_tree; + +void test_AND_op_node_evaluation(); +void test_OR_op_node_evaluation(); + +int main(int argc, char** argv) { + test_AND_op_node_evaluation(); + test_OR_op_node_evaluation(); + + return EXIT_SUCCESS; +} + +void test_AND_op_node_evaluation() { + auto child_expr1 = make_expr(&test_fixture::some_string, op::equals, std::string("hello, world!")); + auto child_expr2 = make_expr(&test_fixture::some_uint, op::less_than, (uint16_t) 500); + + auto expr = make_op_node(child_expr1, boolean_op::AND, child_expr2); + + test_fixture fixture; + fixture.some_string = "hello, world!"; + fixture.some_uint = 499; + assert(expr->evaluate(fixture)); + + fixture.some_uint = 0; + assert(expr->evaluate(fixture)); + + fixture.some_uint = 1; + assert(expr->evaluate(fixture)); + + fixture.some_uint = 250; + assert(expr->evaluate(fixture)); + + fixture.some_uint = 435; + assert(expr->evaluate(fixture)); + + fixture.some_uint = 500; + assert(!expr->evaluate(fixture)); + + fixture.some_uint = 501; + assert(!expr->evaluate(fixture)); + + fixture.some_uint = 9999; + assert(!expr->evaluate(fixture)); + + fixture.some_uint = std::numeric_limits::max(); + assert(!expr->evaluate(fixture)); + + fixture.some_uint = 499; + assert(expr->evaluate(fixture)); + + fixture.some_string = "hello!"; + assert(!expr->evaluate(fixture)); + + fixture.some_string = "hello world!"; + assert(!expr->evaluate(fixture)); + + fixture.some_string = "hello,world!"; + assert(!expr->evaluate(fixture)); + + fixture.some_string = "hello, world!"; + assert(expr->evaluate(fixture)); +} + +void test_OR_op_node_evaluation() { + auto child_expr1 = make_expr(&test_fixture::some_string, op::equals, std::string("hello, world!")); + auto child_expr2 = make_expr(&test_fixture::some_uint, op::less_than, (uint16_t) 500); + + auto expr = make_op_node(child_expr1, boolean_op::OR, child_expr2); + + test_fixture fixture; + fixture.some_string = "hello, world!"; + fixture.some_uint = 499; + assert(expr->evaluate(fixture)); + + fixture.some_uint = 0; + assert(expr->evaluate(fixture)); + + fixture.some_uint = 1; + assert(expr->evaluate(fixture)); + + fixture.some_uint = 250; + assert(expr->evaluate(fixture)); + + fixture.some_uint = 435; + assert(expr->evaluate(fixture)); + + fixture.some_uint = 500; + assert(expr->evaluate(fixture)); + + fixture.some_uint = 501; + assert(expr->evaluate(fixture)); + + fixture.some_uint = 9999; + assert(expr->evaluate(fixture)); + + fixture.some_uint = std::numeric_limits::max(); + assert(expr->evaluate(fixture)); + + fixture.some_uint = 499; + assert(expr->evaluate(fixture)); + + fixture.some_string = "hello!"; + assert(expr->evaluate(fixture)); + + fixture.some_string = "hello world!"; + assert(expr->evaluate(fixture)); + + fixture.some_string = "hello,world!"; + assert(expr->evaluate(fixture)); + + fixture.some_string = "some test string"; + fixture.some_uint = 501; + assert(!expr->evaluate(fixture)); + + fixture.some_string = "hello!"; + fixture.some_uint = 5876; + assert(!expr->evaluate(fixture)); + + fixture.some_string = "hello hello HELLO"; + fixture.some_uint = 12345; + assert(!expr->evaluate(fixture)); + + fixture.some_string = ""; + fixture.some_uint = -1; + assert(!expr->evaluate(fixture)); + + fixture.some_string = ""; + fixture.some_uint = 0; + assert(expr->evaluate(fixture)); + + fixture.some_string = "hello, world!"; + fixture.some_uint = 350; + assert(expr->evaluate(fixture)); +} + diff --git a/tests/test.cpp b/tests/test.cpp deleted file mode 100644 index 997deb8..0000000 --- a/tests/test.cpp +++ /dev/null @@ -1,7 +0,0 @@ -#include - -int main(int argc, char** argv) { - std::cout << "hello world!" << std::endl; - - return EXIT_SUCCESS; -} \ No newline at end of file diff --git a/tests/test_utils.hpp b/tests/test_utils.hpp new file mode 100644 index 0000000..2cfe300 --- /dev/null +++ b/tests/test_utils.hpp @@ -0,0 +1,29 @@ +#pragma once + +#include +#include +#include + +namespace et = attwoodn::expression_tree; + +struct test_fixture { + std::string some_string; + const std::string some_const_string = "this IS 4 T3s7 $tRing "; + char* some_char_ptr; + uint16_t some_uint; + + bool is_some_uint_greater_than_zero() const { + return some_uint; + } +}; + +/** + * Helper function for creating inner expression tree nodes. I'm undecided if this should be included in the public API. +*/ +template +std::unique_ptr> make_op_node(LeftChild* left, et::boolean_op op, RightChild* right) { + auto node = new et::node::expression_tree_op_node(op); + node->set_left(left); + node->set_right(right); + return std::unique_ptr>(node); +} \ No newline at end of file