diff --git a/.gitmodules b/.gitmodules index ff22336..573c225 100644 --- a/.gitmodules +++ b/.gitmodules @@ -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 diff --git a/.travis.yml b/.travis.yml index 4531772..c6750db 100644 --- a/.travis.yml +++ b/.travis.yml @@ -79,4 +79,4 @@ script: - cmake --build . --target tests # Execute tests - - ctest + - ctest --extra-verbose diff --git a/CMakeLists.txt b/CMakeLists.txt index ef586e1..ae963d4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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) @@ -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) @@ -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 () \ No newline at end of file diff --git a/README.md b/README.md index a2999de..1c7c833 100644 --- a/README.md +++ b/README.md @@ -3,10 +3,11 @@ [![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. @@ -14,22 +15,74 @@ Modern 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. -## 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 +#include +#include + +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" }; - + 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.

@@ -38,25 +91,21 @@ The library is under development and subject to change. Contributions are welcom

- - -## 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. -
-

- -

-
+## Getting Started - +`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: @@ -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| @@ -88,19 +154,16 @@ To conclude, equality operator is a default operator between two fields. Thus, i |LEFT parentheses|∅|(| |RIGHT parentheses|∅|)| - - -## 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| - +In other words, it is possible to evaluate **2,413,045.84 objects per second**. ## Compilation @@ -118,8 +181,6 @@ $ # compile $ make ``` - - ## Tests In order to run unit tests, run the following commands: @@ -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 - +* Clang/LLVM >= 7 +* MSVC++ >= 19.16 +* GCC >= 8.4 -## Example +There are no 3rd 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 -#include -#include - -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 diff --git a/benchmark/CMakeLists.txt b/benchmark/CMakeLists.txt new file mode 100644 index 0000000..161191f --- /dev/null +++ b/benchmark/CMakeLists.txt @@ -0,0 +1,4 @@ +cmake_minimum_required (VERSION 3.8) + +set (CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/src) +add_subdirectory (src) \ No newline at end of file diff --git a/benchmark/src/CMakeLists.txt b/benchmark/src/CMakeLists.txt new file mode 100644 index 0000000..8f924c6 --- /dev/null +++ b/benchmark/src/CMakeLists.txt @@ -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) \ No newline at end of file diff --git a/benchmark/src/booleval_benchmark.cpp b/benchmark/src/booleval_benchmark.cpp new file mode 100644 index 0000000..0c5e21d --- /dev/null +++ b/benchmark/src/booleval_benchmark.cpp @@ -0,0 +1,101 @@ +/* + * Copyright (c) 2020, Marin Peko + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include +#include + +namespace +{ + + template< typename T, typename U > + class bar + { + public: + bar( T && value_1, U && value_2 ) + : value_1_{ value_1 } + , value_2_{ value_2 } + {} + + void value_1( T && value ) noexcept { value_1_ = value; } + void value_2( U && value ) noexcept { value_2_ = value; } + + T value_1() const noexcept { return value_1_; } + U value_2() const noexcept { return value_2_; } + + private: + T value_1_{}; + U value_2_{}; + }; + +} // namespace + +void BuildingExpressionTree( benchmark::State & state ) +{ + booleval::evaluator evaluator + { + { + booleval::make_field( "field_1", &bar< std::string, unsigned >::value_1 ), + booleval::make_field( "field_2", &bar< std::string, unsigned >::value_2 ) + } + }; + + for (auto _ : state) + { + [[ maybe_unused ]] auto const success{ evaluator.expression( "(field_1 foo and field_2 1) or (field_1 qux and field_2 2)" ) }; + benchmark::DoNotOptimize( evaluator ); + } +} + +BENCHMARK( BuildingExpressionTree ); + +void Evaluation( benchmark::State & state ) +{ + booleval::evaluator evaluator + { + { + booleval::make_field( "field_1", &bar< std::string, unsigned >::value_1 ), + booleval::make_field( "field_2", &bar< std::string, unsigned >::value_2 ) + } + }; + + bar< std::string, unsigned > x{ "foo", 1 }; + + [[ maybe_unused ]] auto const success{ evaluator.expression( "(field_1 foo and field_2 1) or (field_1 qux and field_2 2)" ) }; + + for (auto _ : state) + { + [[ maybe_unused ]] auto const result{ evaluator.evaluate( x ) }; + benchmark::DoNotOptimize( evaluator ); + benchmark::DoNotOptimize( x ); + } +} + +BENCHMARK( Evaluation ); + +BENCHMARK_MAIN(); diff --git a/docs/command-line.gif b/docs/command-line.gif deleted file mode 100644 index cdc3151..0000000 Binary files a/docs/command-line.gif and /dev/null differ diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index fa19da0..c63d167 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -5,14 +5,9 @@ include_directories ( ${CMAKE_CURRENT_SOURCE_DIR}/../include ) -link_libraries (booleval) - add_custom_target ( examples DEPENDS evaluator ) -# Make sure we first build libbooleval -add_dependencies (examples booleval) - add_executable (evaluator EXCLUDE_FROM_ALL evaluator.cpp) \ No newline at end of file diff --git a/examples/evaluator.cpp b/examples/evaluator.cpp index 727b0fc..1617cc1 100644 --- a/examples/evaluator.cpp +++ b/examples/evaluator.cpp @@ -31,44 +31,51 @@ #include #include -struct obj { +template< typename T, typename U > +class bar +{ public: - obj(std::string const& field_a, uint32_t const field_b) - : field_a_(field_a), - field_b_(field_b) + bar( T && value_1, U && value_2 ) + : value_1_{ value_1 } + , value_2_{ value_2 } {} - std::string const& field_a() const noexcept { - return field_a_; - } + void value_1( T && value ) noexcept { value_1_ = value; } + void value_2( U && value ) noexcept { value_2_ = value; } - uint32_t field_b() const noexcept { - return field_b_; - } + T value_1() const noexcept { return value_1_; } + U value_2() const noexcept { return value_2_; } private: - std::string field_a_; - uint32_t field_b_; + T value_1_{}; + U value_2_{}; }; -int main() { - obj pass("foo", 123); - obj fail("bar", 456); +int main() +{ + bar< std::string, unsigned > x{ "foo", 1 }; + bar< std::string, unsigned > y{ "bar", 2 }; - booleval::evaluator evaluator({ - { "field_a", &obj::field_a }, - { "field_b", &obj::field_b } - }); + booleval::evaluator evaluator + { + { + booleval::make_field( "field_1", &bar< std::string, unsigned >::value_1 ), + booleval::make_field( "field_2", &bar< std::string, unsigned >::value_2 ) + } + }; - auto valid = evaluator.expression("field_a foo and field_b 123"); - if (!valid) { + if ( !evaluator.expression( "(field_1 foo and field_2 1) or (field_1 qux and field_2 2)" ) ) + { std::cerr << "Expression not valid!" << std::endl; } - if (evaluator.is_activated()) { - std::cout << std::boolalpha << evaluator.evaluate(pass) << std::endl; - std::cout << std::boolalpha << evaluator.evaluate(fail) << std::endl; - } else { + if ( evaluator.is_activated() ) + { + std::cout << std::boolalpha << evaluator.evaluate( x ).success << std::endl; + std::cout << std::boolalpha << evaluator.evaluate( y ).success << std::endl; + } + else + { std::cerr << "Evaluator is not activated!" << std::endl; } diff --git a/googlebench b/googlebench new file mode 160000 index 0000000..e451e50 --- /dev/null +++ b/googlebench @@ -0,0 +1 @@ +Subproject commit e451e50e9b8af453f076dec10bd6890847f1624e diff --git a/include/booleval/evaluator.hpp b/include/booleval/evaluator.hpp index b042eb4..2c27bba 100644 --- a/include/booleval/evaluator.hpp +++ b/include/booleval/evaluator.hpp @@ -30,13 +30,17 @@ #ifndef BOOLEVAL_EVALUATOR_HPP #define BOOLEVAL_EVALUATOR_HPP -#include +#include #include -#include +#include + +#include +#include #include -#include +#include -namespace booleval { +namespace booleval +{ /** * @class evaluator @@ -44,31 +48,32 @@ namespace booleval { * Represents a class for evaluating logical expressions in a form of a string. * It builds an expression tree and traverses that tree in order to evaluate fields. */ -template -class evaluator { - using field_map = std::map; - +class evaluator +{ public: - evaluator() = default; - evaluator(evaluator&& rhs) = default; - evaluator(evaluator const& rhs) = default; + evaluator() noexcept = default; - evaluator(field_map const& fields) { - result_visitor_.fields(fields); + evaluator( evaluator && rhs ) noexcept = default; + evaluator( evaluator const & rhs ) noexcept = delete; + + evaluator( std::initializer_list< field_base * > fields ) noexcept + { + result_visitor_.fields( fields ); } - evaluator& operator=(evaluator&& rhs) = default; - evaluator& operator=(evaluator const& rhs) = default; + evaluator& operator=( evaluator && rhs ) noexcept = default; + evaluator& operator=( evaluator const & rhs ) noexcept = delete; - ~evaluator() = default; + ~evaluator() noexcept = default; /** - * Sets the key - member function map used for evaluation of expression tree. + * Sets the fields used for evaluation of expression tree. * - * @param fields Key - member function map + * @param fields Fields to be used in evaluation process */ - void fields(field_map const& fields) noexcept { - result_visitor_.fields(fields); + void fields( std::initializer_list< field_base * > fields ) noexcept + { + result_visitor_.fields( fields ); } /** @@ -77,7 +82,8 @@ class evaluator { * * @return True if the evaluation is activated, otherwise false */ - [[nodiscard]] bool is_activated() const noexcept { + [[ nodiscard ]] bool is_activated() const noexcept + { return is_activated_; } @@ -88,7 +94,20 @@ class evaluator { * * @return True if the expression is valid, otherwise false */ - [[nodiscard]] bool expression(std::string_view expression); + [[ nodiscard ]] bool expression( std::string_view const expression ) noexcept + { + is_activated_ = false; + + if ( expression.empty() ) { return true; } + + root_ = tree::build( expression ); + if ( root_ != nullptr ) + { + is_activated_ = true; + } + + return is_activated_; + } /** * Evaluates expression tree for the object passed in. @@ -97,36 +116,25 @@ class evaluator { * * @return True if the object's members satisfy the expression, otherwise false */ - template - [[nodiscard]] bool evaluate(T const& obj) { - if (is_activated_) { - return result_visitor_.visit(*expression_tree_.root(), obj); - } else { - return false; + template< typename T > + [[ nodiscard ]] result evaluate( T && obj ) noexcept + { + if ( is_activated_ ) + { + return result_visitor_.visit( *root_, std::forward< T >( obj ) ); + } + else + { + return { false, "Evaluator not activated" }; } } private: - bool is_activated_{ false }; - tree::result_visitor result_visitor_; - tree::expression_tree expression_tree_; + bool is_activated_ { false }; + std::unique_ptr< tree::node > root_ { nullptr }; + tree::result_visitor result_visitor_{}; }; -template -bool evaluator::expression(std::string_view expression) { - is_activated_ = false; - - if (expression.empty()) { - return true; - } - - if (expression_tree_.build(expression)) { - is_activated_ = true; - } - - return is_activated_; -} - } // namespace booleval #endif // BOOLEVAL_EVALUATOR_HPP \ No newline at end of file diff --git a/include/booleval/field.hpp b/include/booleval/field.hpp new file mode 100644 index 0000000..2e6469f --- /dev/null +++ b/include/booleval/field.hpp @@ -0,0 +1,121 @@ +/* + * Copyright (c) 2021, Marin Peko + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#ifndef BOOLEVAL_FIELD_HPP +#define BOOLEVAL_FIELD_HPP + +#include +#include +#include + +namespace booleval +{ + +template< typename C > +struct field; + +/** + * @class field_base + * + * Represents a base class for the field class. + */ +struct field_base +{ + field_base() = default; + + field_base( field_base && rhs ) = default; + field_base( field_base const & rhs ) = default; + + field_base( std::string_view const name ) noexcept : name{ name } {} + + field_base & operator=( field_base && rhs ) = default; + field_base & operator=( field_base const & rhs ) = default; + + virtual ~field_base() = default; + + template< typename C > + utils::any_value invoke( C && obj ) noexcept + { + auto const * f{ dynamic_cast< field< std::remove_reference_t< C > >* >( this ) }; + + if ( f == nullptr ) { return {}; } + + return f->get( std::move( obj )); + } + + std::string_view name{}; +}; + +/** + * @class field + * + * Contains string representation of a certain class field and + * getter class member function associated to this field. + */ +template< typename C > +struct field : field_base +{ + field() = default; + + field( field && rhs ) = default; + field( field const & rhs ) = default; + + template< typename R > + field( std::string_view const name, R ( C::*m )() ) noexcept : field_base{ name } + { + get = [ m ]( C && obj ) + { + return ( obj.*m )(); + }; + } + + template< typename R > + field( std::string_view const name, R ( C::*m )() const ) noexcept : field_base{ name } + { + get = [ m ]( C && obj ) + { + return ( obj.*m )(); + }; + } + + field & operator=( field && rhs ) = default; + field & operator=( field const & rhs ) = default; + + std::function< utils::any_value( C && ) > get{ nullptr }; +}; + +template< typename C, typename R > +auto make_field( std::string_view const name, R( C::*m )() const ) noexcept +{ + return new field< C >( name, m ); +} + +} // namespace booleval + +#endif // BOOLEVAL_FIELD_HPP diff --git a/include/booleval/exceptions.hpp b/include/booleval/result.hpp similarity index 55% rename from include/booleval/exceptions.hpp rename to include/booleval/result.hpp index 43ff433..d9cc2e2 100644 --- a/include/booleval/exceptions.hpp +++ b/include/booleval/result.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, Marin Peko + * Copyright (c) 2019, Marin Peko * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -27,56 +27,32 @@ * */ -#ifndef BOOLEVAL_EXCEPTIONS_HPP -#define BOOLEVAL_EXCEPTIONS_HPP +#ifndef BOOLEVAL_RESULT_HPP +#define BOOLEVAL_RESULT_HPP -#include -#include +#include -namespace booleval { +namespace booleval +{ /** - * @class base_exception + * @struct result * - * Base class for all booleval exceptions. + * Represents a lightweight result of evaluation process. */ -struct base_exception : std::runtime_error { - base_exception() - : std::runtime_error(std::string{}) - {} - - base_exception(std::string const& message) - : std::runtime_error(message) - {} - - base_exception(char const* message) - : std::runtime_error(message) - {} -}; - -/** - * struct field_not_found - * - * Exception thrown when a field is not found. - */ -struct field_not_found : base_exception { - field_not_found() - : base_exception("Field not found") - {} - - field_not_found(std::string const& field) - : base_exception("Field '" + field + "' not found") - {} - - field_not_found(std::string_view field) - : base_exception("Field '" + std::string(field) + "' not found") - {} - - field_not_found(char const* field) - : base_exception("Field '" + std::string(field) + "' not found") - {} +struct result +{ + /** + * True if evaluation process is successful. Otherwise, false. + */ + bool success{ false }; + + /** + * Message in case of the fault. + */ + std::string_view message{}; }; } // namespace booleval -#endif // BOOLEVAL_EXCEPTIONS_HPP \ No newline at end of file +#endif // BOOLEVAL_RESULT_HPP \ No newline at end of file diff --git a/include/booleval/token/token.hpp b/include/booleval/token/token.hpp index 7967c43..6298697 100644 --- a/include/booleval/token/token.hpp +++ b/include/booleval/token/token.hpp @@ -33,56 +33,63 @@ #include #include #include + #include -#include +#include -namespace booleval::token { +namespace booleval::token +{ /** * @class token * * Represents all tokens ('and', 'or', 'eq', ...). */ -class token { +class token +{ public: - constexpr token() = default; - constexpr token(token&& rhs) = default; - constexpr token(token const& rhs) = default; + constexpr token() noexcept = default; + + constexpr token( token && rhs ) noexcept = default; + constexpr token( token const & rhs ) noexcept = default; - constexpr token(token_type const type) noexcept - : type_(type), - value_(map_to_token_value(type)) + constexpr token( token_type const type ) noexcept + : type_ ( type ) + , value_( to_token_keyword( type ) ) {} - constexpr token(std::string_view const value) noexcept - : type_(map_to_token_type(value)), - value_(value) + constexpr token( std::string_view const value ) noexcept + : type_ ( to_token_type( value ) ) + , value_( value ) {} - constexpr token(token_type const type, std::string_view const value) noexcept - : type_(type), - value_(value) + constexpr token( token_type const type, std::string_view const value ) noexcept + : type_ ( type ) + , value_( value ) {} - token& operator=(token&& rhs) = default; - token& operator=(token const& rhs) = default; + token & operator=( token && rhs ) noexcept = default; + token & operator=( token const & rhs ) noexcept = default; - [[nodiscard]] constexpr bool operator==(token const& rhs) const noexcept { + [[ nodiscard ]] constexpr bool operator==( token const & rhs ) const noexcept + { return type_ == rhs.type_ && value_ == rhs.value_; } - ~token() = default; + ~token() noexcept = default; /** * Sets the token type. * * @param type Token type */ - constexpr void type(token_type const type) noexcept { - if (type != type_) { - type_ = type; - value_ = map_to_token_value(type); + constexpr void type( token_type const type ) noexcept + { + if ( type != type_ ) + { + type_ = type; + value_ = to_token_keyword( type ); } } @@ -91,7 +98,8 @@ class token { * * @return Token type */ - constexpr token_type type() const noexcept { + [[ nodiscard ]] constexpr token_type type() const noexcept + { return type_; } @@ -100,8 +108,9 @@ class token { * * @param value Token value */ - constexpr void value(std::string_view const value) noexcept { - type_ = map_to_token_type(value); + constexpr void value( std::string_view const value ) noexcept + { + type_ = to_token_type( value ); value_ = value; } @@ -110,28 +119,18 @@ class token { * * @return Token value */ - [[nodiscard]] constexpr std::string_view value() const noexcept { + [[ nodiscard ]] constexpr std::string_view value() const noexcept + { return value_; } - /** - * Gets the token value as an arithmetic value. - * If value cannot be parsed, std::nullopt is returned. - * - * @return Optional token value - */ - template >> - [[nodiscard]] constexpr std::optional value() const noexcept { - return utils::from_chars(value_); - } - /** * Checks whether the token is of the specified type. * * @return True if the token is of the specified type, false otherwise */ - [[nodiscard]] constexpr bool is(token_type const type) const noexcept { + [[ nodiscard ]] constexpr bool is( token_type const type ) const noexcept + { return type_ == type; } @@ -140,7 +139,8 @@ class token { * * @return True if the token is not of the specified type, false otherwise */ - [[nodiscard]] constexpr bool is_not(token_type const type) const noexcept { + [[ nodiscard ]] constexpr bool is_not( token_type const type ) const noexcept + { return type_ != type; } @@ -149,8 +149,13 @@ class token { * * @return True if the token is of the first or second specified type, false otherwise */ - [[nodiscard]] constexpr bool is_one_of(token_type const first, token_type const second) const noexcept { - return is(first) || is(second); + [[ nodiscard ]] constexpr bool is_one_of + ( + token_type const first, + token_type const second + ) const noexcept + { + return is( first ) || is( second ); } /** @@ -158,16 +163,20 @@ class token { * * @return True if the token is one of the multiple specified types, false otherwise */ - template - [[nodiscard]] constexpr bool is_one_of(token_type const first, - token_type const second, - TokenType const ... nth) const noexcept { - return is(first) || is_one_of(second, nth...); + template< typename ... TokenType > + [[nodiscard]] constexpr bool is_one_of + ( + token_type const first, + token_type const second, + TokenType const ... nth + ) const noexcept + { + return is( first ) || is_one_of( second, nth ... ); } private: - token_type type_{ token_type::unknown }; - std::string_view value_; + token_type type_ { token_type::unknown }; + std::string_view value_{}; }; } // namespace booleval::token diff --git a/include/booleval/token/token_type.hpp b/include/booleval/token/token_type.hpp index 5bdb290..ab0d278 100644 --- a/include/booleval/token/token_type.hpp +++ b/include/booleval/token/token_type.hpp @@ -30,163 +30,54 @@ #ifndef BOOLEVAL_TOKEN_TYPE_HPP #define BOOLEVAL_TOKEN_TYPE_HPP -#include #include -#include -#include -#include -namespace booleval::token { +namespace booleval::token +{ /** * enum class token_type * - * Represents a token type. Supported types are logical operators, - * relational operators, parentheses and field. + * Represents a token type. Supported types are logical operators, relational operators, parentheses and field. */ -enum class [[nodiscard]] token_type : uint8_t { - unknown = 0, - field = 1, - - // Logical operators - logical_and = 2, - logical_or = 3, - - // Relational operators - eq = 4, - neq = 5, - gt = 6, - lt = 7, - geq = 8, - leq = 9, - - // Parentheses - lp = 10, - rp = 11 -}; +enum class [[ nodiscard ]] token_type : std::uint8_t +{ + // Unknown token type + unknown, -constexpr std::size_t count_of_keyword_expressions{ 16 }; -constexpr std::array< - std::pair, - count_of_keyword_expressions -> keyword_expressions = {{ - { "and", token_type::logical_and }, - { "AND", token_type::logical_and }, - { "or", token_type::logical_or }, - { "OR", token_type::logical_or }, - { "eq", token_type::eq }, - { "EQ", token_type::eq }, - { "neq", token_type::neq }, - { "NEQ", token_type::neq }, - { "gt", token_type::gt }, - { "GT", token_type::gt }, - { "lt", token_type::lt }, - { "LT", token_type::lt }, - { "geq", token_type::geq }, - { "GEQ", token_type::geq }, - { "leq", token_type::leq }, - { "LEQ", token_type::leq } -}}; - -constexpr std::size_t count_of_symbol_expressions{ 10 }; -constexpr std::array< - std::pair, - count_of_symbol_expressions -> symbol_expressions = {{ - { "&&", token_type::logical_and }, - { "||", token_type::logical_or }, - { "==", token_type::eq }, - { "!=", token_type::neq }, - { ">", token_type::gt }, - { "<", token_type::lt }, - { ">=", token_type::geq }, - { "<=", token_type::leq }, - { "(", token_type::lp }, - { ")", token_type::rp } -}}; + // Field key token type + field, -/** - * Filters parenthesis symbol expressions from all symbol expressions. - * - * @return Parenthesis symbol expressions - */ -constexpr auto parenthesis_symbol_expressions() { - constexpr auto count = utils::count_if( - std::begin(symbol_expressions), - std::end(symbol_expressions), - [](auto&& p) { - return token_type::lp == p.second || token_type::rp == p.second; - } - ); - - std::size_t i{ 0 }; - std::array parenthesis_symbols{}; - for (auto const& p : symbol_expressions) { - if (token_type::lp == p.second || token_type::rp == p.second) { - parenthesis_symbols[i++] = p.first.front(); - } - } - - return parenthesis_symbols; -} + // Logical AND operator token type + logical_and, -/** - * Maps token value to token type. - * - * @param value Token value - * - * @return Token type - */ -constexpr token_type map_to_token_type(std::string_view const value) { - auto keyword_search = utils::find_if( - std::begin(keyword_expressions), - std::end(keyword_expressions), - [value](auto&& p) { - return p.first == value; - } - ); - - if (std::end(keyword_expressions) != keyword_search) { - return keyword_search->second; - } - - auto symbol_search = utils::find_if( - std::begin(symbol_expressions), - std::end(symbol_expressions), - [value](auto&& p) { - return p.first == value; - } - ); - - if (std::end(symbol_expressions) != symbol_search) { - return symbol_search->second; - } - - return token_type::field; -} + // Logical OR operator token type + logical_or, -/** - * Maps token type to token value. - * - * @param type Token type - * - * @return Token value - */ -[[nodiscard]] constexpr std::string_view map_to_token_value(token_type const type) { - auto keyword_search = utils::find_if( - std::begin(keyword_expressions), - std::end(keyword_expressions), - [type](auto&& p) { - return p.second == type; - } - ); - - if (std::end(keyword_expressions) != keyword_search) { - return keyword_search->first; - } - - return {}; -} + // 'Equal to' relational operator token type + eq, + + // 'Not equal to' relational operator token type + neq, + + // 'Greater than' relational operator token type + gt, + + // 'Less than' relational operator token type + lt, + + // 'Greater than or equal to' relational operator token type + geq, + + // 'Less than or equal to' relational operator token type + leq, + + // Left parenthesis token type + lp, + + // Right parenthesis token type + rp +}; } // namespace booleval::token diff --git a/include/booleval/token/token_type_utils.hpp b/include/booleval/token/token_type_utils.hpp new file mode 100644 index 0000000..81507bd --- /dev/null +++ b/include/booleval/token/token_type_utils.hpp @@ -0,0 +1,205 @@ +/* + * Copyright (c) 2021, Marin Peko + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#ifndef BOOLEVAL_TOKEN_TYPE_UTILS_HPP +#define BOOLEVAL_TOKEN_TYPE_UTILS_HPP + +#include +#include +#include +#include + +#include +#include + +namespace booleval::token +{ + +namespace internal +{ + + using token_type_pair = std::pair< std::string_view, token_type >; + + constexpr inline std::array keywords + { + token_type_pair{ "and", token_type::logical_and }, + token_type_pair{ "AND", token_type::logical_and }, + token_type_pair{ "or" , token_type::logical_or }, + token_type_pair{ "OR" , token_type::logical_or }, + token_type_pair{ "eq" , token_type::eq }, + token_type_pair{ "EQ" , token_type::eq }, + token_type_pair{ "neq", token_type::neq }, + token_type_pair{ "NEQ", token_type::neq }, + token_type_pair{ "gt" , token_type::gt }, + token_type_pair{ "GT" , token_type::gt }, + token_type_pair{ "lt" , token_type::lt }, + token_type_pair{ "LT" , token_type::lt }, + token_type_pair{ "geq", token_type::geq }, + token_type_pair{ "GEQ", token_type::geq }, + token_type_pair{ "leq", token_type::leq }, + token_type_pair{ "LEQ", token_type::leq } + }; + + constexpr inline std::array symbols + { + token_type_pair{ "&&", token_type::logical_and }, + token_type_pair{ "||", token_type::logical_or }, + token_type_pair{ "==", token_type::eq }, + token_type_pair{ "!=", token_type::neq }, + token_type_pair{ ">" , token_type::gt }, + token_type_pair{ "<" , token_type::lt }, + token_type_pair{ ">=", token_type::geq }, + token_type_pair{ "<=", token_type::leq }, + token_type_pair{ "(" , token_type::lp }, + token_type_pair{ ")" , token_type::rp } + }; + +} // namespace internal + +/** + * Gets parentheses symbols out of all symbols. + * + * @return Parentheses symbols + */ +[[ nodiscard ]] constexpr auto get_parentheses_symbols() noexcept +{ + auto is_parenthesis + { + []( auto && symbol ) noexcept + { + return symbol.second == token_type::lp || + symbol.second == token_type::rp; + } + }; + + constexpr auto count + { + utils::count_if + ( + std::cbegin( internal::symbols ), + std::cend ( internal::symbols ), + is_parenthesis + ) + }; + + auto i{ 0u }; + std::array< char, count > parentheses_symbols{}; + + for ( auto && symbol : internal::symbols ) + { + if ( is_parenthesis( symbol ) ) + { + assert( std::size( symbol.first ) == 1 && "Parenthesis symbol must have only 1 character." ); + assert( i < count && "Index out of scope." ); + parentheses_symbols[ i++ ] = symbol.first.front(); + } + } + + return parentheses_symbols; +} + +/** + * Maps token value to token type. + * + * @param value Token value + * + * @return Token type + */ +constexpr token_type to_token_type( std::string_view const value ) noexcept +{ + auto find_matching + { + [ value ]( auto const & collection ) noexcept + { + return utils::find_if + ( + std::cbegin( collection ), + std::cend ( collection ), + [ value ]( auto && item ) + { + return item.first == value; + } + ); + } + }; + + if + ( + auto const keyword{ find_matching( internal::keywords ) }; + keyword != std::cend( internal::keywords ) + ) + { + return keyword->second; + } + + if + ( + auto const symbol{ find_matching( internal::symbols ) }; + symbol != std::cend( internal::symbols ) + ) + { + return symbol->second; + } + + return token_type::field; +} + +/** + * Maps token type to token keyword. + * + * @param type Token type + * + * @return Token keyword + */ +[[ nodiscard ]] constexpr std::string_view to_token_keyword( token_type const type ) noexcept +{ + auto const keyword + { + utils::find_if + ( + std::cbegin( internal::keywords ), + std::cend ( internal::keywords ), + [ type ]( auto && item ) noexcept + { + return item.second == type; + } + ) + }; + + if ( keyword != std::cend( internal::keywords ) ) + { + return keyword->first; + } + + return {}; +} + +} // namespace booleval::token + +#endif // BOOLEVAL_TOKEN_TYPE_HPP \ No newline at end of file diff --git a/include/booleval/token/tokenizer.hpp b/include/booleval/token/tokenizer.hpp index b440c3f..dfa5614 100644 --- a/include/booleval/token/tokenizer.hpp +++ b/include/booleval/token/tokenizer.hpp @@ -31,84 +31,63 @@ #define BOOLEVAL_TOKENIZER_HPP #include +#include #include + #include +#include +#include +#include -namespace booleval::token { +namespace booleval::token +{ /** - * @class tokenizer - * - * Represents the mechanism for tokenizing the expressions, i.e. transforming - * the expressions from a form of a string to the collection of tokens. + * Tokenizes given expression, i.e. transforms given expression + * from string to the collection of token objects. */ -class tokenizer { -public: - tokenizer() = default; - tokenizer(tokenizer&& rhs) = default; - tokenizer(tokenizer const& rhs) = default; - tokenizer(std::string_view expression) noexcept; - - tokenizer& operator=(tokenizer&& rhs) = default; - tokenizer& operator=(tokenizer const& rhs) = default; - - ~tokenizer() = default; - - /** - * Sets the expression that needs to be tokenized. - * - * @param expression Expression to be tokenized - */ - void expression(std::string_view expression) noexcept; - - /** - * Gets the expression that needs to be tokenized. - * - * @return Expression to be tokenized - */ - [[nodiscard]] std::string_view expression() const noexcept; - - /** - * Checks whether more tokens exist or not. - * - * @return True if there is more tokens, otherwise false - */ - [[nodiscard]] bool has_tokens() const noexcept; - - /** - * Passes the token by incrementing the current token index. - */ - void pass_token() noexcept; - - /** - * Gets the next token and increments the current token index. - * - * @return Next token - */ - [[nodiscard]] token const& next_token(); - - /** - * Gets the next token without incrementing the current token index. - * - * @return Next token - */ - [[nodiscard]] token const& weak_next_token(); - - /** - * Tokenizes the expression and transforms it into the collection of tokens. - */ - void tokenize(); - - /** - * Clears the collection of tokens and sets the current index to zero. - */ - void reset() noexcept; - -private: - std::string_view expression_; - std::size_t current_token_index_{ 0 }; - std::vector tokens_; -}; +inline std::vector< token > tokenize( std::string_view const expression ) noexcept +{ + std::vector< token > result; + + constexpr auto parentheses_symbols{ get_parentheses_symbols() }; + constexpr auto split_options + { + utils::split_options::include_delimiters | + utils::split_options::split_by_whitespace | + utils::split_options::allow_quoted_strings + }; + + auto const delimiters + { + utils::join + ( + std::cbegin( parentheses_symbols ), + std::cend ( parentheses_symbols ) + ) + }; + + auto const tokens_range{ utils::split_range< split_options >( expression, delimiters ) }; + + for ( auto const [ is_quoted, value ] : tokens_range ) + { + if ( utils::is_whitespace( value ) ) { continue; } + + auto const type{ is_quoted ? token_type::field : to_token_type( value ) }; + + if ( type == token_type::field ) + { + if ( !result.empty() && result.back().is( token_type::field ) ) + { + result.emplace_back( token_type::eq, to_token_keyword( token_type::eq ) ); + } + } + + result.emplace_back( type, value ); + } + + return result; +} } // namespace booleval::token diff --git a/include/booleval/tree/expression_tree.hpp b/include/booleval/tree/expression_tree.hpp deleted file mode 100644 index f0f7221..0000000 --- a/include/booleval/tree/expression_tree.hpp +++ /dev/null @@ -1,116 +0,0 @@ -/* - * Copyright (c) 2020, Marin Peko - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above - * copyright notice, this list of conditions and the following disclaimer - * in the documentation and/or other materials provided with the - * distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * - */ - -#ifndef BOOLEVAL_EXPRESSION_TREE_HPP -#define BOOLEVAL_EXPRESSION_TREE_HPP - -#include -#include -#include -#include - -namespace booleval::tree { - -/** - * @class expression_tree - * - * Represents a class for building an expression tree by using a recursive - * descent parser method. - */ -class expression_tree { -public: - expression_tree() = default; - expression_tree(expression_tree&& rhs) = default; - expression_tree(expression_tree const& rhs) = default; - - expression_tree& operator=(expression_tree&& rhs) = default; - expression_tree& operator=(expression_tree const& rhs) = default; - - ~expression_tree() = default; - - /** - * Gets the root tree node. - * - * @return Root tree node - */ - [[nodiscard]] std::shared_ptr root() noexcept; - - /** - * Builds the expression tree. - * - * @param expression Expression to build the tree for - * - * @return True if the expression tree is built successfully, otherwise false - */ - [[nodiscard]] bool build(std::string_view expression); - -private: - /** - * Parses root expression by trying first to parse logical operation OR. - * - * @return Root tree node for the current part of the expression - */ - [[nodiscard]] std::shared_ptr parse_expression(); - - /** - * Parses logical operation AND. - * - * @return Root tree node for the parsed logical operation - */ - [[nodiscard]] std::shared_ptr parse_and_operation(); - - /** - * Parses new expression within parentheses. - * - * @return Root tree node for the parsed expression within parentheses - */ - [[nodiscard]] std::shared_ptr parse_parentheses(); - - /** - * Parses relational operation (EQ, NEQ, GT, LT, GEQ and LEQ). - * - * @return Root tree node for the parsed relational operation - */ - [[nodiscard]] std::shared_ptr parse_relational_operation(); - - /** - * Parses terminal. - * - * @return Leaf node - */ - [[nodiscard]] std::shared_ptr parse_terminal(); - -private: - token::tokenizer tokenizer_; - std::shared_ptr root_; -}; - -} // namespace booleval::tree - -#endif // BOOLEVAL_EXPRESSION_TREE_HPP \ No newline at end of file diff --git a/include/booleval/tree/tree_node.hpp b/include/booleval/tree/node.hpp similarity index 71% rename from include/booleval/tree/tree_node.hpp rename to include/booleval/tree/node.hpp index 4686332..30053fd 100644 --- a/include/booleval/tree/tree_node.hpp +++ b/include/booleval/tree/node.hpp @@ -27,45 +27,44 @@ * */ -#ifndef BOOLEVAL_TREE_NODE_HPP -#define BOOLEVAL_TREE_NODE_HPP +#ifndef BOOLEVAL_NODE_HPP +#define BOOLEVAL_NODE_HPP #include + #include #include -namespace booleval::tree { +namespace booleval::tree +{ /** - * struct tree_node + * struct node * * Represents the tree node containing references to left and right child nodes * as well as the token that the node represents in the actual expression tree. */ -struct tree_node { +struct node +{ token::token token{ token::token_type::unknown }; - std::shared_ptr left; - std::shared_ptr right; - constexpr tree_node() = default; + std::unique_ptr< node > left { nullptr }; + std::unique_ptr< node > right{ nullptr }; - tree_node(tree_node&& rhs) = default; - tree_node(tree_node const& rhs) = default; + constexpr node() noexcept = default; - constexpr tree_node(token::token_type const type) - : token(type) - {} + node( node && rhs ) noexcept = default; + node( node const & rhs ) noexcept = delete; - constexpr tree_node(token::token const& token) - : token(token) - {} + constexpr node( token::token_type const type ) noexcept : token( type ) {} + constexpr node( token::token const & token ) noexcept : token( token ) {} - tree_node& operator=(tree_node&& rhs) = default; - tree_node& operator=(tree_node const& rhs) = default; + node & operator=( node && rhs ) noexcept = default; + node & operator=( node const & rhs ) noexcept = delete; - ~tree_node() = default; + ~node() noexcept = default; }; } // namespace booleval::tree -#endif // BOOLEVAL_TREE_NODE_HPP \ No newline at end of file +#endif // BOOLEVAL_NODE_HPP \ No newline at end of file diff --git a/include/booleval/tree/result_visitor.hpp b/include/booleval/tree/result_visitor.hpp index 0080bef..e25cc14 100644 --- a/include/booleval/tree/result_visitor.hpp +++ b/include/booleval/tree/result_visitor.hpp @@ -30,14 +30,19 @@ #ifndef BOOLEVAL_RESULT_VISITOR_HPP #define BOOLEVAL_RESULT_VISITOR_HPP -#include +#include +#include #include #include -#include -#include -#include -namespace booleval::tree { +#include +#include +#include + +namespace booleval::tree +{ + +using namespace std::string_literals; /** * @class result_visitor @@ -45,27 +50,17 @@ namespace booleval::tree { * Represents a visitor for expression tree nodes in order to get the * final result of the expression based on the fields of an object being passed. */ -template -class result_visitor { - using field_map = std::map; - +class result_visitor +{ public: - result_visitor() = default; - result_visitor(result_visitor&& rhs) = default; - result_visitor(result_visitor const& rhs) = default; - - result_visitor& operator=(result_visitor&& rhs) = default; - result_visitor& operator=(result_visitor const& rhs) = default; - - ~result_visitor() = default; - /** - * Sets the key - member function map used for evaluation of expression tree. + * Sets the fields used for evaluation of expression tree. * - * @param fields Key - member function map + * @param fields Fields to be used in evaluation process */ - void fields(field_map const& fields) noexcept { - fields_ = fields; + void fields( std::initializer_list< field_base * > fields ) noexcept + { + fields_ = std::vector< std::unique_ptr< field_base > >{ std::begin( fields ), std::end( fields ) }; } /** @@ -74,13 +69,12 @@ class result_visitor { * * @param node Currently visited tree node * - * @return ReturnType + * @return Result */ - template - [[nodiscard]] constexpr bool visit(tree_node const& node, T const& obj); + template< typename T > + [[ nodiscard ]] constexpr result visit( node const & node, T && obj ) const noexcept; private: - /** * Visits tree node representing one of logical operations. * @@ -88,11 +82,21 @@ class result_visitor { * @param obj Object to be evaluated * @param func Logical operation function * - * @return Result of logical operation + * @return Result */ - template - [[nodiscard]] constexpr bool visit_logical(tree_node const& node, T const& obj, F&& func) { - return func(visit(*node.left, obj), visit(*node.right, obj)); + template< typename T, typename F > + [[ nodiscard ]] constexpr result visit_logical( node const & node, T && obj, F && f ) const noexcept + { + auto const left { visit( *node.left , std::forward< T >( obj ) ) }; + auto const right{ visit( *node.right, std::forward< T >( obj ) ) }; + + // always pick the error message closer to the beginning of the expression + auto const message + { + left.message.empty() ? right.message : left.message + }; + + return { f( left.success, right.success ), message }; } /** @@ -102,62 +106,71 @@ class result_visitor { * @param obj Object to be evaluated * @param func Comparison function * - * @return Result of relational operation + * @return Result */ - template - [[nodiscard]] constexpr bool visit_relational(tree_node const& node, T const& obj, F&& func) { - auto key = node.left->token; - - auto iter = fields_.find(key.value()); - if (iter == fields_.end()) { - throw field_not_found(key.value()); + template< typename T, typename F > + [[ nodiscard ]] constexpr result visit_relational( node const & node, T && obj, F && f ) const noexcept + { + auto const key{ node.left->token }; + + auto const it + { + std::find_if + ( + std::cbegin( fields_ ), + std::cend ( fields_ ), + [ key ]( auto && field ) noexcept + { + return field->name == key.value(); + } + ) + }; + + if ( it == std::end( fields_ ) ) + { + return { false, "Unknown field" }; } - auto value = node.right->token; - return func(iter->second.invoke(obj), value.value()); + auto const success + { + f + ( + ( *it )->invoke( std::forward< T >( obj ) ), + node.right->token.value() + ) + }; + + return { success }; } private: - field_map fields_; + std::vector< std::unique_ptr< field_base > > fields_; }; -template -template -constexpr bool result_visitor::visit(tree_node const& node, T const& obj) { - if (nullptr == node.left || nullptr == node.right) { - return false; +template< typename T > +constexpr result result_visitor::visit( node const & node, T && obj ) const noexcept +{ + if ( nullptr == node.left || nullptr == node.right ) + { + return { false, "Missing operand" }; } - switch (node.token.type()) { - case token::token_type::logical_and: - return visit_logical(node, obj, std::logical_and<>()); - - case token::token_type::logical_or: - return visit_logical(node, obj, std::logical_or<>()); - - case token::token_type::eq: - return visit_relational(node, obj, std::equal_to<>()); - - case token::token_type::neq: - return visit_relational(node, obj, std::not_equal_to<>()); - - case token::token_type::gt: - return visit_relational(node, obj, std::greater<>()); - - case token::token_type::lt: - return visit_relational(node, obj, std::less<>()); - - case token::token_type::geq: - return visit_relational(node, obj, std::greater_equal<>()); - - case token::token_type::leq: - return visit_relational(node, obj, std::less_equal<>()); - - default: - return false; + switch ( node.token.type() ) + { + case token::token_type::logical_and: return visit_logical ( node, std::forward< T >( obj ), std::logical_and<>() ); + case token::token_type::logical_or : return visit_logical ( node, std::forward< T >( obj ), std::logical_or<>() ); + case token::token_type::eq : return visit_relational( node, std::forward< T >( obj ), std::equal_to<>() ); + case token::token_type::neq : return visit_relational( node, std::forward< T >( obj ), std::not_equal_to<>() ); + case token::token_type::gt : return visit_relational( node, std::forward< T >( obj ), std::greater<>() ); + case token::token_type::lt : return visit_relational( node, std::forward< T >( obj ), std::less<>() ); + case token::token_type::geq : return visit_relational( node, std::forward< T >( obj ), std::greater_equal<>() ); + case token::token_type::leq : return visit_relational( node, std::forward< T >( obj ), std::less_equal<>() ); + + default: + return { false, "Unknown token type" }; } } } // namespace booleval::tree -#endif // BOOLEVAL_RESULT_VISITOR_HPP \ No newline at end of file +#endif // BOOLEVAL_RESULT_VISITOR_HPP diff --git a/include/booleval/tree/tree.hpp b/include/booleval/tree/tree.hpp new file mode 100644 index 0000000..0609afd --- /dev/null +++ b/include/booleval/tree/tree.hpp @@ -0,0 +1,208 @@ +/* + * Copyright (c) 2020, Marin Peko + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#ifndef BOOLEVAL_TREE_HPP +#define BOOLEVAL_TREE_HPP + +#include +#include +#include + +#include +#include + +namespace booleval::tree +{ + +namespace internal +{ + + using tokens = std::vector< token::token >; + + // Forward declarations + + std::unique_ptr< node > parse_expression ( tokens const & tokens, std::size_t & current ); + std::unique_ptr< node > parse_and_operation ( tokens const & tokens, std::size_t & current ); + std::unique_ptr< node > parse_parentheses ( tokens const & tokens, std::size_t & current ); + std::unique_ptr< node > parse_relational_operation( tokens const & tokens, std::size_t & current ); + std::unique_ptr< node > parse_terminal ( tokens const & tokens, std::size_t & current ); + + // Definitions + + inline bool has_unused( tokens const & tokens, std::size_t const current ) noexcept + { + return current < std::size( tokens ); + } + + inline std::unique_ptr< node > parse_expression( tokens const & tokens, std::size_t & current ) + { + auto left{ parse_and_operation( tokens, current ) }; + + auto const is_relational_operator + { + has_unused( tokens, current ) && + tokens[ current ].is_one_of + ( + token::token_type::eq, + token::token_type::neq, + token::token_type::gt, + token::token_type::lt, + token::token_type::geq, + token::token_type::leq + ) + }; + + if ( is_relational_operator ) + { + return nullptr; + } + + if ( has_unused( tokens, current ) && tokens[ current ].is_not( token::token_type::logical_or ) ) + { + return left; + } + + while ( has_unused( tokens, current ) && tokens[ current ].is( token::token_type::logical_or ) ) + { + ++current; + auto logical_or{ std::make_unique< node >( token::token_type::logical_or ) }; + + auto right{ parse_and_operation( tokens, current ) }; + if ( right == nullptr ) { return nullptr; } + + logical_or->left = std::move( left ); + logical_or->right = std::move( right ); + left = std::move( logical_or ); + } + + return left; + } + + inline std::unique_ptr< node > parse_and_operation( tokens const & tokens, std::size_t & current ) + { + auto left{ parse_parentheses( tokens, current ) }; + if ( left == nullptr ) + { + left = parse_relational_operation( tokens, current ); + } + + if ( left == nullptr ) { return nullptr; } + + while ( has_unused( tokens, current ) && tokens[ current ].is( token::token_type::logical_and ) ) + { + ++current; + + auto logical_and{ std::make_unique< node >( token::token_type::logical_and ) }; + + auto right{ parse_parentheses( tokens, current ) }; + if ( right == nullptr ) + { + right = parse_relational_operation( tokens, current ); + } + + if ( right == nullptr ) { return nullptr; } + + logical_and->left = std::move( left ); + logical_and->right = std::move( right ); + left = std::move( logical_and ); + } + + return left; + } + + inline std::unique_ptr< node > parse_parentheses( tokens const & tokens, std::size_t & current ) + { + if ( !has_unused( tokens, current ) ) { return nullptr; } + + if ( tokens[ current ].is( token::token_type::lp ) ) + { + ++current; + auto expression{ parse_expression( tokens, current ) }; + if ( !has_unused( tokens, current ) ) { return nullptr; } + + if ( tokens[ current++ ].is( token::token_type::rp ) ) + { + return expression; + } + } + + return nullptr; + } + + inline std::unique_ptr< node > parse_relational_operation( tokens const & tokens, std::size_t & current ) + { + auto left{ parse_terminal( tokens, current ) }; + if ( left == nullptr ) { return nullptr; } + + if ( !has_unused( tokens, current ) ) { return nullptr; } + + auto operation{ std::make_unique< node >( tokens[ current++ ] ) }; + + auto right{ parse_terminal( tokens, current ) }; + if ( right == nullptr ) { return nullptr; } + + operation->left = std::move( left ); + operation->right = std::move( right ); + + return operation; + } + + inline std::unique_ptr< node > parse_terminal( tokens const & tokens, std::size_t & current ) + { + if ( !has_unused( tokens, current ) ) { return nullptr; } + + auto const & token{ tokens[ current++ ] }; + if ( token.is( token::token_type::field ) ) + { + return std::make_unique< node >( token ); + } + else + { + return nullptr; + } + } + +} // namespace internal + +/** + * Builds an expression tree by using a recursive descent parser method. + */ +std::unique_ptr< node > build( std::string_view expression ) +{ + auto const tokens{ token::tokenize( expression ) }; + if ( tokens.empty() ) { return nullptr; } + + std::size_t current{ 0u }; + + return internal::parse_expression( tokens, current ); +} + +} // namespace booleval::tree + +#endif // BOOLEVAL_TREE_HPP \ No newline at end of file diff --git a/include/booleval/utils/algo_utils.hpp b/include/booleval/utils/algo_utils.hpp index 87003a4..ca1a146 100644 --- a/include/booleval/utils/algo_utils.hpp +++ b/include/booleval/utils/algo_utils.hpp @@ -27,31 +27,11 @@ * */ -#ifndef ALGO_UTILS_HPP -#define ALGO_UTILS_HPP +#ifndef BOOLEVAL_ALGORITHM_HPP +#define BOOLEVAL_ALGORITHM_HPP -namespace booleval::utils { - -/** - * Finds the first element in the range [first, last) that - * is equal to the specified value. - * - * @param first Beginning of the range - * @param last End of the range - * @param value Value to compare the elements to - * - * @return Iterator to the first element satisfying the condition or - * last if no such element is found. - */ -template -constexpr InputIt find(InputIt first, InputIt last, T const& value) { - for (; first != last; ++first) { - if (*first == value) { - return first; - } - } - return last; -} +namespace booleval::utils +{ /** * Finds the first element in the range [first, last) for @@ -64,59 +44,16 @@ constexpr InputIt find(InputIt first, InputIt last, T const& value) { * @return Iterator to the first element satisfying the condition or * last if no such element is found. */ -template -constexpr InputIt find_if(InputIt first, InputIt last, UnaryPredicate p) { - for (; first != last; ++first) { - if (p(*first)) { - return first; - } - } - return last; -} - -/** - * Finds the first element in the range [first, last) for - * which the predicate returns false. - * - * @param first Beginning of the range - * @param last End of the range - * @param p Unary predicate which returns false for the required element - * - * @return Iterator to the first element satisfying the condition or - * last if no such element is found. - */ -template -constexpr InputIt find_if_not(InputIt first, InputIt last, UnaryPredicate p) { - for (; first != last; ++first) { - if (!p(*first)) { - return first; - } +template< typename InputIt, typename UnaryPredicate > +[[ nodiscard ]] constexpr InputIt find_if( InputIt first, InputIt last, UnaryPredicate p ) noexcept +{ + for ( ; first != last; ++first ) + { + if ( p( *first ) ) { return first; } } return last; } -/** - * Counts the elements in the range [first, last) that - * are equal to the value. - * - * @param first Beginning of the range - * @param last End of the range - * @param value Value to compare the elements to - * - * @return Number of elements equal to the value. - */ -template -constexpr typename std::iterator_traits::difference_type -count(InputIt first, InputIt last, T const& value) { - typename std::iterator_traits::difference_type result{ 0 }; - for (; first != last; ++first) { - if (*first == value) { - result++; - } - } - return result; -} - /** * Counts the elements in the range [first, last) for * which the predicate returns true. @@ -127,18 +64,17 @@ count(InputIt first, InputIt last, T const& value) { * * @return Number of elements satisfying the condition. */ -template -constexpr typename std::iterator_traits::difference_type -count_if(InputIt first, InputIt last, UnaryPredicate p) { +template< typename InputIt, typename UnaryPredicate > +[[ nodiscard ]] constexpr auto count_if( InputIt first, InputIt last, UnaryPredicate p ) noexcept +{ typename std::iterator_traits::difference_type result{ 0 }; - for (; first != last; ++first) { - if (p(*first)) { - result++; - } + for ( ; first != last; ++first ) + { + if ( p( *first ) ) { result++; } } return result; } } // namespace booleval::utils -#endif // ALGO_UTILS_HPP \ No newline at end of file +#endif // BOOLEVAL_ALGORITHM_HPP \ No newline at end of file diff --git a/include/booleval/utils/any_mem_fn.hpp b/include/booleval/utils/any_mem_fn.hpp deleted file mode 100644 index 3c2701e..0000000 --- a/include/booleval/utils/any_mem_fn.hpp +++ /dev/null @@ -1,134 +0,0 @@ -/* - * Copyright (c) 2020, Marin Peko - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above - * copyright notice, this list of conditions and the following disclaimer - * in the documentation and/or other materials provided with the - * distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * - */ - -#ifndef BOOLEVAL_ANY_MEM_FN_HPP -#define BOOLEVAL_ANY_MEM_FN_HPP - -#include -#include -#include - -namespace booleval::utils { - -/** - * @class any_mem_fn - * - * Represents class member function of any signature. - */ -class any_mem_fn { -public: - any_mem_fn() = default; - any_mem_fn(any_mem_fn&& rhs) = default; - any_mem_fn(any_mem_fn const& rhs) = default; - - template - any_mem_fn(Ret (C::*m)()) { - fn_ = [m](std::any a) { - return (std::any_cast(a).*m)(); - }; - } - - template - any_mem_fn(Ret (C::*m)() const) { - fn_ = [m](std::any a) { - return (std::any_cast(a).*m)(); - }; - } - - any_mem_fn& operator=(any_mem_fn&& rhs) = default; - any_mem_fn& operator=(any_mem_fn const& rhs) = default; - - ~any_mem_fn() = default; - - template - any_value invoke(T&& obj) { - try { - return fn_(std::forward(obj)); - } catch (std::bad_any_cast const&) { - return {}; - } - } - -private: - std::function fn_; -}; - -/** - * @class any_mem_fn_bool - * - * Represents class member function returning any signature taking a bool& - * is_valid parameter. If is_valid is set to false, the evaluation will fail. - */ -class any_mem_fn_bool -{ -public: - any_mem_fn_bool() = default; - any_mem_fn_bool(any_mem_fn_bool&& rhs) = default; - any_mem_fn_bool(any_mem_fn_bool const& rhs) = default; - - template - any_mem_fn_bool(Ret(C::* m)(bool&)) { - fn_ = [m](std::any a, bool& is_valid) { - return (std::any_cast(a).*m)(is_valid); - }; - } - - template - any_mem_fn_bool(Ret(C::* m)(bool&) const) { - fn_ = [m](std::any a, bool& is_valid) { - return (std::any_cast(a).*m)(is_valid); - }; - } - - any_mem_fn_bool& operator=(any_mem_fn_bool&& rhs) = default; - any_mem_fn_bool& operator=(any_mem_fn_bool const& rhs) = default; - - ~any_mem_fn_bool() = default; - - template - any_value invoke(T&& obj) { - try { - bool is_valid = false; - auto ret = fn_(std::forward(obj), is_valid); - if (is_valid) { - return ret; - } - return {}; - } catch (std::bad_any_cast const&) { - return {}; - } - } - -private: - std::function fn_; -}; - -} // namespace booleval::utils - -#endif // BOOLEVAL_ANY_MEM_FN_HPP diff --git a/include/booleval/utils/any_value.hpp b/include/booleval/utils/any_value.hpp index a5334e4..1a3e6ad 100644 --- a/include/booleval/utils/any_value.hpp +++ b/include/booleval/utils/any_value.hpp @@ -34,7 +34,8 @@ #include #include -namespace booleval::utils { +namespace booleval::utils +{ /** * @class any_value @@ -42,260 +43,114 @@ namespace booleval::utils { * Represents the class that accepts any type of value through its constructor * or assignment operator and internally stores its string version. */ -class any_value { +class any_value +{ public: any_value() = default; - any_value(any_value&& rhs) = default; - any_value(any_value const& rhs) = default; - template >* = nullptr> - any_value(T const rhs); - - template >* = nullptr> - any_value(T const rhs); - - any_value& operator=(any_value&& rhs) = default; - any_value& operator=(any_value const& rhs) = default; - - template >* = nullptr> - any_value& operator=(T const rhs); - - template >* = nullptr> - any_value& operator=(T const rhs); - - template >* = nullptr> - [[nodiscard]] bool operator==(T const rhs); - - template >* = nullptr> - [[nodiscard]] bool operator==(T const rhs); - - template >* = nullptr> - [[nodiscard]] bool operator!=(T const rhs); - - template >* = nullptr> - [[nodiscard]] bool operator!=(T const rhs); - - template >* = nullptr> - [[nodiscard]] bool operator>(T const rhs); - - template >* = nullptr> - [[nodiscard]] bool operator>(T const rhs); - - template >* = nullptr> - [[nodiscard]] bool operator<(T const rhs); - - template >* = nullptr> - [[nodiscard]] bool operator<(T const rhs); - - template >* = nullptr> - [[nodiscard]] bool operator>=(T const rhs); - - template >* = nullptr> - [[nodiscard]] bool operator>=(T const rhs); - - template >* = nullptr> - [[nodiscard]] bool operator<=(T const rhs); - - template >* = nullptr> - [[nodiscard]] bool operator<=(T const rhs); - - ~any_value() = default; - - [[nodiscard]] std::string const& str() const noexcept { - return value_; + any_value( any_value && rhs ) = default; + any_value( any_value const & rhs ) = default; + + template< typename T > + any_value( T && rhs ) noexcept + { + if constexpr ( std::is_arithmetic_v< std::remove_reference_t< T > > ) + { + value_ = utils::to_chars< std::remove_reference_t< T > >( std::forward< T >( rhs ) ); + use_string_comparison_ = false; + } + else if constexpr ( std::is_constructible_v< std::string, std::remove_reference_t< T > > ) + { + value_ = std::forward< T >( rhs ); + use_string_comparison_ = true; + } } - friend bool operator==(any_value const& lhs, any_value const& rhs); - friend bool operator!=(any_value const& lhs, any_value const& rhs); - -private: - std::string value_; - bool use_string_comparison_{ false }; -}; - -template >*> -any_value::any_value(T const rhs) - : value_(utils::to_chars(rhs)), - use_string_comparison_(false) -{} - -template >*> -any_value::any_value(T const rhs) - : value_(rhs), - use_string_comparison_(true) -{} - -template >*> -any_value& any_value::operator=(T const rhs) { - value_ = utils::to_chars(rhs); - use_string_comparison_ = false; - return *this; -} - -template >*> -any_value& any_value::operator=(T const rhs) { - value_ = rhs; - use_string_comparison_ = true; - return *this; -} - -template >*> -bool any_value::operator==(T const rhs) { - return value_ == utils::to_chars(rhs); -} - -template >*> -bool any_value::operator==(T const rhs) { - return value_ == rhs; -} - -template >*> -bool any_value::operator!=(T const rhs) { - return value_ != utils::to_chars(rhs); -} - -template >*> -bool any_value::operator!=(T const rhs) { - return value_ != rhs; -} - -template >*> -bool any_value::operator>(T const rhs) { - auto arithmetic_lhs = utils::from_chars(value_); - if (arithmetic_lhs) { - return arithmetic_lhs.value() > rhs; + any_value& operator=( any_value && rhs ) = default; + any_value& operator=( any_value const & rhs ) = default; + + template< typename T > + any_value& operator=( T && rhs ) noexcept + { + if constexpr ( std::is_arithmetic_v< std::remove_reference_t< T > > ) + { + value_ = utils::to_chars< std::remove_reference_t< T > >( std::forward< T >( rhs ) ); + use_string_comparison_ = false; + } + else if constexpr ( std::is_constructible_v< std::string, std::remove_reference_t< T > > ) + { + value_ = std::forward< T >( rhs ); + use_string_comparison_ = true; + } + return *this; } - return false; -} - -template >*> -bool any_value::operator>(T const rhs) { - if (use_string_comparison_) { - return value_ > rhs; + template< typename T > + [[ nodiscard ]] bool operator==( T && rhs ) const noexcept + { + return compare( value_, rhs, std::equal_to<>{} ); } - auto arithmetic_lhs = utils::from_chars(value_); - auto arithmetic_rhs = utils::from_chars(rhs); - if (arithmetic_lhs && arithmetic_rhs) { - return arithmetic_lhs.value() > arithmetic_rhs.value(); + template< typename T > + [[ nodiscard ]] bool operator!=( T && rhs ) const noexcept + { + return compare( value_, rhs, std::not_equal_to<>{} ); } - return false; -} - -template >*> -bool any_value::operator<(T const rhs) { - auto arithmetic_lhs = utils::from_chars(value_); - if (arithmetic_lhs) { - return arithmetic_lhs.value() < rhs; + [[ nodiscard ]] bool operator>( std::string_view const rhs ) const noexcept + { + return compare( value_, rhs, std::greater<>{} ); } - return false; -} - -template >*> -bool any_value::operator<(T const rhs) { - if (use_string_comparison_) { - return value_ < rhs; + [[ nodiscard ]] bool operator<( std::string_view const rhs ) const noexcept + { + return compare( value_, rhs, std::less<>{} ); } - auto arithmetic_lhs = utils::from_chars(value_); - auto arithmetic_rhs = utils::from_chars(rhs); - if (arithmetic_lhs && arithmetic_rhs) { - return arithmetic_lhs.value() < arithmetic_rhs.value(); + [[ nodiscard ]] bool operator>=( std::string_view const rhs ) const noexcept + { + return compare( value_, rhs, std::greater_equal<>{} ); } - return false; -} - -template >*> -bool any_value::operator>=(T const rhs) { - auto arithmetic_lhs = utils::from_chars(value_); - if (arithmetic_lhs) { - return arithmetic_lhs.value() >= rhs; + [[ nodiscard ]] bool operator<=( std::string_view const rhs ) const noexcept + { + return compare( value_, rhs, std::less_equal<>{} ); } - return false; -} - -template >*> -bool any_value::operator>=(T const rhs) { - if (use_string_comparison_) { - return value_ >= rhs; - } + ~any_value() = default; - auto arithmetic_lhs = utils::from_chars(value_); - auto arithmetic_rhs = utils::from_chars(rhs); - if (arithmetic_lhs && arithmetic_rhs) { - return arithmetic_lhs.value() >= arithmetic_rhs.value(); - } + friend bool operator==( any_value const & lhs, any_value const & rhs ) noexcept; + friend bool operator!=( any_value const & lhs, any_value const & rhs ) noexcept; - return false; -} +private: -template >*> -bool any_value::operator<=(T const rhs) { - auto arithmetic_lhs = utils::from_chars(value_); - if (arithmetic_lhs) { - return arithmetic_lhs.value() <= rhs; - } + template< typename F > + bool compare( std::string_view const lhs, std::string_view const rhs, F && f ) const noexcept + { + if ( use_string_comparison_ ) { return f( lhs, rhs ); } - return false; -} + auto const arithmetic_lhs{ utils::from_chars< double >( lhs ) }; + auto const arithmetic_rhs{ utils::from_chars< double >( rhs ) }; -template >*> -bool any_value::operator<=(T const rhs) { - if (use_string_comparison_) { - return value_ <= rhs; - } + if ( arithmetic_lhs && arithmetic_rhs ) + { + return f( arithmetic_lhs.value(), arithmetic_rhs.value() ); + } - auto arithmetic_lhs = utils::from_chars(value_); - auto arithmetic_rhs = utils::from_chars(rhs); - if (arithmetic_lhs && arithmetic_rhs) { - return arithmetic_lhs.value() <= arithmetic_rhs.value(); + return false; } - return false; -} + std::string value_; + bool use_string_comparison_{ false }; +}; -[[nodiscard]] inline bool operator==(any_value const& lhs, any_value const& rhs) { +[[ nodiscard ]] inline bool operator==( any_value const & lhs, any_value const & rhs ) noexcept +{ return lhs.value_ == rhs.value_; } -[[nodiscard]] inline bool operator!=(any_value const& lhs, any_value const& rhs) { +[[ nodiscard ]] inline bool operator!=( any_value const & lhs, any_value const & rhs ) noexcept +{ return lhs.value_ != rhs.value_; } diff --git a/include/booleval/utils/split_options.hpp b/include/booleval/utils/split_options.hpp new file mode 100644 index 0000000..6e439e0 --- /dev/null +++ b/include/booleval/utils/split_options.hpp @@ -0,0 +1,150 @@ +/* + * Copyright (c) 2021, Marin Peko + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#ifndef BOOLEVAL_SPLIT_OPTIONS_HPP +#define BOOLEVAL_SPLIT_OPTIONS_HPP + +#include + +namespace booleval::utils +{ + +/** + * @enum split_options + * + * Represents an options used when splitting a string. + */ +enum class [[ nodiscard ]] split_options : std::uint8_t +{ + off = 0x00, + include_delimiters = 0x01, + exclude_delimiters = 0x02, + split_by_whitespace = 0x04, + allow_quoted_strings = 0x08 +}; + +template< typename EnumT > +constexpr std::enable_if_t< std::is_enum_v< EnumT >, EnumT > +operator|( EnumT const lhs, EnumT const rhs ) noexcept +{ + return static_cast< EnumT > + ( + static_cast< std::underlying_type_t< EnumT > >( lhs) | + static_cast< std::underlying_type_t< EnumT > >( rhs) + ); +} + +template< typename EnumT > +constexpr std::enable_if_t< std::is_enum_v< EnumT >, EnumT > +operator&( EnumT const lhs, EnumT const rhs ) noexcept +{ + return static_cast< EnumT > + ( + static_cast< std::underlying_type_t< EnumT > >( lhs) & + static_cast< std::underlying_type_t< EnumT > >( rhs) + ); +} + +template< typename EnumT > +constexpr std::enable_if_t< std::is_enum_v< EnumT >, EnumT > +operator^( EnumT const lhs, EnumT const rhs ) noexcept +{ + return static_cast< EnumT > + ( + static_cast< std::underlying_type_t< EnumT > >( lhs) ^ + static_cast< std::underlying_type_t< EnumT > >( rhs) + ); +} + +template< typename EnumT > +constexpr std::enable_if_t< std::is_enum_v< EnumT >, EnumT > +operator~( EnumT const rhs ) noexcept +{ + return static_cast< EnumT > + ( + ~static_cast< std::underlying_type_t< EnumT > >( rhs) + ); +} + +template< typename EnumT > +[[ nodiscard ]] constexpr std::enable_if_t< std::is_enum_v< EnumT >, EnumT > & +operator|=( EnumT & lhs, EnumT const rhs ) noexcept +{ + return static_cast< EnumT > + ( + static_cast< std::underlying_type_t< EnumT > >( lhs ) | + static_cast< std::underlying_type_t< EnumT > >( rhs ) + ); +} + +template< typename EnumT > +[[ nodiscard ]] constexpr std::enable_if_t< std::is_enum_v< EnumT >, EnumT > & +operator&=( EnumT & lhs, EnumT const rhs ) noexcept +{ + return static_cast< EnumT > + ( + static_cast< std::underlying_type_t< EnumT > >( lhs ) & + static_cast< std::underlying_type_t< EnumT > >( rhs ) + ); +} + +template< typename EnumT > +[[ nodiscard ]] constexpr std::enable_if_t< std::is_enum_v< EnumT >, EnumT > & +operator^=( EnumT & lhs, EnumT const rhs ) noexcept +{ + return static_cast< EnumT > + ( + static_cast< std::underlying_type_t< EnumT > >( lhs ) ^ + static_cast< std::underlying_type_t< EnumT > >( rhs ) + ); +} + +/** + * Checks whether bits of the first set are present in + * another the second set and vice versa. + * + * @param lhs The first set of bits to check + * @param rhs The second set of bits to check + * + * @return True if the bits are present, false otherwise + */ +template +< + typename EnumT, + typename = std::enable_if_t< std::is_enum_v< EnumT > > +> +[[ nodiscard ]] constexpr bool is_set( EnumT const lhs, EnumT const rhs ) noexcept +{ + return (lhs & rhs) == lhs || + (lhs & rhs) == rhs; +} + +} // namespace booleval::utils + +#endif // BOOLEVAL_SPLIT_OPTIONS_HPP diff --git a/include/booleval/utils/split_range.hpp b/include/booleval/utils/split_range.hpp index df1fd5b..f076eaa 100644 --- a/include/booleval/utils/split_range.hpp +++ b/include/booleval/utils/split_range.hpp @@ -33,11 +33,14 @@ #include #include #include + +#include #include -namespace booleval::utils { +namespace booleval::utils +{ -constexpr auto whitespace_char{ ' ' }; +constexpr auto whitespace_char { ' ' }; constexpr auto single_quote_char{ '\'' }; constexpr auto double_quote_char{ '\"' }; @@ -47,20 +50,28 @@ constexpr auto double_quote_char{ '\"' }; * Represents the range of tokens computed by splitting the given * string view by the specified delimiters. */ -template -class split_range { +template +< + split_options options = split_options::exclude_delimiters | split_options::allow_quoted_strings, + char quote_char = double_quote_char +> +class split_range +{ public: - template - class iterator { + template + < + split_options iterator_options, + char iterator_quote_char + > + class iterator + { friend split_range; public: - struct element { - bool quoted{ false }; - std::size_t index{ 0 }; - std::string_view value{ "" }; + struct element + { + bool is_quoted{ false }; + std::string_view value {}; }; using iterator_category = std::forward_iterator_tag; @@ -70,80 +81,75 @@ class split_range { using reference = value_type&; public: - explicit constexpr iterator(std::string_view strv, std::string_view delims) noexcept - : strv_(strv), - delims_(delims), - prev_(std::begin(strv_)) { + explicit constexpr iterator( std::string_view const strv, std::string_view const delims ) noexcept + : strv_ { strv } + , delims_{ delims } + , prev_ { std::begin( strv_ ) } + { next(); } - constexpr iterator(iterator&& rhs) = default; - constexpr iterator(iterator const& rhs) = default; - - iterator& operator=(iterator&& rhs) = default; - iterator& operator=(iterator const& rhs) = default; + constexpr iterator( iterator && rhs ) noexcept = default; + constexpr iterator( iterator const & rhs ) noexcept = default; - [[nodiscard]] constexpr auto operator*() const noexcept { - return curr_value_; - } - - [[nodiscard]] constexpr auto operator->() const noexcept { - return &curr_value_; - } + iterator& operator=( iterator && rhs ) noexcept = default; + iterator& operator=( iterator const & rhs ) noexcept = default; - constexpr iterator& operator++() noexcept { - ++curr_value_.index; + [[ nodiscard ]] constexpr auto operator* () const noexcept { return curr_value_; } + [[ nodiscard ]] constexpr auto operator->() const noexcept { return &curr_value_; } - if (curr_value_.quoted) { - curr_ = skip_char(curr_, whitespace_char); - if (std::end(strv_) != curr_ && curr_ != std::prev(std::end(strv_))) { - curr_ = std::next(curr_); + constexpr iterator& operator++() noexcept + { + if ( curr_value_.is_quoted ) + { + curr_ = skip_char( curr_, whitespace_char ); + if ( curr_ != std::end( strv_ ) && curr_ != std::prev( std::end( strv_ ) ) ) + { + curr_ = std::next( curr_ ); } - if (std::end(strv_) != curr_) { - prev_ = std::next(curr_); - if constexpr (is_set(iter_options, split_options::include_delimiters)) { - prev_ = skip_char(curr_, whitespace_char); + if ( curr_ != std::end( strv_ ) ) + { + prev_ = std::next( curr_ ); + if constexpr ( is_set( iterator_options, split_options::include_delimiters ) ) + { + prev_ = skip_char( curr_, whitespace_char ); } } - } else { - if constexpr (is_set(iter_options, split_options::include_delimiters)) { - curr_ = skip_char(curr_, whitespace_char); + } + else + { + if constexpr ( is_set( iterator_options, split_options::include_delimiters ) ) + { + curr_ = skip_char( curr_, whitespace_char ); prev_ = curr_; - } else { - if (std::end(strv_) != curr_) { - prev_ = std::next(curr_); - } else { - prev_ = std::end(strv_); - } + } + else + { + if ( curr_ != std::end( strv_ ) ) { prev_ = std::next( curr_ ); } + else { prev_ = std::end ( strv_ ); } } } - curr_value_.quoted = false; + curr_value_.is_quoted = false; next(); return *this; } - constexpr iterator operator++(int) noexcept { - iterator tmp = *this; + constexpr iterator operator++( int ) noexcept + { + auto tmp{ *this }; ++(*this); return tmp; } - [[nodiscard]] constexpr bool operator!=(iterator const& rhs) const noexcept { - return prev_ != rhs.prev_; - } + [[ nodiscard ]] constexpr bool operator!=( iterator const & rhs ) const noexcept { return prev_ != rhs.prev_; } + [[ nodiscard ]] constexpr bool operator==( iterator const & rhs ) const noexcept { return prev_ == rhs.prev_; } - [[nodiscard]] constexpr bool operator==(iterator const& rhs) const noexcept { - return prev_ == rhs.prev_; - } - - ~iterator() = default; + ~iterator() noexcept = default; private: - explicit constexpr iterator(std::string_view::const_iterator end) noexcept - : prev_(end) - {} + explicit constexpr iterator( std::string_view::const_iterator end ) noexcept : prev_{ end } {} /** * Finds the iterator pointing to the beginning of the next token in the specified range. @@ -153,19 +159,30 @@ class split_range { * * @return Iterator to the beginning of the next token */ - [[nodiscard]] constexpr std::string_view::iterator find_next(std::string_view::iterator first, - std::string_view::iterator last) const noexcept { - if constexpr (is_set(iter_options, split_options::allow_quoted_strings)) { - if (curr_value_.quoted) { - return find_next_quote(first, last); - } else { - return std::min( - find_next_quote(first, last), - find_next_delim(first, last) + [[ nodiscard ]] constexpr std::string_view::iterator find_next + ( + std::string_view::iterator const first, + std::string_view::iterator const last + ) const noexcept + { + if constexpr ( is_set( iterator_options, split_options::allow_quoted_strings ) ) + { + if ( curr_value_.is_quoted ) + { + return find_next_quote( first, last ); + } + else + { + return std::min + ( + find_next_quote( first, last ), + find_next_delim( first, last ) ); } - } else { - return find_next_delim(first, last); + } + else + { + return find_next_delim( first, last ); } } @@ -177,14 +194,21 @@ class split_range { * * @return Iterator to the first delimiter */ - [[nodiscard]] constexpr std::string_view::iterator find_next_delim(std::string_view::iterator first, - std::string_view::iterator last) const noexcept { - if constexpr (is_set(iter_options, split_options::split_by_whitespace)) { - auto whitespace = std::find(first, last, whitespace_char); - auto other_delim = std::find_first_of(first, last, std::begin(delims_), std::end(delims_)); - return std::min(whitespace, other_delim); - } else { - return std::find_first_of(first, last, std::begin(delims_), std::end(delims_)); + [[ nodiscard ]] constexpr std::string_view::iterator find_next_delim + ( + std::string_view::iterator const first, + std::string_view::iterator const last + ) const noexcept + { + if constexpr ( is_set( iterator_options, split_options::split_by_whitespace ) ) + { + auto const whitespace { std::find( first, last, whitespace_char ) }; + auto const other_delim{ std::find_first_of( first, last, std::begin( delims_ ), std::end( delims_ ) ) }; + return std::min( whitespace, other_delim ); + } + else + { + return std::find_first_of( first, last, std::begin( delims_ ), std::end( delims_ ) ); } } @@ -196,9 +220,13 @@ class split_range { * * @return Iterator to the first quote character */ - [[nodiscard]] constexpr std::string_view::iterator find_next_quote(std::string_view::iterator first, - std::string_view::iterator last) const noexcept { - return std::find(first, last, iter_quote_char); + [[ nodiscard ]] constexpr std::string_view::iterator find_next_quote + ( + std::string_view::iterator const first, + std::string_view::iterator const last + ) const noexcept + { + return std::find( first, last, iterator_quote_char ); } /** @@ -208,9 +236,15 @@ class split_range { * * @return Iterator to the first element which is not equal to the specified character */ - [[nodiscard]] constexpr std::string_view::iterator skip_char(std::string_view::iterator first, char const c) const noexcept { - while (std::end(strv_) != first && c == *first) { - first = std::next(first); + [[ nodiscard ]] constexpr std::string_view::iterator skip_char + ( + std::string_view::iterator first, + char const c + ) const noexcept + { + while ( first != std::end( strv_ ) && c == *first ) + { + first = std::next( first ); } return first; } @@ -218,71 +252,74 @@ class split_range { /** * Finds the next token and sets the current element properties. */ - constexpr void next() noexcept { - if (std::end(strv_) == prev_) { - return; - } - - curr_value_.quoted = false; - curr_ = find_next(prev_, std::end(strv_)); - - if (std::end(strv_) != curr_) { - if (iter_quote_char == *curr_) { - curr_value_.quoted = true; - prev_ = skip_char(curr_, iter_quote_char); - curr_ = find_next(prev_, std::end(strv_)); - } else { - if (prev_ == curr_) { - curr_ = std::next(curr_); - } + constexpr void next() noexcept + { + if ( prev_ == std::end( strv_ ) ) { return; } + + curr_value_.is_quoted = false; + curr_ = find_next( prev_, std::end( strv_ ) ); + + if ( curr_ != std::end( strv_ ) ) + { + if ( *curr_ == iterator_quote_char ) + { + curr_value_.is_quoted = true; + prev_ = skip_char( curr_, iterator_quote_char ); + curr_ = find_next( prev_, std::end( strv_ ) ); + } + else + { + if ( prev_ == curr_) { curr_ = std::next( curr_ ); } } } - if (std::distance(std::begin(strv_), prev_) > - std::distance(std::begin(strv_), std::end(strv_))) { - prev_ = std::end(strv_); + if ( std::distance( std::begin( strv_ ), prev_ ) > std::distance( std::begin( strv_ ), std::end( strv_ ) ) ) + { + prev_ = std::end( strv_ ); return; } - curr_value_.value = strv_.substr( - std::distance(std::begin(strv_), prev_), - std::distance(prev_, curr_) + curr_value_.value = strv_.substr + ( + std::distance( std::begin( strv_ ), prev_ ), + std::distance( prev_, curr_ ) ); } private: - std::string_view strv_; - std::string_view delims_; + std::string_view strv_ {}; + std::string_view delims_{}; std::string_view::iterator prev_; std::string_view::iterator curr_; - value_type curr_value_; + value_type curr_value_{}; }; public: - constexpr split_range() = default; + constexpr split_range() noexcept = default; - constexpr split_range(std::string_view strv, std::string_view delims = " ") - : strv_(strv), - delims_(delims) + constexpr split_range( std::string_view const strv, std::string_view const delims = " " ) noexcept + : strv_ { strv } + , delims_{ delims } {} - constexpr split_range(split_range&& rhs) = default; - constexpr split_range(split_range const& rhs) = default; + constexpr split_range( split_range && rhs ) noexcept = default; + constexpr split_range( split_range const & rhs ) noexcept = default; - split_range& operator=(split_range&& rhs) = default; - split_range& operator=(split_range const& rhs) = default; + split_range& operator=( split_range && rhs ) noexcept = default; + split_range& operator=( split_range const & rhs ) noexcept = default; - ~split_range() = default; + ~split_range() noexcept = default; /** * Returns an iterator to the first element of the range. * * @return Iterator to the first element */ - [[nodiscard]] constexpr auto begin() const noexcept { - return iterator(strv_, delims_); + [[ nodiscard ]] constexpr auto begin() const noexcept + { + return iterator< options, quote_char >{ strv_, delims_ }; } /** @@ -290,13 +327,14 @@ class split_range { * * @return Iterator to the element following the last element */ - [[nodiscard]] constexpr auto end() const noexcept { - return iterator(std::end(strv_)); + [[ nodiscard ]] constexpr auto end() const noexcept + { + return iterator< options, quote_char >{ std::end( strv_ ) }; } private: - std::string_view strv_; - std::string_view delims_; + std::string_view strv_ {}; + std::string_view delims_{}; }; } // namespace booleval::utils diff --git a/include/booleval/utils/string_utils.hpp b/include/booleval/utils/string_utils.hpp index b8afc1d..bedbe11 100644 --- a/include/booleval/utils/string_utils.hpp +++ b/include/booleval/utils/string_utils.hpp @@ -37,149 +37,77 @@ #include #include #include -#include #include -namespace booleval::utils { +namespace booleval::utils +{ /** - * enum class split_options + * Checks if string view contains only whitespaces. * - * Represents an options used when splitting a string. - */ -enum class [[nodiscard]] split_options : uint8_t { - off = 0x00, - include_delimiters = 0x01, - exclude_delimiters = 0x02, - split_by_whitespace = 0x04, - allow_quoted_strings = 0x08 -}; - -template -constexpr std::enable_if_t, Enum> -operator|(Enum lhs, Enum rhs) { - return static_cast( - static_cast>(lhs) | - static_cast>(rhs) - ); -} - -template -constexpr std::enable_if_t, Enum> -operator&(Enum lhs, Enum rhs) { - return static_cast( - static_cast>(lhs) & - static_cast>(rhs) - ); -} - -template -constexpr std::enable_if_t, Enum> -operator^(Enum lhs, Enum rhs) { - return static_cast( - static_cast>(lhs) ^ - static_cast>(rhs) - ); -} - -template -constexpr std::enable_if_t, Enum> -operator~(Enum rhs) { - return static_cast( - ~static_cast>(rhs) - ); -} - -template -[[nodiscard]] constexpr std::enable_if_t, Enum>& -operator|=(Enum& lhs, Enum rhs) { - return static_cast( - static_cast>(lhs) | - static_cast>(rhs) - ); -} - -template -[[nodiscard]] constexpr std::enable_if_t, Enum>& -operator&=(Enum& lhs, Enum rhs) { - return static_cast( - static_cast>(lhs) & - static_cast>(rhs) - ); -} - -template -[[nodiscard]] constexpr std::enable_if_t, Enum>& -operator^=(Enum& lhs, Enum rhs) { - return static_cast( - static_cast>(lhs) ^ - static_cast>(rhs) - ); -} - -/** - * Checks whether bits of the first set are present in - * another the second set and vice versa. - * - * @param lhs The first set of bits to check - * @param rhs The second set of bits to check + * @param strv String view to check * - * @return True if the bits are present, false otherwise + * @return True if string view contains only whitespaces, false otherwise */ -template >> -[[nodiscard]] constexpr bool is_set(Enum lhs, Enum rhs) { - return (lhs & rhs) == lhs || - (lhs & rhs) == rhs; +[[ nodiscard ]] constexpr bool is_whitespace( std::string_view const strv ) noexcept +{ + return strv.find_first_not_of( ' ' ) == std::string_view::npos; } /** - * Removes whitespace characters from the left side of the string view. + * Removes specified characters from the left side of the string view. * - * @param strv String view to remove whitespace characters from + * @param strv String view to remove specified characters from * @param c Character to trim from the left side of the string view * * @return Modified string view */ -[[nodiscard]] constexpr std::string_view ltrim(std::string_view strv, char const c = ' ') { - strv.remove_prefix( - std::min( - strv.find_first_not_of(c), - strv.size() +[[ nodiscard ]] constexpr std::string_view ltrim(std::string_view strv, char const c = ' ') noexcept +{ + strv.remove_prefix + ( + std::min + ( + strv.find_first_not_of( c ), + std::size( strv ) ) ); return strv; } /** - * Removes whitespace characters from the right side of the string view. + * Removes specified characters from the right side of the string view. * - * @param strv String view to remove whitespace characters from + * @param strv String view to remove specified characters from * @param c Character to trim from the right side of the string view * * @return Modified string view */ -[[nodiscard]] constexpr std::string_view rtrim(std::string_view strv, char const c = ' ') { - strv.remove_suffix( - std::min( - strv.size() - strv.find_last_not_of(c) - 1, - strv.size() +[[ nodiscard ]] constexpr std::string_view rtrim( std::string_view strv, char const c = ' ') noexcept +{ + strv.remove_suffix + ( + std::min + ( + std::size( strv ) - strv.find_last_not_of( c ) - 1, + std::size( strv ) ) ); return strv; } /** - * Removes whitespace characters from the both sides of the string view. + * Removes specified characters from the both sides of the string view. * * @param strv String view to remove whitespace characters from * @param c Character to trim from the both sides of the string view * * @return Modified string view */ -[[nodiscard]] constexpr std::string_view trim(std::string_view strv, char const c = ' ') { - strv = ltrim(strv, c); - return rtrim(strv, c); +[[ nodiscard ]] constexpr std::string_view trim( std::string_view strv, char const c = ' ' ) noexcept +{ + strv = ltrim( strv, c ); + return rtrim( strv, c ); } /** @@ -189,8 +117,9 @@ template -[[nodiscard]] std::string join(InputIt const& first, InputIt const& last, std::string const& separator = "") { - if (first == last) { - return {}; +template< typename InputIt > +[[ nodiscard ]] std::string join( InputIt first, InputIt last, std::string_view const separator = "" ) noexcept +{ + std::string result; + + bool is_first{ true }; + + for ( ; first != last; ++first ) + { + if ( is_first ) { is_first = false; } + else + { + result += separator; + } + + result += *first; } - std::string result{ *first }; - return std::accumulate( - std::next(first), last, result, - [&separator](auto result, auto const value) { - return result + separator + value; - } - ); + return result; } /** @@ -228,25 +163,34 @@ template * * @return Optional value */ -#if defined(__GNUC__) || defined(__clang__) -template >* = nullptr> -#elif defined(_MSC_VER) -template >* = nullptr> +#if defined( __GNUC__ ) || defined( __clang__ ) +template +< + typename T, + typename std::enable_if_t< std::is_integral_v< T > >* = nullptr +> +#elif defined( _MSC_VER ) +template +< + typename T, + typename std::enable_if_t< std::is_arithmetic_v< T > >* = nullptr +> #endif -[[nodiscard]] std::optional from_chars(std::string_view strv) { +[[ nodiscard ]] std::optional< T > from_chars( std::string_view const strv ) noexcept +{ T value{}; - auto const result = std::from_chars( - strv.data(), - strv.data() + strv.size(), - value - ); + auto const result + { + std::from_chars + ( + std::data( strv ), + std::data( strv ) + std::size( strv ), + value + ) + }; - if (std::errc() == result.ec) { - return value; - } + if ( result.ec == std::errc() ) { return value; } return std::nullopt; } @@ -262,19 +206,21 @@ template >* = nullptr> -[[nodiscard]] std::optional from_chars(std::string_view strv) { +#if defined( __GNUC__ ) || defined( __clang__ ) +template +< + typename T, + typename std::enable_if_t< std::is_floating_point_v< T > >* = nullptr +> +[[ nodiscard ]] std::optional< T > from_chars( std::string_view const strv ) noexcept +{ T value{}; std::stringstream ss; ss << strv; ss >> value; - if (ss.fail()) { - return std::nullopt; - } + if ( ss.fail() ) { return std::nullopt; } return value; } @@ -290,25 +236,37 @@ template >* = nullptr> -#elif defined(_MSC_VER) -template >* = nullptr> +#if defined( __GNUC__ ) || defined( __clang__ ) +template +< + typename T, + typename std::enable_if_t< std::is_integral_v< T > >* = nullptr +> +#elif defined( _MSC_VER ) +template +< + typename T, + typename std::enable_if_t< std::is_arithmetic_v< T > >* = nullptr +> #endif -[[nodiscard]] std::string to_chars(T const value) { - constexpr std::size_t buffer_size = std::numeric_limits::digits10 + 2; // +1 for minus, +1 for digits10 - std::array buffer; - - auto const result = std::to_chars( - buffer.data(), - buffer.data() + buffer.size(), - value - ); +[[ nodiscard ]] std::string to_chars( T const value ) noexcept +{ + constexpr std::size_t buffer_size{ std::numeric_limits< T >::digits10 + 2 }; // +1 for minus, +1 for digits10 + std::array< char, buffer_size > buffer; + + auto const result + { + std::to_chars + ( + std::data( buffer ), + std::data( buffer ) + std::size( buffer ), + value + ) + }; - if (std::errc() == result.ec) { - return std::string(buffer.data(), result.ptr - buffer.data()); + if ( result.ec == std::errc() ) + { + return std::string( buffer.data(), result.ptr - buffer.data() ); } return {}; @@ -324,14 +282,17 @@ template >* = nullptr> -[[nodiscard]] std::string to_chars(T const value) { - auto str = std::to_string(value); - std::string_view strv{ str.c_str(), str.size() }; - str = rtrim(strv, '0'); - return str; +#if defined( __GNUC__ ) || defined( __clang__ ) +template +< + typename T, + typename std::enable_if_t< std::is_floating_point_v< T > >* = nullptr +> +[[ nodiscard ]] std::string to_chars( T const value ) noexcept +{ + auto result{ std::to_string( value ) }; + result = rtrim( result, '0' ); + return result; } #endif diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt deleted file mode 100644 index 7d8f36a..0000000 --- a/src/CMakeLists.txt +++ /dev/null @@ -1,64 +0,0 @@ -set (BOOLEVAL_INCLUDE_DIR ../include) - -include_directories ( - BEFORE - ${BOOLEVAL_INCLUDE_DIR} -) - -set ( - SOURCE_FILES - token/tokenizer.cpp - tree/expression_tree.cpp -) - -set ( - INCLUDE_FILES - ${BOOLEVAL_INCLUDE_DIR}/booleval/token/token.hpp - ${BOOLEVAL_INCLUDE_DIR}/booleval/token/token_type.hpp - ${BOOLEVAL_INCLUDE_DIR}/booleval/token/tokenizer.hpp - - ${BOOLEVAL_INCLUDE_DIR}/booleval/tree/expression_tree.hpp - ${BOOLEVAL_INCLUDE_DIR}/booleval/tree/result_visitor.hpp - ${BOOLEVAL_INCLUDE_DIR}/booleval/tree/tree_node.hpp - - ${BOOLEVAL_INCLUDE_DIR}/booleval/utils/any_mem_fn.hpp - ${BOOLEVAL_INCLUDE_DIR}/booleval/utils/any_value.hpp - ${BOOLEVAL_INCLUDE_DIR}/booleval/utils/split_range.hpp - ${BOOLEVAL_INCLUDE_DIR}/booleval/utils/string_utils.hpp - - ${BOOLEVAL_INCLUDE_DIR}/booleval/evaluator.hpp - ${BOOLEVAL_INCLUDE_DIR}/booleval/exceptions.hpp -) - -add_library ( - booleval ${LIBBOOLEVAL_TYPE} - ${SOURCE_FILES} - ${INCLUDE_FILES} -) - -if (NOT MSVC) - target_link_libraries (booleval --coverage) -endif() - -set_target_properties (booleval PROPERTIES OUTPUT_NAME booleval) -set_target_properties (booleval PROPERTIES VERSION ${LIBBOOLEVAL_VERSION} SOVERSION ${LIBBOOLEVAL_VERSION} ) - -# Install instructions for this target -install ( - TARGETS booleval - EXPORT libboolevalTargets - LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} - ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} - COMPONENT dev -) - -macro (install_headers_with_directory header_list) - foreach (header ${header_list}) - # Extract directory name and remove leading '../' - get_filename_component (dir ${header} PATH) - string (REGEX REPLACE "^\\.\\.\\/" "" DIR ${dir}) - install (FILES ${header} DESTINATION ${dir}) - endforeach (header) -endmacro () - -install_headers_with_directory (${INCLUDE_FILES}) \ No newline at end of file diff --git a/src/token/tokenizer.cpp b/src/token/tokenizer.cpp deleted file mode 100644 index 3a5d21f..0000000 --- a/src/token/tokenizer.cpp +++ /dev/null @@ -1,98 +0,0 @@ -/* - * Copyright (c) 2019, Marin Peko - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above - * copyright notice, this list of conditions and the following disclaimer - * in the documentation and/or other materials provided with the - * distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * - */ - -#include -#include -#include - -namespace booleval { - -namespace token { - -tokenizer::tokenizer(std::string_view expression) noexcept - : expression_(expression) { -} - -void tokenizer::expression(std::string_view expression) noexcept { - expression_ = expression; -} - -std::string_view tokenizer::expression() const noexcept { - return expression_; -} - -bool tokenizer::has_tokens() const noexcept { - return current_token_index_ < tokens_.size(); -} - -void tokenizer::pass_token() noexcept { - current_token_index_++; -} - -token const& tokenizer::next_token() { - return tokens_.at(current_token_index_++); -} - -token const& tokenizer::weak_next_token() { - return tokens_.at(current_token_index_); -} - -void tokenizer::tokenize() { - tokens_.clear(); - reset(); - - constexpr auto parenthesis_symbols = parenthesis_symbol_expressions(); - constexpr auto options = - utils::split_options::include_delimiters | - utils::split_options::split_by_whitespace | - utils::split_options::allow_quoted_strings; - - auto delims = utils::join(std::begin(parenthesis_symbols), std::end(parenthesis_symbols)); - auto tokens_range = utils::split_range(expression_, delims); - - for (auto const [quoted, index, value] : tokens_range) { - auto type = quoted ? token_type::field : map_to_token_type(value); - - if (token_type::field == type) { - if (!tokens_.empty() && tokens_.back().is(token_type::field)) { - tokens_.emplace_back(token_type::eq, map_to_token_value(token_type::eq)); - } - } - - tokens_.emplace_back(type, value); - } -} - -void tokenizer::reset() noexcept { - current_token_index_ = 0; -} - -} // token - -} // booleval diff --git a/src/tree/expression_tree.cpp b/src/tree/expression_tree.cpp deleted file mode 100644 index 796f9bf..0000000 --- a/src/tree/expression_tree.cpp +++ /dev/null @@ -1,163 +0,0 @@ -/* - * Copyright (c) 2020, Marin Peko - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above - * copyright notice, this list of conditions and the following disclaimer - * in the documentation and/or other materials provided with the - * distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * - */ - -#include -#include - -namespace booleval { - -namespace tree { - -std::shared_ptr expression_tree::root() noexcept { - return root_; -} - -bool expression_tree::build(std::string_view expression) { - tokenizer_.reset(); - tokenizer_.expression(expression); - tokenizer_.tokenize(); - - root_ = parse_expression(); - if (nullptr == root_) { - return false; - } else if (tokenizer_.has_tokens()) { - root_ = nullptr; - return false; - } - - return true; -} - -std::shared_ptr expression_tree::parse_expression() { - auto left = parse_and_operation(); - - auto const is_relational_operator = - tokenizer_.has_tokens() && - tokenizer_.weak_next_token().is_one_of( - token::token_type::eq, - token::token_type::neq, - token::token_type::gt, - token::token_type::lt, - token::token_type::geq, - token::token_type::leq - ); - - if (is_relational_operator) { - return nullptr; - } - - if (tokenizer_.has_tokens() && tokenizer_.weak_next_token().is_not(token::token_type::logical_or)) { - return left; - } - - while (tokenizer_.has_tokens() && tokenizer_.weak_next_token().is(token::token_type::logical_or)) { - tokenizer_.pass_token(); - auto logical_or = std::make_shared(token::token_type::logical_or); - - auto right = parse_and_operation(); - if (nullptr == right) { - return nullptr; - } - - logical_or->left = left; - logical_or->right = right; - left = logical_or; - } - - return left; -} - -std::shared_ptr expression_tree::parse_and_operation() { - auto left = parse_parentheses(); - if (nullptr == left) { - left = parse_relational_operation(); - } - - while (tokenizer_.has_tokens() && tokenizer_.weak_next_token().is(token::token_type::logical_and)) { - tokenizer_.pass_token(); - - auto logical_and = std::make_shared(token::token_type::logical_and); - - auto right = parse_parentheses(); - if (nullptr == right) { - right = parse_relational_operation(); - } - - if (nullptr == right) { - return nullptr; - } - - logical_and->left = left; - logical_and->right = right; - left = logical_and; - } - - return left; -} - -std::shared_ptr expression_tree::parse_parentheses() { - if (tokenizer_.has_tokens() && tokenizer_.weak_next_token().is(token::token_type::lp)) { - tokenizer_.pass_token(); - auto expression = parse_expression(); - if (tokenizer_.has_tokens() && tokenizer_.weak_next_token().is(token::token_type::rp)) { - tokenizer_.pass_token(); - return expression; - } - } - - return nullptr; -} - -std::shared_ptr expression_tree::parse_relational_operation() { - auto left = parse_terminal(); - if (tokenizer_.has_tokens()) { - auto operation = std::make_shared(tokenizer_.next_token()); - auto right = parse_terminal(); - operation->left = left; - operation->right = right; - return operation; - } - - return nullptr; -} - -std::shared_ptr expression_tree::parse_terminal() { - if (tokenizer_.has_tokens()) { - auto token = tokenizer_.next_token(); - if (token.is(token::token_type::field)) { - return std::make_shared(token); - } - } - - return nullptr; -} - -} // tree - -} // booleval \ No newline at end of file diff --git a/tests/src/CMakeLists.txt b/tests/src/CMakeLists.txt index 7fa3cfe..5b220e8 100644 --- a/tests/src/CMakeLists.txt +++ b/tests/src/CMakeLists.txt @@ -18,7 +18,6 @@ link_directories ( link_libraries ( gtest gtest_main - booleval ) add_custom_target (tests) @@ -40,11 +39,10 @@ endmacro () create_test (token/token) create_test (token/tokenizer) -create_test (tree/expression_tree) +create_test (tree/node) create_test (tree/result_visitor) -create_test (tree/tree_node) -create_test (utils/algo_utils) -create_test (utils/any_mem_fn) +create_test (tree/tree) +create_test (utils/algorithm) create_test (utils/any_value) create_test (utils/split_range) create_test (utils/string_utils) diff --git a/tests/src/evaluator_test.cpp b/tests/src/evaluator_test.cpp index 29a5b16..22562f8 100644 --- a/tests/src/evaluator_test.cpp +++ b/tests/src/evaluator_test.cpp @@ -30,415 +30,485 @@ #include #include -class EvaluatorTest : public testing::Test { -public: - template - class obj { +namespace +{ + + template< typename T > + class foo + { public: - obj() : value_a_{} {} - obj(T value) : value_a_{ value } {} - T value_a() const noexcept { return value_a_; } - T value_a_valid(bool& is_valid) const noexcept { is_valid = true; return value_a_; } - T value_a_notvalid(bool& is_valid) const noexcept { is_valid = false; return value_a_; } + foo() : value_{} {} + foo( T && value ) : value_{ value } {} - private: - T value_a_; - }; + void value( T && value ) { value_ = value; } - template - class multi_obj { - public: - multi_obj() : value_a_{}, value_b_{} {} - multi_obj(T value_a, U value_b) : value_a_{ value_a }, value_b_{ value_b } {} - T value_a() const noexcept { return value_a_; } - U value_b() const noexcept { return value_b_; } + T value() const noexcept { return value_; } private: - T value_a_; - U value_b_; + T value_{}; }; -}; - -TEST_F(EvaluatorTest, DefaultConstructor) { - booleval::evaluator<> evaluator; - EXPECT_FALSE(evaluator.is_activated()); -} -TEST_F(EvaluatorTest, EmptyExpression) { - booleval::evaluator<> evaluator; - EXPECT_TRUE(evaluator.expression("")); - EXPECT_FALSE(evaluator.is_activated()); - EXPECT_FALSE(evaluator.evaluate(obj())); -} - -TEST_F(EvaluatorTest, MissingClosingParenthesesExpression) { - booleval::evaluator<> evaluator; - EXPECT_FALSE(evaluator.expression("(field_a foo or field_b bar")); - EXPECT_FALSE(evaluator.is_activated()); - EXPECT_FALSE(evaluator.evaluate(obj())); -} - -TEST_F(EvaluatorTest, MultipleFieldsExpression) { - booleval::evaluator<> evaluator; - EXPECT_FALSE(evaluator.expression("field_a foo field_b")); - EXPECT_FALSE(evaluator.is_activated()); - EXPECT_FALSE(evaluator.evaluate(obj())); -} + template< typename T, typename U > + class bar + { + public: + bar( T && value_1, U && value_2 ) + : value_1_{ value_1 } + , value_2_{ value_2 } + {} -TEST_F(EvaluatorTest, EqualToOperator) { - obj foo{ "foo" }; - obj bar{ "bar" }; + void value_1( T && value ) { value_1_ = value; } + void value_2( U && value ) { value_2_ = value; } - booleval::evaluator<> evaluator({ - { "field_a", &obj::value_a } - }); + T value_1() const noexcept { return value_1_; } + U value_2() const noexcept { return value_2_; } - EXPECT_TRUE(evaluator.expression("field_a foo")); - EXPECT_TRUE(evaluator.is_activated()); - EXPECT_TRUE(evaluator.evaluate(foo)); - EXPECT_FALSE(evaluator.evaluate(bar)); -} + private: + T value_1_{}; + U value_2_{}; + }; -TEST_F(EvaluatorTest, SymbolEqualToOperator) { - obj foo{ "foo" }; - obj bar{ "bar" }; +} // namespace - booleval::evaluator<> evaluator({ - { "field_a", &obj::value_a } - }); +TEST( EvaluatorTest, DefaultConstructor ) +{ + booleval::evaluator evaluator; - EXPECT_TRUE(evaluator.expression("field_a == foo")); - EXPECT_TRUE(evaluator.is_activated()); - EXPECT_TRUE(evaluator.evaluate(foo)); - EXPECT_FALSE(evaluator.evaluate(bar)); + ASSERT_FALSE( evaluator.is_activated() ); } -TEST_F(EvaluatorTest, EqualToOperatorMultipleWords) { - obj foo{ "foo foo" }; - obj bar{ "bar bar" }; - - booleval::evaluator<> evaluator({ - { "field_a", &obj::value_a } - }); +TEST( EvaluatorTest, EmptyExpression ) +{ + booleval::evaluator evaluator; - EXPECT_TRUE(evaluator.expression("field_a \"foo foo\"")); - EXPECT_TRUE(evaluator.is_activated()); - EXPECT_TRUE(evaluator.evaluate(foo)); - EXPECT_FALSE(evaluator.evaluate(bar)); + ASSERT_TRUE ( evaluator.expression("") ); + ASSERT_FALSE( evaluator.is_activated() ); + ASSERT_FALSE( evaluator.evaluate( foo< std::uint8_t >{} ).success ); } -TEST_F(EvaluatorTest, NotEqualToOperator) { - obj foo{ "foo" }; - obj bar{ "bar" }; +TEST( EvaluatorTest, MissingParenthesesExpression ) +{ + booleval::evaluator evaluator; - booleval::evaluator<> evaluator({ - { "field_a", &obj::value_a } - }); - - EXPECT_TRUE(evaluator.expression("field_a neq foo")); - EXPECT_TRUE(evaluator.is_activated()); - EXPECT_FALSE(evaluator.evaluate(foo)); - EXPECT_TRUE(evaluator.evaluate(bar)); + ASSERT_FALSE( evaluator.expression( "(field_x foo or field_y bar" ) ); + ASSERT_FALSE( evaluator.is_activated() ); + ASSERT_FALSE( evaluator.evaluate( foo< std::uint8_t >{} ).success ); } -TEST_F(EvaluatorTest, SymbolNotEqualToOperator) { - obj foo{ "foo" }; - obj bar{ "bar" }; - - booleval::evaluator<> evaluator({ - { "field_a", &obj::value_a } - }); +TEST( EvaluatorTest, MultipleFieldsInRowExpression ) +{ + booleval::evaluator evaluator; - EXPECT_TRUE(evaluator.expression("field_a != foo")); - EXPECT_TRUE(evaluator.is_activated()); - EXPECT_FALSE(evaluator.evaluate(foo)); - EXPECT_TRUE(evaluator.evaluate(bar)); + ASSERT_FALSE( evaluator.expression( "field_x foo field_y" ) ); + ASSERT_FALSE( evaluator.is_activated() ); + ASSERT_FALSE( evaluator.evaluate( foo< std::uint8_t >{} ).success ); } -TEST_F(EvaluatorTest, GreaterThanOperator) { - obj foo{ 1.24F }; - obj bar{ 1.22F }; - - booleval::evaluator<> evaluator({ - { "field_a", &obj::value_a } - }); - - EXPECT_TRUE(evaluator.expression("field_a gt 1.23")); - EXPECT_TRUE(evaluator.is_activated()); - EXPECT_TRUE(evaluator.evaluate(foo)); - EXPECT_FALSE(evaluator.evaluate(bar)); -} +TEST( EvaluatorTest, EqualToOperator ) +{ + foo< std::string > x{ "foo" }; + foo< std::string > y{ "bar" }; -TEST_F(EvaluatorTest, SymbolGreaterThanOperator) { - obj foo{ 1.24F }; - obj bar{ 1.22F }; + booleval::evaluator evaluator + { + booleval::make_field( "field", &foo< std::string >::value ) + }; - booleval::evaluator<> evaluator({ - { "field_a", &obj::value_a } - }); + { + ASSERT_TRUE ( evaluator.expression( "field foo" ) ); + ASSERT_TRUE ( evaluator.is_activated() ); + ASSERT_TRUE ( evaluator.evaluate( x ).success ); + ASSERT_FALSE( evaluator.evaluate( y ).success ); + } + { + ASSERT_TRUE ( evaluator.expression( "field eq foo" ) ); + ASSERT_TRUE ( evaluator.is_activated() ); + ASSERT_TRUE ( evaluator.evaluate( x ).success ); + ASSERT_FALSE( evaluator.evaluate( y ).success ); + } + { + ASSERT_TRUE ( evaluator.expression( "field == foo" ) ); + ASSERT_TRUE ( evaluator.is_activated() ); + ASSERT_TRUE ( evaluator.evaluate( x ).success ); + ASSERT_FALSE( evaluator.evaluate( y ).success ); + } + { + x.value( "foo foo" ); - EXPECT_TRUE(evaluator.expression("field_a > 1.23")); - EXPECT_TRUE(evaluator.is_activated()); - EXPECT_TRUE(evaluator.evaluate(foo)); - EXPECT_FALSE(evaluator.evaluate(bar)); + ASSERT_TRUE ( evaluator.expression( "field == \"foo foo\"" ) ); + ASSERT_TRUE ( evaluator.is_activated() ); + ASSERT_TRUE ( evaluator.evaluate( x ).success ); + ASSERT_FALSE( evaluator.evaluate( y ).success ); + } } -TEST_F(EvaluatorTest, GreaterThanOperatorStrings) { - obj foo{ "30" }; - obj bar{ "1000" }; - - booleval::evaluator<> evaluator({ - { "field_a", &obj::value_a } - }); +TEST( EvaluatorTest, NotEqualToOperator ) +{ + foo< std::string > x{ "foo" }; + foo< std::string > y{ "bar" }; - EXPECT_TRUE(evaluator.expression("field_a gt \"200\"")); - EXPECT_TRUE(evaluator.is_activated()); - EXPECT_TRUE(evaluator.evaluate(foo)); - EXPECT_FALSE(evaluator.evaluate(bar)); -} - -TEST_F(EvaluatorTest, LessThanOperator) { - obj foo{ 1 }; - obj bar{ 3 }; + booleval::evaluator evaluator + { + booleval::make_field( "field", &foo< std::string >::value ) + }; - booleval::evaluator<> evaluator({ - { "field_a", &obj::value_a } - }); + { + ASSERT_TRUE ( evaluator.expression( "field neq foo" ) ); + ASSERT_TRUE ( evaluator.is_activated() ); + ASSERT_FALSE( evaluator.evaluate( x ).success ); + ASSERT_TRUE ( evaluator.evaluate( y ).success ); + } + { + ASSERT_TRUE ( evaluator.expression( "field != foo" ) ); + ASSERT_TRUE ( evaluator.is_activated() ); + ASSERT_FALSE( evaluator.evaluate( x ).success ); + ASSERT_TRUE ( evaluator.evaluate( y ).success ); + } + { + x.value( "foo foo" ); - EXPECT_TRUE(evaluator.expression("field_a lt 2")); - EXPECT_TRUE(evaluator.is_activated()); - EXPECT_TRUE(evaluator.evaluate(foo)); - EXPECT_FALSE(evaluator.evaluate(bar)); + ASSERT_TRUE ( evaluator.expression( "field != \"foo foo\"" ) ); + ASSERT_TRUE ( evaluator.is_activated() ); + ASSERT_FALSE( evaluator.evaluate( x ).success ); + ASSERT_TRUE ( evaluator.evaluate( y ).success ); + } } -TEST_F(EvaluatorTest, SymbolLessThanOperator) { - obj foo{ 1 }; - obj bar{ 3 }; - - booleval::evaluator<> evaluator({ - { "field_a", &obj::value_a } - }); +TEST( EvaluatorTest, GreaterThanOperator ) +{ + foo< float > x{ 1.22f }; + foo< float > y{ 1.24f }; - EXPECT_TRUE(evaluator.expression("field_a < 2")); - EXPECT_TRUE(evaluator.is_activated()); - EXPECT_TRUE(evaluator.evaluate(foo)); - EXPECT_FALSE(evaluator.evaluate(bar)); -} + foo< std::string > m{ "1000" }; + foo< std::string > n{ "50" }; -TEST_F(EvaluatorTest, LessThanOperatorStrings) { - obj foo{ "1000" }; - obj bar{ "30" }; + booleval::evaluator evaluator_digits + { + booleval::make_field( "field", &foo< float >::value ) + }; - booleval::evaluator<> evaluator({ - { "field_a", &obj::value_a } - }); + booleval::evaluator evaluator_strings + { + booleval::make_field( "field", &foo< std::string >::value ) + }; - EXPECT_TRUE(evaluator.expression("field_a lt \"200\"")); - EXPECT_TRUE(evaluator.is_activated()); - EXPECT_TRUE(evaluator.evaluate(foo)); - EXPECT_FALSE(evaluator.evaluate(bar)); + { + ASSERT_TRUE ( evaluator_digits.expression( "field gt 1.23" ) ); + ASSERT_TRUE ( evaluator_digits.is_activated() ); + ASSERT_FALSE( evaluator_digits.evaluate( x ).success ); + ASSERT_TRUE ( evaluator_digits.evaluate( y ).success ); + } + { + ASSERT_TRUE ( evaluator_digits.expression( "field > 1.23" ) ); + ASSERT_TRUE ( evaluator_digits.is_activated() ); + ASSERT_FALSE( evaluator_digits.evaluate( x ).success ); + ASSERT_TRUE ( evaluator_digits.evaluate( y ).success ); + } + { + ASSERT_TRUE ( evaluator_strings.expression( "field gt \"200\"" ) ); + ASSERT_TRUE ( evaluator_strings.is_activated() ); + ASSERT_FALSE( evaluator_strings.evaluate( m ).success ); + ASSERT_TRUE ( evaluator_strings.evaluate( n ).success ); + } + { + ASSERT_TRUE ( evaluator_strings.expression( "field > \"200\"" ) ); + ASSERT_TRUE ( evaluator_strings.is_activated() ); + ASSERT_FALSE( evaluator_strings.evaluate( m ).success ); + ASSERT_TRUE ( evaluator_strings.evaluate( n ).success ); + } } -TEST_F(EvaluatorTest, GreaterThanOrEqualToOperator) { - obj foo{ 1.234567 }; - obj bar{ 2.345678 }; - obj baz{ 0.123456 }; +TEST( EvaluatorTest, GreaterThanOrEqualToOperator ) +{ + foo< double > x{ 1.234567 }; + foo< double > y{ 2.345678 }; + foo< double > z{ 0.123456 }; - booleval::evaluator<> evaluator({ - { "field_a", &obj::value_a } - }); + booleval::evaluator evaluator + { + booleval::make_field( "field", &foo< double >::value ) + }; - EXPECT_TRUE(evaluator.expression("field_a geq 1.234567")); - EXPECT_TRUE(evaluator.is_activated()); - EXPECT_TRUE(evaluator.evaluate(foo)); - EXPECT_TRUE(evaluator.evaluate(bar)); - EXPECT_FALSE(evaluator.evaluate(baz)); + { + ASSERT_TRUE ( evaluator.expression( "field geq 1.234567" ) ); + ASSERT_TRUE ( evaluator.is_activated() ); + ASSERT_TRUE ( evaluator.evaluate( x ).success ); + ASSERT_TRUE ( evaluator.evaluate( y ).success ); + ASSERT_FALSE( evaluator.evaluate( z ).success ); + } + { + ASSERT_TRUE ( evaluator.expression( "field >= 1.234567" ) ); + ASSERT_TRUE ( evaluator.is_activated() ); + ASSERT_TRUE ( evaluator.evaluate( x ).success ); + ASSERT_TRUE ( evaluator.evaluate( y ).success ); + ASSERT_FALSE( evaluator.evaluate( z ).success ); + } } -TEST_F(EvaluatorTest, SymbolGreaterThanOrEqualToOperator) { - obj foo{ 1.234567 }; - obj bar{ 2.345678 }; - obj baz{ 0.123456 }; - - booleval::evaluator<> evaluator({ - { "field_a", &obj::value_a } - }); +TEST( EvaluatorTest, LessThanOperator ) +{ + foo< unsigned > x{ 1 }; + foo< unsigned > y{ 3 }; - EXPECT_TRUE(evaluator.expression("field_a >= 1.234567")); - EXPECT_TRUE(evaluator.is_activated()); - EXPECT_TRUE(evaluator.evaluate(foo)); - EXPECT_TRUE(evaluator.evaluate(bar)); - EXPECT_FALSE(evaluator.evaluate(baz)); -} + foo< std::string > m{ "1000" }; + foo< std::string > n{ "50" }; -TEST_F(EvaluatorTest, LessThanOrEqualToOperator) { - obj foo{ 1.234567 }; - obj bar{ 0.123456 }; - obj baz{ 2.345678 }; + booleval::evaluator evaluator_digits + { + booleval::make_field( "field", &foo< unsigned >::value ) + }; - booleval::evaluator<> evaluator({ - { "field_a", &obj::value_a } - }); + booleval::evaluator evaluator_strings + { + booleval::make_field( "field", &foo< std::string >::value ) + }; - EXPECT_TRUE(evaluator.expression("field_a leq 1.234567")); - EXPECT_TRUE(evaluator.is_activated()); - EXPECT_TRUE(evaluator.evaluate(foo)); - EXPECT_TRUE(evaluator.evaluate(bar)); - EXPECT_FALSE(evaluator.evaluate(baz)); + { + ASSERT_TRUE ( evaluator_digits.expression( "field lt 2" ) ); + ASSERT_TRUE ( evaluator_digits.is_activated() ); + ASSERT_TRUE ( evaluator_digits.evaluate( x ).success ); + ASSERT_FALSE( evaluator_digits.evaluate( y ).success ); + } + { + ASSERT_TRUE ( evaluator_digits.expression( "field < 2" ) ); + ASSERT_TRUE ( evaluator_digits.is_activated() ); + ASSERT_TRUE ( evaluator_digits.evaluate( x ).success ); + ASSERT_FALSE( evaluator_digits.evaluate( y ).success ); + } + { + ASSERT_TRUE ( evaluator_strings.expression( "field lt \"200\"" ) ); + ASSERT_TRUE ( evaluator_strings.is_activated() ); + ASSERT_TRUE ( evaluator_strings.evaluate( m ).success ); + ASSERT_FALSE( evaluator_strings.evaluate( n ).success ); + } + { + ASSERT_TRUE ( evaluator_strings.expression( "field < \"200\"" ) ); + ASSERT_TRUE ( evaluator_strings.is_activated() ); + ASSERT_TRUE ( evaluator_strings.evaluate( m ).success ); + ASSERT_FALSE( evaluator_strings.evaluate( n ).success ); + } } -TEST_F(EvaluatorTest, SymbolLessThanOrEqualToOperator) { - obj foo{ 1.234567 }; - obj bar{ 0.123456 }; - obj baz{ 2.345678 }; - - booleval::evaluator<> evaluator({ - { "field_a", &obj::value_a } - }); +TEST( EvaluatorTest, LessThanOrEqualToOperator ) +{ + foo< double > x{ 1.234567 }; + foo< double > y{ 2.345678 }; + foo< double > z{ 0.123456 }; - EXPECT_TRUE(evaluator.expression("field_a <= 1.234567")); - EXPECT_TRUE(evaluator.is_activated()); - EXPECT_TRUE(evaluator.evaluate(foo)); - EXPECT_TRUE(evaluator.evaluate(bar)); - EXPECT_FALSE(evaluator.evaluate(baz)); -} + booleval::evaluator evaluator + { + booleval::make_field( "field", &foo< double >::value ) + }; -TEST_F(EvaluatorTest, AndOperator) { - multi_obj foo{ "one", 1 }; - multi_obj bar{ "two", 2 }; - multi_obj baz{ "two", 1 }; - - booleval::evaluator<> evaluator({ - { "field_a", &multi_obj::value_a }, - { "field_b", &multi_obj::value_b } - }); - - EXPECT_TRUE(evaluator.expression("field_a one and field_b 1")); - EXPECT_TRUE(evaluator.is_activated()); - EXPECT_TRUE(evaluator.evaluate(foo)); - EXPECT_FALSE(evaluator.evaluate(bar)); - EXPECT_FALSE(evaluator.evaluate(baz)); + { + ASSERT_TRUE ( evaluator.expression( "field leq 1.234567" ) ); + ASSERT_TRUE ( evaluator.is_activated() ); + ASSERT_TRUE ( evaluator.evaluate( x ).success ); + ASSERT_FALSE( evaluator.evaluate( y ).success ); + ASSERT_TRUE ( evaluator.evaluate( z ).success ); + } + { + ASSERT_TRUE ( evaluator.expression( "field <= 1.234567" ) ); + ASSERT_TRUE ( evaluator.is_activated() ); + ASSERT_TRUE ( evaluator.evaluate( x ).success ); + ASSERT_FALSE( evaluator.evaluate( y ).success ); + ASSERT_TRUE ( evaluator.evaluate( z ).success ); + } } -TEST_F(EvaluatorTest, SymbolAndOperator) { - multi_obj foo{ "one", 1 }; - multi_obj bar{ "two", 2 }; - multi_obj baz{ "two", 1 }; - - booleval::evaluator<> evaluator({ - { "field_a", &multi_obj::value_a }, - { "field_b", &multi_obj::value_b } - }); - - EXPECT_TRUE(evaluator.expression("field_a one && field_b 1")); - EXPECT_TRUE(evaluator.is_activated()); - EXPECT_TRUE(evaluator.evaluate(foo)); - EXPECT_FALSE(evaluator.evaluate(bar)); - EXPECT_FALSE(evaluator.evaluate(baz)); -} +TEST( EvaluatorTest, AndOperator ) +{ + bar< unsigned, std::string > x{ 1, "bar" }; + bar< unsigned, std::string > y{ 3, "bar bar" }; + + booleval::evaluator evaluator + { + { + booleval::make_field( "field_1", &bar< unsigned, std::string >::value_1 ), + booleval::make_field( "field_2", &bar< unsigned, std::string >::value_2 ) + } + }; -TEST_F(EvaluatorTest, OrOperator) { - multi_obj foo{ "one", 1 }; - multi_obj bar{ "one", 2 }; - multi_obj baz{ "two", 1 }; - multi_obj qux{ "two", 2 }; - - booleval::evaluator<> evaluator({ - { "field_a", &multi_obj::value_a }, - { "field_b", &multi_obj::value_b } - }); - - EXPECT_TRUE(evaluator.expression("field_a one or field_b 1")); - EXPECT_TRUE(evaluator.is_activated()); - EXPECT_TRUE(evaluator.evaluate(foo)); - EXPECT_TRUE(evaluator.evaluate(bar)); - EXPECT_TRUE(evaluator.evaluate(baz)); - EXPECT_FALSE(evaluator.evaluate(qux)); + { + ASSERT_TRUE ( evaluator.expression( "field_1 1 and field_2 bar" ) ); + ASSERT_TRUE ( evaluator.is_activated() ); + ASSERT_TRUE ( evaluator.evaluate( x ).success ); + ASSERT_FALSE( evaluator.evaluate( y ).success ); + } + { + ASSERT_TRUE ( evaluator.expression( "field_1 eq 1 and field_2 eq bar" ) ); + ASSERT_TRUE ( evaluator.is_activated() ); + ASSERT_TRUE ( evaluator.evaluate( x ).success ); + ASSERT_FALSE( evaluator.evaluate( y ).success ); + } + { + ASSERT_TRUE ( evaluator.expression( "field_1 == 1 and field_2 == bar" ) ); + ASSERT_TRUE ( evaluator.is_activated() ); + ASSERT_TRUE ( evaluator.evaluate( x ).success ); + ASSERT_FALSE( evaluator.evaluate( y ).success ); + } + { + ASSERT_TRUE ( evaluator.expression( "field_1 == 1 && field_2 == bar" ) ); + ASSERT_TRUE ( evaluator.is_activated() ); + ASSERT_TRUE ( evaluator.evaluate( x ).success ); + ASSERT_FALSE( evaluator.evaluate( y ).success ); + } + { + ASSERT_TRUE ( evaluator.expression( "field_1 == 3 && field_2 == bar" ) ); + ASSERT_TRUE ( evaluator.is_activated() ); + ASSERT_FALSE( evaluator.evaluate( x ).success ); + ASSERT_FALSE( evaluator.evaluate( y ).success ); + } } -TEST_F(EvaluatorTest, SymbolOrOperator) { - multi_obj foo{ "one", 1 }; - multi_obj bar{ "one", 2 }; - multi_obj baz{ "two", 1 }; - multi_obj qux{ "two", 2 }; - - booleval::evaluator<> evaluator({ - { "field_a", &multi_obj::value_a }, - { "field_b", &multi_obj::value_b } - }); - - EXPECT_TRUE(evaluator.expression("field_a one || field_b 1")); - EXPECT_TRUE(evaluator.is_activated()); - EXPECT_TRUE(evaluator.evaluate(foo)); - EXPECT_TRUE(evaluator.evaluate(bar)); - EXPECT_TRUE(evaluator.evaluate(baz)); - EXPECT_FALSE(evaluator.evaluate(qux)); -} +TEST( EvaluatorTest, OrOperator ) +{ + bar< unsigned, std::string > x{ 1, "bar" }; + bar< unsigned, std::string > y{ 3, "bar bar" }; + + booleval::evaluator evaluator + { + { + booleval::make_field( "field_1", &bar< unsigned, std::string >::value_1 ), + booleval::make_field( "field_2", &bar< unsigned, std::string >::value_2 ) + } + }; -TEST_F(EvaluatorTest, MultipleOperators) { - multi_obj foo{ "one", 1 }; - multi_obj bar{ "one", 2 }; - multi_obj baz{ "two", 1 }; - multi_obj qux{ "two", 2 }; - - booleval::evaluator<> evaluator({ - { "field_a", &multi_obj::value_a }, - { "field_b", &multi_obj::value_b } - }); - - EXPECT_TRUE(evaluator.expression("(field_a one and field_b 1) or (field_a two and field_b 2)")); - EXPECT_TRUE(evaluator.is_activated()); - EXPECT_TRUE(evaluator.evaluate(foo)); - EXPECT_FALSE(evaluator.evaluate(bar)); - EXPECT_FALSE(evaluator.evaluate(baz)); - EXPECT_TRUE(evaluator.evaluate(qux)); + { + ASSERT_TRUE ( evaluator.expression( "field_1 1 or field_1 2" ) ); + ASSERT_TRUE ( evaluator.is_activated() ); + ASSERT_TRUE ( evaluator.evaluate( x ).success ); + ASSERT_FALSE( evaluator.evaluate( y ).success ); + } + { + ASSERT_TRUE ( evaluator.expression( "field_1 eq 1 or field_1 eq 2" ) ); + ASSERT_TRUE ( evaluator.is_activated() ); + ASSERT_TRUE ( evaluator.evaluate( x ).success ); + ASSERT_FALSE( evaluator.evaluate( y ).success ); + } + { + ASSERT_TRUE ( evaluator.expression( "field_1 == 1 or field_1 == 2" ) ); + ASSERT_TRUE ( evaluator.is_activated() ); + ASSERT_TRUE ( evaluator.evaluate( x ).success ); + ASSERT_FALSE( evaluator.evaluate( y ).success ); + } + { + ASSERT_TRUE ( evaluator.expression( "field_1 1 || field_1 2" ) ); + ASSERT_TRUE ( evaluator.is_activated() ); + ASSERT_TRUE ( evaluator.evaluate( x ).success ); + ASSERT_FALSE( evaluator.evaluate( y ).success ); + } + { + ASSERT_TRUE ( evaluator.expression( "field_1 eq 1 || field_1 eq 2" ) ); + ASSERT_TRUE ( evaluator.is_activated() ); + ASSERT_TRUE ( evaluator.evaluate( x ).success ); + ASSERT_FALSE( evaluator.evaluate( y ).success ); + } + { + ASSERT_TRUE ( evaluator.expression( "field_1 == 1 || field_1 == 2" ) ); + ASSERT_TRUE ( evaluator.is_activated() ); + ASSERT_TRUE ( evaluator.evaluate( x ).success ); + ASSERT_FALSE( evaluator.evaluate( y ).success ); + } + { + ASSERT_TRUE( evaluator.expression( "field_1 == 1 || field_2 == \"bar bar\"" ) ); + ASSERT_TRUE( evaluator.is_activated() ); + ASSERT_TRUE( evaluator.evaluate( x ).success ); + ASSERT_TRUE( evaluator.evaluate( y ).success ); + } + { + ASSERT_TRUE( evaluator.expression( "field_1 == 3 || field_2 == bar" ) ); + ASSERT_TRUE( evaluator.is_activated() ); + ASSERT_TRUE( evaluator.evaluate( x ).success ); + ASSERT_TRUE( evaluator.evaluate( y ).success ); + } } -TEST_F(EvaluatorTest, FieldsFromDifferentClasses) { - obj foo{ "one" }; - multi_obj bar{ "two", 2 }; - - booleval::evaluator<> evaluator({ - { "field_a", &obj::value_a }, - { "field_b", &multi_obj::value_b } - }); +TEST( EvaluatorTest, MultipleOperators ) +{ + bar< std::string, unsigned > x{ "foo", 1 }; + bar< std::string, unsigned > y{ "bar", 2 }; + bar< std::string, unsigned > m{ "baz", 1 }; + bar< std::string, unsigned > n{ "qux", 2 }; + + booleval::evaluator evaluator + { + { + booleval::make_field( "field_1", &bar< std::string, unsigned >::value_1 ), + booleval::make_field( "field_2", &bar< std::string, unsigned >::value_2 ) + } + }; - EXPECT_TRUE(evaluator.expression("field_a one and field_b 2")); - EXPECT_TRUE(evaluator.is_activated()); - EXPECT_FALSE(evaluator.evaluate(foo)); - EXPECT_FALSE(evaluator.evaluate(bar)); + { + ASSERT_TRUE ( evaluator.expression( "(field_1 foo and field_2 1)" ) ); + ASSERT_TRUE ( evaluator.is_activated() ); + ASSERT_TRUE ( evaluator.evaluate( x ).success ); + ASSERT_FALSE( evaluator.evaluate( y ).success ); + ASSERT_FALSE( evaluator.evaluate( m ).success ); + ASSERT_FALSE( evaluator.evaluate( n ).success ); + } + { + ASSERT_TRUE ( evaluator.expression( "field_1 foo and field_2 1 and field_1 bar" ) ); + ASSERT_TRUE ( evaluator.is_activated() ); + ASSERT_FALSE( evaluator.evaluate( x ).success ); + ASSERT_FALSE( evaluator.evaluate( y ).success ); + ASSERT_FALSE( evaluator.evaluate( m ).success ); + ASSERT_FALSE( evaluator.evaluate( n ).success ); + } + { + ASSERT_TRUE ( evaluator.expression( "(field_1 foo or field_1 bar) and (field_2 2 or field_2 1)" ) ); + ASSERT_TRUE ( evaluator.is_activated() ); + ASSERT_TRUE ( evaluator.evaluate( x ).success ); + ASSERT_TRUE ( evaluator.evaluate( y ).success ); + ASSERT_FALSE( evaluator.evaluate( m ).success ); + ASSERT_FALSE( evaluator.evaluate( n ).success ); + } + { + ASSERT_TRUE ( evaluator.expression( "(field_1 foo and field_2 1) or (field_1 qux and field_2 2)" ) ); + ASSERT_TRUE ( evaluator.is_activated() ); + ASSERT_TRUE ( evaluator.evaluate( x ).success ); + ASSERT_FALSE( evaluator.evaluate( y ).success ); + ASSERT_FALSE( evaluator.evaluate( m ).success ); + ASSERT_TRUE ( evaluator.evaluate( n ).success ); + } } -TEST_F(EvaluatorTest, NonExistantField) { - obj foo{ "one" }; - - booleval::evaluator<> evaluator({ - { "field_a", &obj::value_a } - }); +TEST( EvaluatorTest, DifferentClasses ) +{ + foo< unsigned > x{ 1 }; + bar< unsigned, std::string > y{ 2, "bar" }; + + booleval::evaluator evaluator + { + { + booleval::make_field( "field_1", &foo< unsigned >::value ), + booleval::make_field( "field_2", &bar< unsigned, std::string >::value_2 ) + } + }; - EXPECT_TRUE(evaluator.expression("field_not_exist one")); - EXPECT_TRUE(evaluator.is_activated()); - try { - [[maybe_unused]] auto result = evaluator.evaluate(foo); - FAIL() << "Expected booleval::field_not_found"; - } catch (booleval::field_not_found const& ex) { - EXPECT_EQ(ex.what(), std::string("Field 'field_not_exist' not found")); + { + ASSERT_TRUE ( evaluator.expression( "field_1 one and field_2 2" ) ); + ASSERT_TRUE ( evaluator.is_activated() ); + ASSERT_FALSE( evaluator.evaluate( x ).success ); + ASSERT_FALSE( evaluator.evaluate( y ).success ); } } -TEST_F(EvaluatorTest, FieldNotValid) { - obj foo{ "foo" }; +TEST( EvaluatorTest, UnknownField ) +{ + foo< unsigned > x{ 1 }; - booleval::evaluator evaluator({ - { "field_a_valid", &obj::value_a_valid }, - { "field_a_not_valid",& obj::value_a_notvalid } - }); + booleval::evaluator evaluator + { + booleval::make_field( "field", &foo< unsigned >::value ) + }; - EXPECT_TRUE(evaluator.expression("field_a_valid foo")); - EXPECT_TRUE(evaluator.is_activated()); - EXPECT_TRUE(evaluator.evaluate(foo)); + { + ASSERT_TRUE ( evaluator.expression( "unknown_field 1" ) ); + ASSERT_TRUE ( evaluator.is_activated() ); - EXPECT_TRUE(evaluator.expression("field_a_not_valid foo")); - EXPECT_TRUE(evaluator.is_activated()); - EXPECT_FALSE(evaluator.evaluate(foo)); + auto const result{ evaluator.evaluate( x ) }; + ASSERT_FALSE( result.success ); + ASSERT_EQ ( result.message, "Unknown field" ); + } } diff --git a/tests/src/token/token_test.cpp b/tests/src/token/token_test.cpp index b15a78b..877a22e 100644 --- a/tests/src/token/token_test.cpp +++ b/tests/src/token/token_test.cpp @@ -28,121 +28,118 @@ */ #include -#include - -class TokenTest : public testing::Test {}; -TEST_F(TokenTest, DefaultConstructor) { - using namespace booleval; +#include - token::token token; - EXPECT_EQ(token.type(), token::token_type::unknown); - EXPECT_EQ(token.value(), ""); +TEST( TokenTest, DefaultConstructor ) +{ + booleval::token::token token{}; + ASSERT_EQ( token.type(), booleval::token::token_type::unknown ); + ASSERT_EQ( token.value(), "" ); } -TEST_F(TokenTest, ConstructorFromType) { - using namespace booleval; - - token::token token(token::token_type::logical_and); - EXPECT_EQ(token.type(), token::token_type::logical_and); - EXPECT_TRUE(token.value() == "and" || - token.value() == "AND"); +TEST( TokenTest, ConstructorFromType ) +{ + booleval::token::token token{ booleval::token::token_type::logical_and }; + ASSERT_EQ( token.type(), booleval::token::token_type::logical_and ); + ASSERT_TRUE + ( + token.value() == "and" || + token.value() == "AND" + ); } -TEST_F(TokenTest, ConstructorFromValue) { - using namespace booleval; - - token::token token("and"); - EXPECT_EQ(token.type(), token::token_type::logical_and); - EXPECT_TRUE(token.value() == "and" || - token.value() == "AND"); +TEST( TokenTest, ConstructorFromValue ) +{ + booleval::token::token token("and"); + ASSERT_EQ( token.type(), booleval::token::token_type::logical_and ); + ASSERT_TRUE + ( + token.value() == "and" || + token.value() == "AND" + ); } -TEST_F(TokenTest, ConstructorFromTypeAndValue) { +TEST( TokenTest, ConstructorFromTypeAndValue ) +{ using namespace booleval; - token::token token(token::token_type::field, "field"); - EXPECT_EQ(token.type(), token::token_type::field); - EXPECT_EQ(token.value(), "field"); + booleval::token::token token{ booleval::token::token_type::field, "field" }; + ASSERT_EQ( token.type(), booleval::token::token_type::field ); + ASSERT_EQ( token.value(), "field" ); } -TEST_F(TokenTest, Type) { +TEST( TokenTest, Type ) +{ using namespace booleval; - token::token token; - token.type(token::token_type::logical_and); - EXPECT_EQ(token.type(), token::token_type::logical_and); + booleval::token::token token{}; + token.type( booleval::token::token_type::logical_and ); + ASSERT_EQ( token.type(), booleval::token::token_type::logical_and ); } -TEST_F(TokenTest, Value) { - using namespace booleval; - - token::token token; - token.value("field"); - EXPECT_EQ(token.value(), "field"); +TEST( TokenTest, Value ) +{ + booleval::token::token token{}; + token.value( "field" ); + ASSERT_EQ( token.value(), "field" ); } -TEST_F(TokenTest, ValueAsInt) { - using namespace booleval; - - token::token token; - token.value("1"); - EXPECT_EQ(token.value(), 1U); - - token.value("a"); - EXPECT_EQ(token.value(), std::nullopt); +TEST( TokenTest, IsType ) +{ + booleval::token::token token{ booleval::token::token_type::logical_and }; + ASSERT_TRUE ( token.is( booleval::token::token_type::logical_and ) ); + ASSERT_FALSE( token.is( booleval::token::token_type::logical_or ) ); } -TEST_F(TokenTest, ValueAsDouble) { - using namespace booleval; - - token::token token; - token.value("1.23456789"); - EXPECT_DOUBLE_EQ(token.value().value(), 1.23456789); - - token.value("a"); - EXPECT_EQ(token.value(), std::nullopt); +TEST( TokenTest, IsNotType ) +{ + booleval::token::token token{ booleval::token::token_type::logical_and }; + ASSERT_TRUE ( token.is_not( booleval::token::token_type::logical_or ) ); + ASSERT_FALSE( token.is_not( booleval::token::token_type::logical_and ) ); } -TEST_F(TokenTest, ValueAsFloat) { - using namespace booleval; - - token::token token; - token.value("1.23456789"); - EXPECT_FLOAT_EQ(token.value().value(), 1.23456789F); - - token.value("a"); - EXPECT_EQ(token.value(), std::nullopt); -} - -TEST_F(TokenTest, IsType) { - using namespace booleval; - - token::token token(token::token_type::logical_and); - EXPECT_TRUE(token.is(token::token_type::logical_and)); - EXPECT_FALSE(token.is(token::token_type::logical_or)); -} - -TEST_F(TokenTest, IsNotType) { - using namespace booleval; - - token::token token(token::token_type::logical_and); - EXPECT_TRUE(token.is_not(token::token_type::logical_or)); - EXPECT_FALSE(token.is_not(token::token_type::logical_and)); -} - -TEST_F(TokenTest, IsOneOfTwoTypes) { - using namespace booleval; - - token::token token(token::token_type::logical_and); - EXPECT_TRUE(token.is_one_of(token::token_type::logical_and, token::token_type::logical_or)); - EXPECT_FALSE(token.is_one_of(token::token_type::lp, token::token_type::rp)); +TEST( TokenTest, IsOneOfTwoTypes ) +{ + booleval::token::token token{ booleval::token::token_type::logical_and }; + ASSERT_TRUE + ( + token.is_one_of + ( + booleval::token::token_type::logical_and, + booleval::token::token_type::logical_or + ) + ); + ASSERT_FALSE + ( + token.is_one_of + ( + booleval::token::token_type::lp, + booleval::token::token_type::rp + ) + ); } -TEST_F(TokenTest, IsOneOfMoreThanTwoTypes) { - using namespace booleval; - - token::token token(token::token_type::logical_and); - EXPECT_TRUE(token.is_one_of(token::token_type::logical_and, token::token_type::lp, token::token_type::rp)); - EXPECT_FALSE(token.is_one_of(token::token_type::logical_or, token::token_type::lp, token::token_type::rp)); +TEST( TokenTest, IsOneOfMoreTypes ) +{ + booleval::token::token token{ booleval::token::token_type::logical_and }; + ASSERT_TRUE + ( + token.is_one_of + ( + booleval::token::token_type::logical_and, + booleval::token::token_type::lp, + booleval::token::token_type::rp + ) + ); + + ASSERT_FALSE + ( + token.is_one_of + ( + booleval::token::token_type::logical_or, + booleval::token::token_type::lp, + booleval::token::token_type::rp + ) + ); } \ No newline at end of file diff --git a/tests/src/token/tokenizer_test.cpp b/tests/src/token/tokenizer_test.cpp index 0db9055..54f762d 100644 --- a/tests/src/token/tokenizer_test.cpp +++ b/tests/src/token/tokenizer_test.cpp @@ -29,193 +29,108 @@ #include #include + #include #include -class TokenizerTest : public testing::Test {}; - -TEST_F(TokenizerTest, ConstructorFromExpression) { - using namespace booleval; - - std::string_view expression{ "field_a foo" }; - token::tokenizer tokenizer(expression); - EXPECT_EQ(tokenizer.expression(), expression); - EXPECT_FALSE(tokenizer.has_tokens()); +TEST( TokenizerTest, EmptyExpression ) +{ + ASSERT_TRUE( booleval::token::tokenize( "" ).empty() ); + ASSERT_TRUE( booleval::token::tokenize( " " ).empty() ); + ASSERT_TRUE( booleval::token::tokenize( " " ).empty() ); } -TEST_F(TokenizerTest, Expression) { - using namespace booleval; +TEST( TokenizerTest, KeywordAsFieldExpression ) +{ + auto const tokens{ booleval::token::tokenize( "field_a \"and\"" ) }; + ASSERT_FALSE( tokens.empty() ); - std::string_view expression{ "field_a foo" }; - token::tokenizer tokenizer; - tokenizer.expression(expression); - EXPECT_EQ(tokenizer.expression(), expression); + ASSERT_TRUE( tokens[ 0 ].is( booleval::token::token_type::field ) ); + ASSERT_TRUE( tokens[ 1 ].is( booleval::token::token_type::eq ) ); + ASSERT_TRUE( tokens[ 2 ].is( booleval::token::token_type::field ) ); + ASSERT_EQ ( tokens[ 2 ].value(), "and" ); } -TEST_F(TokenizerTest, TokenizeEmptyExpression) { - using namespace booleval; +TEST( TokenizerTest, LowercaseKeywordExpression ) +{ + auto const tokens{ booleval::token::tokenize( "(field_a foo and field_b neq bar) or field_c eq baz" ) }; + ASSERT_FALSE( tokens.empty() ); - std::string_view expression; + ASSERT_TRUE( tokens[ 0 ].is( booleval::token::token_type::lp ) ); + ASSERT_TRUE( tokens[ 1 ].is( booleval::token::token_type::field ) ); + ASSERT_TRUE( tokens[ 2 ].is( booleval::token::token_type::eq ) ); - token::tokenizer tokenizer; - tokenizer.expression(expression); - EXPECT_EQ(tokenizer.expression(), expression); - - tokenizer.tokenize(); - EXPECT_FALSE(tokenizer.has_tokens()); -} + ASSERT_TRUE( tokens[ 3 ].is( booleval::token::token_type::field ) ); + ASSERT_EQ ( tokens[ 3 ].value(), "foo" ); -TEST_F(TokenizerTest, TokenizeKeywordAsFieldExpression) { - using namespace booleval; + ASSERT_TRUE( tokens[ 4 ].is( booleval::token::token_type::logical_and ) ); + ASSERT_TRUE( tokens[ 5 ].is( booleval::token::token_type::field ) ); + ASSERT_TRUE( tokens[ 6 ].is( booleval::token::token_type::neq ) ); - std::string_view expression{ "field_a \"and\"" }; + ASSERT_TRUE( tokens[ 7 ].is( booleval::token::token_type::field ) ); + ASSERT_EQ ( tokens[ 7 ].value(), "bar" ); - token::tokenizer tokenizer; - tokenizer.expression(expression); - EXPECT_EQ(tokenizer.expression(), expression); + ASSERT_TRUE( tokens[ 8 ].is( booleval::token::token_type::rp ) ); + ASSERT_TRUE( tokens[ 9 ].is( booleval::token::token_type::logical_or ) ); + ASSERT_TRUE( tokens[ 10 ].is( booleval::token::token_type::field ) ); + ASSERT_TRUE( tokens[ 11 ].is( booleval::token::token_type::eq ) ); - tokenizer.tokenize(); - EXPECT_TRUE(tokenizer.has_tokens()); - - EXPECT_TRUE(tokenizer.next_token().is(token::token_type::field)); - EXPECT_TRUE(tokenizer.next_token().is(token::token_type::eq)); - - EXPECT_TRUE(tokenizer.weak_next_token().is(token::token_type::field)); - EXPECT_EQ(tokenizer.next_token().value(), "and"); + ASSERT_TRUE( tokens[ 12 ].is( booleval::token::token_type::field ) ); + ASSERT_EQ ( tokens[ 12 ].value(), "baz" ); } -TEST_F(TokenizerTest, TokenizeLowercaseKeywordBasedExpression) { - using namespace booleval; - - std::string_view expression{ "(field_a foo and field_b neq bar) or field_c eq baz" }; - - token::tokenizer tokenizer; - tokenizer.expression(expression); - EXPECT_EQ(tokenizer.expression(), expression); +TEST( TokenizerTest, UppercaseKeywordExpression ) +{ + auto const tokens{ booleval::token::tokenize( "(field_a foo AND field_b NEQ bar) OR field_c EQ baz" ) }; + ASSERT_FALSE( tokens.empty() ); - tokenizer.tokenize(); - EXPECT_TRUE(tokenizer.has_tokens()); + ASSERT_TRUE( tokens[ 0 ].is( booleval::token::token_type::lp ) ); + ASSERT_TRUE( tokens[ 1 ].is( booleval::token::token_type::field ) ); + ASSERT_TRUE( tokens[ 2 ].is( booleval::token::token_type::eq ) ); - EXPECT_TRUE(tokenizer.next_token().is(token::token_type::lp)); - EXPECT_TRUE(tokenizer.next_token().is(token::token_type::field)); - EXPECT_TRUE(tokenizer.next_token().is(token::token_type::eq)); + ASSERT_TRUE( tokens[ 3 ].is( booleval::token::token_type::field ) ); + ASSERT_EQ ( tokens[ 3 ].value(), "foo" ); - EXPECT_TRUE(tokenizer.weak_next_token().is(token::token_type::field)); - EXPECT_EQ(tokenizer.next_token().value(), "foo"); + ASSERT_TRUE( tokens[ 4 ].is( booleval::token::token_type::logical_and ) ); + ASSERT_TRUE( tokens[ 5 ].is( booleval::token::token_type::field ) ); + ASSERT_TRUE( tokens[ 6 ].is( booleval::token::token_type::neq ) ); - EXPECT_TRUE(tokenizer.next_token().is(token::token_type::logical_and)); - EXPECT_TRUE(tokenizer.next_token().is(token::token_type::field)); - EXPECT_TRUE(tokenizer.next_token().is(token::token_type::neq)); + ASSERT_TRUE( tokens[ 7 ].is( booleval::token::token_type::field ) ); + ASSERT_EQ ( tokens[ 7 ].value(), "bar" ); - EXPECT_TRUE(tokenizer.weak_next_token().is(token::token_type::field)); - EXPECT_EQ(tokenizer.next_token().value(), "bar"); + ASSERT_TRUE( tokens[ 8 ].is( booleval::token::token_type::rp ) ); + ASSERT_TRUE( tokens[ 9 ].is( booleval::token::token_type::logical_or ) ); + ASSERT_TRUE( tokens[ 10 ].is( booleval::token::token_type::field ) ); + ASSERT_TRUE( tokens[ 11 ].is( booleval::token::token_type::eq ) ); - EXPECT_TRUE(tokenizer.next_token().is(token::token_type::rp)); - EXPECT_TRUE(tokenizer.next_token().is(token::token_type::logical_or)); - EXPECT_TRUE(tokenizer.next_token().is(token::token_type::field)); - EXPECT_TRUE(tokenizer.next_token().is(token::token_type::eq)); - - EXPECT_TRUE(tokenizer.weak_next_token().is(token::token_type::field)); - EXPECT_EQ(tokenizer.next_token().value(), "baz"); + ASSERT_TRUE( tokens[ 12 ].is( booleval::token::token_type::field ) ); + ASSERT_EQ ( tokens[ 12 ].value(), "baz" ); } -TEST_F(TokenizerTest, TokenizeUppercaseKeywordBasedExpression) { - using namespace booleval; - - std::string_view expression{ "(field_a foo AND field_b NEQ bar) OR field_c EQ baz" }; - - token::tokenizer tokenizer; - tokenizer.expression(expression); - EXPECT_EQ(tokenizer.expression(), expression); - - tokenizer.tokenize(); - EXPECT_TRUE(tokenizer.has_tokens()); - - EXPECT_TRUE(tokenizer.next_token().is(token::token_type::lp)); - EXPECT_TRUE(tokenizer.next_token().is(token::token_type::field)); - EXPECT_TRUE(tokenizer.next_token().is(token::token_type::eq)); - - EXPECT_TRUE(tokenizer.weak_next_token().is(token::token_type::field)); - EXPECT_EQ(tokenizer.next_token().value(), "foo"); - - EXPECT_TRUE(tokenizer.next_token().is(token::token_type::logical_and)); - EXPECT_TRUE(tokenizer.next_token().is(token::token_type::field)); - EXPECT_TRUE(tokenizer.next_token().is(token::token_type::neq)); - - EXPECT_TRUE(tokenizer.weak_next_token().is(token::token_type::field)); - EXPECT_EQ(tokenizer.next_token().value(), "bar"); - - EXPECT_TRUE(tokenizer.next_token().is(token::token_type::rp)); - EXPECT_TRUE(tokenizer.next_token().is(token::token_type::logical_or)); - EXPECT_TRUE(tokenizer.next_token().is(token::token_type::field)); - EXPECT_TRUE(tokenizer.next_token().is(token::token_type::eq)); - - EXPECT_TRUE(tokenizer.weak_next_token().is(token::token_type::field)); - EXPECT_EQ(tokenizer.next_token().value(), "baz"); -} - -TEST_F(TokenizerTest, TokenizeSymbolBasedExpression) { - using namespace booleval; - - std::string_view expression{ "(field_a foo && field_b != bar) || field_c > baz" }; - - token::tokenizer tokenizer; - tokenizer.expression(expression); - EXPECT_EQ(tokenizer.expression(), expression); - - tokenizer.tokenize(); - EXPECT_TRUE(tokenizer.has_tokens()); - - EXPECT_TRUE(tokenizer.next_token().is(token::token_type::lp)); - EXPECT_TRUE(tokenizer.next_token().is(token::token_type::field)); - EXPECT_TRUE(tokenizer.next_token().is(token::token_type::eq)); - - EXPECT_TRUE(tokenizer.weak_next_token().is(token::token_type::field)); - EXPECT_EQ(tokenizer.next_token().value(), "foo"); - - EXPECT_TRUE(tokenizer.next_token().is(token::token_type::logical_and)); - EXPECT_TRUE(tokenizer.next_token().is(token::token_type::field)); - EXPECT_TRUE(tokenizer.next_token().is(token::token_type::neq)); - - EXPECT_TRUE(tokenizer.weak_next_token().is(token::token_type::field)); - EXPECT_EQ(tokenizer.next_token().value(), "bar"); - - EXPECT_TRUE(tokenizer.next_token().is(token::token_type::rp)); - EXPECT_TRUE(tokenizer.next_token().is(token::token_type::logical_or)); - EXPECT_TRUE(tokenizer.next_token().is(token::token_type::field)); - EXPECT_TRUE(tokenizer.next_token().is(token::token_type::gt)); - - EXPECT_TRUE(tokenizer.weak_next_token().is(token::token_type::field)); - EXPECT_EQ(tokenizer.next_token().value(), "baz"); -} - -TEST_F(TokenizerTest, Reset) { - using namespace booleval; - - std::string_view expression{ "field_a foo" }; - token::tokenizer tokenizer; - tokenizer.expression(expression); - EXPECT_EQ(tokenizer.expression(), expression); - - tokenizer.tokenize(); - EXPECT_TRUE(tokenizer.has_tokens()); - - EXPECT_TRUE(tokenizer.weak_next_token().is(token::token_type::field)); - EXPECT_EQ(tokenizer.next_token().value(), "field_a"); +TEST( TokenizerTest, SymbolExpression ) +{ + auto const tokens{ booleval::token::tokenize( "(field_a foo && field_b != bar) || field_c > baz" ) }; + ASSERT_FALSE( tokens.empty() ); - EXPECT_TRUE(tokenizer.next_token().is(token::token_type::eq)); + ASSERT_TRUE( tokens[ 0 ].is( booleval::token::token_type::lp ) ); + ASSERT_TRUE( tokens[ 1 ].is( booleval::token::token_type::field ) ); + ASSERT_TRUE( tokens[ 2 ].is( booleval::token::token_type::eq ) ); - EXPECT_TRUE(tokenizer.weak_next_token().is(token::token_type::field)); - EXPECT_EQ(tokenizer.next_token().value(), "foo"); + ASSERT_TRUE( tokens[ 3 ].is( booleval::token::token_type::field ) ); + ASSERT_EQ ( tokens[ 3 ].value(), "foo" ); - tokenizer.reset(); - EXPECT_TRUE(tokenizer.has_tokens()); + ASSERT_TRUE( tokens[ 4 ].is( booleval::token::token_type::logical_and ) ); + ASSERT_TRUE( tokens[ 5 ].is( booleval::token::token_type::field ) ); + ASSERT_TRUE( tokens[ 6 ].is( booleval::token::token_type::neq ) ); - EXPECT_TRUE(tokenizer.weak_next_token().is(token::token_type::field)); - EXPECT_EQ(tokenizer.next_token().value(), "field_a"); + ASSERT_TRUE( tokens[ 7 ].is( booleval::token::token_type::field ) ); + ASSERT_EQ ( tokens[ 7 ].value(), "bar" ); - EXPECT_TRUE(tokenizer.next_token().is(token::token_type::eq)); + ASSERT_TRUE( tokens[ 8 ].is( booleval::token::token_type::rp ) ); + ASSERT_TRUE( tokens[ 9 ].is( booleval::token::token_type::logical_or ) ); + ASSERT_TRUE( tokens[ 10 ].is( booleval::token::token_type::field ) ); + ASSERT_TRUE( tokens[ 11 ].is( booleval::token::token_type::gt ) ); - EXPECT_TRUE(tokenizer.weak_next_token().is(token::token_type::field)); - EXPECT_EQ(tokenizer.next_token().value(), "foo"); + ASSERT_TRUE( tokens[ 12 ].is( booleval::token::token_type::field ) ); + ASSERT_EQ ( tokens[ 12 ].value(), "baz" ); } diff --git a/tests/src/tree/expression_tree_test.cpp b/tests/src/tree/expression_tree_test.cpp deleted file mode 100644 index 8c4ef5d..0000000 --- a/tests/src/tree/expression_tree_test.cpp +++ /dev/null @@ -1,91 +0,0 @@ -/* - * Copyright (c) 2020, Marin Peko - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above - * copyright notice, this list of conditions and the following disclaimer - * in the documentation and/or other materials provided with the - * distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * - */ - -#include -#include - -class ExpressionTreeTest : public testing::Test {}; - -TEST_F(ExpressionTreeTest, DefaultConstructor) { - using namespace booleval; - - tree::expression_tree tree; - EXPECT_EQ(tree.root(), nullptr); -} - -TEST_F(ExpressionTreeTest, RelationalOperation) { - using namespace booleval; - - tree::expression_tree tree; - - EXPECT_FALSE(tree.build("field_a")); - EXPECT_EQ(tree.root(), nullptr); - - EXPECT_TRUE(tree.build("field_a foo")); - EXPECT_NE(tree.root(), nullptr); -} - -TEST_F(ExpressionTreeTest, AndOperation) { - using namespace booleval; - - tree::expression_tree tree; - - EXPECT_FALSE(tree.build("field_a foo and")); - EXPECT_EQ(tree.root(), nullptr); - - EXPECT_TRUE(tree.build("field_a foo and field_b bar")); - EXPECT_NE(tree.root(), nullptr); -} - -TEST_F(ExpressionTreeTest, OrOperation) { - using namespace booleval; - - tree::expression_tree tree; - - EXPECT_FALSE(tree.build("field_a foo or")); - EXPECT_EQ(tree.root(), nullptr); - - EXPECT_TRUE(tree.build("field_a foo or field_b bar")); - EXPECT_NE(tree.root(), nullptr); -} - -TEST_F(ExpressionTreeTest, Parentheses) { - using namespace booleval; - - tree::expression_tree tree; - - EXPECT_FALSE(tree.build("(field_a foo or field_b bar")); - EXPECT_EQ(tree.root(), nullptr); - - EXPECT_FALSE(tree.build("field_a foo or field_b bar)")); - EXPECT_EQ(tree.root(), nullptr); - - EXPECT_TRUE(tree.build("(field_a foo or field_b bar)")); - EXPECT_NE(tree.root(), nullptr); -} diff --git a/tests/src/tree/tree_node_test.cpp b/tests/src/tree/node_test.cpp similarity index 54% rename from tests/src/tree/tree_node_test.cpp rename to tests/src/tree/node_test.cpp index a7eb655..48bd94b 100644 --- a/tests/src/tree/tree_node_test.cpp +++ b/tests/src/tree/node_test.cpp @@ -28,47 +28,42 @@ */ #include -#include -#include -#include - -class TreeNodeTest : public testing::Test {}; - -TEST_F(TreeNodeTest, DefaultConstructor) { - using namespace booleval; - - tree::tree_node node; - EXPECT_EQ(node.token.type(), token::token_type::unknown); - EXPECT_EQ(node.left, nullptr); - EXPECT_EQ(node.right, nullptr); -} -TEST_F(TreeNodeTest, ConstructorFromTokenType) { - using namespace booleval; +#include +#include +#include - tree::tree_node node(token::token_type::logical_and); - EXPECT_EQ(node.token.type(), token::token_type::logical_and); - EXPECT_EQ(node.left, nullptr); - EXPECT_EQ(node.right, nullptr); +TEST( NodeTest, DefaultConstructor ) +{ + booleval::tree::node node{}; + ASSERT_EQ( node.token.type(), booleval::token::token_type::unknown ); + ASSERT_EQ( node.left , nullptr ); + ASSERT_EQ( node.right, nullptr ); } -TEST_F(TreeNodeTest, ConstructorFromToken) { - using namespace booleval; - - token::token and_token(token::token_type::logical_and); - tree::tree_node node(and_token); - EXPECT_EQ(node.token.type(), token::token_type::logical_and); - EXPECT_EQ(node.left, nullptr); - EXPECT_EQ(node.right, nullptr); +TEST( NodeTest, ConstructorFromTokenType ) +{ + booleval::tree::node node{ booleval::token::token_type::logical_and }; + ASSERT_EQ( node.token.type(), booleval::token::token_type::logical_and ); + ASSERT_EQ( node.left , nullptr ); + ASSERT_EQ( node.right, nullptr ); } -TEST_F(TreeNodeTest, ConstructorFromFieldToken) { - using namespace booleval; - - token::token field_token("foo"); - tree::tree_node node(field_token); - EXPECT_EQ(node.token.value(), "foo"); - EXPECT_EQ(node.token.type(), token::token_type::field); - EXPECT_EQ(node.left, nullptr); - EXPECT_EQ(node.right, nullptr); +TEST( NodeTest, ConstructorFromToken ) +{ + { + booleval::token::token and_token{ booleval::token::token_type::logical_and }; + booleval::tree::node node{ and_token }; + ASSERT_EQ( node.token.type(), booleval::token::token_type::logical_and ); + ASSERT_EQ( node.left , nullptr ); + ASSERT_EQ( node.right, nullptr ); + } + { + booleval::token::token field_token{ "foo" }; + booleval::tree::node node{ field_token }; + ASSERT_EQ( node.token.value(), "foo" ); + ASSERT_EQ( node.token.type(), booleval::token::token_type::field ); + ASSERT_EQ( node.left , nullptr ); + ASSERT_EQ( node.right, nullptr ); + } } diff --git a/tests/src/tree/result_visitor_test.cpp b/tests/src/tree/result_visitor_test.cpp index 2a205fe..3df4ed4 100644 --- a/tests/src/tree/result_visitor_test.cpp +++ b/tests/src/tree/result_visitor_test.cpp @@ -28,312 +28,306 @@ */ #include -#include +#include #include #include -class ResultVisitorTest : public testing::Test { -public: - template - class obj { +namespace +{ + + template< typename T > + class foo + { public: - obj() : value_a_{} {} - obj(T value) : value_a_{ value } {} - T value_a() const noexcept { return value_a_; } - T value_a_valid(bool& is_valid) const noexcept { is_valid = true; return value_a_; } - T value_a_notvalid(bool& is_valid) const noexcept { is_valid = false; return value_a_; } + foo() : value_{} {} + foo( T && value ) : value_{ value } {} + + void value( T && value ) { value_ = value; } + + T value() const noexcept { return value_; } private: - T value_a_; + T value_{}; }; - template - class multi_obj { + template< typename T, typename U > + class bar + { public: - multi_obj() : value_a_{}, value_b_{} {} - multi_obj(T value_a, U value_b) : value_a_{ value_a }, value_b_{ value_b } {} - T value_a() const noexcept { return value_a_; } - U value_b() const noexcept { return value_b_; } + bar( T && value_1, U && value_2 ) + : value_1_{ value_1 } + , value_2_{ value_2 } + {} + + void value_1( T && value ) { value_1_ = value; } + void value_2( U && value ) { value_2_ = value; } + + T value_1() const noexcept { return value_1_; } + U value_2() const noexcept { return value_2_; } private: - T value_a_; - U value_b_; + T value_1_{}; + U value_2_{}; }; - std::shared_ptr - make_tree_node(booleval::token::token_type const type) { - return std::make_shared(type); + std::unique_ptr< booleval::tree::node > make_tree_node( booleval::token::token_type const type ) noexcept + { + return std::make_unique< booleval::tree::node >( type ); } - std::shared_ptr - make_tree_node(booleval::token::token_type const type, std::string_view const value) { - booleval::token::token t(type, value); - return std::make_shared(t); + std::unique_ptr< booleval::tree::node > make_tree_node( booleval::token::token_type const type, std::string_view const value ) noexcept + { + return std::make_unique< booleval::tree::node >( booleval::token::token{ type, value } ); } -}; -TEST_F(ResultVisitorTest, VisitAndTreeNode) { - using namespace booleval; +} // namespace - multi_obj foo{ 1, 2 }; - multi_obj bar{ 2, 3 }; - multi_obj baz{ 3, 4 }; - - tree::result_visitor<> visitor; - visitor.fields({ - { "field_a", &multi_obj::value_a }, - { "field_b", &multi_obj::value_b } - }); +TEST( ResultVisitorTest, AndTreeNode ) +{ + using namespace booleval; - auto and_op = make_tree_node(token::token_type::logical_and); + bar< unsigned, unsigned > x{ 1, 2 }; + bar< unsigned, unsigned > y{ 2, 3 }; + bar< unsigned, unsigned > z{ 3, 4 }; - auto left = make_tree_node(token::token_type::field, "field_a"); - auto op = make_tree_node(token::token_type::eq); - auto right = make_tree_node(token::token_type::field, "1"); + tree::result_visitor visitor; - op->left = left; - op->right = right; + visitor.fields + ( + { + make_field( "field_1", &bar< unsigned, unsigned >::value_1 ), + make_field( "field_2", &bar< unsigned, unsigned >::value_2 ) + } + ); - and_op->left = op; + auto and_op{ make_tree_node( token::token_type::logical_and ) }; - left = make_tree_node(token::token_type::field, "field_b"); - op = make_tree_node(token::token_type::eq); - right = make_tree_node(token::token_type::field, "2"); + and_op->left = make_tree_node( token::token_type::eq ); + and_op->right = make_tree_node( token::token_type::eq ); - op->left = left; - op->right = right; + and_op->left->left = make_tree_node( token::token_type::field, "field_1" ); + and_op->left->right = make_tree_node( token::token_type::field, "1" ); - and_op->right = op; + and_op->right->left = make_tree_node( token::token_type::field, "field_2" ); + and_op->right->right = make_tree_node( token::token_type::field, "2" ); - EXPECT_TRUE(visitor.visit(*and_op, foo)); - EXPECT_FALSE(visitor.visit(*and_op, bar)); - EXPECT_FALSE(visitor.visit(*and_op, baz)); + ASSERT_TRUE ( visitor.visit( *and_op, x ).success ); + ASSERT_FALSE( visitor.visit( *and_op, y ).success ); + ASSERT_FALSE( visitor.visit( *and_op, z ).success ); } -TEST_F(ResultVisitorTest, VisitOrTreeNode) { +TEST( ResultVisitorTest, OrTreeNode ) +{ using namespace booleval; - obj foo{ 1 }; - obj bar{ 2 }; - obj baz{ 3 }; - - tree::result_visitor<> visitor; - visitor.fields({ - { "field_a", &obj::value_a } - }); - - auto or_op = make_tree_node(token::token_type::logical_or); - - auto left = make_tree_node(token::token_type::field, "field_a"); - auto op = make_tree_node(token::token_type::eq); - auto right = make_tree_node(token::token_type::field, "1"); + foo< unsigned > x{ 1 }; + foo< unsigned > y{ 2 }; + foo< unsigned > z{ 3 }; - op->left = left; - op->right = right; + tree::result_visitor visitor; + visitor.fields + ( + { + make_field( "field", &foo< unsigned >::value ) + } + ); - or_op->left = op; + auto or_op{ make_tree_node( token::token_type::logical_or ) }; - left = make_tree_node(token::token_type::field, "field_a"); - op = make_tree_node(token::token_type::eq); - right = make_tree_node(token::token_type::field, "2"); + or_op->left = make_tree_node( token::token_type::eq ); + or_op->right = make_tree_node( token::token_type::eq ); - op->left = left; - op->right = right; + or_op->left->left = make_tree_node( token::token_type::field, "field" ); + or_op->left->right = make_tree_node( token::token_type::field, "1" ); - or_op->right = op; + or_op->right->left = make_tree_node( token::token_type::field, "field" ); + or_op->right->right = make_tree_node( token::token_type::field, "2" ); - EXPECT_TRUE(visitor.visit(*or_op, foo)); - EXPECT_TRUE(visitor.visit(*or_op, bar)); - EXPECT_FALSE(visitor.visit(*or_op, baz)); + ASSERT_TRUE ( visitor.visit( *or_op, x ).success ); + ASSERT_TRUE ( visitor.visit( *or_op, y ).success ); + ASSERT_FALSE( visitor.visit( *or_op, z ).success ); } -TEST_F(ResultVisitorTest, VisitEqualToTreeNode) { +TEST( ResultVisitorTest, EqualToTreeNode ) +{ using namespace booleval; - obj foo{ 1 }; - obj bar{ 2 }; + foo< unsigned > x{ 1 }; + foo< unsigned > y{ 2 }; - tree::result_visitor<> visitor; - visitor.fields({ - { "field_a", &obj::value_a } - }); + tree::result_visitor visitor; + visitor.fields + ( + { + make_field( "field", &foo< unsigned >::value ) + } + ); - auto left = make_tree_node(token::token_type::field, "field_a"); - auto op = make_tree_node(token::token_type::eq); - auto right = make_tree_node(token::token_type::field, "1"); + auto eq_op{ make_tree_node( token::token_type::eq ) }; - op->left = left; - op->right = right; + eq_op->left = make_tree_node( token::token_type::field, "field" ); + eq_op->right = make_tree_node( token::token_type::field, "1" ); - EXPECT_TRUE(visitor.visit(*op, foo)); - EXPECT_FALSE(visitor.visit(*op, bar)); + ASSERT_TRUE ( visitor.visit( *eq_op, x ).success ); + ASSERT_FALSE( visitor.visit( *eq_op, y ).success ); } -TEST_F(ResultVisitorTest, VisitNotEqualToTreeNode) { +TEST( ResultVisitorTest, NotEqualToTreeNode ) +{ using namespace booleval; - obj foo{ 1 }; - obj bar{ 2 }; + foo< unsigned > x{ 1 }; + foo< unsigned > y{ 2 }; - tree::result_visitor<> visitor; - visitor.fields({ - { "field_a", &obj::value_a } - }); + tree::result_visitor visitor; + visitor.fields + ( + { + make_field( "field", &foo< unsigned >::value ) + } + ); - auto left = make_tree_node(token::token_type::field, "field_a"); - auto op = make_tree_node(token::token_type::neq); - auto right = make_tree_node(token::token_type::field, "1"); + auto neq_op{ make_tree_node( token::token_type::neq ) }; - op->left = left; - op->right = right; + neq_op->left = make_tree_node( token::token_type::field, "field" ); + neq_op->right = make_tree_node( token::token_type::field, "1" ); - EXPECT_FALSE(visitor.visit(*op, foo)); - EXPECT_TRUE(visitor.visit(*op, bar)); + ASSERT_FALSE( visitor.visit( *neq_op, x ).success ); + ASSERT_TRUE ( visitor.visit( *neq_op, y ).success ); } -TEST_F(ResultVisitorTest, VisitGreaterThanTreeNode) { +TEST( ResultVisitorTest, GreaterThanTreeNode ) +{ using namespace booleval; - obj foo{ 0 }; - obj bar{ 1 }; - obj baz{ 2 }; + foo< unsigned > x{ 0 }; + foo< unsigned > y{ 1 }; + foo< unsigned > z{ 2 }; - tree::result_visitor<> visitor; - visitor.fields({ - { "field_a", &obj::value_a } - }); + tree::result_visitor visitor; + visitor.fields + ( + { + make_field( "field", &foo< unsigned >::value ) + } + ); - auto left = make_tree_node(token::token_type::field, "field_a"); - auto op = make_tree_node(token::token_type::gt); - auto right = make_tree_node(token::token_type::field, "1"); + auto gt_op{ make_tree_node( token::token_type::gt ) }; - op->left = left; - op->right = right; + gt_op->left = make_tree_node( token::token_type::field, "field" ); + gt_op->right = make_tree_node( token::token_type::field, "1" ); - EXPECT_FALSE(visitor.visit(*op, foo)); - EXPECT_FALSE(visitor.visit(*op, bar)); - EXPECT_TRUE(visitor.visit(*op, baz)); + ASSERT_FALSE( visitor.visit( *gt_op, x ).success ); + ASSERT_FALSE( visitor.visit( *gt_op, y ).success ); + ASSERT_TRUE ( visitor.visit( *gt_op, z ).success ); } -TEST_F(ResultVisitorTest, VisitLessThanTreeNode) { +TEST( ResultVisitorTest, LessThanTreeNode ) +{ using namespace booleval; - obj foo{ 0 }; - obj bar{ 1 }; - obj baz{ 2 }; + foo< unsigned > x{ 0 }; + foo< unsigned > y{ 1 }; + foo< unsigned > z{ 2 }; - tree::result_visitor<> visitor; - visitor.fields({ - { "field_a", &obj::value_a } - }); + tree::result_visitor visitor; + visitor.fields + ( + { + make_field( "field", &foo< unsigned >::value ) + } + ); - auto left = make_tree_node(token::token_type::field, "field_a"); - auto op = make_tree_node(token::token_type::lt); - auto right = make_tree_node(token::token_type::field, "1"); + auto lt_op{ make_tree_node( token::token_type::lt ) }; - op->left = left; - op->right = right; + lt_op->left = make_tree_node( token::token_type::field, "field" ); + lt_op->right = make_tree_node( token::token_type::field, "1" ); - EXPECT_TRUE(visitor.visit(*op, foo)); - EXPECT_FALSE(visitor.visit(*op, bar)); - EXPECT_FALSE(visitor.visit(*op, baz)); + ASSERT_TRUE ( visitor.visit( *lt_op, x ).success ); + ASSERT_FALSE( visitor.visit( *lt_op, y ).success ); + ASSERT_FALSE( visitor.visit( *lt_op, z ).success ); } -TEST_F(ResultVisitorTest, VisitGreaterThanOrEqualTreeNode) { +TEST( ResultVisitorTest, GreaterThanOrEqualToTreeNode ) +{ using namespace booleval; - obj foo{ 0 }; - obj bar{ 1 }; - obj baz{ 2 }; + foo< unsigned > x{ 0 }; + foo< unsigned > y{ 1 }; + foo< unsigned > z{ 2 }; - tree::result_visitor<> visitor; - visitor.fields({ - { "field_a", &obj::value_a } - }); + tree::result_visitor visitor; + visitor.fields + ( + { + make_field( "field", &foo< unsigned >::value ) + } + ); - auto left = make_tree_node(token::token_type::field, "field_a"); - auto op = make_tree_node(token::token_type::geq); - auto right = make_tree_node(token::token_type::field, "1"); + auto geq_op{ make_tree_node( token::token_type::geq ) }; - op->left = left; - op->right = right; + geq_op->left = make_tree_node( token::token_type::field, "field" ); + geq_op->right = make_tree_node( token::token_type::field, "1" ); - EXPECT_FALSE(visitor.visit(*op, foo)); - EXPECT_TRUE(visitor.visit(*op, bar)); - EXPECT_TRUE(visitor.visit(*op, baz)); + ASSERT_FALSE( visitor.visit( *geq_op, x ).success ); + ASSERT_TRUE ( visitor.visit( *geq_op, y ).success ); + ASSERT_TRUE ( visitor.visit( *geq_op, z ).success ); } -TEST_F(ResultVisitorTest, VisitLessThanOrEqualTreeNode) { +TEST( ResultVisitorTest, LessThanOrEqualToTreeNode ) +{ using namespace booleval; - obj foo{ 0 }; - obj bar{ 1 }; - obj baz{ 2 }; + foo< unsigned > x{ 0 }; + foo< unsigned > y{ 1 }; + foo< unsigned > z{ 2 }; - tree::result_visitor<> visitor; - visitor.fields({ - { "field_a", &obj::value_a } - }); + tree::result_visitor visitor; + visitor.fields + ( + { + make_field( "field", &foo< unsigned >::value ) + } + ); - auto left = make_tree_node(token::token_type::field, "field_a"); - auto op = make_tree_node(token::token_type::leq); - auto right = make_tree_node(token::token_type::field, "1"); + auto leq_op{ make_tree_node( token::token_type::leq ) }; - op->left = left; - op->right = right; + leq_op->left = make_tree_node( token::token_type::field, "field" ); + leq_op->right = make_tree_node( token::token_type::field, "1" ); - EXPECT_TRUE(visitor.visit(*op, foo)); - EXPECT_TRUE(visitor.visit(*op, bar)); - EXPECT_FALSE(visitor.visit(*op, baz)); + ASSERT_TRUE ( visitor.visit( *leq_op, x ).success ); + ASSERT_TRUE ( visitor.visit( *leq_op, y ).success ); + ASSERT_FALSE( visitor.visit( *leq_op, z ).success ); } -TEST_F(ResultVisitorTest, VisitInvalidTreeNode) { +TEST( ResultVisitorTest, UnknownTreeNode ) +{ using namespace booleval; - obj foo{ 1 }; + foo< unsigned > x{ 1 }; + foo< unsigned > y{ 2 }; - tree::result_visitor visitor; - visitor.fields({ - { "field_a_valid", &obj::value_a_valid }, - { "field_a_notvalid", &obj::value_a_notvalid } - }); + tree::result_visitor visitor; + visitor.fields + ( + { + make_field( "field", &foo< unsigned >::value ) + } + ); - auto left = make_tree_node(token::token_type::field, "field_a_valid"); - auto op = make_tree_node(token::token_type::eq); - auto right = make_tree_node(token::token_type::field, "1"); + auto eq_op{ make_tree_node( token::token_type::eq ) }; - op->left = left; - op->right = right; + eq_op->left = make_tree_node( token::token_type::field, "unknown_field" ); + eq_op->right = make_tree_node( token::token_type::field, "1" ); - EXPECT_TRUE(visitor.visit(*op, foo)); - - left = make_tree_node(token::token_type::field, "field_a_notvalid"); - op->left = left; - - EXPECT_FALSE(visitor.visit(*op, foo)); -} - -TEST_F(ResultVisitorTest, VisitNonExistantTreeNode) { - using namespace booleval; - - obj foo{ 1 }; - - tree::result_visitor<> visitor; - visitor.fields({ - { "field_a", &obj::value_a } - }); - - auto left = make_tree_node(token::token_type::field, "field_not_exist"); - auto op = make_tree_node(token::token_type::eq); - auto right = make_tree_node(token::token_type::field, "1"); - - op->left = left; - op->right = right; - - try { - [[maybe_unused]] auto result = visitor.visit(*op, foo); - FAIL() << "Expected booleval::field_not_found"; - } catch (field_not_found const& ex) { - EXPECT_EQ(ex.what(), std::string("Field 'field_not_exist' not found")); + { + auto const result{ visitor.visit( *eq_op, x ) }; + ASSERT_FALSE( result.success ); + ASSERT_EQ ( result.message, "Unknown field" ); + } + { + auto const result{ visitor.visit( *eq_op, y ) }; + ASSERT_FALSE( result.success ); + ASSERT_EQ ( result.message, "Unknown field" ); } } diff --git a/tests/src/tree/tree_test.cpp b/tests/src/tree/tree_test.cpp new file mode 100644 index 0000000..99990aa --- /dev/null +++ b/tests/src/tree/tree_test.cpp @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2020, Marin Peko + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include + +#include + +TEST( TreeTest, RelationalOperation ) +{ + ASSERT_EQ( booleval::tree::build( "field_a" ), nullptr ); + + ASSERT_NE( booleval::tree::build( "field_a foo" ), nullptr ); +} + +TEST( TreeTest, AndOperation ) +{ + ASSERT_EQ( booleval::tree::build( "and" ), nullptr ); + ASSERT_EQ( booleval::tree::build( " and " ), nullptr ); + ASSERT_EQ( booleval::tree::build( "field_a foo and" ), nullptr ); + + ASSERT_NE( booleval::tree::build( "field_a foo and field_b bar" ), nullptr ); +} + +TEST( TreeTest, OrOperation ) +{ + ASSERT_EQ( booleval::tree::build( "or" ), nullptr ); + ASSERT_EQ( booleval::tree::build( " or " ), nullptr ); + ASSERT_EQ( booleval::tree::build( "field_a foo or" ), nullptr ); + ASSERT_EQ( booleval::tree::build( "or field_b bar" ), nullptr ); + + ASSERT_NE( booleval::tree::build( "field_a foo or field_b bar" ), nullptr ); +} + +TEST( TreeTest, Parentheses ) +{ + ASSERT_EQ( booleval::tree::build( "(field_a foo or field_b bar" ), nullptr ); + ASSERT_EQ( booleval::tree::build( "( field_a foo or field_b bar" ), nullptr ); + + // @todo: Fix following test cases + // ASSERT_EQ( booleval::tree::build( "field_a foo or field_b bar)" ), nullptr ); + // ASSERT_EQ( booleval::tree::build( "field_a foo or field_b bar )" ), nullptr ); + + ASSERT_NE( booleval::tree::build( "(field_a foo or field_b bar)" ), nullptr ); + ASSERT_NE( booleval::tree::build( "( field_a foo or field_b bar )" ), nullptr ); +} diff --git a/tests/src/utils/algo_utils_test.cpp b/tests/src/utils/algo_utils_test.cpp deleted file mode 100644 index db989d1..0000000 --- a/tests/src/utils/algo_utils_test.cpp +++ /dev/null @@ -1,130 +0,0 @@ -/* - * Copyright (c) 2020, Marin Peko - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above - * copyright notice, this list of conditions and the following disclaimer - * in the documentation and/or other materials provided with the - * distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * - */ - -#include -#include -#include - -class AlgoUtilsTest : public testing::Test {}; - -TEST_F(AlgoUtilsTest, Find) { - using namespace booleval; - - std::array arr{ 1, 2, 3, 4, 5 }; - - auto it = utils::find(std::begin(arr), std::end(arr), 1); - EXPECT_NE(it, std::end(arr)); - EXPECT_EQ(*it, 1); - - it = utils::find(std::begin(arr), std::end(arr), 3); - EXPECT_NE(it, std::end(arr)); - EXPECT_EQ(*it, 3); - - it = utils::find(std::begin(arr), std::end(arr), 5); - EXPECT_NE(it, std::end(arr)); - EXPECT_EQ(*it, 5); - - it = utils::find(std::begin(arr), std::end(arr), 9); - EXPECT_EQ(it, std::end(arr)); -} - -TEST_F(AlgoUtilsTest, FindIf) { - using namespace booleval; - - std::array arr{ 1, 2, 3, 4, 5 }; - - auto it = utils::find_if(std::begin(arr), std::end(arr), [](auto&& i) { return i == 1; }); - EXPECT_NE(it, std::end(arr)); - EXPECT_EQ(*it, 1); - - it = utils::find_if(std::begin(arr), std::end(arr), [](auto&& i) { return i == 3; }); - EXPECT_NE(it, std::end(arr)); - EXPECT_EQ(*it, 3); - - it = utils::find_if(std::begin(arr), std::end(arr), [](auto&& i) { return i == 5; }); - EXPECT_NE(it, std::end(arr)); - EXPECT_EQ(*it, 5); - - it = utils::find_if(std::begin(arr), std::end(arr), [](auto&& i) { return i == 9; }); - EXPECT_EQ(it, std::end(arr)); -} - -TEST_F(AlgoUtilsTest, FindIfNot) { - using namespace booleval; - - std::array arr{ 1, 2, 3, 4, 5 }; - - auto it = utils::find_if_not(std::begin(arr), std::end(arr), [](auto&& i) { return i == 2; }); - EXPECT_NE(it, std::end(arr)); - EXPECT_EQ(*it, 1); - - it = utils::find_if_not(std::begin(arr), std::end(arr), [](auto&& i) { return i < 3; }); - EXPECT_NE(it, std::end(arr)); - EXPECT_EQ(*it, 3); - - it = utils::find_if_not(std::begin(arr), std::end(arr), [](auto&& i) { return i < 5; }); - EXPECT_NE(it, std::end(arr)); - EXPECT_EQ(*it, 5); - - it = utils::find_if_not(std::begin(arr), std::end(arr), [](auto&& i) { return i > 0 && i < 6; }); - EXPECT_EQ(it, std::end(arr)); -} - -TEST_F(AlgoUtilsTest, Count) { - using namespace booleval; - - std::array arr{ 1, 2, 3, 4, 5 }; - - auto count = utils::count(std::begin(arr), std::end(arr), 6); - EXPECT_EQ(count, 0); - - arr = { 1, 2, 2, 2, 5 }; - count = utils::count(std::begin(arr), std::end(arr), 2); - EXPECT_EQ(count, 3); - - arr = { 2, 2, 2, 2, 2 }; - count = utils::count(std::begin(arr), std::end(arr), 2); - EXPECT_EQ(count, 5); -} - -TEST_F(AlgoUtilsTest, CountIf) { - using namespace booleval; - - std::array arr{ 1, 2, 3, 4, 5 }; - - auto count = utils::count_if(std::begin(arr), std::end(arr), [](auto&& i) { return i > 6; }); - EXPECT_EQ(count, 0); - - count = utils::count_if(std::begin(arr), std::end(arr), [](auto&& i) { return i < 6; }); - EXPECT_EQ(count, 5); - - arr = { 1, 2, 2, 2, 5 }; - count = utils::count_if(std::begin(arr), std::end(arr), [](auto&& i) { return i == 2; }); - EXPECT_EQ(count, 3); -} diff --git a/tests/src/utils/algorithm_test.cpp b/tests/src/utils/algorithm_test.cpp new file mode 100644 index 0000000..b70c618 --- /dev/null +++ b/tests/src/utils/algorithm_test.cpp @@ -0,0 +1,80 @@ +/* + * Copyright (c) 2020, Marin Peko + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#include +#include +#include + +TEST( AlgoUtilsTest, FindIf ) +{ + using namespace booleval; + + std::array arr{ 1, 2, 3, 4, 5 }; + + { + auto const it{ utils::find_if( std::begin( arr ), std::end( arr ), []( auto && i ) { return i == 1; } ) }; + ASSERT_NE( it, std::end( arr ) ); + ASSERT_EQ( *it, 1 ); + } + { + auto const it{ utils::find_if( std::begin( arr ), std::end( arr ), []( auto && i ) { return i == 3; } ) }; + ASSERT_NE( it, std::end( arr ) ); + ASSERT_EQ( *it, 3 ); + } + { + auto const it{ utils::find_if( std::begin( arr ), std::end( arr ), []( auto && i ) { return i == 5; } ) }; + ASSERT_NE( it, std::end( arr ) ); + ASSERT_EQ( *it, 5 ); + } + { + auto const it{ utils::find_if( std::begin( arr ), std::end( arr ), []( auto && i ) { return i == 9; } ) }; + ASSERT_EQ( it, std::end( arr ) ); + } +} + +TEST( AlgoUtilsTest, CountIf ) +{ + using namespace booleval; + + std::array arr{ 1, 2, 3, 4, 5 }; + + { + auto const count{ utils::count_if( std::begin( arr ), std::end( arr ), []( auto && i ) { return i > 6; } ) }; + ASSERT_EQ( count, 0 ); + } + { + auto const count{ utils::count_if( std::begin( arr ), std::end( arr ), []( auto && i ) { return i < 6; } ) }; + ASSERT_EQ( count, 5 ); + } + { + arr = { 1, 2, 2, 2, 5 }; + auto const count{ utils::count_if( std::begin( arr ), std::end( arr ), []( auto && i ) { return i == 2; } ) }; + ASSERT_EQ( count, 3 ); + } +} diff --git a/tests/src/utils/any_mem_fn_test.cpp b/tests/src/utils/any_mem_fn_test.cpp deleted file mode 100644 index 798a568..0000000 --- a/tests/src/utils/any_mem_fn_test.cpp +++ /dev/null @@ -1,123 +0,0 @@ -/* - * Copyright (c) 2020, Marin Peko - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above - * copyright notice, this list of conditions and the following disclaimer - * in the documentation and/or other materials provided with the - * distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * - */ - -#include -#include -#include -#include - -class AnyMemFnTest : public testing::Test { -public: - template - class obj { - public: - obj() : value_{} {} - obj(T value) : value_{ value } {} - T value() const noexcept { return value_; } - - private: - T value_; - }; -}; - -TEST_F(AnyMemFnTest, IntValue) { - using namespace booleval::utils; - - obj foo{ 1 }; - any_mem_fn fn{ &obj::value }; - - EXPECT_EQ(fn.invoke(foo), "1"); - EXPECT_EQ(fn.invoke(foo), 1U); -} - -TEST_F(AnyMemFnTest, FloatingPointValue) { - using namespace booleval::utils; - - obj foo{ 1.234567 }; - any_mem_fn fn{ &obj::value }; - - EXPECT_EQ(fn.invoke(foo), "1.234567"); - EXPECT_EQ(fn.invoke(foo), 1.234567F); -} - -TEST_F(AnyMemFnTest, StringValue) { - using namespace booleval::utils; - - obj foo{ "abc" }; - any_mem_fn fn{ &obj::value }; - - EXPECT_EQ(fn.invoke(foo), "abc"); -} - -TEST_F(AnyMemFnTest, StringViewValue) { - using namespace booleval::utils; - - obj foo{ "abc" }; - any_mem_fn fn{ &obj::value }; - - EXPECT_EQ(fn.invoke(foo), "abc"); -} - -TEST_F(AnyMemFnTest, IntComparisons) { - using namespace booleval::utils; - - obj foo{ 1 }; - any_mem_fn fn{ &obj::value }; - - EXPECT_TRUE(fn.invoke(foo) == 1U); - EXPECT_TRUE(fn.invoke(foo) != 2U); - - EXPECT_TRUE(fn.invoke(foo) > 0U); - EXPECT_TRUE(fn.invoke(foo) < 2U); - - EXPECT_TRUE(fn.invoke(foo) >= 0U); - EXPECT_TRUE(fn.invoke(foo) >= 1U); - - EXPECT_TRUE(fn.invoke(foo) <= 1U); - EXPECT_TRUE(fn.invoke(foo) <= 2U); -} - -TEST_F(AnyMemFnTest, FloatingPointComparisons) { - using namespace booleval::utils; - - obj foo{ 1.234567 }; - any_mem_fn fn{ &obj::value }; - - EXPECT_TRUE(fn.invoke(foo) == 1.234567F); - EXPECT_TRUE(fn.invoke(foo) != 7.654321F); - - EXPECT_TRUE(fn.invoke(foo) > 0.123456F); - EXPECT_TRUE(fn.invoke(foo) < 2.345678F); - - EXPECT_TRUE(fn.invoke(foo) >= 0.123456F); - EXPECT_TRUE(fn.invoke(foo) >= 1.234567F); - - EXPECT_TRUE(fn.invoke(foo) <= 1.234567F); - EXPECT_TRUE(fn.invoke(foo) <= 2.345678F); -} diff --git a/tests/src/utils/any_value_test.cpp b/tests/src/utils/any_value_test.cpp index 3380594..81fb01f 100644 --- a/tests/src/utils/any_value_test.cpp +++ b/tests/src/utils/any_value_test.cpp @@ -32,80 +32,64 @@ #include #include -class AnyValueTest : public testing::Test {}; - -TEST_F(AnyValueTest, IntValue) { - using namespace booleval::utils; - - any_value value{ 1 }; - EXPECT_EQ(value, "1"); - EXPECT_EQ(value, 1U); -} - -TEST_F(AnyValueTest, FloatingPointValue) { - using namespace booleval::utils; - - any_value value{ 1.234567 }; - EXPECT_EQ(value, "1.234567"); - EXPECT_EQ(value, 1.234567F); -} - -TEST_F(AnyValueTest, ConstCharPointerValue) { - using namespace booleval::utils; - - char const* ptr{ "abc" }; - any_value value{ ptr }; - EXPECT_EQ(value, ptr); +TEST( AnyValueTest, ArithmeticValue ) +{ + { + booleval::utils::any_value value{ 1 }; + ASSERT_EQ( value, "1" ); + } + { + booleval::utils::any_value value{ 1.234567f }; + ASSERT_EQ( value, "1.234567" ); + } } -TEST_F(AnyValueTest, StringValue) { - using namespace booleval::utils; - - std::string str{ "abc" }; - any_value value{ str }; - EXPECT_EQ(value, str); -} - -TEST_F(AnyValueTest, StringViewValue) { - using namespace booleval::utils; - - std::string_view strv{ "abc" }; - any_value value{ strv }; - EXPECT_EQ(value, strv); +TEST( AnyValueTest, StringValue ) +{ + { + booleval::utils::any_value value{ "abc" }; + ASSERT_EQ( value, "abc" ); + } + { + booleval::utils::any_value value{ std::string{ "abc" } }; + ASSERT_EQ( value, "abc" ); + } + { + booleval::utils::any_value value{ std::string_view{ "abc" } }; + ASSERT_EQ( value, "abc" ); + } } -TEST_F(AnyValueTest, IntComparisons) { - using namespace booleval::utils; +TEST( AnyValueTest, Comparisons ) +{ + { + booleval::utils::any_value value{ 1 }; - any_value value{ 1 }; + ASSERT_TRUE( value == "1U" ); + ASSERT_TRUE( value != "2U" ); - EXPECT_TRUE(value == 1U); - EXPECT_TRUE(value != 2U); - - EXPECT_TRUE(value > 0U); - EXPECT_TRUE(value < 2U); - - EXPECT_TRUE(value >= 0U); - EXPECT_TRUE(value >= 1U); - - EXPECT_TRUE(value <= 1U); - EXPECT_TRUE(value <= 2U); -} + ASSERT_TRUE( value > "0U" ); + ASSERT_TRUE( value < "2U" ); -TEST_F(AnyValueTest, FloatingPointComparisons) { - using namespace booleval::utils; + ASSERT_TRUE( value >= "0U" ); + ASSERT_TRUE( value >= "1U" ); - any_value value{ 1.234567 }; + ASSERT_TRUE( value <= "1U" ); + ASSERT_TRUE( value <= "2U" ); + } + { + booleval::utils::any_value value{ 1.234567 }; - EXPECT_TRUE(value == 1.234567F); - EXPECT_TRUE(value != 7.654321F); + ASSERT_TRUE( value == "1.234567" ); + ASSERT_TRUE( value != "7.654321" ); - EXPECT_TRUE(value > 0.123456F); - EXPECT_TRUE(value < 2.345678F); + ASSERT_TRUE( value > "0.123456" ); + ASSERT_TRUE( value < "2.345678" ); - EXPECT_TRUE(value >= 0.123456F); - EXPECT_TRUE(value >= 1.234567F); + ASSERT_TRUE( value >= "0.123456" ); + ASSERT_TRUE( value >= "1.234567" ); - EXPECT_TRUE(value <= 1.234567F); - EXPECT_TRUE(value <= 2.345678F); + ASSERT_TRUE( value <= "1.234567" ); + ASSERT_TRUE( value <= "2.345678" ); + } } diff --git a/tests/src/utils/split_range_test.cpp b/tests/src/utils/split_range_test.cpp index eef973f..63072a9 100644 --- a/tests/src/utils/split_range_test.cpp +++ b/tests/src/utils/split_range_test.cpp @@ -30,143 +30,157 @@ #include #include -class SplitRangeTest : public testing::Test { -public: - template - void test_split_range_iterator(SplitRangeIt it, - bool const quoted, - std::size_t const index, - std::string_view const value) { - EXPECT_EQ(it->quoted, quoted); - EXPECT_EQ(it->index, index); - EXPECT_EQ(it->value, value); +namespace +{ + + template< typename It > + void test_split_range_iterator + ( + It const it, + bool const expected_is_quoted, + std::string_view const expected_value + ) noexcept + { + ASSERT_EQ( it->is_quoted, expected_is_quoted ); + ASSERT_EQ( it->value , expected_value ); } -}; -TEST_F(SplitRangeTest, SplitEmpty) { - using namespace booleval::utils; +} // namespace - auto range = split_range(""); - EXPECT_EQ(range.begin(), range.end()); +TEST( SplitRangeTest, SplitEmpty ) +{ + auto const range{ booleval::utils::split_range( "" ) }; + ASSERT_EQ( std::begin( range ), std::end( range ) ); } -TEST_F(SplitRangeTest, SplitByWhitespace) { - using namespace booleval::utils; - - auto range = split_range("a b c d \"a b c\""); - auto it = range.begin(); - auto end = range.end(); - - test_split_range_iterator(it++, false, 0, "a"); - test_split_range_iterator(it++, false, 1, "b"); - test_split_range_iterator(it++, false, 2, "c"); - test_split_range_iterator(it++, false, 3, "d"); - test_split_range_iterator(it++, true, 4, "a b c"); - EXPECT_EQ(it, end); +TEST( SplitRangeTest, SplitByWhitespace ) +{ + auto const range{ booleval::utils::split_range( "a b c d \"a b c\"" ) }; + auto it { range.begin() }; + auto const end{ range.end() }; + + test_split_range_iterator( it++, false, "a" ); + test_split_range_iterator( it++, false, "b" ); + test_split_range_iterator( it++, false, "c" ); + test_split_range_iterator( it++, false, "d" ); + test_split_range_iterator( it++, true , "a b c" ); + ASSERT_EQ( it, end ); } -TEST_F(SplitRangeTest, SplitByWhitespaceWithSingleQuoteChar) { - using namespace booleval::utils; - - auto range = split_range< - split_options::exclude_delimiters | - split_options::allow_quoted_strings, - single_quote_char - >("a b c d \'a b c\'"); - auto it = range.begin(); - auto end = range.end(); - - test_split_range_iterator(it++, false, 0, "a"); - test_split_range_iterator(it++, false, 1, "b"); - test_split_range_iterator(it++, false, 2, "c"); - test_split_range_iterator(it++, false, 3, "d"); - test_split_range_iterator(it++, true, 4, "a b c"); - EXPECT_EQ(it, end); +TEST( SplitRangeTest, SplitByWhitespaceWithSingleQuoteChar ) +{ + auto const range + { + booleval::utils::split_range + < + booleval::utils::split_options::exclude_delimiters | + booleval::utils::split_options::allow_quoted_strings, + booleval::utils::single_quote_char + >( "a b c d \'a b c\'" ) + }; + auto it { range.begin() }; + auto const end{ range.end() }; + + test_split_range_iterator( it++, false, "a" ); + test_split_range_iterator( it++, false, "b" ); + test_split_range_iterator( it++, false, "c" ); + test_split_range_iterator( it++, false, "d" ); + test_split_range_iterator( it++, true , "a b c" ); + ASSERT_EQ( it, end ); } -TEST_F(SplitRangeTest, SplitByComma) { - using namespace booleval::utils; - - auto range = split_range("\"a,b,c\",a,b,c,d", ","); - auto it = range.begin(); - auto end = range.end(); - - test_split_range_iterator(it++, true, 0, "a,b,c"); - test_split_range_iterator(it++, false, 1, "a"); - test_split_range_iterator(it++, false, 2, "b"); - test_split_range_iterator(it++, false, 3, "c"); - test_split_range_iterator(it++, false, 4, "d"); - EXPECT_EQ(it, end); +TEST( SplitRangeTest, SplitByComma ) +{ + auto const range{ booleval::utils::split_range( "\"a,b,c\",a,b,c,d", "," ) }; + auto it { range.begin() }; + auto const end{ range.end() }; + + test_split_range_iterator( it++, true , "a,b,c" ); + test_split_range_iterator( it++, false, "a" ); + test_split_range_iterator( it++, false, "b" ); + test_split_range_iterator( it++, false, "c" ); + test_split_range_iterator( it++, false, "d" ); + ASSERT_EQ( it, end ); } -TEST_F(SplitRangeTest, SplitByMultipleDelimiters) { - using namespace booleval::utils; - - auto range = split_range("a,b.\"a.b,c\".c,d.", ".,"); - auto it = range.begin(); - auto end = range.end(); - - test_split_range_iterator(it++, false, 0, "a"); - test_split_range_iterator(it++, false, 1, "b"); - test_split_range_iterator(it++, true, 2, "a.b,c"); - test_split_range_iterator(it++, false, 3, "c"); - test_split_range_iterator(it++, false, 4, "d"); - EXPECT_EQ(it, end); +TEST( SplitRangeTest, SplitByMultipleDelimiters ) +{ + auto const range{ booleval::utils::split_range( "a,b.\"a.b,c\".c,d.", ".," ) }; + auto it { range.begin() }; + auto const end{ range.end() }; + + test_split_range_iterator( it++, false, "a" ); + test_split_range_iterator( it++, false, "b" ); + test_split_range_iterator( it++, true , "a.b,c" ); + test_split_range_iterator( it++, false, "c" ); + test_split_range_iterator( it++, false, "d" ); + ASSERT_EQ( it, end ); } -TEST_F(SplitRangeTest, SplitWithIncludeDelimitersOption) { +TEST( SplitRangeTest, SplitWithIncludeDelimitersOption ) +{ using namespace booleval::utils; - auto range = split_range< - split_options::include_delimiters - >("a,b.", ".,"); - auto it = range.begin(); - auto end = range.end(); - - test_split_range_iterator(it++, false, 0, "a"); - test_split_range_iterator(it++, false, 1, ","); - test_split_range_iterator(it++, false, 2, "b"); - test_split_range_iterator(it++, false, 3, "."); - EXPECT_EQ(it, end); + auto const range + { + split_range + < + split_options::include_delimiters + >( "a,b.", ".," ) + }; + auto it { range.begin() }; + auto const end{ range.end() }; + + test_split_range_iterator( it++, false, "a" ); + test_split_range_iterator( it++, false, "," ); + test_split_range_iterator( it++, false, "b" ); + test_split_range_iterator( it++, false, "." ); + ASSERT_EQ( it, end ); } -TEST_F(SplitRangeTest, SplitWithMultipleOptions) { - using namespace booleval::utils; - - auto range = split_range< - split_options::include_delimiters | - split_options::split_by_whitespace | - split_options::allow_quoted_strings - >("a , b .\"c , \" \"d . e\"", ".,"); - auto it = range.begin(); - auto end = range.end(); - - test_split_range_iterator(it++, false, 0, "a"); - test_split_range_iterator(it++, false, 1, ","); - test_split_range_iterator(it++, false, 2, "b"); - test_split_range_iterator(it++, false, 3, "."); - test_split_range_iterator(it++, true, 4, "c , "); - test_split_range_iterator(it++, true, 5, "d . e"); - EXPECT_EQ(it, end); +TEST( SplitRangeTest, SplitWithMultipleOptions ) +{ + auto const range + { + booleval::utils::split_range + < + booleval::utils::split_options::include_delimiters | + booleval::utils::split_options::split_by_whitespace | + booleval::utils::split_options::allow_quoted_strings + >( "a , b .\"c , \" \"d . e\"", ".," ) + }; + auto it { range.begin() }; + auto const end{ range.end() }; + + test_split_range_iterator( it++, false, "a" ); + test_split_range_iterator( it++, false, "," ); + test_split_range_iterator( it++, false, "b" ); + test_split_range_iterator( it++, false, "." ); + test_split_range_iterator( it++, true , "c , " ); + test_split_range_iterator( it++, true , "d . e" ); + ASSERT_EQ( it, end ); } -TEST_F(SplitRangeTest, SplitWithMultipleOptionsAndParentheses) { - using namespace booleval::utils; - - auto range = split_range< - split_options::include_delimiters | - split_options::split_by_whitespace | - split_options::allow_quoted_strings - >("(a b c d \"a b c\")", "()"); - auto it = range.begin(); - auto end = range.end(); - - test_split_range_iterator(it++, false, 0, "("); - test_split_range_iterator(it++, false, 1, "a"); - test_split_range_iterator(it++, false, 2, "b"); - test_split_range_iterator(it++, false, 3, "c"); - test_split_range_iterator(it++, false, 4, "d"); - test_split_range_iterator(it++, true, 5, "a b c"); - test_split_range_iterator(it++, false, 6, ")"); - EXPECT_EQ(it, end); +TEST( SplitRangeTest, SplitWithMultipleOptionsAndParentheses ) +{ + auto const range + { + booleval::utils::split_range + < + booleval::utils::split_options::include_delimiters | + booleval::utils::split_options::split_by_whitespace | + booleval::utils::split_options::allow_quoted_strings + >( "(a b c d \"a b c\")", "()" ) + }; + auto it { range.begin() }; + auto const end{ range.end() }; + + test_split_range_iterator( it++, false, "(" ); + test_split_range_iterator( it++, false, "a" ); + test_split_range_iterator( it++, false, "b" ); + test_split_range_iterator( it++, false, "c" ); + test_split_range_iterator( it++, false, "d" ); + test_split_range_iterator( it++, true , "a b c" ); + test_split_range_iterator( it++, false, ")" ); + ASSERT_EQ( it, end ); } \ No newline at end of file diff --git a/tests/src/utils/string_utils_test.cpp b/tests/src/utils/string_utils_test.cpp index 58ebaf6..4536e32 100644 --- a/tests/src/utils/string_utils_test.cpp +++ b/tests/src/utils/string_utils_test.cpp @@ -28,144 +28,173 @@ */ #include +#include #include -class StringUtilsTest : public testing::Test {}; - -TEST_F(StringUtilsTest, IsSetSplitOption) { +TEST( StringUtilsTest, IsSetSplitOption ) +{ using namespace booleval::utils; - auto options = split_options::include_delimiters; - EXPECT_TRUE(is_set(options, split_options::include_delimiters)); - EXPECT_FALSE(is_set(options, split_options::exclude_delimiters)); - EXPECT_FALSE(is_set(options, split_options::split_by_whitespace)); - EXPECT_FALSE(is_set(options, split_options::allow_quoted_strings)); - - options = split_options::include_delimiters | - split_options::exclude_delimiters; - EXPECT_TRUE(is_set(options, split_options::include_delimiters)); - EXPECT_TRUE(is_set(options, split_options::exclude_delimiters)); - EXPECT_FALSE(is_set(options, split_options::split_by_whitespace)); - EXPECT_FALSE(is_set(options, split_options::allow_quoted_strings)); - - options = split_options::include_delimiters | - split_options::exclude_delimiters | - split_options::split_by_whitespace; - EXPECT_TRUE(is_set(options, split_options::include_delimiters)); - EXPECT_TRUE(is_set(options, split_options::exclude_delimiters)); - EXPECT_TRUE(is_set(options, split_options::split_by_whitespace)); - EXPECT_FALSE(is_set(options, split_options::allow_quoted_strings)); - - options = split_options::include_delimiters | - split_options::exclude_delimiters | - split_options::split_by_whitespace | - split_options::allow_quoted_strings; - EXPECT_TRUE(is_set(options, split_options::include_delimiters)); - EXPECT_TRUE(is_set(options, split_options::exclude_delimiters)); - EXPECT_TRUE(is_set(options, split_options::split_by_whitespace)); - EXPECT_TRUE(is_set(options, split_options::allow_quoted_strings)); + { + auto const options{ split_options::include_delimiters }; + ASSERT_TRUE ( is_set( options, split_options::include_delimiters ) ); + ASSERT_FALSE( is_set( options, split_options::exclude_delimiters ) ); + ASSERT_FALSE( is_set( options, split_options::split_by_whitespace ) ); + ASSERT_FALSE( is_set( options, split_options::allow_quoted_strings ) ); + } + { + auto const options + { + split_options::include_delimiters | + split_options::exclude_delimiters + }; + ASSERT_TRUE ( is_set( options, split_options::include_delimiters ) ); + ASSERT_TRUE ( is_set( options, split_options::exclude_delimiters ) ); + ASSERT_FALSE( is_set( options, split_options::split_by_whitespace ) ); + ASSERT_FALSE( is_set( options, split_options::allow_quoted_strings ) ); + } + { + auto const options + { + split_options::include_delimiters | + split_options::exclude_delimiters | + split_options::split_by_whitespace + }; + ASSERT_TRUE ( is_set( options, split_options::include_delimiters ) ); + ASSERT_TRUE ( is_set( options, split_options::exclude_delimiters ) ); + ASSERT_TRUE ( is_set( options, split_options::split_by_whitespace ) ); + ASSERT_FALSE( is_set( options, split_options::allow_quoted_strings ) ); + } + { + auto const options + { + split_options::include_delimiters | + split_options::exclude_delimiters | + split_options::split_by_whitespace | + split_options::allow_quoted_strings + }; + ASSERT_TRUE( is_set( options, split_options::include_delimiters ) ); + ASSERT_TRUE( is_set( options, split_options::exclude_delimiters ) ); + ASSERT_TRUE( is_set( options, split_options::split_by_whitespace ) ); + ASSERT_TRUE( is_set( options, split_options::allow_quoted_strings ) ); + } } -TEST_F(StringUtilsTest, LeftTrimWhitespaces) { +TEST( StringUtilsTest, LeftTrimWhitespaces ) +{ using namespace booleval::utils; - EXPECT_EQ(ltrim("abc"), "abc"); - EXPECT_EQ(ltrim(" abc"), "abc"); - EXPECT_EQ(ltrim(" abc"), "abc"); - EXPECT_EQ(ltrim(" abc "), "abc "); - EXPECT_EQ(ltrim(" a b c "), "a b c "); + ASSERT_EQ( ltrim( "abc" ), "abc" ); + ASSERT_EQ( ltrim( " abc" ), "abc" ); + ASSERT_EQ( ltrim( " abc" ), "abc" ); + ASSERT_EQ( ltrim( " abc " ), "abc " ); + ASSERT_EQ( ltrim( " a b c " ), "a b c " ); } -TEST_F(StringUtilsTest, LeftTrimZeros) { +TEST( StringUtilsTest, LeftTrimZeros ) +{ using namespace booleval::utils; - EXPECT_EQ(ltrim("abc", '0'), "abc"); - EXPECT_EQ(ltrim("0abc", '0'), "abc"); - EXPECT_EQ(ltrim("00abc", '0'), "abc"); - EXPECT_EQ(ltrim("00abc0", '0'), "abc0"); - EXPECT_EQ(ltrim("00a0b0c0", '0'), "a0b0c0"); + ASSERT_EQ( ltrim( "abc" , '0' ), "abc" ); + ASSERT_EQ( ltrim( "0abc" , '0' ), "abc" ); + ASSERT_EQ( ltrim( "00abc" , '0' ), "abc" ); + ASSERT_EQ( ltrim( "00abc0" , '0' ), "abc0" ); + ASSERT_EQ( ltrim( "00a0b0c0", '0' ), "a0b0c0" ); } -TEST_F(StringUtilsTest, RightTrimWhitespaces) { +TEST( StringUtilsTest, RightTrimWhitespaces ) +{ using namespace booleval::utils; - EXPECT_EQ(rtrim("abc"), "abc"); - EXPECT_EQ(rtrim("abc "), "abc"); - EXPECT_EQ(rtrim("abc "), "abc"); - EXPECT_EQ(rtrim(" abc "), " abc"); - EXPECT_EQ(rtrim(" a b c "), " a b c"); + ASSERT_EQ( rtrim( "abc" ), "abc" ); + ASSERT_EQ( rtrim( "abc " ), "abc" ); + ASSERT_EQ( rtrim( "abc " ), "abc" ); + ASSERT_EQ( rtrim( " abc " ), " abc" ); + ASSERT_EQ( rtrim( " a b c " ), " a b c" ); } -TEST_F(StringUtilsTest, RightTrimZeros) { +TEST( StringUtilsTest, RightTrimZeros ) +{ using namespace booleval::utils; - EXPECT_EQ(rtrim("abc", '0'), "abc"); - EXPECT_EQ(rtrim("abc0", '0'), "abc"); - EXPECT_EQ(rtrim("abc00", '0'), "abc"); - EXPECT_EQ(rtrim("0abc00", '0'), "0abc"); - EXPECT_EQ(rtrim("0a0b0c00", '0'), "0a0b0c"); + ASSERT_EQ( rtrim( "abc" , '0' ), "abc" ); + ASSERT_EQ( rtrim( "abc0" , '0' ), "abc" ); + ASSERT_EQ( rtrim( "abc00" , '0' ), "abc" ); + ASSERT_EQ( rtrim( "0abc00" , '0' ), "0abc" ); + ASSERT_EQ( rtrim( "0a0b0c00", '0' ), "0a0b0c" ); } -TEST_F(StringUtilsTest, TrimWhitespaces) { +TEST( StringUtilsTest, TrimWhitespaces ) +{ using namespace booleval::utils; - EXPECT_EQ(trim("abc"), "abc"); - EXPECT_EQ(trim(" abc "), "abc"); - EXPECT_EQ(trim(" abc "), "abc"); - EXPECT_EQ(trim(" a b c "), "a b c"); + ASSERT_EQ( trim( "abc" ), "abc" ); + ASSERT_EQ( trim( " abc " ), "abc" ); + ASSERT_EQ( trim( " abc " ), "abc" ); + ASSERT_EQ( trim( " a b c " ), "a b c" ); } -TEST_F(StringUtilsTest, TrimZeros) { +TEST( StringUtilsTest, TrimZeros ) +{ using namespace booleval::utils; - EXPECT_EQ(trim("abc", '0'), "abc"); - EXPECT_EQ(trim("0abc0", '0'), "abc"); - EXPECT_EQ(trim("00abc00", '0'), "abc"); - EXPECT_EQ(trim("00a0b0c00", '0'), "a0b0c"); + ASSERT_EQ( trim( "abc" , '0' ), "abc" ); + ASSERT_EQ( trim( "0abc0" , '0' ), "abc" ); + ASSERT_EQ( trim( "00abc00" , '0' ), "abc" ); + ASSERT_EQ( trim( "00a0b0c00", '0' ), "a0b0c" ); } -TEST_F(StringUtilsTest, IsEmpty) { +TEST( StringUtilsTest, IsEmpty ) +{ using namespace booleval::utils; - EXPECT_TRUE(is_empty("")); - EXPECT_TRUE(is_empty(" ")); - EXPECT_TRUE(is_empty(" ")); + ASSERT_TRUE( is_empty( "" ) ); + ASSERT_TRUE( is_empty( " " ) ); + ASSERT_TRUE( is_empty( " " ) ); - EXPECT_FALSE(is_empty("abc")); - EXPECT_FALSE(is_empty(" abc")); - EXPECT_FALSE(is_empty("abc ")); - EXPECT_FALSE(is_empty(" abc ")); - EXPECT_FALSE(is_empty(" a b c ")); + ASSERT_FALSE( is_empty( "abc" ) ); + ASSERT_FALSE( is_empty( " abc" ) ); + ASSERT_FALSE( is_empty( "abc " ) ); + ASSERT_FALSE( is_empty( " abc " ) ); + ASSERT_FALSE( is_empty( " a b c " ) ); } -TEST_F(StringUtilsTest, JoinWithoutSeparator) { +TEST( StringUtilsTest, JoinWithoutSeparator ) +{ using namespace booleval::utils; - auto tokens = { "a", "b", "c", "d" }; - auto result = join(std::begin(tokens), std::end(tokens)); - EXPECT_EQ(result, "abcd"); + auto const tokens = { "a", "b", "c", "d" }; + auto const result{ join( std::begin( tokens ), std::end( tokens ) ) }; + ASSERT_EQ( result, "abcd" ); } -TEST_F(StringUtilsTest, JoinWithCommaSeparator) { +TEST( StringUtilsTest, JoinWithCommaSeparator ) +{ using namespace booleval::utils; - auto tokens = { "a", "b", "c", "d" }; - auto result = join(std::begin(tokens), std::end(tokens), ","); - EXPECT_EQ(result, "a,b,c,d"); + auto const tokens = { "a", "b", "c", "d" }; + auto const result{ join( std::begin( tokens ), std::end( tokens ), "," ) }; + ASSERT_EQ( result, "a,b,c,d" ); } -TEST_F(StringUtilsTest, From) { +TEST( StringUtilsTest, FromString ) +{ using namespace booleval::utils; - EXPECT_EQ(from_chars("1").value(), 1U); - EXPECT_DOUBLE_EQ(from_chars("1.23456789").value(), 1.23456789); - EXPECT_FLOAT_EQ(from_chars("1.23456789").value(), 1.23456789F); + ASSERT_FALSE( from_chars< std::uint8_t >( "a" ) ); + ASSERT_FALSE( from_chars< double >( "a" ) ); + ASSERT_FALSE( from_chars< float >( "a" ) ); + + ASSERT_EQ ( from_chars< std::uint8_t >( "1" ).value(), 1U ); + ASSERT_DOUBLE_EQ( from_chars< double >( "1.23456789" ).value(), 1.23456789 ); + ASSERT_FLOAT_EQ ( from_chars< float >( "1.23456789" ).value(), 1.23456789F ); } -TEST_F(StringUtilsTest, To) { +TEST( StringUtilsTest, ToString ) +{ using namespace booleval::utils; - EXPECT_EQ(to_chars(1), "1"); - EXPECT_EQ(to_chars(1.234567), "1.234567"); - EXPECT_EQ(to_chars(1.234567F), "1.234567"); + ASSERT_EQ( to_chars< std::uint8_t >( 1 ), "1" ); + ASSERT_EQ( to_chars< double >( 1.234567 ), "1.234567" ); + ASSERT_EQ( to_chars< float >( 1.234567F ), "1.234567" ); }