Skip to content

Commit f25064b

Browse files
authored
Merge pull request #8 from attwoodn/implement-expression-tree
Implement expression tree
2 parents 27b8535 + 2537a19 commit f25064b

File tree

6 files changed

+507
-62
lines changed

6 files changed

+507
-62
lines changed

include/attwoodn/expression_tree.hpp

Lines changed: 155 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
#pragma once
22

3+
#include <memory>
34
#include <stdexcept>
45

56
namespace attwoodn::expression_tree {
@@ -53,13 +54,13 @@ namespace attwoodn::expression_tree {
5354
}
5455
}
5556

56-
enum class boolean_op {
57-
AND,
58-
OR
59-
};
60-
6157
namespace node {
6258

59+
enum class boolean_op {
60+
AND,
61+
OR
62+
};
63+
6364
template<typename Obj, typename LeftChild, typename RightChild>
6465
class expression_tree_op_node;
6566

@@ -83,7 +84,17 @@ namespace attwoodn::expression_tree {
8384
* False if the given object did not satisfy the expression in this node and the expressions of all
8485
* nodes under this node in the expression tree.
8586
*/
86-
virtual bool evaluate(const Obj& obj) = 0;
87+
virtual bool evaluate(const Obj& obj) const = 0;
88+
89+
/**
90+
* @brief Performs a deep clone of pointers to this base class to avoid object slicing.
91+
*/
92+
auto clone() const {
93+
return std::unique_ptr<expression_tree_node<Obj>>(clone_impl());
94+
}
95+
96+
protected:
97+
virtual expression_tree_node<Obj>* clone_impl() const = 0;
8798
};
8899

89100
/**
@@ -95,6 +106,11 @@ namespace attwoodn::expression_tree {
95106
public:
96107
using this_type = expression_tree_op_node<Obj, LeftChild, RightChild>;
97108

109+
expression_tree_op_node() = delete;
110+
expression_tree_op_node(expression_tree_op_node&& other) = delete;
111+
expression_tree_op_node& operator=(const expression_tree_op_node& other) = delete;
112+
expression_tree_op_node& operator=(expression_tree_op_node&& other) = delete;
113+
98114
expression_tree_op_node(boolean_op bool_op)
99115
: bool_op_(bool_op) {}
100116

@@ -123,7 +139,7 @@ namespace attwoodn::expression_tree {
123139
delete l;
124140
}
125141

126-
bool evaluate(const Obj& obj) override {
142+
bool evaluate(const Obj& obj) const override {
127143
if(!left_ || !right_) {
128144
throw std::runtime_error("expression_tree_op_node has a missing child node");
129145
}
@@ -151,10 +167,10 @@ namespace attwoodn::expression_tree {
151167
* and the other node that was AND'ed with this node. This node becomes the left child. The other node becomes
152168
* the right child.
153169
*/
154-
template<typename OtherOp, typename OtherCompValue, typename OtherLeafNode,
155-
std::enable_if<std::is_same<OtherLeafNode, expression_tree_leaf_node<Obj, OtherOp, OtherCompValue>>::value>* = nullptr>
156-
expression_tree_op_node<Obj, this_type, OtherLeafNode>* AND (OtherLeafNode* other) {
157-
auto* op_node = new expression_tree_op_node<Obj, this_type, OtherLeafNode>(boolean_op::AND);
170+
template<typename OtherOp, typename OtherCompValue>
171+
auto* AND (expression_tree_leaf_node<Obj, OtherOp, OtherCompValue>* other) {
172+
using ret = expression_tree_op_node<Obj, this_type, expression_tree_leaf_node<Obj, OtherOp, OtherCompValue>>;
173+
ret* op_node = new ret(boolean_op::AND);
158174
op_node->set_left(this);
159175
op_node->set_right(other);
160176
return op_node;
@@ -166,10 +182,10 @@ namespace attwoodn::expression_tree {
166182
* and the other node that was OR'ed with this node. This node becomes the left child. The other node becomes
167183
* the right child.
168184
*/
169-
template<typename OtherOp, typename OtherCompValue, typename OtherLeafNode,
170-
std::enable_if<std::is_same<OtherLeafNode, expression_tree_leaf_node<Obj, OtherOp, OtherCompValue>>::value>* = nullptr>
171-
expression_tree_op_node<Obj, this_type, OtherLeafNode>* OR (OtherLeafNode* other) {
172-
auto* op_node = new expression_tree_op_node<Obj, this_type, OtherLeafNode>(boolean_op::OR);
185+
template<typename OtherOp, typename OtherCompValue>
186+
auto* OR (expression_tree_leaf_node<Obj, OtherOp, OtherCompValue>* other) {
187+
using ret = expression_tree_op_node<Obj, this_type, expression_tree_leaf_node<Obj, OtherOp, OtherCompValue>>;
188+
ret* op_node = new ret(boolean_op::OR);
173189
op_node->set_left(this);
174190
op_node->set_right(other);
175191
return op_node;
@@ -181,10 +197,10 @@ namespace attwoodn::expression_tree {
181197
* and the other node that was AND'ed with this node. This node becomes the left child. The other node becomes
182198
* the right child.
183199
*/
184-
template<typename OtherLeftChild, typename OtherRightChild, typename OtherOpNode,
185-
std::enable_if<std::is_same<OtherOpNode, expression_tree_op_node<Obj, OtherLeftChild, OtherRightChild>>::value>* = nullptr>
186-
expression_tree_op_node<Obj, this_type, OtherOpNode>* AND (OtherOpNode* other) {
187-
auto* op_node = new expression_tree_op_node<Obj, this_type, OtherOpNode>(boolean_op::AND);
200+
template<typename OtherLeftChild, typename OtherRightChild>
201+
auto* AND (expression_tree_op_node<Obj, OtherLeftChild, OtherRightChild>* other) {
202+
using ret = expression_tree_op_node<Obj, this_type, expression_tree_op_node<Obj, OtherLeftChild, OtherRightChild>>;
203+
ret* op_node = new ret(boolean_op::AND);
188204
op_node->set_left(this);
189205
op_node->set_right(other);
190206
return op_node;
@@ -196,10 +212,10 @@ namespace attwoodn::expression_tree {
196212
* and the other node that was OR'ed with this node. This node becomes the left child. The other node becomes
197213
* the right child.
198214
*/
199-
template<typename OtherLeftChild, typename OtherRightChild, typename OtherOpNode,
200-
std::enable_if<std::is_same<OtherOpNode, expression_tree_op_node<Obj, OtherLeftChild, OtherRightChild>>::value>* = nullptr>
201-
expression_tree_op_node<Obj, this_type, OtherOpNode>* OR (OtherOpNode* other) {
202-
auto* op_node = new expression_tree_op_node<Obj, this_type, OtherOpNode>(boolean_op::OR);
215+
template<typename OtherLeftChild, typename OtherRightChild>
216+
auto* OR (expression_tree_op_node<Obj, OtherLeftChild, OtherRightChild>* other) {
217+
using ret = expression_tree_op_node<Obj, this_type, expression_tree_op_node<Obj, OtherLeftChild, OtherRightChild>>;
218+
ret* op_node = new ret(boolean_op::OR);
203219
op_node->set_left(this);
204220
op_node->set_right(other);
205221
return op_node;
@@ -209,6 +225,11 @@ namespace attwoodn::expression_tree {
209225
boolean_op bool_op_;
210226
LeftChild* left_ { nullptr };
211227
RightChild* right_ { nullptr };
228+
229+
protected:
230+
virtual expression_tree_op_node<Obj, LeftChild, RightChild>* clone_impl() const override {
231+
return new expression_tree_op_node<Obj, LeftChild, RightChild>(*this);
232+
}
212233
};
213234

214235
/**
@@ -221,6 +242,13 @@ namespace attwoodn::expression_tree {
221242
public:
222243
using this_type = expression_tree_leaf_node<Obj, Op, CompValue>;
223244

245+
expression_tree_leaf_node() = delete;
246+
247+
expression_tree_leaf_node(const expression_tree_leaf_node& other) = default;
248+
expression_tree_leaf_node(expression_tree_leaf_node&& other) = default;
249+
expression_tree_leaf_node& operator=(const expression_tree_leaf_node& other) = default;
250+
expression_tree_leaf_node& operator=(expression_tree_leaf_node&& other) = default;
251+
224252
/**
225253
* @brief Constructor that accepts a reference to a member variable of Obj
226254
*/
@@ -239,7 +267,7 @@ namespace attwoodn::expression_tree {
239267

240268
~expression_tree_leaf_node() override {};
241269

242-
bool evaluate(const Obj& obj) override {
270+
bool evaluate(const Obj& obj) const override {
243271
if (member_func_ && member_var_) {
244272
throw std::runtime_error("expression_tree_leaf_node has both a member function reference " +
245273
std::string("and member variable reference. Only one is permitted"));
@@ -273,10 +301,10 @@ namespace attwoodn::expression_tree {
273301
* and the other node that was AND'ed with this node. This node becomes the left child. The other node becomes
274302
* the right child.
275303
*/
276-
template<typename OtherOp, typename OtherCompValue, typename OtherLeafNode,
277-
std::enable_if<std::is_same<OtherLeafNode, expression_tree_leaf_node<Obj, OtherOp, OtherCompValue>>::value>* = nullptr>
278-
expression_tree_op_node<Obj, this_type, OtherLeafNode>* AND (OtherLeafNode* other) {
279-
auto* op_node = new expression_tree_op_node<Obj, this_type, OtherLeafNode>(boolean_op::AND);
304+
template<typename OtherOp, typename OtherCompValue>
305+
auto* AND (expression_tree_leaf_node<Obj, OtherOp, OtherCompValue>* other) {
306+
using ret = expression_tree_op_node<Obj, this_type, expression_tree_leaf_node<Obj, OtherOp, OtherCompValue>>;
307+
ret* op_node = new ret(boolean_op::AND);
280308
op_node->set_left(this);
281309
op_node->set_right(other);
282310
return op_node;
@@ -288,10 +316,10 @@ namespace attwoodn::expression_tree {
288316
* and the other node that was OR'ed with this node. This node becomes the left child. The other node becomes
289317
* the right child.
290318
*/
291-
template<typename OtherOp, typename OtherCompValue, typename OtherLeafNode,
292-
std::enable_if<std::is_same<OtherLeafNode, expression_tree_leaf_node<Obj, OtherOp, OtherCompValue>>::value>* = nullptr>
293-
expression_tree_op_node<Obj, this_type, OtherLeafNode>* OR (OtherLeafNode* other) {
294-
auto* op_node = new expression_tree_op_node<Obj, this_type, OtherLeafNode>(boolean_op::OR);
319+
template<typename OtherOp, typename OtherCompValue>
320+
auto* OR (expression_tree_leaf_node<Obj, OtherOp, OtherCompValue>* other) {
321+
using ret = expression_tree_op_node<Obj, this_type, expression_tree_leaf_node<Obj, OtherOp, OtherCompValue>>;
322+
ret* op_node = new ret(boolean_op::OR);
295323
op_node->set_left(this);
296324
op_node->set_right(other);
297325
return op_node;
@@ -303,10 +331,10 @@ namespace attwoodn::expression_tree {
303331
* and the other node that was AND'ed with this node. This node becomes the left child. The other node becomes
304332
* the right child.
305333
*/
306-
template<typename OtherLeftChild, typename OtherRightChild, typename OtherOpNode,
307-
std::enable_if<std::is_same<OtherOpNode, expression_tree_op_node<Obj, OtherLeftChild, OtherRightChild>>::value>* = nullptr>
308-
expression_tree_op_node<Obj, this_type, OtherOpNode>* AND (OtherOpNode* other) {
309-
auto* op_node = new expression_tree_op_node<Obj, this_type, OtherOpNode>(boolean_op::AND);
334+
template<typename OtherLeftChild, typename OtherRightChild>
335+
auto* AND (expression_tree_op_node<Obj, OtherLeftChild, OtherRightChild>* other) {
336+
using ret = expression_tree_op_node<Obj, this_type, expression_tree_op_node<Obj, OtherLeftChild, OtherRightChild>>;
337+
ret* op_node = new ret(boolean_op::AND);
310338
op_node->set_left(this);
311339
op_node->set_right(other);
312340
return op_node;
@@ -318,10 +346,10 @@ namespace attwoodn::expression_tree {
318346
* and the other node that was OR'ed with this node. This node becomes the left child. The other node becomes
319347
* the right child.
320348
*/
321-
template<typename OtherLeftChild, typename OtherRightChild, typename OtherOpNode,
322-
std::enable_if<std::is_same<OtherOpNode, expression_tree_op_node<Obj, OtherLeftChild, OtherRightChild>>::value>* = nullptr>
323-
expression_tree_op_node<Obj, this_type, OtherOpNode>* OR (OtherOpNode* other) {
324-
auto* op_node = new expression_tree_op_node<Obj, this_type, OtherOpNode>(boolean_op::OR);
349+
template<typename OtherLeftChild, typename OtherRightChild>
350+
auto* OR (expression_tree_op_node<Obj, OtherLeftChild, OtherRightChild>* other) {
351+
using ret = expression_tree_op_node<Obj, this_type, expression_tree_op_node<Obj, OtherLeftChild, OtherRightChild>>;
352+
ret* op_node = new ret(boolean_op::OR);
325353
op_node->set_left(this);
326354
op_node->set_right(other);
327355
return op_node;
@@ -332,6 +360,11 @@ namespace attwoodn::expression_tree {
332360
const CompValue Obj::* member_var_ = nullptr;
333361
Op logical_op_;
334362
CompValue comp_value_;
363+
364+
protected:
365+
virtual expression_tree_leaf_node<Obj, Op, CompValue>* clone_impl() const override {
366+
return new expression_tree_leaf_node<Obj, Op, CompValue>(*this);
367+
}
335368
};
336369

337370
}
@@ -362,4 +395,85 @@ namespace attwoodn::expression_tree {
362395
node::expression_tree_leaf_node<Obj, Op, CompValue>* make_expr( CompValue (Obj::* member_func)() const, Op op, CompValue comp_value ) {
363396
return new node::expression_tree_leaf_node<Obj, Op, CompValue>( member_func, op, comp_value );
364397
}
365-
}
398+
399+
template<typename Obj>
400+
class expression_tree {
401+
public:
402+
expression_tree() = delete;
403+
404+
expression_tree(node::expression_tree_node<Obj>* expr) {
405+
if(!expr) {
406+
throw std::runtime_error("Attempted to construct an expression_tree using a null expression");
407+
}
408+
expr_ = expr->clone().release();
409+
delete expr;
410+
}
411+
412+
expression_tree(std::unique_ptr<node::expression_tree_node<Obj>> expr)
413+
: expression_tree(expr.release()) {}
414+
415+
expression_tree(const expression_tree& other) {
416+
if(!other.expr_) {
417+
throw std::runtime_error("Attempted to copy construct an expression_tree " +
418+
std::string("from an expression_tree with a null expression"));
419+
}
420+
expr_ = other.expr_->clone().release();
421+
}
422+
423+
expression_tree(expression_tree&& other) {
424+
if(!other.expr_) {
425+
throw std::runtime_error("Attempted to move construct an expression_tree " +
426+
std::string("from an expression_tree with a null expression"));
427+
}
428+
expr_ = other.expr_;
429+
other.expr_ = nullptr;
430+
}
431+
432+
expression_tree& operator=(const expression_tree& other) {
433+
if(!other.expr_) {
434+
throw std::runtime_error("Attempted copy assignment from an expression_tree with a null expression");
435+
}
436+
delete expr_;
437+
expr_ = other.expr_->clone().release();
438+
return *this;
439+
}
440+
441+
expression_tree& operator=(expression_tree&& other) {
442+
if(!other.expr_) {
443+
throw std::runtime_error("Attempted move assignment from an expression_tree with a null expression");
444+
}
445+
446+
if(this != &other) {
447+
delete expr_;
448+
expr_ = other.expr_;
449+
other.expr_ = nullptr;
450+
}
451+
return *this;
452+
}
453+
454+
~expression_tree() {
455+
delete expr_;
456+
}
457+
458+
/**
459+
* @brief Evaluates the given object to determine if it satisfies the expressions defined in this expression tree.
460+
*
461+
* @returns True if the given object satisfied the expression tree conditions;
462+
* False if the given object did not satisfy the expression tree conditions.
463+
*/
464+
bool evaluate(const Obj& obj) const {
465+
if(!expr_) {
466+
throw std::runtime_error("expression_tree has a null root expression node");
467+
}
468+
469+
try {
470+
return expr_->evaluate(obj);
471+
} catch(std::exception& e) {
472+
return false;
473+
}
474+
}
475+
476+
private:
477+
node::expression_tree_node<Obj>* expr_ = nullptr;
478+
};
479+
}

tests/CMakeLists.txt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,4 +15,9 @@ if(BUILD_TESTING)
1515
target_compile_options( expression_tree_op_node_test PRIVATE -fsanitize=address )
1616
add_test( expression_tree_op_node_test ${EXECUTABLE_OUTPUT_PATH}/expression_tree_op_node_test )
1717

18+
add_executable( expression_tree_test expression_tree.cpp )
19+
target_link_libraries( expression_tree_test "-fsanitize=address" )
20+
target_compile_options( expression_tree_test PRIVATE -fsanitize=address )
21+
add_test( expression_tree_test ${EXECUTABLE_OUTPUT_PATH}/expression_tree_test )
22+
1823
endif()

0 commit comments

Comments
 (0)