├── .clang-format ├── .clang-tidy ├── .cmake-format.yaml ├── .github ├── FUNDING.yml └── workflows │ └── build_cmake.yml ├── CMakeLists.txt ├── LICENSE ├── README.md ├── cmake ├── Cache.cmake ├── CompilerWarnings.cmake ├── Conan.cmake ├── Doxygen.cmake ├── PreventInSourceBuilds.cmake ├── Sanitizers.cmake ├── StandardProjectSettings.cmake └── StaticAnalyzers.cmake ├── include └── lambda_coroutines │ └── lambda_coroutines.hpp ├── src ├── CMakeLists.txt └── main.cpp └── test ├── CMakeLists.txt ├── catch_main.cpp ├── constexpr_tests.cpp └── tests.cpp /.clang-format: -------------------------------------------------------------------------------- 1 | AccessModifierOffset: -2 2 | AlignAfterOpenBracket: DontAlign 3 | AlignConsecutiveAssignments: false 4 | AlignConsecutiveDeclarations: false 5 | AlignEscapedNewlines: Left 6 | AlignOperands: true 7 | AlignTrailingComments: false 8 | AllowAllParametersOfDeclarationOnNextLine: false 9 | AllowShortBlocksOnASingleLine: true 10 | AllowShortCaseLabelsOnASingleLine: false 11 | AllowShortFunctionsOnASingleLine: All 12 | AllowShortIfStatementsOnASingleLine: true 13 | AllowShortLoopsOnASingleLine: true 14 | AlwaysBreakAfterDefinitionReturnType: None 15 | AlwaysBreakAfterReturnType: None 16 | AlwaysBreakBeforeMultilineStrings: true 17 | AlwaysBreakTemplateDeclarations: false 18 | BinPackArguments: false 19 | BinPackParameters: false 20 | BraceWrapping: 21 | AfterClass: true 22 | AfterControlStatement: false 23 | AfterEnum: false 24 | AfterFunction: true 25 | AfterNamespace: false 26 | AfterObjCDeclaration: false 27 | AfterStruct: true 28 | AfterUnion: false 29 | BeforeCatch: false 30 | BeforeElse: false 31 | IndentBraces: false 32 | SplitEmptyFunction: false 33 | SplitEmptyNamespace: true 34 | SplitEmptyRecord: true 35 | BreakAfterJavaFieldAnnotations: true 36 | BreakBeforeBinaryOperators: NonAssignment 37 | BreakBeforeBraces: Custom 38 | BreakBeforeInheritanceComma: true 39 | BreakBeforeTernaryOperators: true 40 | BreakConstructorInitializers: BeforeColon 41 | BreakConstructorInitializersBeforeComma: false 42 | BreakStringLiterals: true 43 | ColumnLimit: 0 44 | CommentPragmas: '^ IWYU pragma:' 45 | CompactNamespaces: false 46 | ConstructorInitializerAllOnOneLineOrOnePerLine: false 47 | ConstructorInitializerIndentWidth: 2 48 | ContinuationIndentWidth: 2 49 | Cpp11BracedListStyle: false 50 | DerivePointerAlignment: false 51 | DisableFormat: false 52 | ExperimentalAutoDetectBinPacking: true 53 | FixNamespaceComments: true 54 | ForEachMacros: 55 | - foreach 56 | - Q_FOREACH 57 | - BOOST_FOREACH 58 | IncludeCategories: 59 | - Priority: 2 60 | Regex: ^"(llvm|llvm-c|clang|clang-c)/ 61 | - Priority: 3 62 | Regex: ^(<|"(gtest|gmock|isl|json)/) 63 | - Priority: 1 64 | Regex: .* 65 | IncludeIsMainRegex: (Test)?$ 66 | IndentCaseLabels: false 67 | IndentWidth: 2 68 | IndentWrappedFunctionNames: true 69 | JavaScriptQuotes: Leave 70 | JavaScriptWrapImports: true 71 | KeepEmptyLinesAtTheStartOfBlocks: true 72 | Language: Cpp 73 | MacroBlockBegin: '' 74 | MacroBlockEnd: '' 75 | MaxEmptyLinesToKeep: 2 76 | NamespaceIndentation: Inner 77 | ObjCBlockIndentWidth: 7 78 | ObjCSpaceAfterProperty: true 79 | ObjCSpaceBeforeProtocolList: false 80 | PointerAlignment: Right 81 | ReflowComments: true 82 | SortIncludes: false 83 | SortUsingDeclarations: false 84 | SpaceAfterCStyleCast: false 85 | SpaceAfterTemplateKeyword: false 86 | SpaceBeforeAssignmentOperators: true 87 | SpaceBeforeParens: ControlStatements 88 | SpaceInEmptyParentheses: false 89 | SpacesBeforeTrailingComments: 0 90 | SpacesInAngles: false 91 | SpacesInCStyleCastParentheses: false 92 | SpacesInContainerLiterals: true 93 | SpacesInParentheses: false 94 | SpacesInSquareBrackets: false 95 | Standard: Cpp11 96 | TabWidth: 8 97 | UseTab: Never 98 | 99 | -------------------------------------------------------------------------------- /.clang-tidy: -------------------------------------------------------------------------------- 1 | --- 2 | Checks: '*,-fuchsia-*,-google-*,-zircon-*,-abseil-*,-modernize-use-trailing-return-type,-llvm-*' 3 | WarningsAsErrors: '*' 4 | HeaderFilterRegex: '' 5 | FormatStyle: none 6 | 7 | 8 | -------------------------------------------------------------------------------- /.cmake-format.yaml: -------------------------------------------------------------------------------- 1 | additional_commands: 2 | foo: 3 | flags: 4 | - BAR 5 | - BAZ 6 | kwargs: 7 | DEPENDS: '*' 8 | HEADERS: '*' 9 | SOURCES: '*' 10 | bullet_char: '*' 11 | dangle_parens: false 12 | enum_char: . 13 | line_ending: unix 14 | line_width: 120 15 | max_pargs_hwrap: 3 16 | separate_ctrl_name_with_space: false 17 | separate_fn_name_with_space: false 18 | tab_size: 2 19 | 20 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: lefticus 4 | patreon: lefticus 5 | -------------------------------------------------------------------------------- /.github/workflows/build_cmake.yml: -------------------------------------------------------------------------------- 1 | 2 | on: [push] 3 | 4 | 5 | jobs: 6 | build: 7 | name: ${{ matrix.os }}-${{ matrix.buildtype }} 8 | runs-on: ${{ matrix.os }} 9 | strategy: 10 | fail-fast: false 11 | matrix: 12 | os: [ ubuntu-latest, macos-latest, windows-latest ] 13 | buildtype: [Release, Debug] 14 | 15 | steps: 16 | - uses: actions/checkout@v2 17 | 18 | - name: Create Build Environment 19 | # Some projects don't allow in-source building, so create a separate build directory 20 | # We'll use this as our working directory for all subsequent commands 21 | run: cmake -E make_directory ${{runner.workspace}}/build 22 | 23 | 24 | - name: Install conan 25 | shell: bash 26 | run: | 27 | which pip3 && pip3 --version || pip --version 28 | which pip3 && pip3 install conan || pip install conan 29 | [[ -f ~/.profile ]] && source ~/.profile 30 | conan --version 31 | 32 | - name: Configure CMake 33 | # Use a bash shell so we can use the same syntax for environment variable 34 | # access regardless of the host operating system 35 | shell: bash 36 | working-directory: ${{runner.workspace}}/build 37 | # Note the current convention is to use the -S and -B options here to specify source 38 | # and build directories, but this is only available with CMake 3.13 and higher. 39 | # The CMake binaries on the Github Actions machines are (as of this writing) 3.12 40 | # 41 | # We need to source the profile file to make sure conan is in PATH 42 | run: | 43 | [[ -f ~/.profile ]] && source ~/.profile 44 | cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=${{matrix.buildtype}} 45 | 46 | - name: Build 47 | working-directory: ${{runner.workspace}}/build 48 | shell: bash 49 | # Execute the build. You can specify a specific target with "--target " 50 | run: cmake --build . --config ${{matrix.buildtype}} 51 | 52 | - name: Test 53 | working-directory: ${{runner.workspace}}/build 54 | shell: bash 55 | # Execute tests defined by the CMake configuration. 56 | # See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail 57 | run: ctest -C ${{matrix.buildtype}} 58 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.15) 2 | 3 | # Set the project name to your project name, my project isn't very descriptive 4 | project(lambda_coroutines CXX) 5 | include(cmake/StandardProjectSettings.cmake) 6 | include(cmake/PreventInSourceBuilds.cmake) 7 | 8 | # Link this 'library' to set the c++ standard / compile-time options requested 9 | add_library(project_options INTERFACE) 10 | target_compile_features(project_options INTERFACE cxx_std_17) 11 | 12 | if(CMAKE_CXX_COMPILER_ID MATCHES ".*Clang") 13 | option(ENABLE_BUILD_WITH_TIME_TRACE "Enable -ftime-trace to generate time tracing .json files on clang" OFF) 14 | if(ENABLE_BUILD_WITH_TIME_TRACE) 15 | add_compile_definitions(project_options INTERFACE -ftime-trace) 16 | endif() 17 | endif() 18 | 19 | # Link this 'library' to use the warnings specified in CompilerWarnings.cmake 20 | add_library(project_warnings INTERFACE) 21 | 22 | # enable cache system 23 | include(cmake/Cache.cmake) 24 | 25 | # standard compiler warnings 26 | include(cmake/CompilerWarnings.cmake) 27 | set_project_warnings(project_warnings) 28 | 29 | # sanitizer options if supported by compiler 30 | include(cmake/Sanitizers.cmake) 31 | enable_sanitizers(project_options) 32 | 33 | # enable doxygen 34 | include(cmake/Doxygen.cmake) 35 | enable_doxygen() 36 | 37 | # allow for static analysis options 38 | include(cmake/StaticAnalyzers.cmake) 39 | 40 | option(BUILD_SHARED_LIBS "Enable compilation of shared libraries" OFF) 41 | option(ENABLE_TESTING "Enable Test Builds" ON) 42 | 43 | # Set up some extra Conan dependencies based on our needs before loading Conan 44 | set(CONAN_EXTRA_REQUIRES "") 45 | set(CONAN_EXTRA_OPTIONS "") 46 | 47 | include(cmake/Conan.cmake) 48 | run_conan() 49 | 50 | if(ENABLE_TESTING) 51 | enable_testing() 52 | message("Building Tests. Be sure to check out test/constexpr_tests for constexpr testing") 53 | add_subdirectory(test) 54 | endif() 55 | 56 | add_subdirectory(src) 57 | 58 | 59 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | For more information, please refer to 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Lambda Coroutines 2 | 3 | [![codecov](https://codecov.io/gh/lefticus/lambda_coroutines/branch/master/graph/badge.svg)](https://codecov.io/gh/lefticus/lambda_coroutines) 4 | ![CMake](https://github.com/lefticus/lambda_coroutines/workflows/CMake/badge.svg) 5 | 6 | ## Description 7 | 8 | A lightweight macro-based coroutine / resumable / cooperative multitasking function utility designed for C++14 lambdas. 9 | 10 | # Examples 11 | 12 | ## Rotating Infinite Sequence 13 | 14 | ```cpp 15 | // lambda must be mutable 16 | auto next_direction = [state=0]() mutable { 17 | // set up coroutine, needs a captured integral `state` value 18 | lambda_co_begin(state); 19 | 20 | // for eternity yeild the next possible value 21 | while (true) { 22 | lambda_co_yield(directions::Left); 23 | lambda_co_yield(directions::Right); 24 | lambda_co_yield(directions::Up); 25 | lambda_co_yield(directions::Down); 26 | } 27 | 28 | lambda_co_end(); 29 | }; 30 | 31 | int main() { 32 | auto val1 = next_direction(); // returns Left 33 | auto val2 = next_direction(); // returns Right 34 | // etc 35 | } 36 | ``` 37 | 38 | ## Obligatory Fibonacci Sequence 39 | 40 | ```cpp 41 | // generates the set of all fibonacci numbers representable by a ull, returns 42 | // empty optional at end of list 43 | auto fib = [state = 0, fib_2 = 0ULL, fib_1 = 1ULL]() mutable -> std::optional { 44 | lambda_co_begin(state); 45 | 46 | lambda_co_yield(0); 47 | lambda_co_yield(1); 48 | 49 | while (fib_1 < std::numeric_limits::max() / 2) { 50 | fib_2 = std::exchange(fib_1, fib_2 + fib_1); 51 | lambda_co_yield(fib_1); 52 | } 53 | 54 | lambda_co_return({}); 55 | }; 56 | ``` 57 | 58 | ## Ranged `for` Statement With Coroutine 59 | 60 | ```cpp 61 | // Using the Obligatory Fibonacci Sequence 62 | fmt::print("All possible Fib numbers representable by 'unsigned long long'"); 63 | for (const auto value : lambda_coroutines::while_has_value(fib)) { 64 | fmt::print("{}\n", value); 65 | } 66 | ``` 67 | 68 | ## CPU Instruction Decoding State Machine 69 | 70 | [Compiler Explorer playground](https://godbolt.org/z/7dr8j7) for this example. 71 | 72 | ```cpp 73 | enum OpCodes : std::uint8_t { 74 | ADD = 0, 75 | STA = 1, 76 | NOP = 2 77 | }; 78 | struct Machine { 79 | std::uint8_t PC{0}; 80 | std::uint8_t A{0}; 81 | std::array RAM{STA, 10, ADD, 15}; 82 | }; 83 | 84 | Machine machine; 85 | 86 | auto CPU = [state = 0, &machine, op = OpCodes::NOP]() mutable { 87 | lambda_co_begin(state); 88 | 89 | while(true) { 90 | op = static_cast(machine.RAM[machine.PC]); 91 | ++machine.PC; 92 | if (op == OpCodes::STA) { 93 | lambda_co_yield(); 94 | machine.A = machine.RAM[machine.PC++]; 95 | lambda_co_yield(); 96 | } else if (op == OpCodes::ADD) { 97 | lambda_co_yield(); 98 | machine.A += machine.RAM[machine.PC++]; 99 | lambda_co_yield(); 100 | } else if (op == OpCodes::NOP) { 101 | lambda_co_yield(); 102 | }; 103 | } 104 | 105 | lambda_co_end(); 106 | }; 107 | ``` 108 | 109 | 110 | 111 | # Limitations 112 | 113 | * Cannot use `switch` statements in coroutine code 114 | * Cannot declare variables in coroutine code 115 | * Anything you care about must be part of the lambda capture declaration 116 | 117 | 118 | # Using In Your Project 119 | 120 | It is one VERY simple header file https://github.com/lefticus/lambda_coroutines/blob/main/include/lambda_coroutines/lambda_coroutines.hpp 121 | -------------------------------------------------------------------------------- /cmake/Cache.cmake: -------------------------------------------------------------------------------- 1 | option(ENABLE_CACHE "Enable cache if available" ON) 2 | if(NOT ENABLE_CACHE) 3 | return() 4 | endif() 5 | 6 | set(CACHE_OPTION 7 | "ccache" 8 | CACHE STRING "Compiler cache to be used") 9 | set(CACHE_OPTION_VALUES "ccache" "sccache") 10 | set_property(CACHE CACHE_OPTION PROPERTY STRINGS ${CACHE_OPTION_VALUES}) 11 | list( 12 | FIND 13 | CACHE_OPTION_VALUES 14 | ${CACHE_OPTION} 15 | CACHE_OPTION_INDEX) 16 | 17 | if(${CACHE_OPTION_INDEX} EQUAL -1) 18 | message( 19 | STATUS 20 | "Using custom compiler cache system: '${CACHE_OPTION}', explicitly supported entries are ${CACHE_OPTION_VALUES}") 21 | endif() 22 | 23 | find_program(CACHE_BINARY ${CACHE_OPTION}) 24 | if(CACHE_BINARY) 25 | message(STATUS "${CACHE_OPTION} found and enabled") 26 | set(CMAKE_CXX_COMPILER_LAUNCHER ${CACHE_BINARY}) 27 | else() 28 | message(WARNING "${CACHE_OPTION} is enabled but was not found. Not using it") 29 | endif() 30 | -------------------------------------------------------------------------------- /cmake/CompilerWarnings.cmake: -------------------------------------------------------------------------------- 1 | # from here: 2 | # 3 | # https://github.com/lefticus/cppbestpractices/blob/master/02-Use_the_Tools_Available.md 4 | 5 | function(set_project_warnings project_name) 6 | option(WARNINGS_AS_ERRORS "Treat compiler warnings as errors" TRUE) 7 | 8 | set(MSVC_WARNINGS 9 | /W4 # Baseline reasonable warnings 10 | /w14242 # 'identifier': conversion from 'type1' to 'type1', possible loss of data 11 | /w14254 # 'operator': conversion from 'type1:field_bits' to 'type2:field_bits', possible loss of data 12 | /w14263 # 'function': member function does not override any base class virtual member function 13 | /w14265 # 'classname': class has virtual functions, but destructor is not virtual instances of this class may not 14 | # be destructed correctly 15 | /w14287 # 'operator': unsigned/negative constant mismatch 16 | /we4289 # nonstandard extension used: 'variable': loop control variable declared in the for-loop is used outside 17 | # the for-loop scope 18 | /w14296 # 'operator': expression is always 'boolean_value' 19 | /w14311 # 'variable': pointer truncation from 'type1' to 'type2' 20 | /w14545 # expression before comma evaluates to a function which is missing an argument list 21 | /w14546 # function call before comma missing argument list 22 | /w14547 # 'operator': operator before comma has no effect; expected operator with side-effect 23 | /w14549 # 'operator': operator before comma has no effect; did you intend 'operator'? 24 | /w14555 # expression has no effect; expected expression with side- effect 25 | /w14619 # pragma warning: there is no warning number 'number' 26 | /w14640 # Enable warning on thread un-safe static member initialization 27 | /w14826 # Conversion from 'type1' to 'type_2' is sign-extended. This may cause unexpected runtime behavior. 28 | /w14905 # wide string literal cast to 'LPSTR' 29 | /w14906 # string literal cast to 'LPWSTR' 30 | /w14928 # illegal copy-initialization; more than one user-defined conversion has been implicitly applied 31 | /permissive- # standards conformance mode for MSVC compiler. 32 | ) 33 | 34 | set(CLANG_WARNINGS 35 | -Wall 36 | -Wextra # reasonable and standard 37 | -Wshadow # warn the user if a variable declaration shadows one from a parent context 38 | -Wnon-virtual-dtor # warn the user if a class with virtual functions has a non-virtual destructor. This helps 39 | # catch hard to track down memory errors 40 | -Wold-style-cast # warn for c-style casts 41 | -Wcast-align # warn for potential performance problem casts 42 | -Wunused # warn on anything being unused 43 | -Woverloaded-virtual # warn if you overload (not override) a virtual function 44 | -Wpedantic # warn if non-standard C++ is used 45 | -Wconversion # warn on type conversions that may lose data 46 | -Wsign-conversion # warn on sign conversions 47 | -Wnull-dereference # warn if a null dereference is detected 48 | -Wdouble-promotion # warn if float is implicit promoted to double 49 | -Wformat=2 # warn on security issues around functions that format output (ie printf) 50 | ) 51 | 52 | if(WARNINGS_AS_ERRORS) 53 | set(CLANG_WARNINGS ${CLANG_WARNINGS} -Werror) 54 | set(MSVC_WARNINGS ${MSVC_WARNINGS} /WX) 55 | endif() 56 | 57 | set(GCC_WARNINGS 58 | ${CLANG_WARNINGS} 59 | -Wmisleading-indentation # warn if indentation implies blocks where blocks do not exist 60 | -Wduplicated-cond # warn if if / else chain has duplicated conditions 61 | -Wduplicated-branches # warn if if / else branches have duplicated code 62 | -Wlogical-op # warn about logical operations being used where bitwise were probably wanted 63 | -Wuseless-cast # warn if you perform a cast to the same type 64 | ) 65 | 66 | if(MSVC) 67 | set(PROJECT_WARNINGS ${MSVC_WARNINGS}) 68 | elseif(CMAKE_CXX_COMPILER_ID MATCHES ".*Clang") 69 | set(PROJECT_WARNINGS ${CLANG_WARNINGS}) 70 | elseif(CMAKE_CXX_COMPILER_ID STREQUAL "GNU") 71 | set(PROJECT_WARNINGS ${GCC_WARNINGS}) 72 | else() 73 | message(AUTHOR_WARNING "No compiler warnings set for '${CMAKE_CXX_COMPILER_ID}' compiler.") 74 | endif() 75 | 76 | target_compile_options(${project_name} INTERFACE ${PROJECT_WARNINGS}) 77 | 78 | endfunction() 79 | -------------------------------------------------------------------------------- /cmake/Conan.cmake: -------------------------------------------------------------------------------- 1 | macro(run_conan) 2 | # Download automatically, you can also just copy the conan.cmake file 3 | if(NOT EXISTS "${CMAKE_BINARY_DIR}/conan.cmake") 4 | message(STATUS "Downloading conan.cmake from https://github.com/conan-io/cmake-conan") 5 | file(DOWNLOAD "https://github.com/conan-io/cmake-conan/raw/v0.15/conan.cmake" "${CMAKE_BINARY_DIR}/conan.cmake") 6 | endif() 7 | 8 | include(${CMAKE_BINARY_DIR}/conan.cmake) 9 | 10 | conan_add_remote( 11 | NAME 12 | bincrafters 13 | URL 14 | https://api.bintray.com/conan/bincrafters/public-conan) 15 | 16 | conan_cmake_run( 17 | REQUIRES 18 | ${CONAN_EXTRA_REQUIRES} 19 | catch2/2.13.0 20 | docopt.cpp/0.6.3 21 | fmt/6.2.0 22 | OPTIONS 23 | ${CONAN_EXTRA_OPTIONS} 24 | BASIC_SETUP 25 | CMAKE_TARGETS # individual targets to link to 26 | BUILD 27 | missing) 28 | endmacro() 29 | -------------------------------------------------------------------------------- /cmake/Doxygen.cmake: -------------------------------------------------------------------------------- 1 | function(enable_doxygen) 2 | option(ENABLE_DOXYGEN "Enable doxygen doc builds of source" OFF) 3 | if(ENABLE_DOXYGEN) 4 | set(DOXYGEN_CALLER_GRAPH YES) 5 | set(DOXYGEN_CALL_GRAPH YES) 6 | set(DOXYGEN_EXTRACT_ALL YES) 7 | find_package(Doxygen REQUIRED dot) 8 | doxygen_add_docs(doxygen-docs ${PROJECT_SOURCE_DIR}) 9 | 10 | endif() 11 | endfunction() 12 | -------------------------------------------------------------------------------- /cmake/PreventInSourceBuilds.cmake: -------------------------------------------------------------------------------- 1 | # 2 | # This function will prevent in-source builds 3 | function(AssureOutOfSourceBuilds) 4 | # make sure the user doesn't play dirty with symlinks 5 | get_filename_component(srcdir "${CMAKE_SOURCE_DIR}" REALPATH) 6 | get_filename_component(bindir "${CMAKE_BINARY_DIR}" REALPATH) 7 | 8 | # disallow in-source builds 9 | if("${srcdir}" STREQUAL "${bindir}") 10 | message("######################################################") 11 | message("Warning: in-source builds are disabled") 12 | message("Please create a separate build directory and run cmake from there") 13 | message("######################################################") 14 | message(FATAL_ERROR "Quitting configuration") 15 | endif() 16 | endfunction() 17 | 18 | assureoutofsourcebuilds() 19 | -------------------------------------------------------------------------------- /cmake/Sanitizers.cmake: -------------------------------------------------------------------------------- 1 | function(enable_sanitizers project_name) 2 | 3 | if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU" OR CMAKE_CXX_COMPILER_ID MATCHES ".*Clang") 4 | option(ENABLE_COVERAGE "Enable coverage reporting for gcc/clang" FALSE) 5 | 6 | if(ENABLE_COVERAGE) 7 | target_compile_options(${project_name} INTERFACE --coverage -O0 -g) 8 | target_link_libraries(${project_name} INTERFACE --coverage) 9 | endif() 10 | 11 | set(SANITIZERS "") 12 | 13 | option(ENABLE_SANITIZER_ADDRESS "Enable address sanitizer" FALSE) 14 | if(ENABLE_SANITIZER_ADDRESS) 15 | list(APPEND SANITIZERS "address") 16 | endif() 17 | 18 | option(ENABLE_SANITIZER_LEAK "Enable leak sanitizer" FALSE) 19 | if(ENABLE_SANITIZER_LEAK) 20 | list(APPEND SANITIZERS "leak") 21 | endif() 22 | 23 | option(ENABLE_SANITIZER_UNDEFINED_BEHAVIOR "Enable undefined behavior sanitizer" FALSE) 24 | if(ENABLE_SANITIZER_UNDEFINED_BEHAVIOR) 25 | list(APPEND SANITIZERS "undefined") 26 | endif() 27 | 28 | option(ENABLE_SANITIZER_THREAD "Enable thread sanitizer" FALSE) 29 | if(ENABLE_SANITIZER_THREAD) 30 | if("address" IN_LIST SANITIZERS OR "leak" IN_LIST SANITIZERS) 31 | message(WARNING "Thread sanitizer does not work with Address and Leak sanitizer enabled") 32 | else() 33 | list(APPEND SANITIZERS "thread") 34 | endif() 35 | endif() 36 | 37 | option(ENABLE_SANITIZER_MEMORY "Enable memory sanitizer" FALSE) 38 | if(ENABLE_SANITIZER_MEMORY AND CMAKE_CXX_COMPILER_ID MATCHES ".*Clang") 39 | if("address" IN_LIST SANITIZERS 40 | OR "thread" IN_LIST SANITIZERS 41 | OR "leak" IN_LIST SANITIZERS) 42 | message(WARNING "Memory sanitizer does not work with Address, Thread and Leak sanitizer enabled") 43 | else() 44 | list(APPEND SANITIZERS "memory") 45 | endif() 46 | endif() 47 | 48 | list( 49 | JOIN 50 | SANITIZERS 51 | "," 52 | LIST_OF_SANITIZERS) 53 | 54 | endif() 55 | 56 | if(LIST_OF_SANITIZERS) 57 | if(NOT 58 | "${LIST_OF_SANITIZERS}" 59 | STREQUAL 60 | "") 61 | target_compile_options(${project_name} INTERFACE -fsanitize=${LIST_OF_SANITIZERS}) 62 | target_link_libraries(${project_name} INTERFACE -fsanitize=${LIST_OF_SANITIZERS}) 63 | endif() 64 | endif() 65 | 66 | endfunction() 67 | -------------------------------------------------------------------------------- /cmake/StandardProjectSettings.cmake: -------------------------------------------------------------------------------- 1 | # Set a default build type if none was specified 2 | if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) 3 | message(STATUS "Setting build type to 'RelWithDebInfo' as none was specified.") 4 | set(CMAKE_BUILD_TYPE 5 | RelWithDebInfo 6 | CACHE STRING "Choose the type of build." FORCE) 7 | # Set the possible values of build type for cmake-gui, ccmake 8 | set_property( 9 | CACHE CMAKE_BUILD_TYPE 10 | PROPERTY STRINGS 11 | "Debug" 12 | "Release" 13 | "MinSizeRel" 14 | "RelWithDebInfo") 15 | endif() 16 | 17 | # Generate compile_commands.json to make it easier to work with clang based tools 18 | set(CMAKE_EXPORT_COMPILE_COMMANDS ON) 19 | 20 | option(ENABLE_IPO "Enable Interprocedural Optimization, aka Link Time Optimization (LTO)" OFF) 21 | 22 | if(ENABLE_IPO) 23 | include(CheckIPOSupported) 24 | check_ipo_supported( 25 | RESULT 26 | result 27 | OUTPUT 28 | output) 29 | if(result) 30 | set(CMAKE_INTERPROCEDURAL_OPTIMIZATION TRUE) 31 | else() 32 | message(SEND_ERROR "IPO is not supported: ${output}") 33 | endif() 34 | endif() 35 | -------------------------------------------------------------------------------- /cmake/StaticAnalyzers.cmake: -------------------------------------------------------------------------------- 1 | option(ENABLE_CPPCHECK "Enable static analysis with cppcheck" OFF) 2 | option(ENABLE_CLANG_TIDY "Enable static analysis with clang-tidy" OFF) 3 | option(ENABLE_INCLUDE_WHAT_YOU_USE "Enable static analysis with include-what-you-use" OFF) 4 | 5 | if(ENABLE_CPPCHECK) 6 | find_program(CPPCHECK cppcheck) 7 | if(CPPCHECK) 8 | set(CMAKE_CXX_CPPCHECK 9 | ${CPPCHECK} 10 | --suppress=missingInclude 11 | --enable=all 12 | --inline-suppr 13 | --inconclusive 14 | -i 15 | ${CMAKE_SOURCE_DIR}/imgui/lib) 16 | else() 17 | message(SEND_ERROR "cppcheck requested but executable not found") 18 | endif() 19 | endif() 20 | 21 | if(ENABLE_CLANG_TIDY) 22 | find_program(CLANGTIDY clang-tidy) 23 | if(CLANGTIDY) 24 | set(CMAKE_CXX_CLANG_TIDY ${CLANGTIDY} -extra-arg=-Wno-unknown-warning-option) 25 | else() 26 | message(SEND_ERROR "clang-tidy requested but executable not found") 27 | endif() 28 | endif() 29 | 30 | if(ENABLE_INCLUDE_WHAT_YOU_USE) 31 | find_program(INCLUDE_WHAT_YOU_USE include-what-you-use) 32 | if(INCLUDE_WHAT_YOU_USE) 33 | set(CMAKE_CXX_INCLUDE_WHAT_YOU_USE ${INCLUDE_WHAT_YOU_USE}) 34 | else() 35 | message(SEND_ERROR "include-what-you-use requested but executable not found") 36 | endif() 37 | endif() 38 | -------------------------------------------------------------------------------- /include/lambda_coroutines/lambda_coroutines.hpp: -------------------------------------------------------------------------------- 1 | #ifndef LAMBDA_COROUTINES_HPP 2 | #define LAMBDA_COROUTINES_HPP 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | // because these are macros we cannot meaningfully use namespaces 10 | 11 | // https://stackoverflow.com/questions/57137351/line-is-not-constexpr-in-msvc 12 | // https://www.chiark.greenend.org.uk/~sgtatham/coroutines.html 13 | #define LAMBDA_CO_CONCATENATE_(a, b) a##b 14 | #define LAMBDA_CO_CONCATENATE(a, b) LAMBDA_CO_CONCATENATE_(a, b) 15 | #define LAMBDA_CO_CONSTEXPR_LINE long(LAMBDA_CO_CONCATENATE(__LINE__, U)) 16 | 17 | 18 | namespace lambda_coroutines { 19 | // Internal tool for checking lambda coroutine requirements 20 | template 21 | constexpr void lambda_co_status_check(ParamType &value) 22 | { 23 | static_assert(std::is_integral_v, 24 | "integral state value required"); 25 | static_assert(!std::is_const_v, "mutable lambda required"); 26 | static_assert(sizeof(value) >= 4, "state value must be at least 32 bits"); 27 | } 28 | 29 | #define lambda_co_begin(state_variable) \ 30 | ::lambda_coroutines::lambda_co_status_check(state_variable); \ 31 | auto &coroutine_lambda_internal_state_ref = state_variable; \ 32 | switch (state_variable) { \ 33 | default: \ 34 | case 0: 35 | 36 | #define lambda_co_return(return_value) \ 37 | } \ 38 | return return_value 39 | 40 | #define lambda_co_yield(yield_value) \ 41 | coroutine_lambda_internal_state_ref = LAMBDA_CO_CONSTEXPR_LINE; \ 42 | return yield_value; \ 43 | case LAMBDA_CO_CONSTEXPR_LINE: 44 | 45 | #define lambda_co_end() } 46 | 47 | template 48 | [[nodiscard]] constexpr auto range( 49 | Lambda lambda, 50 | std::size_t skip_ = 0, 51 | std::optional length_ = {}, 52 | std::size_t stride_ = 1) 53 | { 54 | struct Range 55 | { 56 | Lambda generator; 57 | std::optional length; 58 | std::size_t stride; 59 | std::size_t position = 0; 60 | 61 | struct Iterator 62 | { 63 | std::reference_wrapper lambda; 64 | std::reference_wrapper position; 65 | std::optional length; 66 | std::size_t stride = 1; 67 | bool is_end = false; 68 | 69 | [[nodiscard]] constexpr bool has_more() const noexcept 70 | { 71 | if (length && (position.get() >= length.value())) { 72 | return false; 73 | } 74 | 75 | return true; 76 | } 77 | 78 | constexpr bool increment_position() 79 | { 80 | if (has_more()) { 81 | ++position.get(); 82 | return true; 83 | } else { 84 | return false; 85 | } 86 | } 87 | 88 | constexpr auto &operator++() 89 | { 90 | for (std::size_t i = 1; i < stride; ++i) { 91 | if (increment_position()) { 92 | lambda(); 93 | } 94 | } 95 | return *this; 96 | } 97 | 98 | constexpr bool operator==(const Iterator &rhs) const 99 | { 100 | // return false; 101 | if (is_end && !rhs.has_more()) { 102 | return true; 103 | } 104 | if (rhs.is_end && !has_more()) { 105 | return true; 106 | } 107 | if (rhs.is_end && is_end) { 108 | return true; 109 | } 110 | 111 | return false; 112 | } 113 | 114 | constexpr bool operator!=(const Iterator &rhs) const 115 | { 116 | return !(*this == rhs); 117 | } 118 | 119 | [[nodiscard]] constexpr decltype(auto) operator*() 120 | { 121 | increment_position(); 122 | return lambda(); 123 | } 124 | }; 125 | 126 | [[nodiscard]] constexpr auto begin() noexcept 127 | { 128 | return Iterator{ generator, position, length, stride, false }; 129 | } 130 | 131 | [[nodiscard]] constexpr auto end() noexcept 132 | { 133 | return Iterator{ generator, position, length, stride, true }; 134 | } 135 | }; 136 | 137 | for (std::size_t i = 0; i < skip_; ++i) { 138 | lambda(); 139 | } 140 | return Range{ std::move(lambda), length_, stride_ }; 141 | } 142 | 143 | template 144 | [[nodiscard]] constexpr auto while_has_value(Lambda lambda) 145 | { 146 | struct Range 147 | { 148 | Lambda generator; 149 | 150 | struct Iterator 151 | { 152 | std::reference_wrapper lambda; 153 | std::invoke_result_t current_value{}; 154 | 155 | Iterator(Lambda &lambda_, bool is_end_) 156 | : lambda(lambda_), current_value([&]() { 157 | return is_end_ ? decltype(current_value){} : lambda(); 158 | }()) {} 159 | 160 | constexpr auto &operator++() 161 | { 162 | current_value = lambda(); 163 | return *this; 164 | } 165 | 166 | constexpr bool operator==(const Iterator &rhs) const 167 | { 168 | if (!current_value && !rhs.current_value) { 169 | return true; 170 | } 171 | 172 | return false; 173 | } 174 | 175 | constexpr bool operator!=(const Iterator &rhs) const 176 | { 177 | return !(*this == rhs); 178 | } 179 | 180 | [[nodiscard]] constexpr decltype(auto) operator*() 181 | { 182 | return current_value.value(); 183 | } 184 | }; 185 | 186 | [[nodiscard]] constexpr auto begin() noexcept 187 | { 188 | return Iterator{ generator, false }; 189 | } 190 | 191 | [[nodiscard]] constexpr auto end() noexcept 192 | { 193 | return Iterator{ generator, true }; 194 | } 195 | }; 196 | 197 | return Range{ std::move(lambda) }; 198 | } 199 | 200 | }// namespace lambda_coroutines 201 | #endif 202 | -------------------------------------------------------------------------------- /src/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | 2 | # Generic test that uses conan libs 3 | add_executable(lambda_coroutines main.cpp) 4 | target_link_libraries( 5 | lambda_coroutines 6 | PRIVATE project_options 7 | project_warnings 8 | CONAN_PKG::docopt.cpp 9 | CONAN_PKG::fmt) 10 | 11 | target_include_directories(lambda_coroutines PRIVATE "${CMAKE_SOURCE_DIR}/include") -------------------------------------------------------------------------------- /src/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "lambda_coroutines/lambda_coroutines.hpp" 5 | #include 6 | #include 7 | 8 | static constexpr auto USAGE = 9 | R"(Lambda Coroutines Test 10 | Usage: 11 | lambda_coroutines [options] 12 | 13 | Options: 14 | -h --help Show this screen. 15 | --version Show version. 16 | --stride= Stepping between fib numbers [default: 1]. 17 | )"; 18 | 19 | int main(int argc, const char **argv) 20 | { 21 | std::map args = docopt::docopt(USAGE, 22 | { std::next(argv), std::next(argv, argc) }, 23 | true,// show help if requested 24 | "Lambda Coroutines 0.1");// version string 25 | 26 | 27 | const auto stride = static_cast(args["--stride"].asLong()); 28 | 29 | // generates a new fib each time called, no concern with overflow 30 | auto fib = [state = 0, fib_2 = 0u, fib_1 = 1u]() mutable -> unsigned int { 31 | lambda_co_begin(state); 32 | 33 | lambda_co_yield(0); 34 | lambda_co_yield(1); 35 | 36 | while (true) { 37 | fib_2 = std::exchange(fib_1, fib_2 + fib_1); 38 | lambda_co_yield(fib_1); 39 | } 40 | 41 | lambda_co_return(0); 42 | }; 43 | 44 | // generates the set of all fibonacci numbers representable by a ull, returns 45 | // empty optional at end of list 46 | auto fib2 = [state = 0, fib_2 = 0ULL, fib_1 = 1ULL]() mutable -> std::optional { 47 | lambda_co_begin(state); 48 | 49 | lambda_co_yield(0); 50 | lambda_co_yield(1); 51 | 52 | while (fib_1 < std::numeric_limits::max() / 2) { 53 | fib_2 = std::exchange(fib_1, fib_2 + fib_1); 54 | lambda_co_yield(fib_1); 55 | } 56 | 57 | lambda_co_return({}); 58 | }; 59 | 60 | fmt::print("Requested Range of Fib Numbers:\n"); 61 | for (const auto value : lambda_coroutines::range(fib, 0, 15, stride)) { 62 | fmt::print("{}\n", value); 63 | } 64 | 65 | fmt::print("All possible Fib numbers representable by 'unsigned long long'"); 66 | for (const auto value : lambda_coroutines::while_has_value(fib2)) { 67 | fmt::print("{}\n", value); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /test/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # automatically enable catch2 to generate ctest targets 2 | if(CONAN_CATCH2_ROOT_DEBUG) 3 | include(${CONAN_CATCH2_ROOT_DEBUG}/lib/cmake/Catch2/Catch.cmake) 4 | else() 5 | include(${CONAN_CATCH2_ROOT}/lib/cmake/Catch2/Catch.cmake) 6 | endif() 7 | 8 | add_library(catch_main STATIC catch_main.cpp) 9 | target_link_libraries(catch_main PUBLIC CONAN_PKG::catch2) 10 | target_link_libraries(catch_main PRIVATE project_options) 11 | 12 | add_executable(tests tests.cpp) 13 | target_link_libraries(tests PRIVATE project_warnings project_options catch_main) 14 | target_include_directories(tests PRIVATE "${CMAKE_SOURCE_DIR}/include") 15 | 16 | # automatically discover tests that are defined in catch based test files you can modify the unittests. TEST_PREFIX to 17 | # whatever you want, or use different for different binaries 18 | catch_discover_tests( 19 | tests 20 | TEST_PREFIX 21 | "unittests." 22 | EXTRA_ARGS 23 | -s 24 | --reporter=xml 25 | --out=tests.xml) 26 | 27 | # Add a file containing a set of constexpr tests 28 | add_executable(constexpr_tests constexpr_tests.cpp) 29 | target_link_libraries(constexpr_tests PRIVATE project_options project_warnings catch_main) 30 | target_include_directories(constexpr_tests PRIVATE "${CMAKE_SOURCE_DIR}/include") 31 | 32 | catch_discover_tests( 33 | constexpr_tests 34 | TEST_PREFIX 35 | "constexpr." 36 | EXTRA_ARGS 37 | -s 38 | --reporter=xml 39 | --out=constexpr.xml) 40 | 41 | # Disable the constexpr portion of the test, and build again this allows us to have an executable that we can debug when 42 | # things go wrong with the constexpr testing 43 | add_executable(relaxed_constexpr_tests constexpr_tests.cpp) 44 | target_link_libraries(relaxed_constexpr_tests PRIVATE project_options project_warnings catch_main) 45 | target_compile_definitions(relaxed_constexpr_tests PRIVATE -DCATCH_CONFIG_RUNTIME_STATIC_REQUIRE) 46 | target_include_directories(relaxed_constexpr_tests PRIVATE "${CMAKE_SOURCE_DIR}/include") 47 | 48 | catch_discover_tests( 49 | relaxed_constexpr_tests 50 | TEST_PREFIX 51 | "relaxed_constexpr." 52 | EXTRA_ARGS 53 | -s 54 | --reporter=xml 55 | --out=relaxed_constexpr.xml) 56 | -------------------------------------------------------------------------------- /test/catch_main.cpp: -------------------------------------------------------------------------------- 1 | #define CATCH_CONFIG_MAIN // This tells the catch header to generate a main 2 | 3 | #include 4 | 5 | 6 | -------------------------------------------------------------------------------- /test/constexpr_tests.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include 5 | #include "lambda_coroutines/lambda_coroutines.hpp" 6 | 7 | 8 | // need our own constexpr exchange because that only 9 | // comes with C++20 10 | template 11 | constexpr T 12 | exchange(T &obj, U &&new_value) 13 | { 14 | T old_value = std::move(obj); 15 | obj = std::forward(new_value); 16 | return old_value; 17 | } 18 | 19 | constexpr std::array get_first_5() 20 | { 21 | auto fib = [state = 0, fib_2 = 0ULL, fib_1 = 1ULL]() mutable -> std::optional { 22 | lambda_co_begin(state); 23 | 24 | lambda_co_yield(0); 25 | lambda_co_yield(1); 26 | 27 | while (fib_1 < std::numeric_limits::max() / 2) { 28 | fib_2 = exchange(fib_1, fib_2 + fib_1); 29 | lambda_co_yield(fib_1); 30 | } 31 | 32 | lambda_co_return({}); 33 | }; 34 | 35 | return { fib().value(), fib().value(), fib().value(), fib().value(), fib().value() }; 36 | } 37 | 38 | TEST_CASE("lambda_coroutines are constexpr capable", "[constexpr]") 39 | { 40 | STATIC_REQUIRE(get_first_5()[4] == 3ULL); 41 | } 42 | -------------------------------------------------------------------------------- /test/tests.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "lambda_coroutines/lambda_coroutines.hpp" 3 | 4 | TEST_CASE("Generate infinite sequence") 5 | { 6 | enum struct directions { 7 | Left, 8 | Right, 9 | Up, 10 | Down 11 | }; 12 | 13 | auto next_direction = [state=0]() mutable { 14 | lambda_co_begin(state); 15 | 16 | while (true) { 17 | lambda_co_yield(directions::Left); 18 | lambda_co_yield(directions::Right); 19 | lambda_co_yield(directions::Up); 20 | lambda_co_yield(directions::Down); 21 | } 22 | 23 | lambda_co_end(); 24 | }; 25 | 26 | REQUIRE(next_direction() == directions::Left); 27 | REQUIRE(next_direction() == directions::Right); 28 | REQUIRE(next_direction() == directions::Up); 29 | REQUIRE(next_direction() == directions::Down); 30 | REQUIRE(next_direction() == directions::Left); 31 | REQUIRE(next_direction() == directions::Right); 32 | } 33 | 34 | TEST_CASE("Cooperative multi tasking") 35 | { 36 | enum OpCodes : std::uint8_t { 37 | ADD = 0, 38 | STA = 1, 39 | NOP = 2 40 | }; 41 | struct Machine { 42 | std::uint8_t PC{0}; 43 | std::uint8_t A{0}; 44 | std::array RAM{STA, 10, ADD, 15}; 45 | }; 46 | 47 | Machine machine; 48 | 49 | auto CPU = [state = 0, &machine, op = OpCodes::NOP]() mutable { 50 | lambda_co_begin(state); 51 | 52 | while(true) { 53 | op = static_cast(machine.RAM[machine.PC]); 54 | ++machine.PC; 55 | if (op == OpCodes::STA) { 56 | lambda_co_yield(); 57 | machine.A = machine.RAM[machine.PC++]; 58 | lambda_co_yield(); 59 | } else if (op == OpCodes::ADD) { 60 | lambda_co_yield(); 61 | machine.A += machine.RAM[machine.PC++]; 62 | lambda_co_yield(); 63 | } else if (op == OpCodes::NOP) { 64 | lambda_co_yield(); 65 | }; 66 | } 67 | 68 | lambda_co_end(); 69 | }; 70 | 71 | CPU(); 72 | REQUIRE(machine.PC == 1); 73 | REQUIRE(machine.A == 0); 74 | CPU(); 75 | REQUIRE(machine.PC == 2); 76 | REQUIRE(machine.A == 10); 77 | CPU(); 78 | REQUIRE(machine.PC == 3); 79 | REQUIRE(machine.A == 10); 80 | CPU(); 81 | REQUIRE(machine.PC == 4); 82 | REQUIRE(machine.A == 25); 83 | } --------------------------------------------------------------------------------