Skip to content

Commit

Permalink
Merge pull request #61 from m-peko/bits-of-refactoring
Browse files Browse the repository at this point in the history
Bits of refactoring
  • Loading branch information
m-peko authored Jul 19, 2021
2 parents e01a0c4 + 0feda19 commit 054a05e
Show file tree
Hide file tree
Showing 45 changed files with 2,981 additions and 3,227 deletions.
5 changes: 5 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,8 @@
path = googletest
url = https://github.com/google/googletest.git
ignore = dirty

[submodule "googlebench"]
path = googlebench
url = https://github.com/google/benchmark.git
ignore = dirty
2 changes: 1 addition & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -79,4 +79,4 @@ script:
- cmake --build . --target tests

# Execute tests
- ctest
- ctest --extra-verbose
32 changes: 25 additions & 7 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@ cmake_minimum_required (VERSION 3.2)

project (booleval)

option (BOOLEVAL_BUILD_EXAMPLES "Build examples" ON)
option (BOOLEVAL_BUILD_TESTS "Build tests" ON)
option (BOOLEVAL_BUILD_EXAMPLES "Build examples" ON)
option (BOOLEVAL_BUILD_BENCHMARK "Build benchmark" OFF)

# Compile in release mode by default
if (NOT CMAKE_BUILD_TYPE)
Expand Down Expand Up @@ -62,12 +63,6 @@ if (NOT CMAKE_INSTALL_LIBDIR)
set (CMAKE_INSTALL_LIBDIR lib)
endif ()

##########################
### Add subdirectories ###
##########################

add_subdirectory (src)

if (BOOLEVAL_BUILD_EXAMPLES)
message (STATUS "Examples have been enabled")
add_subdirectory (examples)
Expand All @@ -94,3 +89,26 @@ if (BOOLEVAL_BUILD_TESTS)
message (STATUS "googletest git submodule is absent. Run `git submodule init && git submodule update` to get it")
endif ()
endif ()

