├── .gitignore ├── _config.yml ├── test ├── testrunner.cpp ├── CMakeLists.txt └── moar_rulez.cpp ├── .gitmodules ├── CMakeLists.txt ├── .drone.yml ├── Dockerfile ├── LICENSE.md ├── README.md └── include └── moar_rulez.hpp /.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | *~ -------------------------------------------------------------------------------- /_config.yml: -------------------------------------------------------------------------------- 1 | theme: jekyll-theme-dinky -------------------------------------------------------------------------------- /test/testrunner.cpp: -------------------------------------------------------------------------------- 1 | #define CATCH_CONFIG_MAIN 2 | #include "catch.hpp" 3 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "test/Catch"] 2 | path = test/Catch 3 | url = https://github.com/philsquared/Catch 4 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.8) 2 | enable_testing() 3 | project(moar-rulez) 4 | add_subdirectory(test) 5 | -------------------------------------------------------------------------------- /.drone.yml: -------------------------------------------------------------------------------- 1 | pipeline: 2 | build: 3 | image: gcc-build 4 | commands: 5 | - mkdir build 6 | - cd build 7 | - cmake .. 8 | - make 9 | - ./test/tests 10 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM gcc:latest 2 | RUN wget https://cmake.org/files/v3.8/cmake-3.8.1-Linux-x86_64.sh 3 | RUN echo y | sh ./cmake-3.8.1-Linux-x86_64.sh --prefix=/usr/local --exclude-subdir 4 | RUN apt-get update -y && apt-get install -y git 5 | 6 | -------------------------------------------------------------------------------- /test/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | enable_testing() 2 | 3 | # Prepare "Catch" library for other executables 4 | set(CATCH_INCLUDE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/Catch/include) 5 | set(MR_INCLUDE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/../include) 6 | 7 | add_library(Catch INTERFACE) 8 | 9 | target_include_directories(Catch INTERFACE ${CATCH_INCLUDE_DIR}) 10 | target_include_directories(Catch INTERFACE ${MR_INCLUDE_DIR}) 11 | 12 | # Make test executable 13 | set(TEST_SOURCES 14 | ${CMAKE_CURRENT_SOURCE_DIR}/testrunner.cpp 15 | ${CMAKE_CURRENT_SOURCE_DIR}/moar_rulez.cpp 16 | ) 17 | add_executable(tests ${TEST_SOURCES}) 18 | target_link_libraries(tests Catch) 19 | target_compile_features(tests PRIVATE cxx_std_17) 20 | 21 | add_test(all tests) 22 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright (c) 2017 Simon Pettersson 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build Status](http://ec2-54-245-163-75.us-west-2.compute.amazonaws.com/api/badges/simonvpe/moar-rulez/status.svg)](http://ec2-54-245-163-75.us-west-2.compute.amazonaws.com/simonvpe/moar-rulez) 2 | 3 | # MOAR RULEZ 4 | Moar-rulez aims to be a very simple, very fast, yet very useful single header rules engine library. It uses operator everloads to allow chaining of rules with regular boolean operators and other helper functions. 5 | 6 | The implementation is basically modeled as a behavior tree implemented with constexpr functions so that the boilerplate and glue code can be moved out of runtime and into compile time. Speed, clean code and simplicity has been the focus for every line of code. 7 | 8 | ## Getting Started ## 9 | 10 | There are no dependencies but project has only been tested with gcc 7.0.1. 11 | 12 | ## Installing ## 13 | 14 | Simply drop the `include/moar_rulez.hpp` file in your project and you're good to go. 15 | 16 | ## Example ## 17 | This example actually gets reduced down to a single assembler instruction if you compile it with gcc 7.0.1 and `-O3` optimization. That is indeed blazingly fast! 18 | ```c++ 19 | #include 20 | using moar_rulez::make_rule; 21 | using moar_rulez::State; 22 | using moar_rulez::eq; 23 | using moar_rulez::set; 24 | using moar_rulez::execute; 25 | using moar_rulez::success; 26 | using moar_rulez::fail; 27 | 28 | bool dude_can_open_the_door() { 29 | auto door_open = false; 30 | auto door_locked = true; 31 | auto the_dude_has_key = true; 32 | auto the_dude_position = 0; 33 | constexpr auto door_position = 100; 34 | 35 | const auto move_the_dude = make_rule([&]{ 36 | if(the_dude_position == door_position && !door_open) return State::Fail; 37 | the_dude_position += 1; 38 | return State::Success; 39 | }); 40 | 41 | const auto unlock_door = make_rule([&]{ 42 | if(the_dude_position != door_position || !the_dude_has_key) return State::Fail; 43 | door_locked = false; 44 | return State::Success; 45 | }); 46 | 47 | const auto open_door = make_rule([&]{ 48 | if(the_dude_position != door_position || door_locked || door_open) return State::Fail; 49 | door_open = true; 50 | return State::Success; 51 | }); 52 | 53 | const auto rules = 54 | eq(the_dude_position, door_position) 55 | && unlock_door 56 | && open_door 57 | || move_the_dude; 58 | 59 | // Keep running until the dude is past the door 60 | while( the_dude_position <= door_position ) { 61 | if( fail == execute(rules) ) return false; 62 | } 63 | return true; 64 | } 65 | ``` 66 | 67 | This compiles to the following assembler instructions (x86_64) 68 | ```asm 69 | the_dude_can_open_the_door(): 70 | mov eax, 1 71 | ret 72 | ``` 73 | 74 | ## Running the tests ## 75 | Make sure you have a C++17 compiler (preferably gcc 7.0.1) and cmake >= 3.7 installed, then 76 | ``` 77 | $ git clone https://github.com/simonvpe/moar-rulez.git 78 | $ cd moar-rulez && mkdir build && cd build && cmake .. && make && ./test/tests 79 | ``` 80 | # Built With # 81 | * gcc 7.0.1 82 | * cmake 3.8 83 | 84 | # Authors # 85 | * Simon Pettersson 86 | 87 | # License # 88 | This project is licensed under the MIT License - see the LICENSE.md file for details 89 | -------------------------------------------------------------------------------- /include/moar_rulez.hpp: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017 Simon Pettersson 2 | 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | 10 | // The above copyright notice and this permission notice shall be included in all 11 | // copies or substantial portions of the Software. 12 | 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | // SOFTWARE. 20 | 21 | #pragma once 22 | 23 | #ifndef NDEBUG 24 | # include 25 | #endif 26 | 27 | namespace moar_rulez { 28 | enum class State { Fail, Success, Running }; 29 | 30 | // Rule factory 31 | 32 | template struct Rule { TExecute execute; }; 33 | 34 | 35 | #ifndef NDEBUG 36 | static inline int indent = 0; 37 | 38 | constexpr inline auto make_rule_impl(auto f) { 39 | return Rule{f}; 40 | } 41 | 42 | constexpr inline auto debug(Rule rule, const char* name) { 43 | return make_rule_impl([rule,name]{ 44 | for(auto i = 0 ; i < indent ; ++i) std::cerr << " "; 45 | std::cerr << ">> Running rule " << '<' << name << "> \n"; 46 | ++indent; 47 | const auto result = rule.execute(); 48 | --indent; 49 | for(auto i = 0 ; i < indent ; ++i) std::cerr << " "; 50 | std::cerr << "<< " << (result == State::Success ? "Succes" : 51 | result == State::Fail ? "Fail" : 52 | "Running") 53 | << ' ' << name << " \n"; 54 | 55 | return result; 56 | }); 57 | } 58 | 59 | constexpr inline auto make_rule(auto f, const char* name = "unknown") { 60 | const auto rule = make_rule_impl(f); 61 | return debug(rule, name); 62 | } 63 | #else 64 | constexpr inline auto debug(Rule rule, const char*) { 65 | return rule; 66 | } 67 | 68 | constexpr inline auto make_rule(auto f, const char* name = nullptr) { 69 | return Rule{f}; 70 | } 71 | #endif 72 | 73 | constexpr inline auto operator~(Rule rule) { 74 | return make_rule([rule]{ 75 | const auto result = rule.execute(); 76 | return result == State::Fail ? State::Success : 77 | result == State::Success ? State::Fail : 78 | State::Running; 79 | }, "NOT"); 80 | } 81 | 82 | constexpr inline auto operator&&(Rule left, Rule right) { 83 | return make_rule([left, right]{ 84 | const auto lresult = left.execute(); 85 | if(lresult == State::Fail || lresult == State::Running) return lresult; 86 | const auto rresult = right.execute(); 87 | if(rresult == State::Fail || rresult == State::Running) return rresult; 88 | return State::Success; 89 | }, "AND"); 90 | } 91 | 92 | constexpr inline auto operator||(Rule left, Rule right) { 93 | return make_rule([left, right]{ 94 | const auto lresult = left.execute(); 95 | if(lresult == State::Success || lresult == State::Running) return lresult; 96 | const auto rresult = right.execute(); 97 | if(rresult == State::Success || rresult == State::Running) return rresult; 98 | return State::Fail; 99 | }, "OR"); 100 | } 101 | 102 | // Rule factories 103 | template 104 | constexpr inline auto eq(const auto& ref) { 105 | return make_rule([&ref]{ 106 | return (TValue == ref) ? State::Success : State::Fail; 107 | }, "EQ"); 108 | } 109 | 110 | constexpr inline auto eq(const auto& ref, const auto& value) { 111 | return make_rule([&ref,&value]{ 112 | return (value == ref) ? State::Success : State::Fail; 113 | }, "EQ"); 114 | } 115 | 116 | template 117 | constexpr inline auto ne(const auto& ref) { 118 | return make_rule([&ref]{ 119 | return (TValue != ref) ? State::Success : State::Fail; 120 | }, "NE"); 121 | } 122 | 123 | constexpr inline auto ne(const auto& ref, const auto& value) { 124 | return make_rule([&ref,&value]{ 125 | return (value != ref) ? State::Success : State::Fail; 126 | }, "NE"); 127 | } 128 | 129 | template 130 | constexpr inline auto gt(const auto& ref) { 131 | return make_rule([&ref]{ 132 | return (TValue < ref) ? State::Success : State::Fail; 133 | }, "GT"); 134 | } 135 | 136 | constexpr inline auto gt(const auto& ref, const auto& value) { 137 | return make_rule([&ref,&value]{ 138 | return (value < ref) ? State::Success : State::Fail; 139 | }, "GT"); 140 | } 141 | 142 | template 143 | constexpr inline auto lt(const auto& ref) { 144 | return make_rule([&ref]{ 145 | return (TValue > ref) ? State::Success : State::Fail; 146 | }, "LT"); 147 | } 148 | 149 | constexpr inline auto lt(const auto& ref, const auto& value) { 150 | return make_rule([&ref,&value]{ 151 | return (value > ref) ? State::Success : State::Fail; 152 | }, "LT"); 153 | } 154 | 155 | // Manipulation of state 156 | template 157 | constexpr inline auto set(auto& ref) { 158 | return make_rule([&ref]{ 159 | ref = TValue; 160 | return State::Success; 161 | }, "SET"); 162 | } 163 | 164 | constexpr inline auto set(auto& ref, const auto& value) { 165 | return make_rule([&ref,&value]{ 166 | ref = value; 167 | return State::Success; 168 | }, "SET"); 169 | } 170 | 171 | // Constant rules 172 | 173 | static constexpr auto success = make_rule([]{ return State::Success; }, "success"); 174 | static constexpr auto fail = make_rule([]{ return State::Fail; }, "fail"); 175 | static constexpr auto running = make_rule([]{ return State::Running; }, "running"); 176 | 177 | // Convenience, makes less typing 178 | 179 | constexpr inline bool operator==(Rule lhs, State rhs) { 180 | return lhs.execute() == rhs; 181 | } 182 | 183 | // Execution 184 | 185 | inline State execute(Rule rule) { 186 | return rule.execute(); 187 | } 188 | 189 | } // namespace moar_rulez 190 | -------------------------------------------------------------------------------- /test/moar_rulez.cpp: -------------------------------------------------------------------------------- 1 | // vim: tabstop=3 expandtab shiftwidth=4 softtabstop=4 2 | 3 | #include 4 | #include 5 | 6 | using moar_rulez::execute; 7 | using moar_rulez::success; 8 | using moar_rulez::fail; 9 | using moar_rulez::running; 10 | using moar_rulez::eq; 11 | using moar_rulez::ne; 12 | using moar_rulez::gt; 13 | using moar_rulez::lt; 14 | using moar_rulez::State; 15 | using moar_rulez::make_rule; 16 | using moar_rulez::set; 17 | 18 | SCENARIO("All rules should work independently") { 19 | 20 | GIVEN("constants") { 21 | CHECK( success == execute(success) ); 22 | CHECK( fail == execute(fail) ); 23 | CHECK( running == execute(running) ); 24 | } 25 | GIVEN("operator&&") { 26 | CHECK( success == execute(success && success) ); 27 | CHECK( fail == execute(success && fail) ); 28 | CHECK( fail == execute(fail && success) ); 29 | CHECK( fail == execute(fail && fail) ); 30 | CHECK( fail == execute(fail && running) ); 31 | CHECK( running == execute(running && fail) ); 32 | CHECK( running == execute(success && running) ); 33 | CHECK( running == execute(running && success) ); 34 | CHECK( running == execute(running && running) ); 35 | } 36 | GIVEN("operator||") { 37 | CHECK( success == execute(success || success) ); 38 | CHECK( success == execute(success || fail) ); 39 | CHECK( success == execute(fail || success) ); 40 | CHECK( success == execute(success || running) ); 41 | CHECK( running == execute(running || success) ); 42 | CHECK( fail == execute(fail || fail) ); 43 | CHECK( running == execute(fail || running) ); 44 | CHECK( running == execute(running || fail) ); 45 | CHECK( running == execute(running || running) ); 46 | } 47 | 48 | GIVEN("operator~") { 49 | CHECK( success == execute(~fail) ); 50 | CHECK( fail == execute(~success) ); 51 | CHECK( running == execute(~running) ); 52 | } 53 | 54 | GIVEN("eq") { 55 | const auto eqvar = 5; 56 | CHECK( success == execute(eq(eqvar,5)) ); 57 | CHECK( fail == execute(eq(eqvar,7)) ); 58 | CHECK( success == execute(eq<5>(eqvar)) ); 59 | CHECK( fail == execute(eq<7>(eqvar)) ); 60 | } 61 | GIVEN("ne") { 62 | const auto nevar = 4; 63 | CHECK( success == execute(ne(nevar,2)) ); 64 | CHECK( fail == execute(ne(nevar,4)) ); 65 | CHECK( success == execute(ne<2>(nevar)) ); 66 | CHECK( fail == execute(ne<4>(nevar)) ); 67 | } 68 | GIVEN("gt") { 69 | const auto gtvar = 4; 70 | CHECK( success == execute(gt(gtvar,2)) ); 71 | CHECK( fail == execute(gt(gtvar,4)) ); 72 | CHECK( fail == execute(gt(gtvar,5)) ); 73 | CHECK( success == execute(gt<2>(gtvar)) ); 74 | CHECK( fail == execute(gt<4>(gtvar)) ); 75 | CHECK( fail == execute(gt<5>(gtvar)) ); 76 | } 77 | GIVEN("lt") { 78 | const auto ltvar = 4; 79 | CHECK( success == execute(lt(ltvar,5)) ); 80 | CHECK( fail == execute(lt(ltvar,4)) ); 81 | CHECK( fail == execute(lt(ltvar,2)) ); 82 | CHECK( success == execute(lt<5>(ltvar)) ); 83 | CHECK( fail == execute(lt<4>(ltvar)) ); 84 | CHECK( fail == execute(lt<2>(ltvar)) ); 85 | } 86 | GIVEN("set") { 87 | auto setvar = 0; 88 | CHECK( success == execute(set(setvar,42)) ); 89 | CHECK( 42 == setvar ); 90 | CHECK( success == execute(set<43>(setvar)) ); 91 | CHECK( 43 == setvar ); 92 | } 93 | } 94 | 95 | SCENARIO("Complex sequences") { 96 | CHECK( success == execute(success && success || running) ); 97 | CHECK( running == execute(success && success && running) ); 98 | CHECK( success == execute(success && success || fail) ); 99 | CHECK( fail == execute(success && success && fail) ); 100 | CHECK( success == execute(success || running && success) ); 101 | CHECK( success == execute(success || running && running) ); 102 | CHECK( success == execute(success || running || fail) ); 103 | CHECK( success == execute(success || running && fail) ); 104 | CHECK( running == execute(success && running && success) ); 105 | CHECK( running == execute(success && running || running) ); 106 | CHECK( running == execute(success && running || fail) ); 107 | CHECK( success == execute(success || fail && success) ); 108 | CHECK( success == execute(success || fail || running) ); 109 | CHECK( success == execute(success || fail && running) ); 110 | CHECK( success == execute(success || fail && fail) ); 111 | CHECK( fail == execute(success && fail && success) ); 112 | CHECK( running == execute(success && fail || running) ); 113 | CHECK( fail == execute(success && fail && running) ); 114 | CHECK( fail == execute(success && fail || fail) ); 115 | CHECK( running == execute(running || success && success) ); 116 | CHECK( running == execute(running || success && running) ); 117 | CHECK( running == execute(running || success || fail) ); 118 | CHECK( running == execute(running || success && fail) ); 119 | CHECK( running == execute(running && success || success) ); 120 | CHECK( running == execute(running && success && running) ); 121 | CHECK( running == execute(running && success || fail) ); 122 | CHECK( running == execute(running && success && fail) ); 123 | CHECK( running == execute(running && running || success) ); 124 | CHECK( running == execute(running && running && success) ); 125 | CHECK( running == execute(running && running || fail) ); 126 | CHECK( running == execute(running && running && fail) ); 127 | CHECK( running == execute(running || fail || success) ); 128 | CHECK( running == execute(running || fail && success) ); 129 | CHECK( running == execute(running || fail && running) ); 130 | CHECK( running == execute(running || fail && fail) ); 131 | CHECK( running == execute(running && fail || success) ); 132 | CHECK( running == execute(running && fail && success) ); 133 | CHECK( running == execute(running && fail && running) ); 134 | CHECK( running == execute(running && fail || fail) ); 135 | CHECK( success == execute(fail || success && success) ); 136 | CHECK( success == execute(fail || success || running) ); 137 | CHECK( running == execute(fail || success && running) ); 138 | CHECK( fail == execute(fail || success && fail) ); 139 | CHECK( success == execute(fail && success || success) ); 140 | CHECK( running == execute(fail && success || running) ); 141 | CHECK( fail == execute(fail && success && running) ); 142 | CHECK( fail == execute(fail && success && fail) ); 143 | CHECK( running == execute(fail || running || success) ); 144 | CHECK( running == execute(fail || running && success) ); 145 | CHECK( running == execute(fail || running && running) ); 146 | CHECK( running == execute(fail || running && fail) ); 147 | CHECK( success == execute(fail && running || success) ); 148 | CHECK( fail == execute(fail && running && success) ); 149 | CHECK( running == execute(fail && running || running) ); 150 | CHECK( fail == execute(fail && running && fail) ); 151 | CHECK( success == execute(fail && fail || success) ); 152 | CHECK( fail == execute(fail && fail && success) ); 153 | CHECK( running == execute(fail && fail || running) ); 154 | CHECK( fail == execute(fail && fail && running) ); 155 | } 156 | 157 | SCENARIO("Example: A dude tries to open a door") { 158 | auto door_open = false; 159 | auto door_locked = true; 160 | auto failed = false; 161 | auto dude_has_key = true; 162 | auto dude_position = 0; 163 | constexpr auto door_position = 100; 164 | 165 | const auto move_dude = make_rule([&]{ 166 | dude_position += 1; 167 | return State::Success; 168 | }); 169 | 170 | const auto unlock_door = make_rule([&]{ 171 | if(dude_position != door_position || !dude_has_key) return State::Fail; 172 | door_locked = false; 173 | return State::Success; 174 | }); 175 | 176 | const auto open_door = make_rule([&]{ 177 | if(dude_position != door_position || door_locked) return State::Fail; 178 | door_open = true; 179 | return State::Success; 180 | }); 181 | 182 | const auto try_open = 183 | eq(dude_position, door_position) 184 | && (unlock_door || set(failed,true)) 185 | && open_door; 186 | 187 | const auto rules = 188 | try_open 189 | || move_dude; 190 | 191 | const auto run = [&]{ 192 | while( door_locked && !failed ) execute(rules); 193 | }; 194 | 195 | GIVEN("the dude has no key") { 196 | dude_has_key = false; 197 | run(); 198 | CHECK( false == door_open ); 199 | CHECK( true == failed ); 200 | } 201 | GIVEN("the dude has a key") { 202 | dude_has_key = true; 203 | run(); 204 | CHECK( true == door_open ); 205 | CHECK( false == failed ); 206 | } 207 | } 208 | --------------------------------------------------------------------------------