Skip to content

Commit a8e970f

Browse files
committed
updates README, adds a unit test comparing std::vector
1 parent 752a4b0 commit a8e970f

File tree

2 files changed

+74
-24
lines changed

2 files changed

+74
-24
lines changed

README.md

Lines changed: 39 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,8 @@ struct my_type {
4040

4141
...
4242

43-
// the Cpp Expression Tree library can be used to create an expression tree to evaluate instances of my_type
43+
// the Cpp Expression Tree library can be used to create an expression tree to
44+
// evaluate instances of my_type
4445

4546
// create an expression tree: my_bool == true OR (get_my_int() > 0 AND my_int < 10)
4647
expression_tree<my_type> expr {
@@ -51,46 +52,41 @@ expression_tree<my_type> expr {
5152
)
5253
};
5354

55+
5456
// create an instance of my_type that satisfies the above expression
5557
my_type obj;
5658
obj.my_bool = true;
5759
obj.my_int = 4;
60+
assert(expr.evaluate(obj)); // returns true - obj matches the expression
5861

59-
// evaluating obj against the expression_tree above returns true
60-
assert(expr.evaluate(obj));
6162

6263
// update obj so that my_int is outside the range 1..9
6364
obj.my_bool = true;
6465
obj.my_int = 12;
66+
assert(expr.evaluate(obj)); // returns true - obj matches the expression
6567

66-
// evaluating obj against the expression_tree above returns true
67-
assert(expr.evaluate(obj));
6868

6969
// update obj so that my_bool is false and my_int is outside the range 1..9
7070
obj.my_bool = false;
7171
obj.my_int = 0;
72-
73-
// evaluating obj against the expression_tree above returns false
74-
assert(!expr.evaluate(obj));
72+
assert(!expr.evaluate(obj)); // returns false - obj does not match the expression
7573
```
7674

77-
Below is a diagram showing the content of the `expression_tree<my_type>` that was created in the example code above:
75+
Below is a diagram showing the content of the `expression_tree<my_type>` created in the example code above:
7876

7977
<p align="center">
8078
<img src="docs/a_quick_example_expression_tree.png"/>
8179
</p>
8280

83-
As you can imagine, this example code can be expanded to fit a variety of use cases and struct/class type structures. More complex code examples are provided in the documentation below. Further, there are a number of unit tests located in the `tests` directory, which may be helpful for getting familiar with the library.
81+
As you can imagine, this example code can be expanded to fit a variety of use cases and user-defined types. More complex code examples are provided in the documentation below. Further, there are a number of unit tests located in the `tests` directory, which may be helpful for getting familiar with the library.
8482

8583
## Creating Expression Trees
8684

87-
The `expression_tree` class is a templated, RAII container class that takes ownership of user-defined expressions. The `expression_tree` class can be moved and/or copied to different contexts while maintaining consistency and safety. The template parameter of `expression_tree` is the type of object that the `expression_tree` can evaluate. Assuming there is a user-defined class named `my_type`, the templated `expression_tree` type would look like this: `expression_tree<my_type>`. The template argument of `expression_tree` cannot be a primitive type, like `int`, `char`, or `double`.
85+
The `expression_tree` class is a templated, RAII container class that takes ownership of user-defined expressions. Instances of `expression_tree` can be moved and/or copied to different contexts while maintaining consistency and memory safety. The template parameter of `expression_tree` defines the type of object that the `expression_tree` will evaluate. Assuming there is a user-defined struct named `my_type`, the templated `expression_tree` type would look like this: `expression_tree<my_type>`. The template argument of `expression_tree` cannot be a primitive type, like `int`, `char`, or `double`.
8886

8987
An `expression_tree` cannot be default constructed - it must be initialized with an expression. Users can easily and intuitively define expressions using one of the `make_expr` helper functions found in the namespace `attwoodn::expression_tree`. `make_expr` generates heap-allocated pointers to expression tree nodes and returns them. As such, the returned expression tree node pointers should be managed carefully. If the returned pointers are not wrapped in an `expression_tree` or a smart pointer, they will need to be explicitly `delete`d by the calling code.
9088

91-
// TODO document the thre make_expr functions
92-
93-
Here are some examples of how you might handle the return value from one of the `make_expr` helper functions:
89+
Here are some examples of how a user may safely handle the return value from one of the `make_expr` helper functions:
9490
```cpp
9591
#include <attwoodn/expression_tree.hpp>
9692

@@ -136,6 +132,8 @@ auto* expr_raw = make_expr(&my_type::my_bool, op::equals, true);
136132
delete expr_raw;
137133
```
138134
135+
// TODO document the three make_expr functions
136+
139137
Please see the section below for more information about expression tree nodes.
140138
141139
## Types of Expression Tree Nodes
@@ -152,17 +150,32 @@ Expression tree op nodes contain a boolean operation (AND/OR) and have reference
152150
153151
## Logical Operators
154152
155-
There are several logical operator functions defined in the namespace `attwoodn::expression_tree::op`, which can be used to create individual expressions within the tree. The included operators are:
153+
There are several logical operator functions defined in the namespace `attwoodn::expression_tree::op`, which can be used to create expression tree leaf nodes. The included operators are:
156154
* equals
157155
* not_equals
158156
* less_than
159157
* greater_than
160158
159+
Each of the above logical operator functions are templated, and are overloaded to permit passing arguments of either a `const T&` type, or a `T*` type. This means that value types, references, and pointers are all permissible for comparison.
160+
161+
Note that there is a known limitation to comparing `T*` types, such as `char*`, using the above operator functions. With `T*` types, no iteration is performed, so comparison is performed only on the data located at the beginning of the pointer address. For example:
162+
163+
```cpp
164+
assert(op::equals("test", "testing 123")); // returns true
165+
166+
assert(!op::equals(std::string("test"), std::string("testing 123"))); // returns false
167+
```
168+
169+
Should users wish to compare an iterable collection of elements using the provided operator functions, they should compare container types, such as `std::vector<T>`, instead of pointer types like `T*`.
170+
161171
Users of the library can also easily define their own logical operators and use them when creating expressions. Here is an example of how a user might create their own operator functions and use them in an expression:
162172

163173
```cpp
174+
#include <attwoodn/expression_tree.hpp>
175+
164176
using namespace attwoodn::expression_tree;
165177

178+
// imagine there are two user-defined types, like so:
166179
struct packet_payload {
167180
uint16_t error_code;
168181
std::string data;
@@ -173,24 +186,28 @@ struct packet_payload {
173186
}
174187
};
175188

189+
// data packet contains an instance of packet_payload, which needs to be evaluated
176190
class data_packet {
177191
public:
178192
std::string sender_name;
179193
packet_payload payload;
180194
};
181195

196+
182197
...
183198

184-
// user creates their own logical operator for evaluating incoming packet_payload objects
199+
200+
// user creates their own logical operator for evaluating incoming packet_payload objects.
201+
// only the first function argument is used. The other is an "empty", ignored instance
185202
auto is_small_packet_payload = [](const packet_payload& incoming, const packet_payload&) -> bool {
186203
if(incoming.error_code == 0 && incoming.checksum_ok && incoming.payload_size() <= 10) {
187204
return true;
188205
}
189206
return false;
190207
};
191208

192-
// Create an expression that only accepts small, non-errored data packets from Jim.
193-
// evaluate packet contents using the user-defined lambda operator defined above
209+
// User creates an expression that only accepts small, non-errored data packets from Jim.
210+
// The expression evaluates the packet_payload using the user-defined lambda operator created above
194211
expression_tree<data_packet> expr {
195212
make_expr(&data_packet::sender_name, op::equals, std::string("Jim"))
196213
->AND(make_expr(&data_packet::payload, is_small_packet_payload, packet_payload()))
@@ -212,20 +229,20 @@ assert(!expr.evaluate(incoming_packet)); // fails evaluation. No messages fr
212229
// Jim sends a packet with a bad checksum
213230
incoming_packet.sender_name = "Jim";
214231
incoming_packet.payload.checksum_ok = false;
215-
assert(!expr.evaluate(incoming_packet)); // fails evaluation. The packet was from Jim, but the checksum was bad
232+
assert(!expr.evaluate(incoming_packet)); // fails evaluation. The packet was from Jim, but checksum was bad
216233

217234
// Jim sends a packet whose payload is too big
218235
incoming_packet.payload.checksum_ok = true;
219236
incoming_packet.payload.data = "Boy do I have a long story for you - so I was talking to Pam ...";
220-
assert(!expr.evaluate(incoming_packet)); // fails evaluation. The packet's payload was too big. Give me the TLDR next time, Jim
237+
assert(!expr.evaluate(incoming_packet)); // fails evaluation. The payload was too big. Give me the TLDR, Jim
221238

222239
// Jim sends a small, rude packet
223240
incoming_packet.payload.data = "Dwight sux";
224-
assert(expr.evaluate(incoming_packet)); // passes evaluation. The packet's payload was the right size this time
241+
assert(expr.evaluate(incoming_packet)); // passes evaluation. The payload was the right size this time
225242

226243
// Jim sends a packet has an error code
227244
incoming_packet.payload.error_code = 404;
228-
assert(!expr.evaluate(incoming_packet)); // fails evaluation. The packet's payload had an error code
245+
assert(!expr.evaluate(incoming_packet)); // fails evaluation. The payload had an error code
229246
```
230247
231248
## Boolean Operators

tests/expression_tree.cpp

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,24 @@
11
#include <attwoodn/expression_tree.hpp>
22
#include "test_utils.hpp"
33
#include <functional>
4+
#include <vector>
45
#include <cassert>
56

67
using namespace attwoodn::expression_tree;
78

8-
void test_expression_tree_std_string_template();
99
void test_quick_example_expression_tree();
10+
void test_expression_tree_std_string_template();
11+
void test_expression_tree_std_vector_template();
1012
void test_simple_expression_tree();
1113
void test_complex_expression_tree();
1214
void test_moved_expression_tree();
1315
void test_copied_expression_tree();
1416
void test_user_defined_operator();
1517

1618
int main(int argc, char** argv) {
17-
test_expression_tree_std_string_template();
1819
test_quick_example_expression_tree();
20+
test_expression_tree_std_string_template();
21+
test_expression_tree_std_vector_template();
1922
test_simple_expression_tree();
2023
test_complex_expression_tree();
2124
test_moved_expression_tree();
@@ -60,6 +63,36 @@ void test_expression_tree_std_string_template() {
6063
assert(!expr.evaluate("good bye"));
6164
}
6265

66+
void test_expression_tree_std_vector_template() {
67+
struct my_vec_wrapper {
68+
std::vector<int> int_vec;
69+
};
70+
71+
expression_tree<my_vec_wrapper> expr {
72+
make_expr(&my_vec_wrapper::int_vec, op::equals, {1, 2, 3})
73+
};
74+
75+
my_vec_wrapper obj;
76+
77+
obj.int_vec = {1, 2, 3};
78+
assert(expr.evaluate(obj));
79+
80+
obj.int_vec = {1, 2, 3, 4};
81+
assert(!expr.evaluate(obj));
82+
83+
obj.int_vec = {3, 2, 1};
84+
assert(!expr.evaluate(obj));
85+
86+
obj.int_vec = {};
87+
assert(!expr.evaluate(obj));
88+
89+
obj.int_vec = {1, 2, 3, 1};
90+
assert(!expr.evaluate(obj));
91+
92+
obj.int_vec = {1, 2, 3, 2, 1};
93+
assert(!expr.evaluate(obj));
94+
}
95+
6396
void test_simple_expression_tree() {
6497
auto test_procedure = [](const expression_tree<test_fixture>& expr) {
6598
test_fixture fixture;

0 commit comments

Comments
 (0)