if (BOOLEVAL_BUILD_BENCHMARK)
# Only include googlebench if the git submodule has been fetched
if (EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/googlebench/CMakeLists.txt")
# Enable benchmark and add the benchmark directory
message (STATUS "Benchmark has been enabled")
set (GOOGLEBENCH_ROOT ${CMAKE_CURRENT_SOURCE_DIR}/googlebench)
set (GOOGLEBENCH_INCLUDE ${GOOGLEBENCH_ROOT}/googlebench/include)
set (GOOGLEBENCH_BINARY_DIR ${CMAKE_CURRENT_BINARY_DIR}/googlebench)
set (GOOGLEBENCH_LIBRARY ${GOOGLEBENCH_BINARY_DIR}/googlebench)

set (BENCHMARK_ENABLE_GTEST_TESTS OFF)

if (NOT CMAKE_BUILD_TYPE MATCHES Debug)
add_definitions(-DNDEBUG)
endif()

add_subdirectory (googlebench)
add_subdirectory (benchmark)
else ()
message (STATUS "googlebench git submodule is absent. Run `git submodule init && git submodule update` to get it")
endif ()
endif ()
210 changes: 114 additions & 96 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,33 +3,86 @@
<img src="docs/booleval-title.png"/>

[![license](https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat)](https://github.com/m-peko/booleval/blob/master/LICENSE)
[![Build Status](https://travis-ci.org/m-peko/booleval.svg?branch=master)](https://travis-ci.org/m-peko/booleval)
[![Build Status](https://app.travis-ci.com/m-peko/booleval.svg?branch=master)](https://travis-ci.org/m-peko/booleval)
[![codecov](https://codecov.io/gh/m-peko/booleval/branch/master/graph/badge.svg)](https://codecov.io/gh/m-peko/booleval)
[![Standard](https://img.shields.io/badge/C%2B%2B-17-blue.svg)](https://en.wikipedia.org/wiki/C%2B%2B17)

Modern C++17 library for evaluating logical expressions.
Header-only C++17 library for evaluating logical expressions.

</div>

<br/>

The library is under development and subject to change. Contributions are welcome. You can also log an issue if you have a wish for enhancement or if you spot a bug.

## Overview
## Quick start

1. [About](#about)
2. [Motivation](#motivation)
3. [Specification](#specification)
4. [Requirements](#requirements)
5. [Compilation](#compilation)
6. [Tests](#tests)
7. [Example](#example)
8. [Support](#support)
```cpp
#include <string>
#include <iostream>
#include <booleval/evaluator.hpp>

template< typename T >
class foo
{
public:
foo() : value_{} {}
foo( T && value ) : value_{ value } {}

void value( T && value ) { value_ = value; }

T value() const noexcept { return value_; }

private:
T value_{};
};

int main()
{
foo< std::string > x{ "foo" };
foo< std::string > y{ "bar" };

<a name="about"></a>
booleval::evaluator evaluator
{
booleval::make_field( "field", &foo< std::string >::value )
};

## About
if ( !evaluator.expression( "field eq foo" ) )
{
std::cerr << "Expression not valid!" << std::endl;
}

`booleval` is a small C++17 library for evaluating logical expressions. It implements recursive descent parser mechanism for building an expression tree for a user-defined logical expression. After the expression tree is being built, map of fields and values representing a certain object can be passed to the `evaluator` component of this library which will evaluate those values to `true` or `false` according to the user-defined logical expression.
if ( evaluator.is_activated() )
{
std::cout << std::boolalpha << evaluator.evaluate( x ) << std::endl; // true
std::cout << std::boolalpha << evaluator.evaluate( y ) << std::endl; // false
}

return 0;
}
```

## Table of Contents

* [Motivation](#motivation)
* [Getting Started](#getting-started)
* [Zero Copy](#zero-copy)
* [EQUAL TO Operator](#equal-to-operator)
* [Valid Expressions](#valid-expressions)
* [Invalid Expressions](#invalid-expressions)
* [Evaluation Result](#evaluation-result)
* [Supported Tokens](#supported-tokens)
* [Benchmark](#benchmark)
* [Compilation](#compilation)
* [Tests](#tests)
* [Compiler Compatibility](#compiler-compatibility)
* [Contributing](#contributing)
* [License](#license)
* [Support](#support)

## Motivation

`booleval` is a header-only C++17 library for evaluating logical expressions. It implements recursive descent parser mechanism for building an expression tree for a user-defined logical expression. After the expression tree is being built, map of fields and values representing a certain object can be passed to the `evaluator` component of this library which will evaluate those values to `true` or `false` according to the user-defined logical expression.

<br/>
<p align="center">
Expand All @@ -38,25 +91,21 @@ The library is under development and subject to change. Contributions are welcom
<br/>
<br/>

<a name="motivation"></a>

## Motivation

In programming languages like Java and C# accessing arbitrary fields inside a class represents an omnipresent problem. However, it got solved by introducing a **reflections** feature. This feature provides us information about the class to which a certain object belongs to and, also, the methods of that class which we can invoke at runtime.

Since the reflection feature is missing in C++, `booleval` library is implemented. It checks whether the members of a class to which an object belongs to have certain values. Members of a class are specified in a string format and can be used to form a logical expression.

Providing an end-user a functionality of specifying a logical expression is a common way of filtering out a large amounts of objects. E.g. [`tcpdump`](https://www.tcpdump.org/manpages/tcpdump.1.html) and [BPF](https://en.wikipedia.org/wiki/Berkeley_Packet_Filter) (Berkeley Packet Filter), network tools available on most UNIX-like operating systems, have pretty much the same syntax for their filter expression.

<br/>
<p align="center">
<img src="docs/command-line.gif"/>
</p>
<br/>
## Getting Started

<a name="specification"></a>
`booleval` is a header-only C++17 library for evaluating logical expressions.

## Specification
### Zero Copy

In order to improve performance, `booleval` library does not copy objects that are being evaluated.

### EQUAL TO operator

EQUAL TO operator is an optional operator. Therefore, logical expression that checks whether a field with the name `field_a` has a value of `foo` can be constructed in a two different ways:

Expand All @@ -65,14 +114,31 @@ EQUAL TO operator is an optional operator. Therefore, logical expression that ch

To conclude, equality operator is a default operator between two fields. Thus, it **does not need** to be specified in the logical expression.

### Examples of valid expressions
### Valid expressions

- `(field_a foo and field_b bar) or field_a bar`
- `(field_a eq foo and field_b eq bar) or field_a eq bar`

### Examples of invalid expressions
### Invalid expressions

- `(field_a foo and field_b bar` _Note: Missing closing parentheses_
- `field_a foo bar` _Note: Two field values in a row_

### Evaluation Result

Result of evaluation process contains two information:

- `success`: `true` if evaluation process is successful; otherwise, `false`
- `message`: meaningful message if evaluation process is unsuccessful; otherwise, empty message

This kind of enriched lightweight result is being used instead of exceptions for performance reasons.

Examples of messages:

- `"Missing operand"`
- `"Unknown field"`
- `"Unknown token type"`

### Supported tokens

|Name|Keyword|Symbol|
Expand All @@ -88,19 +154,16 @@ To conclude, equality operator is a default operator between two fields. Thus, i
|LEFT parentheses|&empty;|(|
|RIGHT parentheses|&empty;|)|

<a name="requirements"></a>

## Requirements
## Benchmark

`booleval` project requires C++17 compiler and has been tested on:
Following table shows benchmark results:

- gcc 8.4.0
- clang 7.0.0
- msvc 19.16

There are no 3rd party dependencies.
|Benchmark|Time|Iterations
|:---:|:---:|:---:|
|Building expression tree|3817 ns|180904|
|Evaluation|1285 ns|532522|

<a name="compilation"></a>
In other words, it is possible to evaluate **2,413,045.84 objects per second**.

## Compilation

Expand All @@ -118,8 +181,6 @@ $ # compile
$ make
```

<a name="tests"></a>

## Tests

In order to run unit tests, run the following commands:
Expand All @@ -142,71 +203,28 @@ $ # run tests
$ make test
```

If you find that any tests **fail**, please create a ticket in the
issue tracker indicating the following information:
- platform
- architecture
- library version
- minimal reproducible example
## Compiler Compatibility

<a name="example"></a>
* Clang/LLVM >= 7
* MSVC++ >= 19.16
* GCC >= 8.4

## Example
There are no 3<sup>rd</sup> party dependencies.

Let's say we have a large number of objects coming through our interface. Objects can be of the following `class obj` type:
## Contributing

```c++
struct obj {
std::string field_a_;
uint32_t field_b_;
Feel free to contribute.

public:
obj(std::string const& field_a, uint32_t const field_b)
: field_a_(field_a),
field_b_(field_b)
{}
If you find that any of the tests **fail**, please create a ticket in the issue tracker indicating the following information:

std::string const& field_a() {
return field_a_;
}
* platform
* architecture
* library version
* minimal reproducible example

uint32_t field_b() {
return field_b_;
}
};
```
## License

In our application, we want to let end-users to specify some sort of a rule which will filter out only those objects that contain certain field values. This rule can have a form of a following logical expression `field_a foo and field_b 123`. Now, we can use `booleval::evaluator` component to check whether objects conform specified rule.

```c++
#include <string>
#include <iostream>
#include <booleval/evaluator.hpp>

int main() {
obj pass("foo", 123);
obj fail("bar", 456);

booleval::evaluator evaluator({
{ "field_a", &obj::field_a },
{ "field_b", &obj::field_b }
});

auto valid = evaluator.expression("field_a foo and field_b 123");
if (!valid) {
std::cerr << "Expression not valid!" << std::endl;
}

if (evaluator.is_activated()) {
std::cout << std::boolalpha << evaluator.evaluate(pass) << std::endl; // output: true
std::cout << std::boolalpha << evaluator.evaluate(fail) << std::endl; // output: false
} else {
std::cerr << "Evaluator is not activated!" << std::endl;
}

return 0;
}
```
The project is available under the [MIT](https://opensource.org/licenses/MIT) license.

## Support

Expand Down
4 changes: 4 additions & 0 deletions benchmark/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
cmake_minimum_required (VERSION 3.8)

set (CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/src)
add_subdirectory (src)
28 changes: 28 additions & 0 deletions benchmark/src/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
cmake_minimum_required (VERSION 3.8)

# Use bitflags's include directories + Google Benchmark include directories
include_directories (
${PROJECT_SOURCE_DIR}/include/
${GOOGLEBENCH_INCLUDE}
)

link_directories (
${GOOGLEBENCH_LIBRARY}
)

# Link against GoogleBenchmark and bitflags
link_libraries (benchmark)

add_custom_target (benchmarks)

macro (create_benchmark benchmark_name)
string (REPLACE "/" "_" binary_name ${benchmark_name})
set (binary_name "${binary_name}_benchmark")
add_executable (${binary_name} EXCLUDE_FROM_ALL "${benchmark_name}_benchmark.cpp")
add_test (${benchmark_name} ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/${binary_name})
add_dependencies (benchmarks ${binary_name})
endmacro ()

# Benchmarks

create_benchmark (booleval)
Loading

0 comments on commit 054a05e

Please sign in to comment.