Code Monkey home page Code Monkey logo

booleval's Introduction

license Build Status codecov Standard

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


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.

Quick start

#include <string>
#include <iostream>
#include <booleval/evaluator.hpp>

class foo
{
public:
    foo( std::string value ) noexcept : value_{ std::move( value ) } {}

    std::string const & value() const noexcept { return value_; }

private:
    std::string value_{};
};

int main()
{
    foo x{ "foo" };
    foo y{ "bar" };

    booleval::evaluator evaluator
    {
        booleval::make_field( "field", &foo::value )
    };

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

    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

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.




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 and BPF (Berkeley Packet Filter), network tools available on most UNIX-like operating systems, have pretty much the same syntax for their filter expression.

Getting Started

booleval is a header-only C++17 library for evaluating logical expressions.

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:

  • EQUAL TO operator is specified in the expression: field_a eq foo
  • EQUAL TO operator is not specified in the expression: field_a foo

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

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

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
AND operator AND / and &&
OR operator OR / or | |
EQUAL TO operator EQ / eq ==
NOT EQUAL TO operator NEQ / neq !=
GREATER THAN operator GT / gt >
LESS THAN operator LT / lt <
GREATER THAN OR EQUAL TO operator GEQ / geq >=
LESS THAN OR EQUAL TO operator LEQ / leq <=
LEFT parentheses โˆ… (
RIGHT parentheses โˆ… )

Benchmark

Following table shows benchmark results:

Benchmark Time Iterations
Building expression tree 3817 ns 180904
Evaluation 1285 ns 532522

In other words, it is possible to evaluate 2,413,045.84 objects per second.

Compilation

In order to compile the library, run the following commands:

$ # create the build directory
$ mkdir build
$ cd build

$ # configure the project
$ cmake ../

$ # compile
$ make

Tests

In order to run unit tests, run the following commands:

$ # fetch the googletest submodule, needed for tests
$ git submodule init
$ git submodule update

$ mkdir build
$ cd build

$ # configure the project
$ cmake ..

$ # compile tests
$ make tests

$ # run tests
$ make test

Compiler Compatibility

  • Clang/LLVM >= 7
  • MSVC++ >= 19.16
  • GCC >= 8.4

There are no 3rd party dependencies.

Contributing

Feel free to contribute.

If you find that any of the tests fail, please create a ticket in the issue tracker indicating the following information:

  • platform
  • architecture
  • library version
  • minimal reproducible example

License

The project is available under the MIT license.

Support

If you like the work booleval library is doing, please consider supporting it:

Buy Me A Coffee

booleval's People

Contributors

m-peko avatar marimeireles avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar

booleval's Issues

Add benchmark results

Benchmark results are necessary in order not to worsen performance by every following change.

Allow quoted strings in expressions

This code is awesome and exactly what I was looking for, thanks for developing and sharing @m-peko

The one thing that is missing, or I can't seem to get to work, is being able to quote string constants.
i.e. field_name == 'string with spaces'

Is there a way to do this currently or is this feature planned?

Question: Set operations

Is it possible to implement set operations (intersection/etc) for the current implementation? If so, how would they look?
I suppose each set will be represented as a balanced subtree? Thx.

Custom booleval exceptions and handling field null/invalid conditions

Hi Marin,

I have some evaluator fields which can sometimes not be available (be null) and it would be good if there was a clean way to handle this. I have been experimenting with ways to do this and the easiest solution I have come up with is to allow the evaluator field_map functions to throw a specialised booleval exception (based on std::exception or std::runtime_error) i.e. booleval_field_null. We could catch this new booleval_field_null exception in result_visitor.visit_relational() and then rethrow with more context (i.e. name of the field) and then it can be caught by the code that calls evaluator.evaluate() (outside of booleval code) and handled/logged there.

There is also an unhandled exception in result_visitor.visit_relational if key.value() is not found in the fields_ map which could also throw an booleval_field_unknown exception.

Do you have any thoughts on this?

Cheers

James

Incorrect relational operator evaluation

Given:

obj pass("21", 123);

where field_a is of type std::string,

evaluator.expression("field_a < "121"");

Evaluates to true which is incorrect when comparing strings. It should evaluate to false as for string comparison, "21" is greater than "123" whereas when evaluating as a number 21 is less than 123.

In this example, field_a is of type std::string and the comparison is with "123" (quoted string), so string comparison should be used and not arithmetic comparison.

Missing class initializers

Hi Marin,

There are a few classes that are missing class initializers. I will submit a pull request for this shortly.

Cheers

James

booleval is deleting the evaluated object

I just started to integrate booleval and found this issue where the object I'm evaluating is being deleted multiple times by booleval, resulting in a core dump because in my case its triggering a double free.

I have reproduced the behavior in a small snippet (ignore the malloc/free ... that just mirrors my actual use case and is what drew my attention to the issues when i core dumped for a double free)

#include <stdio.h>
#include <string_view>
#include <booleval/evaluator.hpp>

struct MyClass {
    size_t _seq;
    void * buffer;

    MyClass(size_t seq) {
        printf(">> MyClass::ctor\n");

        //buffer = malloc(1000);
        _seq = seq;
    }
    
    ~MyClass() {
        printf("<< MyClass::dtor\n");
        //free(buffer);
    }
    
    size_t seq() { 
        return _seq; 
    }
};


u_int32_t count(std::string_view filter) {

   u_int32_t count=0;

    booleval::evaluator evaluator({
        {"seq", &MyClass::seq }
    });

    auto valid = evaluator.expression(filter);
    if (!valid) {
       throw std::runtime_error("count_pdus expression not valid");
    }

    std::vector<MyClass*> vector;

    for(int x=0;x<10;x++) {
        MyClass* c = new MyClass(x);
        vector.push_back(c);
    }

    for(MyClass* cc : vector) {

        if (evaluator.is_activated()) {
            if(evaluator.evaluate(*cc)) ++count;
        } else {
            throw std::runtime_error("Evaluator is not activated");
        }
   }
  return count;
}

int main(void) {
  u_int32_t x = count("(seq == 1)");
  printf("count = %u\n",x);
  return 0;
}

If you were to run that you would see multiple calls to the destructor for each object evaluated. If uncomment the malloc/free gdb shows this backtrace:

#0  0x00007ffff6e03387 in raise () from /lib64/libc.so.6
#1  0x00007ffff6e04a78 in abort () from /lib64/libc.so.6
#2  0x00007ffff6e45ed7 in __libc_message () from /lib64/libc.so.6
#3  0x00007ffff6e4e299 in _int_free () from /lib64/libc.so.6
#4  0x00007ffff7770e23 in std::basic_string<char, std::char_traits<char>, std::allocator<char> >::~basic_string() () from /lib64/libstdc++.so.6
#5  0x00000000004ac94e in booleval::utils::any_value::~any_value (this=0x7ffff4e84370, __in_chrg=<optimized out>)
    at /home/rjo/src/iascollector/booleval/include/booleval/utils/any_value.hpp:120
#6  0x00000000004acafe in booleval::tree::result_visitor<booleval::utils::any_mem_fn>::visit_relational<MyClass, std::equal_to<void> >(booleval::tree::tree_node const&, MyClass const&, std::equal_to<void>&&) (this=0x7ffff4e84498, node=..., obj=..., func=<unknown type in /home/rjo/src/iascollector/build/iascollector, CU 0x3e5544, DIE 0x432b1a>)
    at /home/rjo/src/iascollector/booleval/include/booleval/tree/result_visitor.hpp:119
#7  0x00000000004abc17 in booleval::tree::result_visitor<booleval::utils::any_mem_fn>::visit<MyClass> (this=0x7ffff4e84498, node=..., obj=...)
    at /home/rjo/src/iascollector/booleval/include/booleval/tree/result_visitor.hpp:141
#8  0x00000000004ab0e8 in booleval::evaluator<booleval::utils::any_mem_fn>::evaluate<MyClass> (this=0x7ffff4e84490, obj=...)
    at /home/rjo/src/iascollector/booleval/include/booleval/evaluator.hpp:103

I should note that the same behavior occurs if a non-pointer type is used in the evaluation.

Any idea whats going on here?

Quoted field

As per the discussion on fluentcpp, it would be useful to be able to quote a field to allow space(s) and reserved words in the field. eg

field_a "a test" and field_b 123
field_a "and" and field_b 123

Unhandled exception

If you try:

obja pass("21", 123.1);
objb fail("bar", 456.2);

booleval::evaluator evaluator({
    { "field_aa", &obja::field_aa },
    { "field_bb", &objb::field_bb }
    });

evaluator.expression("field_aa \"21\" and field_bb 123.1");

if (evaluator.is_activated()) {
    std::cout << std::boolalpha << evaluator.evaluate(pass) << std::endl;
    //std::cout << std::boolalpha << evaluator.evaluate(fail) << std::endl;
} else
    std::cerr << "Expression not valid!" << std::endl;

Where you are trying to evaluate an expression with fields defined from multiple classes, then although this compiles OK, you get at run-time un-handled exception error.

Evaluate() return value

Wouldn't it be better if evaluate() returns false if is_activated_ is false? Returning true gives the wrong impression that the expression has been evaluated when it hasn't if is_activated_ is false??

Symbols >= and <= don't work

Hi @m-peko.

I have run into an issue which I am struggling to solve at the moment. The symbols >= and <= are not working whereas GEQ and LEQ are working so its something to do with the parsing as it seems to think they are > or < and the expression fails as not valid.

I will continue looking and trying to solve it but wanted you to know there is an issue there.

Cheers

James

Quoted keyword

A quoted keyword is considered an invalid expression

obj pass("and", 123);
...
evaluator.expression("field_a "and" and field_b 123");

should be valid as "and" is a literal and not a keyword - but is considered a keyword and rejected as an invalid expression.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.