├── .clang-format ├── .github ├── FUNDING.yml └── workflows │ ├── cmake.yml │ ├── coverage.yml │ ├── sanitizers.yml │ └── valgrind.yml ├── .gitignore ├── CMakeLists.txt ├── LICENSE ├── README.md ├── cmake ├── CodeCoverage.cmake ├── FetchGTest.cmake └── Sanitizers.cmake ├── develop.sh ├── develop └── include │ ├── data.h │ ├── do_notation.h │ ├── hspp_develop.h │ ├── range.h │ └── typeclass.h ├── hspp.svg ├── include ├── concurrent.h ├── hspp.h ├── monadTrans.h └── parser.h ├── sample ├── CMakeLists.txt ├── common.h ├── coroutine.cpp ├── coroutine.sh ├── lyahfgg.cpp ├── lyahfgg12.cpp ├── lyahfgg2.cpp ├── lyahfgg6.cpp ├── lyahfgg7.cpp ├── parse_expr.cpp ├── pause.cpp ├── proposal.cpp └── walk_the_line.cpp ├── single-header.sh └── test ├── CMakeLists.txt └── hspp ├── CMakeLists.txt ├── concurrent.cpp ├── monadTrans.cpp └── test.cpp / .clang-format: -------------------------------------------------------------------------------- 1 | BasedOnStyle: Microsoft -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: [bowenfu] # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] 4 | patreon: bfu # Replace with a single Patreon username 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: bowenfu # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry 9 | liberapay: bfu # Replace with a single Liberapay username 10 | issuehunt: # Replace with a single IssueHunt username 11 | otechie: # Replace with a single Otechie username 12 | custom: https://afdian.net/a/amazing42 # http://paypal.me/bowfu # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] 13 | -------------------------------------------------------------------------------- /.github/workflows/cmake.yml: -------------------------------------------------------------------------------- 1 | name: CMake 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | branches: [ main ] 8 | 9 | env: 10 | # Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.) 11 | BUILD_TYPE: Release 12 | 13 | jobs: 14 | build: 15 | # The CMake configure and build commands are platform agnostic and should work equally 16 | # well on Windows or Mac. You can convert this to a matrix build if you need 17 | # cross-platform coverage. 18 | # See: https://docs.github.com/en/free-pro-team@latest/actions/learn-github-actions/managing-complex-workflows#using-a-build-matrix 19 | runs-on: ${{ matrix.os }} 20 | 21 | strategy: 22 | matrix: 23 | os: [macos-latest, ubuntu-latest, windows-latest] 24 | std: [17, 20] 25 | exclude: # Skip win + std20 due to out of heap memory 26 | - os: windows-latest 27 | std: 20 28 | 29 | steps: 30 | - uses: actions/checkout@v2 31 | 32 | - name: Configure CMake 33 | # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make. 34 | # See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type 35 | run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DCMAKE_CXX_STANDARD=${{matrix.std}} 36 | 37 | - name: Build 38 | # Build your program with the given configuration 39 | run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} # --parallel 40 | 41 | - name: Test 42 | working-directory: ${{github.workspace}}/build 43 | # Execute tests defined by the CMake configuration. 44 | # See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail 45 | run: ctest --output-on-failure --parallel 8 -C ${{env.BUILD_TYPE}} 46 | -------------------------------------------------------------------------------- /.github/workflows/coverage.yml: -------------------------------------------------------------------------------- 1 | name: Test Coverage 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | branches: [ main ] 8 | 9 | env: 10 | # Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.) 11 | BUILD_TYPE: Coverage 12 | 13 | jobs: 14 | build: 15 | # The CMake configure and build commands are platform agnostic and should work equally 16 | # well on Windows or Mac. You can convert this to a matrix build if you need 17 | # cross-platform coverage. 18 | # See: https://docs.github.com/en/free-pro-team@latest/actions/learn-github-actions/managing-complex-workflows#using-a-build-matrix 19 | runs-on: ${{ matrix.os }} 20 | 21 | strategy: 22 | matrix: 23 | os: [ubuntu-latest] 24 | std: [20] 25 | 26 | steps: 27 | - uses: actions/checkout@v2 28 | 29 | - name: Install dependencies 30 | run: sudo apt install gcovr 31 | 32 | - name: Configure CMake 33 | # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make. 34 | # See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type 35 | run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DCMAKE_CXX_STANDARD=${{matrix.std}} 36 | 37 | - name: Build 38 | # Build your program with the given configuration 39 | run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} --parallel 4 40 | 41 | - name: Test 42 | working-directory: ${{github.workspace}}/build 43 | # Execute tests defined by the CMake configuration. 44 | # See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail 45 | run: ctest --output-on-failure --parallel 4 -C ${{env.BUILD_TYPE}} 46 | 47 | - name: Compute Coverage 48 | working-directory: ${{github.workspace}}/build 49 | run: gcovr -r .. -f ../include/ -x coverage.xml . 50 | 51 | - name: Upload Coverage 52 | working-directory: ${{github.workspace}}/build 53 | shell: bash 54 | run: bash <(curl -s https://codecov.io/bash) -f coverage.xml 55 | -------------------------------------------------------------------------------- /.github/workflows/sanitizers.yml: -------------------------------------------------------------------------------- 1 | name: Sanitizers 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | branches: [ main ] 8 | 9 | jobs: 10 | build: 11 | # The CMake configure and build commands are platform agnostic and should work equally 12 | # well on Windows or Mac. You can convert this to a matrix build if you need 13 | # cross-platform coverage. 14 | # See: https://docs.github.com/en/free-pro-team@latest/actions/learn-github-actions/managing-complex-workflows#using-a-build-matrix 15 | runs-on: ${{ matrix.os }} 16 | 17 | strategy: 18 | matrix: 19 | os: [ubuntu-latest] 20 | std: [20] 21 | cxx: [clang++] 22 | build_type: [ASAN, TSAN, UBSAN, LSAN] # MSAN not supported by macos 23 | fail-fast: false 24 | 25 | steps: 26 | - uses: actions/checkout@v2 27 | 28 | - name: Configure CMake 29 | env: 30 | CXX: ${{ matrix.cxx }} 31 | # ASAN_OPTIONS: suppressions=MyASan.supp 32 | 33 | # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make. 34 | # See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type 35 | run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{ matrix.build_type }} -DCMAKE_CXX_STANDARD=${{matrix.std}} 36 | 37 | - name: Build 38 | # Build your program with the given configuration 39 | run: cmake --build ${{github.workspace}}/build --config ${{ matrix.build_type }} --parallel 4 40 | 41 | - name: Test 42 | working-directory: ${{github.workspace}}/build 43 | # Execute tests defined by the CMake configuration. 44 | # See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail 45 | run: ctest --output-on-failure --parallel 4 -C ${{ matrix.build_type }} --exclude-regex atomically.3 46 | 47 | -------------------------------------------------------------------------------- /.github/workflows/valgrind.yml: -------------------------------------------------------------------------------- 1 | name: valgrind 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | branches: [ main ] 8 | 9 | env: 10 | # Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.) 11 | BUILD_TYPE: Debug 12 | 13 | jobs: 14 | build: 15 | # The CMake configure and build commands are platform agnostic and should work equally 16 | # well on Windows or Mac. You can convert this to a matrix build if you need 17 | # cross-platform coverage. 18 | # See: https://docs.github.com/en/free-pro-team@latest/actions/learn-github-actions/managing-complex-workflows#using-a-build-matrix 19 | runs-on: ${{ matrix.os }} 20 | 21 | strategy: 22 | matrix: 23 | os: [ubuntu-latest] 24 | std: [20] 25 | 26 | steps: 27 | - uses: actions/checkout@v2 28 | 29 | - name: Install valgrind 30 | run: sudo apt-get install valgrind 31 | 32 | - name: Configure CMake 33 | # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make. 34 | # See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type 35 | run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DCMAKE_CXX_STANDARD=${{matrix.std}} -DCTEST_MEMORYCHECK_COMMAND=/usr/bin/valgrind 36 | 37 | - name: Build 38 | # Build your program with the given configuration 39 | run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} --parallel 4 40 | 41 | - name: Test 42 | working-directory: ${{github.workspace}}/build 43 | # Execute tests defined by the CMake configuration. 44 | # See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail 45 | run: | 46 | ctest --output-on-failure --parallel 4 -C ${{env.BUILD_TYPE}} --exclude-regex atomically.3 --overwrite MemoryCheckCommandOptions="--leak-check=full --error-exitcode=100" -T memcheck 47 | # --overwrite MemoryCheckSuppressionFile=/path/to/valgrind.suppressions \ 48 | 49 | - name: Cat log 50 | if: failure() 51 | run: | 52 | cat ${{github.workspace}}/build/Testing/Temporary/MemoryChecker*.log 53 | 54 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Prerequisites 2 | *.d 3 | 4 | # Compiled Object files 5 | *.slo 6 | *.lo 7 | *.o 8 | *.obj 9 | 10 | # Precompiled Headers 11 | *.gch 12 | *.pch 13 | 14 | # Compiled Dynamic libraries 15 | *.so 16 | *.dylib 17 | *.dll 18 | 19 | # Fortran module files 20 | *.mod 21 | *.smod 22 | 23 | # Compiled Static libraries 24 | *.lai 25 | *.la 26 | *.a 27 | *.lib 28 | 29 | # Executables 30 | *.exe 31 | *.out 32 | *.app 33 | 34 | _deps/ 35 | .vscode/* 36 | **/CMakeFiles 37 | **/cmake_install* 38 | **/CTest* 39 | **/*Cache* 40 | **/unittests*cmake 41 | Dart* 42 | Testing 43 | log 44 | Makefile 45 | build/* -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.15...3.19) 2 | 3 | project( 4 | hspp 5 | VERSION 0.0.1 6 | LANGUAGES CXX) 7 | 8 | list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/cmake") 9 | 10 | set(CMAKE_COLOR_MAKEFILE on) 11 | 12 | if (NOT CMAKE_CXX_STANDARD) 13 | set(CMAKE_CXX_STANDARD 20) 14 | endif() 15 | 16 | message(STATUS "CXX_STANDARD: ${CMAKE_CXX_STANDARD}") 17 | 18 | list(APPEND 19 | BASE_COMPILE_FLAGS 20 | "$<$,$,$>:-Wall;-Wextra;-pedantic;-Werror;-Wno-parentheses;-Wno-shadow;-Wconversion;-Wsign-conversion>" 21 | "$<$:/W4>") # /WX for -Werror 22 | 23 | list(APPEND 24 | BASE_COMPILE_FLAGS 25 | "$<$:-DFOR_CLANG>" 26 | "$<$:-DFOR_APPLECLANG>" 27 | "$<$:-DFOR_GNU;-Wno-maybe-uninitialized>" 28 | "$<$:-DFOR_MSVC>" 29 | ) 30 | 31 | # Fix atomic lib linking for gcc. 32 | list(APPEND 33 | BASE_ADDITIONAL_LIBS 34 | "$<$,$>:atomic>") 35 | 36 | set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/bin) 37 | set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/lib) 38 | set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/lib) 39 | 40 | if (CMAKE_BUILD_TYPE STREQUAL "Coverage") 41 | include(CodeCoverage) 42 | list(APPEND BASE_COMPILE_FLAGS "-g;-O0;-fprofile-arcs;-ftest-coverage") 43 | endif() #CMAKE_BUILD_TYPE STREQUAL "Coverage" 44 | 45 | if (CMAKE_BUILD_TYPE STREQUAL "MSAN") 46 | add_link_options("-L${PROJECT_SOURCE_DIR}/libcxx_msan/lib;-lc++abi") 47 | endif() #CMAKE_BUILD_TYPE STREQUAL "MSAN" 48 | 49 | # Target. 50 | add_library(hspp INTERFACE) 51 | target_include_directories(hspp INTERFACE 52 | ${PROJECT_SOURCE_DIR}/include) 53 | 54 | if(CMAKE_PROJECT_NAME STREQUAL PROJECT_NAME) 55 | include(CTest) 56 | if(BUILD_TESTING) 57 | include(Sanitizers) 58 | add_subdirectory(test) 59 | add_subdirectory(sample) 60 | endif() 61 | endif() 62 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # hspp 2 | 3 | ![hspp](./hspp.svg) 4 | 5 | ## hspp: bring Haskell Style Programming to C++. 6 | 7 | ![Standard](https://img.shields.io/badge/c%2B%2B-17/20-blue.svg) 8 | 9 | ![Platform](https://img.shields.io/badge/platform-linux-blue) 10 | ![Platform](https://img.shields.io/badge/platform-osx-blue) 11 | ![Platform](https://img.shields.io/badge/platform-win-blue) 12 | 13 | [![CMake](https://github.com/BowenFu/hspp/actions/workflows/cmake.yml/badge.svg)](https://github.com/BowenFu/hspp/actions/workflows/cmake.yml) 14 | [![CMake](https://github.com/BowenFu/hspp/actions/workflows/sanitizers.yml/badge.svg)](https://github.com/BowenFu/hspp/actions/workflows/sanitizers.yml) 15 | ![GitHub license](https://img.shields.io/github/license/BowenFu/hspp.svg) 16 | [![codecov](https://codecov.io/gh/BowenFu/hspp/branch/main/graph/badge.svg)](https://codecov.io/gh/BowenFu/hspp) 17 | 18 | C++ are introducing monadic interfaces. We have compared hspp and some proposals, refer to [std::optional and std::expected](https://github.com/BowenFu/hspp/blob/main/sample/proposal.cpp) for more details. 19 | 20 | 21 | ## Mom, can we have monadic do notation / monad comprehension in C++? 22 | 23 | Here you are! 24 | 25 | [badge.godbolt]: https://img.shields.io/badge/try-godbolt-blue 26 | 27 | ### Sample 1 for Maybe (similar to std::optional) Monad used in do notation 28 | 29 | The sample is originated from Learn You a Haskell for Great Good! 30 | 31 | "Pierre has decided to take a break from his job at the fish farm and try tightrope walking. He's not that bad at it, but he does have one problem: birds keep landing on his balancing pole! 32 | Let's say that he keeps his balance if the number of birds on the left side of the pole and on the right side of the pole is within three." 33 | 34 | Note that Pierre may also suddenly slip and fall when there is a banana. 35 | 36 | Original Haskell version 37 | 38 | ```haskell 39 | routine :: Maybe Pole 40 | routine = do 41 | start <- return (0,0) 42 | first <- landLeft 2 start 43 | Nothing 44 | second <- landRight 2 first 45 | landLeft 1 second 46 | ``` 47 | 48 | C++ version using hspp 49 | 50 | [godbolt3]: https://godbolt.org/z/9T5sa64nE 51 | 52 | [![Try it on godbolt][badge.godbolt]][godbolt3] 53 | 54 | ```c++ 55 | Id start, first, second; 56 | auto const routine = do_( 57 | start <= return_ | Pole{0,0}), 58 | first <= (landLeft | 2 | start), 59 | nothing, 60 | second <= (landRight | 2 | first), 61 | landLeft | 1 | second 62 | ); 63 | ``` 64 | 65 | ### Sample 2 for Either (similar to std::expected) Monad used in do notation 66 | 67 | In [p0323r0](https://open-std.org/JTC1/SC22/WG21/docs/papers/2016/p0323r0.pdf), there is a proposal to add a utility class to represent expected monad. 68 | 69 | A use case would be like 70 | ```c++ 71 | expected safe_divide(int i, int j) 72 | { 73 | if (j == 0) return make_unexpected(arithmetic_errc::divide_by_zero); 74 | if (i%j != 0) return make_unexpected(arithmetic_errc::not_integer_division); 75 | else return i / j; 76 | } 77 | 78 | // i / k + j / k 79 | expected f(int i, int j, int k) 80 | { 81 | return safe_divide(i, k).bind([=](int q1) 82 | { 83 | return safe_divide(j,k).bind([=](int q2) 84 | { 85 | return q1+q2; 86 | }); 87 | }); 88 | } 89 | ``` 90 | 91 | In hspp, the codes would be like 92 | ```c++ 93 | auto safe_divide(int i, int j) -> Either 94 | { 95 | if (j == 0) return toLeft | arithmetic_errc::divide_by_zero; 96 | if (i%j != 0) return toLeft | arithmetic_errc::not_integer_division; 97 | else return toRight || i / j; 98 | } 99 | 100 | auto f(int i, int j, int k) -> Either 101 | { 102 | Id q1, q2; 103 | return do_( 104 | q1 <= safe_divide(i, k), 105 | q2 <= safe_divide(j, k), 106 | return_ || q1 + q2 107 | ); 108 | } 109 | ``` 110 | 111 | Refer to [proposal sample](https://github.com/BowenFu/hspp/blob/main/sample/proposal.cpp) for complete sample codes. 112 | 113 | ### Sample 3 for monadic do notation for a vector monad 114 | 115 | Filter even numbers. 116 | 117 | [godbolt1]: https://godbolt.org/z/MM7MW9MPY 118 | 119 | [![Try it on godbolt][badge.godbolt]][godbolt1] 120 | 121 | ```c++ 122 | using namespace hspp; 123 | using namespace hspp::doN; 124 | Id i; 125 | auto const result = do_( 126 | i <= std::vector{1, 2, 3, 4}, 127 | guard | (i % 2 == 0), 128 | return_ | i 129 | ); 130 | ``` 131 | 132 | ### Sample 4 for monad comprehension for a range monad 133 | 134 | Obtain an infinite range of Pythagorean triples. 135 | 136 | Haskell version 137 | 138 | ```haskell 139 | triangles = [(i, j, k) | k <- [1..], i <- [1..k], j <- [i..k] , i^2 + j^2 == k^2] 140 | ``` 141 | 142 | [godbolt2]: https://godbolt.org/z/o7qj6o111 143 | 144 | [![Try it on godbolt][badge.godbolt]][godbolt2] 145 | 146 | ```c++ 147 | using namespace hspp::doN; 148 | using namespace hspp::data; 149 | Id i, j, k; 150 | auto const rng = _( 151 | makeTuple<3> | i | j | k, 152 | k <= (enumFrom | 1), 153 | i <= (within | 1 | k), 154 | j <= (within | i | k), 155 | if_ || (i*i + j*j == k*k) 156 | ); 157 | ``` 158 | 159 | Equivalent version using RangeV3 would be [link](http://ericniebler.com/2014/04/27/range-comprehensions/) 160 | 161 | ```C++ 162 | using namespace ranges; 163 | 164 | // Lazy ranges for generating integer sequences 165 | auto const intsFrom = view::iota; 166 | auto const ints = [=](int i, int j) 167 | { 168 | return view::take(intsFrom(i), j-i+1); 169 | }; 170 | 171 | // Define an infinite range of all the Pythagorean 172 | // triples: 173 | auto triples = 174 | view::for_each(intsFrom(1), [](int z) 175 | { 176 | return view::for_each(ints(1, z), [=](int x) 177 | { 178 | return view::for_each(ints(x, z), [=](int y) 179 | { 180 | return yield_if(x*x + y*y == z*z, 181 | std::make_tuple(x, y, z)); 182 | }); 183 | }); 184 | }); 185 | ``` 186 | 187 | 188 | ### Sample 5 for Function Monad used in do notation 189 | 190 | We have two functions, plus1, and showStr. With do notation we construct a new function that will accept an integer as argument and return a tuple of results of the two functions. 191 | 192 | [godbolt4]: https://godbolt.org/z/d58Ezbxjz 193 | 194 | [![Try it on godbolt][badge.godbolt]][godbolt4] 195 | 196 | ```c++ 197 | auto plus1 = toFunc<> | [](int x){ return 1+x; }; 198 | auto showStr = toFunc<> | [](int x){ return show | x; }; 199 | 200 | Id x; 201 | Id y; 202 | auto go = do_( 203 | x <= plus1, 204 | y <= showStr, 205 | return_ || makeTuple<2> | x | y 206 | ); 207 | auto result = go | 3; 208 | std::cout << std::get<0>(result) << std::endl; 209 | std::cout << std::get<1>(result) << std::endl; 210 | ``` 211 | 212 | ### Sample 6 for parser combinator 213 | 214 | Original haskell version [Monadic Parsing in Haskell](https://www.cambridge.org/core/journals/journal-of-functional-programming/article/monadic-parsing-in-haskell/E557DFCCE00E0D4B6ED02F3FB0466093) 215 | 216 | ```haskell 217 | expr, term, factor, digit :: Parser Int 218 | 219 | digit = do {x <- token (sat isDigit); return (ord x - ord '0')} 220 | 221 | factor = digit +++ do {symb "("; n <- expr; symbol ")"; return n} 222 | 223 | term = factor `chainl1` mulop 224 | 225 | expr = term `chainl1` addop 226 | ``` 227 | 228 | C++ version 229 | [parse_expr](https://github.com/BowenFu/hspp/blob/main/sample/parse_expr.cpp) 230 | 231 | [godbolt5]: https://godbolt.org/z/r7WTYjGYa 232 | 233 | [![Try it on godbolt][badge.godbolt]][godbolt5] 234 | 235 | ```c++ 236 | Id x; 237 | auto const digit = do_( 238 | x <= (token || sat | isDigit), 239 | return_ | (x - '0') 240 | ); 241 | 242 | extern TEParser const expr; 243 | 244 | Id n; 245 | auto const factor = 246 | digit 247 | do_( 248 | symb | "("s, 249 | n <= expr, 250 | symb | ")"s, 251 | return_ | n 252 | ); 253 | 254 | auto const term = factor mulOp; 255 | 256 | TEParser const expr = toTEParser || (term addOp); 257 | ``` 258 | 259 | ### Sample 7 for STM / concurrent 260 | 261 | [concurrent.cpp](https://github.com/BowenFu/hspp/blob/main/test/hspp/concurrent.cpp) 262 | 263 | [godbolt6]: https://godbolt.org/z/zj9Mc1h4h 264 | 265 | [![Try it on godbolt][badge.godbolt]][godbolt6] 266 | 267 | 268 | Transfer from one account to another one atomically. 269 | ```c++ 270 | Id from, to; 271 | Id v1, v2; 272 | auto io_ = do_( 273 | from <= (newTVarIO | Integer{200}), 274 | to <= (newTVarIO | Integer{100}), 275 | transfer | from | to | 50, 276 | v1 <= (showAccount | from), 277 | v2 <= (showAccount | to), 278 | hassert | (v1 == 150) | "v1 should be 150", 279 | hassert | (v2 == 150) | "v2 should be 150" 280 | ); 281 | io_.run(); 282 | ``` 283 | 284 | Withdraw from an account but waiting for sufficient money. 285 | ```c++ 286 | Id acc; 287 | auto io_ = do_( 288 | acc <= (newTVarIO | Integer{100}), 289 | forkIO | (delayDeposit | acc | 1), 290 | putStr | "Trying to withdraw money...\n", 291 | atomically | (limitedWithdrawSTM | acc | 101), 292 | putStr | "Successful withdrawal!\n" 293 | ); 294 | 295 | io_.run(); 296 | ``` 297 | 298 | And we can also compose two STMs with `orElse` 299 | ```c++ 300 | // (limitedWithdraw2 acc1 acc2 amt) withdraws amt from acc1, 301 | // if acc1 has enough money, otherwise from acc2. 302 | // If neither has enough, it retries. 303 | constexpr auto limitedWithdraw2 = toFunc<> | [](Account acc1, Account acc2, Integer amt) 304 | { 305 | return orElse | (limitedWithdrawSTM | acc1 | amt) | (limitedWithdrawSTM | acc2 | amt); 306 | }; 307 | 308 | Id acc1, acc2; 309 | auto io_ = do_( 310 | acc1 <= (atomically | (newTVar | Integer{100})), 311 | acc2 <= (atomically | (newTVar | Integer{100})), 312 | showAcc | "Left pocket" | acc1, 313 | showAcc | "Right pocket" | acc2, 314 | forkIO | (delayDeposit | acc2 | 1), 315 | print | "Withdrawing $101 from either pocket...", 316 | atomically | (limitedWithdraw2 | acc1 | acc2 | Integer{101}), 317 | print | "Successful withdrawal!", 318 | showAcc | "Left pocket" | acc1, 319 | showAcc | "Right pocket" | acc2 320 | ); 321 | 322 | io_.run(); 323 | ``` 324 | 325 | ### Sample 8 for coroutine 326 | 327 | [coroutine](https://github.com/BowenFu/hspp/blob/main/sample/coroutine.cpp) 328 | 329 | ```c++ 330 | using O = std::string; 331 | using I = std::string; 332 | 333 | // coroutine for handling strings 334 | Id name, color; 335 | auto const example1 = do_( 336 | name <= (yield | "What's your name? "), 337 | lift || putStrLn | ("Hello, " + name), 338 | color <= (yield | "What's your favorite color? "), 339 | lift || putStrLn | ("I like " + color + ", too.") 340 | ); 341 | 342 | auto const foreverKResult = foreverK | [](std::string str) -> Producing 343 | { 344 | return do_( 345 | lift || putStrLn | str, 346 | (lift | getLine) >>= yield 347 | ); 348 | }; 349 | 350 | // coroutine for handling io 351 | auto const stdOutIn = toConsumingPtr_ || foreverKResult; 352 | 353 | // let the two coroutines hand over control to each other by turn. 354 | auto io_ = example1 stdOutIn; 355 | io_.run(); 356 | 357 | ``` 358 | 359 | The sample running would be like 360 | ``` 361 | > What's your name? 362 | < Bob 363 | > Hello, Bob 364 | > What's your favorite color? 365 | < Red 366 | > I like Red, too. 367 | ``` 368 | 369 | ## Haskell vs Hspp (Incomplete list) 370 | 371 | | Haskell | Hspp | 372 | | ------- | ---- | 373 | | function | Function / GenericFunction | 374 | | f x y | f \| x \| y | 375 | | f $ g x | f \|\| g \| x| 376 | | f . g $ x | f \ g \|\| x| 377 | | a \`f\` b | a \ b | 378 | |[f x \| x <- xs, p x]| \_(f \| x, x <= xs, if\_ \|\| p \| x) | 379 | | list (lazy) | range | 380 | | list (strict) | std::vector/list/forward_list| 381 | | do {patA <- action1; action2} | do_(patA <= action1, action2) | 382 | | f <$> v | f \ v | 383 | | f <*> v | f \ v | 384 | | pure a | pure \| a | 385 | | m1 >> m2 | m1 >> m2 | 386 | | m1 >>= f | m1 >>= f | 387 | | return a | return_ \| a | 388 | 389 | 390 | ## Why bother? 391 | 392 | The library is 393 | 394 | 1. for fun, 395 | 2. to explore the interesting features of Haskell, 396 | 3. to explore the boundary of C++, 397 | 4. to facilitate the translation of some interesting Haskell codes to C++. 398 | 399 | This library is still in active development and not production ready. 400 | 401 | Discussions / issues / PRs are welcome. 402 | 403 | ## Related 404 | 405 | Haskell pattern matching is not covered in this repo. You may be interested in [match(it)](https://github.com/BowenFu/matchit.cpp) if you want to see how pattern matching works in C++. 406 | 407 | ## Support this project 408 | 409 | Please star the repo, share the repo, or [sponsor $1](https://github.com/sponsors/BowenFu) to let me know this project matters. 410 | 411 | ## Star History 412 | 413 | [![Star History Chart](https://api.star-history.com/svg?repos=bowenfu/hspp&type=Date)](https://star-history.com/#bowenfu/hspp&Date) 414 | -------------------------------------------------------------------------------- /cmake/CodeCoverage.cmake: -------------------------------------------------------------------------------- 1 | # 2 | # 2012-01-31, Lars Bilke 3 | # - Enable Code Coverage 4 | # 5 | # 2013-09-17, Joakim Söderberg 6 | # - Added support for Clang. 7 | # - Some additional usage instructions. 8 | # 9 | # USAGE: 10 | 11 | # 0. (Mac only) If you use Xcode 5.1 make sure to patch geninfo as described here: 12 | # http://stackoverflow.com/a/22404544/80480 13 | # 14 | # 1. Copy this file into your cmake modules path. 15 | # 16 | # 2. Add the following line to your CMakeLists.txt: 17 | # INCLUDE(CodeCoverage) 18 | # 19 | # 3. Set compiler flags to turn off optimization and enable coverage: 20 | # SET(CMAKE_CXX_FLAGS "-g -O0 -fprofile-arcs -ftest-coverage") 21 | # SET(CMAKE_C_FLAGS "-g -O0 -fprofile-arcs -ftest-coverage") 22 | # 23 | # 3. Use the function SETUP_TARGET_FOR_COVERAGE to create a custom make target 24 | # which runs your test executable and produces a lcov code coverage report: 25 | # Example: 26 | # SETUP_TARGET_FOR_COVERAGE( 27 | # my_coverage_target # Name for custom target. 28 | # test_driver # Name of the test driver executable that runs the tests. 29 | # # NOTE! This should always have a ZERO as exit code 30 | # # otherwise the coverage generation will not complete. 31 | # coverage # Name of output directory. 32 | # ) 33 | # 34 | # 4. Build a Debug build: 35 | # cmake -DCMAKE_BUILD_TYPE=Debug .. 36 | # make 37 | # make my_coverage_target 38 | # 39 | # 40 | 41 | # Check prereqs 42 | FIND_PROGRAM(GCOV_PATH gcov) 43 | FIND_PROGRAM(LCOV_PATH lcov) 44 | FIND_PROGRAM(GENHTML_PATH genhtml) 45 | FIND_PROGRAM( GCOVR_PATH gcovr PATHS ${CMAKE_SOURCE_DIR}/tests) 46 | 47 | IF(NOT GCOV_PATH) 48 | MESSAGE(FATAL_ERROR "gcov not found! Aborting...") 49 | ENDIF() # NOT GCOV_PATH 50 | 51 | IF(NOT CMAKE_COMPILER_IS_GNUCXX) 52 | # Clang version 3.0.0 and greater now supports gcov as well. 53 | MESSAGE(WARNING "Compiler is not GNU gcc! Clang Version 3.0.0 and greater supports gcov as well, but older versions don't.") 54 | 55 | IF(NOT "${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang") 56 | MESSAGE(FATAL_ERROR "Compiler is not GNU gcc! Aborting...") 57 | ENDIF() 58 | ENDIF() # NOT CMAKE_COMPILER_IS_GNUCXX 59 | 60 | SET(CMAKE_CXX_FLAGS_COVERAGE 61 | "-g -O0 --coverage -fprofile-arcs -ftest-coverage" 62 | CACHE STRING "Flags used by the C++ compiler during coverage builds." 63 | FORCE ) 64 | SET(CMAKE_C_FLAGS_COVERAGE 65 | "-g -O0 --coverage -fprofile-arcs -ftest-coverage" 66 | CACHE STRING "Flags used by the C compiler during coverage builds." 67 | FORCE ) 68 | SET(CMAKE_EXE_LINKER_FLAGS_COVERAGE 69 | "" 70 | CACHE STRING "Flags used for linking binaries during coverage builds." 71 | FORCE ) 72 | SET(CMAKE_SHARED_LINKER_FLAGS_COVERAGE 73 | "" 74 | CACHE STRING "Flags used by the shared libraries linker during coverage builds." 75 | FORCE ) 76 | MARK_AS_ADVANCED( 77 | CMAKE_CXX_FLAGS_COVERAGE 78 | CMAKE_C_FLAGS_COVERAGE 79 | CMAKE_EXE_LINKER_FLAGS_COVERAGE 80 | CMAKE_SHARED_LINKER_FLAGS_COVERAGE ) 81 | 82 | IF ( NOT (CMAKE_BUILD_TYPE STREQUAL "Debug" OR CMAKE_BUILD_TYPE STREQUAL "Coverage")) 83 | MESSAGE( WARNING "Code coverage results with an optimized (non-Debug) build may be misleading" ) 84 | ENDIF() # NOT CMAKE_BUILD_TYPE STREQUAL "Debug" 85 | 86 | 87 | # Param _targetname The name of new the custom make target 88 | # Param _testrunner The name of the target which runs the tests. 89 | # MUST return ZERO always, even on errors. 90 | # If not, no coverage report will be created! 91 | # Param _outputname lcov output is generated as _outputname.info 92 | # HTML report is generated in _outputname/index.html 93 | # Optional fourth parameter is passed as arguments to _testrunner 94 | # Pass them in list form, e.g.: "-j;2" for -j 2 95 | FUNCTION(SETUP_TARGET_FOR_COVERAGE _targetname _testrunner _outputname) 96 | 97 | IF(NOT LCOV_PATH) 98 | MESSAGE(FATAL_ERROR "lcov not found! Aborting...") 99 | ENDIF() # NOT LCOV_PATH 100 | 101 | IF(NOT GENHTML_PATH) 102 | MESSAGE(FATAL_ERROR "genhtml not found! Aborting...") 103 | ENDIF() # NOT GENHTML_PATH 104 | 105 | # Setup target 106 | ADD_CUSTOM_TARGET(${_targetname} 107 | 108 | # Cleanup lcov 109 | ${LCOV_PATH} --directory . --zerocounters 110 | 111 | # Run tests 112 | COMMAND ${_testrunner} ${ARGV3} 113 | 114 | # Capturing lcov counters and generating report 115 | #COMMAND ${LCOV_PATH} --directory . --capture --output-file ${_outputname}.info 116 | COMMAND ${LCOV_PATH} --directory . --capture --output-file ${_outputname}.info --rc lcov_branch_coverage=1 117 | COMMAND ${LCOV_PATH} --remove ${_outputname}.info 'build/*' 'tests/*' '/usr/*' --output-file ${_outputname}.info.cleaned 118 | COMMAND ${GENHTML_PATH} -o ${_outputname} ${_outputname}.info.cleaned 119 | COMMAND ${CMAKE_COMMAND} -E remove ${_outputname}.info ${_outputname}.info.cleaned 120 | 121 | WORKING_DIRECTORY ${CMAKE_BINARY_DIR} 122 | COMMENT "Resetting code coverage counters to zero.\nProcessing code coverage counters and generating report." 123 | ) 124 | 125 | # Show info where to find the report 126 | ADD_CUSTOM_COMMAND(TARGET ${_targetname} POST_BUILD 127 | COMMAND ; 128 | COMMENT "Open ./${_outputname}/index.html in your browser to view the coverage report." 129 | ) 130 | 131 | ENDFUNCTION() # SETUP_TARGET_FOR_COVERAGE 132 | 133 | # Param _targetname The name of new the custom make target 134 | # Param _testrunner The name of the target which runs the tests 135 | # Param _outputname cobertura output is generated as _outputname.xml 136 | # Optional fourth parameter is passed as arguments to _testrunner 137 | # Pass them in list form, e.g.: "-j;2" for -j 2 138 | FUNCTION(SETUP_TARGET_FOR_COVERAGE_COBERTURA _targetname _testrunner _outputname) 139 | 140 | IF(NOT PYTHON_EXECUTABLE) 141 | MESSAGE(FATAL_ERROR "Python not found! Aborting...") 142 | ENDIF() # NOT PYTHON_EXECUTABLE 143 | 144 | IF(NOT GCOVR_PATH) 145 | MESSAGE(FATAL_ERROR "gcovr not found! Aborting...") 146 | ENDIF() # NOT GCOVR_PATH 147 | 148 | ADD_CUSTOM_TARGET(${_targetname} 149 | 150 | # Run tests 151 | ${_testrunner} ${ARGV3} 152 | 153 | # Running gcovr 154 | COMMAND ${GCOVR_PATH} -x -r ${CMAKE_SOURCE_DIR} -e '${CMAKE_SOURCE_DIR}/tests/'-e'${CMAKE_SOURCE_DIR}/build/' -o ${_outputname}.xml 155 | WORKING_DIRECTORY ${CMAKE_BINARY_DIR} 156 | COMMENT "Running gcovr to produce Cobertura code coverage report." 157 | ) 158 | 159 | # Show info where to find the report 160 | ADD_CUSTOM_COMMAND(TARGET ${_targetname} POST_BUILD 161 | COMMAND ; 162 | COMMENT "Cobertura code coverage report saved in ${_outputname}.xml." 163 | ) 164 | 165 | ENDFUNCTION() # SETUP_TARGET_FOR_COVERAGE_COBERTURA -------------------------------------------------------------------------------- /cmake/FetchGTest.cmake: -------------------------------------------------------------------------------- 1 | include(FetchContent) 2 | 3 | FetchContent_Declare( 4 | googletest 5 | GIT_REPOSITORY https://github.com/google/googletest.git 6 | GIT_TAG dcc92d0ab6c4ce022162a23566d44f673251eee4) 7 | 8 | FetchContent_GetProperties(googletest) 9 | if(NOT googletest_POPULATED) 10 | FetchContent_Populate(googletest) 11 | add_subdirectory(${googletest_SOURCE_DIR} ${googletest_BINARY_DIR} 12 | EXCLUDE_FROM_ALL) 13 | endif() 14 | 15 | message(STATUS "GTest binaries are present at ${googletest_BINARY_DIR}") -------------------------------------------------------------------------------- /cmake/Sanitizers.cmake: -------------------------------------------------------------------------------- 1 | # Build Types 2 | set(CMAKE_BUILD_TYPE ${CMAKE_BUILD_TYPE} 3 | CACHE STRING "Choose the type of build, options are: None Debug Release RelWithDebInfo MinSizeRel TSAN ASAN LSAN MSAN UBSAN" 4 | FORCE) 5 | 6 | # ThreadSanitizer 7 | set(CMAKE_C_FLAGS_TSAN 8 | "-fsanitize=thread -g -O1" 9 | CACHE STRING "Flags used by the C compiler during ThreadSanitizer builds." 10 | FORCE) 11 | set(CMAKE_CXX_FLAGS_TSAN 12 | "-fsanitize=thread -g -O1" 13 | CACHE STRING "Flags used by the C++ compiler during ThreadSanitizer builds." 14 | FORCE) 15 | 16 | # AddressSanitize 17 | set(CMAKE_C_FLAGS_ASAN 18 | "-fsanitize=address -fno-optimize-sibling-calls -fsanitize-address-use-after-scope -fno-omit-frame-pointer -g -O1" 19 | ignorelist.txt 20 | CACHE STRING "Flags used by the C compiler during AddressSanitizer builds." 21 | FORCE) 22 | set(CMAKE_CXX_FLAGS_ASAN 23 | "-fsanitize=address -fno-optimize-sibling-calls -fsanitize-address-use-after-scope -fno-omit-frame-pointer -g -O1" 24 | CACHE STRING "Flags used by the C++ compiler during AddressSanitizer builds." 25 | FORCE) 26 | 27 | # LeakSanitizer 28 | set(CMAKE_C_FLAGS_LSAN 29 | "-fsanitize=leak -fno-omit-frame-pointer -g -O1" 30 | CACHE STRING "Flags used by the C compiler during LeakSanitizer builds." 31 | FORCE) 32 | set(CMAKE_CXX_FLAGS_LSAN 33 | "-fsanitize=leak -fno-omit-frame-pointer -g -O1" 34 | CACHE STRING "Flags used by the C++ compiler during LeakSanitizer builds." 35 | FORCE) 36 | 37 | # MemorySanitizer 38 | set(CMAKE_C_FLAGS_MSAN 39 | "-fsanitize=memory -fno-optimize-sibling-calls -fsanitize-memory-track-origins=2 -fno-omit-frame-pointer -g -O2 -fsanitize-blacklist=${PROJECT_SOURCE_DIR}/msan.blacklist -fsanitize-recover=memory" 40 | CACHE STRING "Flags used by the C compiler during MemorySanitizer builds." 41 | FORCE) 42 | set(CMAKE_CXX_FLAGS_MSAN 43 | "-fsanitize=memory -fno-optimize-sibling-calls -fsanitize-memory-track-origins=2 -fno-omit-frame-pointer -g -O2 -fsanitize-blacklist=${PROJECT_SOURCE_DIR}/msan.blacklist -fsanitize-recover=memory" 44 | CACHE STRING "Flags used by the C++ compiler during MemorySanitizer builds." 45 | FORCE) 46 | 47 | # UndefinedBehaviour 48 | set(CMAKE_C_FLAGS_UBSAN 49 | "-fsanitize=undefined" 50 | CACHE STRING "Flags used by the C compiler during UndefinedBehaviourSanitizer builds." 51 | FORCE) 52 | set(CMAKE_CXX_FLAGS_UBSAN 53 | "-fsanitize=undefined" 54 | CACHE STRING "Flags used by the C++ compiler during UndefinedBehaviourSanitizer builds." 55 | FORCE) 56 | -------------------------------------------------------------------------------- /develop.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | cd develop/include 4 | awk 1 hspp_develop.h > ../../include/hspp.h 5 | -------------------------------------------------------------------------------- /develop/include/do_notation.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022 Bowen Fu 3 | * Distributed Under The Apache-2.0 License 4 | */ 5 | 6 | #ifndef HSPP_DO_NOTATION_H 7 | #define HSPP_DO_NOTATION_H 8 | 9 | namespace hspp 10 | { 11 | 12 | namespace doN 13 | { 14 | 15 | template 16 | class LetExpr : public F 17 | { 18 | public: 19 | LetExpr(F f) 20 | : F{std::move(f)} 21 | {} 22 | }; 23 | 24 | template 25 | class IsLetExpr : public std::false_type 26 | { 27 | }; 28 | 29 | template 30 | class IsLetExpr> : public std::true_type 31 | { 32 | }; 33 | 34 | template 35 | constexpr auto isLetExprV = IsLetExpr>::value; 36 | 37 | // make sure Id is not a const obj. 38 | template 39 | class Id 40 | { 41 | using OptT = std::optional; 42 | std::shared_ptr mT = std::make_shared(); 43 | public: 44 | constexpr Id() = default; 45 | constexpr auto const& value() const 46 | { 47 | if (!mT) 48 | { 49 | throw std::runtime_error{"Invalid id!"}; 50 | } 51 | if (!mT->has_value()) 52 | { 53 | std::cerr << "mT : " << mT.get() << std::endl; 54 | throw std::runtime_error{"Id has no binding!"}; 55 | } 56 | return mT->value(); 57 | } 58 | constexpr void bind(T v) const 59 | { 60 | const_cast(*mT) = std::move(v); 61 | } 62 | 63 | constexpr auto operator=(T const& d) 64 | { 65 | bind(d); 66 | return LetExpr([]{}); 67 | } 68 | 69 | // return let expr 70 | template 71 | constexpr auto operator=(Nullary const& f) 72 | { 73 | static_assert(std::is_same_v>>); 74 | return LetExpr([*this, f]{ bind(f()); }); 75 | } 76 | 77 | }; 78 | 79 | template 80 | class Nullary : public T 81 | { 82 | public: 83 | using T::operator(); 84 | }; 85 | 86 | template 87 | constexpr auto nullary(T const &t) 88 | { 89 | return Nullary{t}; 90 | } 91 | 92 | template 93 | constexpr auto toTENullaryImpl(Nullary const &t) 94 | { 95 | return nullary(std::function>{t}); 96 | } 97 | 98 | constexpr auto toTENullary = toGFunc<1> | [](auto const& t) 99 | { 100 | return toTENullaryImpl(t); 101 | }; 102 | 103 | template , void>> 104 | constexpr auto evalDeferredImpl(T&& t) 105 | { 106 | static_assert(std::is_same_v, MonadType>>); 107 | return t(); 108 | } 109 | 110 | template 111 | class DeMonad 112 | { 113 | using T = DataType; 114 | public: 115 | constexpr DeMonad(M const& m, Id id) 116 | : mM{m} 117 | , mId{std::move(id)} 118 | { 119 | } 120 | constexpr decltype(auto) m() const 121 | { 122 | return mM; 123 | } 124 | constexpr auto id() const -> Id 125 | { 126 | return mId; 127 | } 128 | 129 | private: 130 | // FIXME reference for lvalue, value for rvalue 131 | // std::reference_wrapper mM; 132 | M const mM; 133 | Id mId; 134 | }; 135 | 136 | template 137 | class IsDeMonad : public std::false_type 138 | {}; 139 | template 140 | class IsDeMonad> : public std::true_type 141 | {}; 142 | template 143 | constexpr static auto isDeMonadV = IsDeMonad>::value; 144 | 145 | 146 | template > 147 | constexpr auto operator<= (Id>& id, M const& m) 148 | { 149 | return DeMonad{m, id}; 150 | } 151 | 152 | template >>> 153 | constexpr auto operator<= (Id& id, Nullary const& n) 154 | { 155 | using MT = std::invoke_result_t>; 156 | return nullary([=] { return DeMonad{evaluate_(n), id}; }); 157 | } 158 | 159 | template 160 | constexpr auto operator<= (Id& id, DeferredPure const& n) 161 | { 162 | return id = n.mData; 163 | } 164 | 165 | template 166 | class EvalTraits 167 | { 168 | public: 169 | template 170 | constexpr static decltype(auto) evalImpl(T const &v) 171 | { 172 | return v; 173 | } 174 | }; 175 | 176 | template 177 | class EvalTraits> 178 | { 179 | public: 180 | constexpr static decltype(auto) evalImpl(Nullary const &e) { return e(); } 181 | }; 182 | 183 | // Only allowed in nullary 184 | template 185 | class EvalTraits> 186 | { 187 | public: 188 | constexpr static decltype(auto) evalImpl(Id const &id) 189 | { 190 | return id.value(); 191 | } 192 | }; 193 | 194 | template 195 | constexpr decltype(auto) evaluate_(T const &t) 196 | { 197 | return EvalTraits::evalImpl(t); 198 | } 199 | 200 | #define UN_OP_FOR_NULLARY(op) \ 201 | template , bool> = true> \ 202 | constexpr auto operator op(T&&t) \ 203 | { \ 204 | return nullary([=] { return op evaluate_(t); }); \ 205 | } 206 | 207 | #define BIN_OP_FOR_NULLARY(op) \ 208 | template || isNullaryOrIdV, bool> = \ 210 | true> \ 211 | constexpr auto operator op(T t, U u) \ 212 | { \ 213 | return nullary([t=std::move(t), u=std::move(u)] { return evaluate_(t) op evaluate_(u); }); \ 214 | } 215 | 216 | using hspp::operator>>; 217 | 218 | BIN_OP_FOR_NULLARY(|) 219 | BIN_OP_FOR_NULLARY(||) 220 | BIN_OP_FOR_NULLARY(>>) 221 | BIN_OP_FOR_NULLARY(*) 222 | BIN_OP_FOR_NULLARY(+) 223 | BIN_OP_FOR_NULLARY(==) 224 | BIN_OP_FOR_NULLARY(!=) 225 | BIN_OP_FOR_NULLARY(%) 226 | BIN_OP_FOR_NULLARY(<) 227 | BIN_OP_FOR_NULLARY(>) 228 | BIN_OP_FOR_NULLARY(<=) 229 | BIN_OP_FOR_NULLARY(>=) 230 | BIN_OP_FOR_NULLARY(&&) 231 | BIN_OP_FOR_NULLARY(-) 232 | 233 | template 234 | constexpr auto funcWithParams(Id const& param, BodyBaker const& bodyBaker) 235 | { 236 | return toFunc<> | [=](T const& t) 237 | { 238 | // bind before baking body. 239 | param.bind(t); 240 | auto result = evaluate_(bodyBaker()); 241 | return result; 242 | }; 243 | } 244 | 245 | template 246 | constexpr auto doImpl(Head const& head) 247 | { 248 | return evalDeferred | head; 249 | } 250 | 251 | template >>, void>> 252 | constexpr auto doImplNullaryDeMonad(Nullary const& dmN, Rest const&... rest) 253 | { 254 | return doImpl(dmN(), rest...); 255 | } 256 | 257 | template 258 | constexpr auto doImpl(LetExpr const& le, Rest const&... rest) 259 | { 260 | le(); 261 | return evaluate_(doImpl(rest...)); 262 | } 263 | 264 | template 265 | constexpr auto doImpl(DeMonad const& dm, Rest const&... rest) 266 | { 267 | static_assert(std::is_same_v, MonadType>); 268 | auto const bodyBaker = [=] { return doImpl(rest...);}; 269 | return dm.m() >>= funcWithParams(dm.id(), bodyBaker); 270 | } 271 | 272 | template && !isLetExprV, void>> 273 | constexpr auto doImpl(Head const& head, Rest const&... rest) 274 | { 275 | if constexpr (isNullaryOrIdV) 276 | { 277 | if constexpr (isDeMonadV>) 278 | { 279 | return doImplNullaryDeMonad(head, rest...); 280 | } 281 | else 282 | { 283 | return (evalDeferred | head) >> doImpl(rest...); 284 | } 285 | } 286 | else 287 | { 288 | return (evalDeferred | head) >> doImpl(rest...); 289 | } 290 | } 291 | 292 | // Get class type that is a monad. 293 | template 294 | struct MonadClassImpl; 295 | 296 | template <> 297 | struct MonadClassImpl> 298 | { 299 | using Type = void; 300 | }; 301 | 302 | template 303 | struct SameMType : std::false_type{}; 304 | 305 | template 306 | struct SameMType>, std::void_t>> 307 | { 308 | constexpr static auto value = std::is_same_v, MonadType>; 309 | }; 310 | 311 | static_assert(SameMType, hspp::data::Maybe>::value); 312 | 313 | template 314 | struct MonadClassImpl, std::enable_if_t, void>> 315 | { 316 | using Type = Head; 317 | private: 318 | using RType = typename MonadClassImpl>::Type; 319 | static_assert(std::is_same_v || SameMType::value); 320 | }; 321 | 322 | template 323 | struct MonadClassImpl, Rest...>, std::enable_if_t, void>> 324 | { 325 | using Type = Head; 326 | private: 327 | using RType = typename MonadClassImpl>::Type; 328 | static_assert(std::is_same_v || SameMType::value); 329 | }; 330 | 331 | template 332 | struct MonadClassImpl, std::enable_if_t && !isDeMonadV, void>> 333 | { 334 | using Type = typename MonadClassImpl>::Type; 335 | }; 336 | 337 | 338 | // Get class type that is a monad. 339 | template 340 | struct MonadClass 341 | { 342 | using Type = typename MonadClassImpl>::Type; 343 | static_assert(!std::is_same_v); 344 | }; 345 | 346 | template 347 | using MonadClassType = typename MonadClass...>::Type; 348 | 349 | static_assert(isMonadV>); 350 | static_assert(std::is_same_v>, data::Maybe>); 351 | 352 | template 353 | constexpr auto do_(Head const& head, Rest const&... rest) 354 | { 355 | using MClass = MonadClassType; 356 | auto result = doImpl(head, rest...); 357 | static_assert(!isNullaryOrIdV); 358 | return result; 359 | } 360 | 361 | template 362 | constexpr auto doInner(Args&&... args) 363 | { 364 | return nullary([=] { return do_(evaluate_(args)...); }); 365 | } 366 | 367 | template 368 | constexpr auto _(Head const& head, Rest const&... rest) 369 | { 370 | return do_(rest..., return_ | head); 371 | } 372 | 373 | constexpr auto if_ = guard; 374 | 375 | // used for doN, so that Id/Nullary can be used with ifThenElse. 376 | constexpr auto ifThenElse = toGFunc<3> | [](auto pred, auto then_, auto else_) 377 | { 378 | using ThenResultT = decltype(evaluate_(then_)); 379 | using ElseResultT = decltype(evaluate_(else_)); 380 | if constexpr (isMonadV) 381 | { 382 | using MClass = MonadClassType; 383 | return nullary([pred=std::move(pred), then_=std::move(then_), else_=std::move(else_)] { return evaluate_(pred) ? (evalDeferred | evaluate_(then_)) : (evalDeferred | evaluate_(else_)); }); 384 | } 385 | else 386 | { 387 | static_assert(std::is_same_v); 388 | return nullary([pred=std::move(pred), then_=std::move(then_), else_=std::move(else_)] { return evaluate_(pred) ? evaluate_(then_) : evaluate_(else_); }); 389 | } 390 | }; 391 | 392 | } // namespace doN 393 | 394 | } // namespace hspp 395 | 396 | #endif // HSPP_DO_NOTATION_H 397 | -------------------------------------------------------------------------------- /develop/include/hspp_develop.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022 Bowen Fu 3 | * Distributed Under The Apache-2.0 License 4 | */ 5 | 6 | #ifndef HSPP_DEVELOP_H 7 | #define HSPP_DEVELOP_H 8 | 9 | #include "../develop/include/range.h" 10 | #include "../develop/include/data.h" 11 | #include "../develop/include/typeclass.h" 12 | #include "../develop/include/do_notation.h" 13 | 14 | #endif // HSPP_DEVELOP_H 15 | -------------------------------------------------------------------------------- /develop/include/range.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022 Bowen Fu 3 | * Distributed Under The Apache-2.0 License 4 | */ 5 | 6 | #ifndef HSPP_RANGE_H 7 | #define HSPP_RANGE_H 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | namespace hspp 16 | { 17 | namespace data 18 | { 19 | 20 | template 21 | class Range : public Repr 22 | { 23 | }; 24 | 25 | template 26 | class IsRange : public std::false_type 27 | {}; 28 | template 29 | class IsRange> : public std::true_type 30 | {}; 31 | template 32 | constexpr static auto isRangeV = IsRange>::value; 33 | 34 | template 35 | constexpr auto ownedRange(Repr&& repr) 36 | { 37 | return Range, std::decay_t>{std::forward(repr)}; 38 | } 39 | 40 | template 41 | class RefView; 42 | 43 | template 44 | constexpr auto nonOwnedRange(Repr const& repr) 45 | { 46 | return ownedRange(RefView(repr)); 47 | } 48 | 49 | template 50 | class EmptyView 51 | { 52 | public: 53 | class Iter 54 | { 55 | public: 56 | auto& operator++() 57 | { 58 | return *this; 59 | } 60 | Data operator*() const 61 | { 62 | throw std::runtime_error{"Never reach here!"}; 63 | } 64 | bool hasValue() const 65 | { 66 | return false; 67 | } 68 | }; 69 | class Sentinel 70 | {}; 71 | friend bool operator!=(Iter const& iter, Sentinel const&) 72 | { 73 | return iter.hasValue(); 74 | } 75 | constexpr EmptyView() = default; 76 | auto begin() const 77 | { 78 | return Iter{}; 79 | } 80 | auto end() const 81 | { 82 | return Sentinel{}; 83 | } 84 | }; 85 | 86 | template 87 | class SingleView 88 | { 89 | public: 90 | class Iter 91 | { 92 | public: 93 | constexpr Iter(SingleView const& singleView) 94 | : mView{singleView} 95 | , mHasValue{true} 96 | {} 97 | auto& operator++() 98 | { 99 | mHasValue = false; 100 | return *this; 101 | } 102 | auto operator*() const 103 | { 104 | return mView.get().mBase; 105 | } 106 | bool hasValue() const 107 | { 108 | return mHasValue; 109 | } 110 | private: 111 | std::reference_wrapper mView; 112 | bool mHasValue; 113 | }; 114 | class Sentinel 115 | {}; 116 | friend bool operator!=(Iter const& iter, Sentinel const&) 117 | { 118 | return iter.hasValue(); 119 | } 120 | constexpr SingleView(Base base) 121 | : mBase{std::move(base)} 122 | {} 123 | auto begin() const 124 | { 125 | return Iter{*this}; 126 | } 127 | auto end() const 128 | { 129 | return Sentinel{}; 130 | } 131 | private: 132 | Base mBase; 133 | }; 134 | 135 | static_assert(!isRangeV>); 136 | static_assert(isRangeV>>); 137 | 138 | template 139 | class RepeatView 140 | { 141 | public: 142 | class Iter 143 | { 144 | public: 145 | constexpr Iter(RepeatView const& repeatView) 146 | : mView{repeatView} 147 | {} 148 | auto& operator++() 149 | { 150 | return *this; 151 | } 152 | auto operator*() const 153 | { 154 | return mView.get().mBase; 155 | } 156 | bool hasValue() const 157 | { 158 | return true; 159 | } 160 | private: 161 | std::reference_wrapper mView; 162 | }; 163 | class Sentinel 164 | {}; 165 | friend bool operator!=(Iter const& iter, Sentinel const&) 166 | { 167 | return iter.hasValue(); 168 | } 169 | constexpr RepeatView(Base base) 170 | : mBase{std::move(base)} 171 | {} 172 | auto begin() const 173 | { 174 | return Iter(*this); 175 | } 176 | auto end() const 177 | { 178 | return Sentinel{}; 179 | } 180 | private: 181 | Base mBase; 182 | }; 183 | 184 | template 185 | class IotaView 186 | { 187 | using StepT = decltype(std::declval() - std::declval()); 188 | public: 189 | class Iter 190 | { 191 | public: 192 | constexpr Iter(Num start, Num end, StepT step) 193 | : mNum{start} 194 | , mBound{end} 195 | , mStep{step} 196 | {} 197 | auto& operator++() 198 | { 199 | mNum = static_cast(mNum + mStep); 200 | return *this; 201 | } 202 | auto operator*() const 203 | { 204 | return mNum; 205 | } 206 | bool hasValue() const 207 | { 208 | if constexpr(includeUpperbound) 209 | { 210 | if (mStep > 0) 211 | { 212 | return mNum <= mBound; 213 | } 214 | else 215 | { 216 | return mNum >= mBound; 217 | } 218 | } 219 | else 220 | { 221 | if (mStep > 0) 222 | { 223 | return mNum < mBound; 224 | } 225 | else 226 | { 227 | return mNum > mBound; 228 | } 229 | } 230 | } 231 | private: 232 | Num mNum; 233 | Num mBound; 234 | StepT mStep; 235 | }; 236 | class Sentinel 237 | {}; 238 | friend bool operator!=(Iter const& iter, Sentinel const&) 239 | { 240 | return iter.hasValue(); 241 | } 242 | constexpr IotaView(Num begin, Num end, StepT step = 1) 243 | : mBegin{begin} 244 | , mEnd{end} 245 | , mStep{step} 246 | {} 247 | constexpr IotaView(Num begin) 248 | : IotaView{begin, std::numeric_limits::max()} 249 | {} 250 | auto begin() const 251 | { 252 | return Iter(mBegin, mEnd, mStep); 253 | } 254 | auto end() const 255 | { 256 | return Sentinel{}; 257 | } 258 | private: 259 | Num mBegin; 260 | Num mEnd; 261 | StepT mStep; 262 | }; 263 | 264 | template 265 | class RefView 266 | { 267 | public: 268 | constexpr RefView(Base const& base) 269 | : mBase{base} 270 | {} 271 | auto begin() const 272 | { 273 | return mBase.get().begin(); 274 | } 275 | auto end() const 276 | { 277 | return mBase.get().end(); 278 | } 279 | private: 280 | std::reference_wrapper mBase; 281 | }; 282 | 283 | template 284 | class MapView 285 | { 286 | public: 287 | class Iter 288 | { 289 | public: 290 | constexpr Iter(MapView const& mapView) 291 | : mView{mapView} 292 | , mBaseIter{mView.get().mBase.begin()} 293 | {} 294 | auto& operator++() 295 | { 296 | ++mBaseIter; 297 | return *this; 298 | } 299 | auto operator*() const 300 | { 301 | return mView.get().mFunc(*mBaseIter); 302 | } 303 | bool hasValue() const 304 | { 305 | return mBaseIter != mView.get().mBase.end(); 306 | } 307 | private: 308 | std::reference_wrapper mView; 309 | std::decay_t mBaseIter; 310 | }; 311 | class Sentinel 312 | {}; 313 | friend bool operator!=(Iter const& iter, Sentinel const&) 314 | { 315 | return iter.hasValue(); 316 | } 317 | constexpr MapView(Base base, Func func) 318 | : mBase{std::move(base)} 319 | , mFunc{std::move(func)} 320 | {} 321 | auto begin() const 322 | { 323 | return Iter(*this); 324 | } 325 | auto end() const 326 | { 327 | return Sentinel{}; 328 | } 329 | private: 330 | Base mBase; 331 | Func mFunc; 332 | }; 333 | 334 | template 335 | class FilterView 336 | { 337 | public: 338 | class Iter 339 | { 340 | private: 341 | void fixIter() 342 | { 343 | while (hasValue() && !mView.get().mPred(*mBaseIter)) 344 | { 345 | ++mBaseIter; 346 | }; 347 | } 348 | public: 349 | constexpr Iter(FilterView const& filterView) 350 | : mView{filterView} 351 | , mBaseIter{mView.get().mBase.begin()} 352 | { 353 | fixIter(); 354 | } 355 | auto& operator++() 356 | { 357 | ++mBaseIter; 358 | fixIter(); 359 | return *this; 360 | } 361 | auto operator*() const 362 | { 363 | return *mBaseIter; 364 | } 365 | bool hasValue() const 366 | { 367 | return mBaseIter != mView.get().mBase.end(); 368 | } 369 | private: 370 | std::reference_wrapper mView; 371 | std::decay_t mBaseIter; 372 | }; 373 | class Sentinel 374 | {}; 375 | friend bool operator!=(Iter const& iter, Sentinel const&) 376 | { 377 | return iter.hasValue(); 378 | } 379 | constexpr FilterView(Pred pred, Base base) 380 | : mPred{std::move(pred)} 381 | , mBase{std::move(base)} 382 | {} 383 | auto begin() const 384 | { 385 | return Iter(*this); 386 | } 387 | auto end() const 388 | { 389 | return Sentinel{}; 390 | } 391 | private: 392 | Pred mPred; 393 | Base mBase; 394 | }; 395 | 396 | template 397 | class TakeView 398 | { 399 | public: 400 | class Iter 401 | { 402 | public: 403 | constexpr Iter(TakeView const& takeView) 404 | : mView{takeView} 405 | , mBaseIter{mView.get().mBase.begin()} 406 | , mCount{} 407 | { 408 | } 409 | auto& operator++() 410 | { 411 | ++mBaseIter; 412 | ++mCount; 413 | return *this; 414 | } 415 | auto operator*() const 416 | { 417 | return *mBaseIter; 418 | } 419 | bool hasValue() const 420 | { 421 | return mCount < mView.get().mLimit && mBaseIter != mView.get().mBase.end(); 422 | } 423 | private: 424 | std::reference_wrapper mView; 425 | std::decay_t mBaseIter; 426 | size_t mCount; 427 | }; 428 | class Sentinel 429 | {}; 430 | friend bool operator!=(Iter const& iter, Sentinel const&) 431 | { 432 | return iter.hasValue(); 433 | } 434 | constexpr TakeView(Base base, size_t number) 435 | : mBase{std::move(base)} 436 | , mLimit{number} 437 | {} 438 | auto begin() const 439 | { 440 | return Iter(*this); 441 | } 442 | auto end() const 443 | { 444 | return Sentinel{}; 445 | } 446 | private: 447 | Base mBase; 448 | size_t mLimit; 449 | }; 450 | 451 | template 452 | class TakeWhileView 453 | { 454 | public: 455 | class Iter 456 | { 457 | public: 458 | constexpr Iter(TakeWhileView const& takeWhileView) 459 | : mView{takeWhileView} 460 | , mBaseIter{mView.get().mBase.begin()} 461 | { 462 | } 463 | auto& operator++() 464 | { 465 | ++mBaseIter; 466 | return *this; 467 | } 468 | auto operator*() const 469 | { 470 | return *mBaseIter; 471 | } 472 | bool hasValue() const 473 | { 474 | return mBaseIter != mView.get().mBase.end() && std::invoke(mView.get().mPred, *mBaseIter); 475 | } 476 | private: 477 | std::reference_wrapper mView; 478 | std::decay_t mBaseIter; 479 | }; 480 | class Sentinel 481 | {}; 482 | friend bool operator!=(Iter const& iter, Sentinel const&) 483 | { 484 | return iter.hasValue(); 485 | } 486 | constexpr TakeWhileView(Pred pred, Base base) 487 | : mPred{std::move(pred)} 488 | , mBase{std::move(base)} 489 | {} 490 | auto begin() const 491 | { 492 | return Iter(*this); 493 | } 494 | auto end() const 495 | { 496 | return Sentinel{}; 497 | } 498 | private: 499 | Pred mPred; 500 | Base mBase; 501 | }; 502 | 503 | template 504 | class DropView 505 | { 506 | public: 507 | class Iter 508 | { 509 | public: 510 | constexpr Iter(DropView const& dropView) 511 | : mView{dropView} 512 | , mBaseIter{mView.get().mBase.begin()} 513 | { 514 | for (size_t i = 0; i < mView.get().mNum; ++i) 515 | { 516 | if (!hasValue()) 517 | { 518 | break; 519 | } 520 | ++mBaseIter; 521 | } 522 | } 523 | auto& operator++() 524 | { 525 | ++mBaseIter; 526 | return *this; 527 | } 528 | auto operator*() const 529 | { 530 | return *mBaseIter; 531 | } 532 | bool hasValue() const 533 | { 534 | return mBaseIter != mView.get().mBase.end(); 535 | } 536 | private: 537 | std::reference_wrapper mView; 538 | std::decay_t mBaseIter; 539 | }; 540 | class Sentinel 541 | {}; 542 | friend bool operator!=(Iter const& iter, Sentinel const&) 543 | { 544 | return iter.hasValue(); 545 | } 546 | constexpr DropView(Base base, size_t number) 547 | : mBase{std::move(base)} 548 | , mNum{number} 549 | {} 550 | auto begin() const 551 | { 552 | return Iter(*this); 553 | } 554 | auto end() const 555 | { 556 | return Sentinel{}; 557 | } 558 | private: 559 | Base mBase; 560 | size_t mNum; 561 | }; 562 | 563 | template 564 | class DropWhileView 565 | { 566 | public: 567 | class Iter 568 | { 569 | public: 570 | constexpr Iter(DropWhileView const& dropWhileView) 571 | : mView{dropWhileView} 572 | , mBaseIter{mView.get().mBase.begin()} 573 | { 574 | while (hasValue() && mView.get().mPred(*mBaseIter)) 575 | { 576 | ++mBaseIter; 577 | } 578 | } 579 | auto& operator++() 580 | { 581 | ++mBaseIter; 582 | return *this; 583 | } 584 | auto operator*() const 585 | { 586 | return *mBaseIter; 587 | } 588 | bool hasValue() const 589 | { 590 | return mBaseIter != mView.get().mBase.end(); 591 | } 592 | private: 593 | std::reference_wrapper mView; 594 | std::decay_t mBaseIter; 595 | }; 596 | class Sentinel 597 | {}; 598 | friend bool operator!=(Iter const& iter, Sentinel const&) 599 | { 600 | return iter.hasValue(); 601 | } 602 | constexpr DropWhileView(Pred pred, Base base) 603 | : mPred{std::move(pred)} 604 | , mBase{std::move(base)} 605 | {} 606 | auto begin() const 607 | { 608 | return Iter(*this); 609 | } 610 | auto end() const 611 | { 612 | return Sentinel{}; 613 | } 614 | private: 615 | Pred mPred; 616 | Base mBase; 617 | }; 618 | 619 | template 620 | class CycleView 621 | { 622 | public: 623 | class Iter 624 | { 625 | public: 626 | constexpr Iter(CycleView const& cycleView) 627 | : mView{cycleView} 628 | , mBaseIter{mView.get().mBase.begin()} 629 | , mEmpty{!(mBaseIter != mView.get().mBase.end())} 630 | { 631 | } 632 | auto& operator++() 633 | { 634 | if (mEmpty) 635 | { 636 | return *this; 637 | } 638 | ++mBaseIter; 639 | if (!((mBaseIter != mView.get().mBase.end()))) 640 | { 641 | mBaseIter = mView.get().mBase.begin(); 642 | } 643 | return *this; 644 | } 645 | auto operator*() const 646 | { 647 | return *mBaseIter; 648 | } 649 | bool hasValue() const 650 | { 651 | return !mEmpty; 652 | } 653 | private: 654 | std::reference_wrapper mView; 655 | std::decay_t mBaseIter; 656 | bool mEmpty; 657 | }; 658 | class Sentinel 659 | {}; 660 | friend bool operator!=(Iter const& iter, Sentinel const&) 661 | { 662 | return iter.hasValue(); 663 | } 664 | constexpr CycleView(Base base) 665 | : mBase{std::move(base)} 666 | {} 667 | auto begin() const 668 | { 669 | return Iter(*this); 670 | } 671 | auto end() const 672 | { 673 | return Sentinel{}; 674 | } 675 | private: 676 | Base mBase; 677 | }; 678 | 679 | template 680 | class JoinView 681 | { 682 | public: 683 | class Iter 684 | { 685 | private: 686 | auto& fetch() 687 | { 688 | if (!mCache) 689 | { 690 | using T = std::decay_t; 691 | mCache = std::make_shared(std::move(*mBaseIter)); 692 | } 693 | return *mCache; 694 | } 695 | void advanceBase() 696 | { 697 | ++mBaseIter; 698 | mCache.reset(); 699 | } 700 | void fixIter() 701 | { 702 | while (!(mInnerIter != fetch().end())) 703 | { 704 | advanceBase(); 705 | if (!hasValue()) 706 | { 707 | break; 708 | } 709 | mInnerIter = fetch().begin(); 710 | } 711 | } 712 | 713 | public: 714 | constexpr Iter(JoinView const& view) 715 | : mView{view} 716 | , mBaseIter{mView.get().mBase.begin()} 717 | , mCache{} 718 | // This can be invalid if base view is empty, but the compiler requires initialization of mInnerIter. 719 | , mInnerIter{fetch().begin()} 720 | { 721 | fixIter(); 722 | } 723 | auto& operator++() 724 | { 725 | ++mInnerIter; 726 | fixIter(); 727 | return *this; 728 | } 729 | auto operator*() const 730 | { 731 | return *mInnerIter; 732 | } 733 | bool hasValue() const 734 | { 735 | return mBaseIter != mView.get().mBase.end(); 736 | } 737 | private: 738 | std::reference_wrapper mView; 739 | std::decay_t mBaseIter; 740 | // not thread-safe 741 | // have to use std::shared_ptr instead of Maybe, because Maybe requrires copy assignments. 742 | // TODO: use placement new to avoid heap memory allocation. 743 | mutable std::shared_ptr> mCache; 744 | std::decay_tbegin())> mInnerIter; 745 | }; 746 | class Sentinel 747 | {}; 748 | friend bool operator!=(Iter const& iter, Sentinel const&) 749 | { 750 | return iter.hasValue(); 751 | } 752 | constexpr JoinView(Base base) 753 | : mBase{std::move(base)} 754 | {} 755 | auto begin() const 756 | { 757 | return Iter(*this); 758 | } 759 | auto end() const 760 | { 761 | return Sentinel{}; 762 | } 763 | private: 764 | Base mBase; 765 | }; 766 | 767 | namespace impl 768 | { 769 | template 770 | auto getBegins(std::tuple const& bases) 771 | { 772 | auto result = std::apply([](auto&&... views) 773 | { 774 | return std::make_tuple((views.begin())...); 775 | }, bases); 776 | return result; 777 | } 778 | } 779 | 780 | template 781 | class ProductView 782 | { 783 | public: 784 | class Iter 785 | { 786 | public: 787 | constexpr Iter(ProductView const& view) 788 | : mView{view} 789 | , mIters{impl::getBegins(mView.get().mBases)} 790 | { 791 | } 792 | auto& operator++() 793 | { 794 | next(); 795 | return *this; 796 | } 797 | auto operator*() const 798 | { 799 | return std::apply([](auto&&... iters) 800 | { 801 | return std::make_tuple(((*iters))...); 802 | }, mIters); 803 | } 804 | bool hasValue() const 805 | { 806 | return std::get<0>(mIters) != std::get<0>(mView.get().mBases).end(); 807 | } 808 | private: 809 | 810 | std::reference_wrapper mView; 811 | std::decay_t mIters; 812 | 813 | template > - 1> 814 | void next() 815 | { 816 | auto& iter = std::get(mIters); 817 | ++iter; 818 | if constexpr (I != 0) 819 | { 820 | auto const view = std::get(mView.get().mBases); 821 | if (iter == view.end()) 822 | { 823 | iter = view.begin(); 824 | next(); 825 | } 826 | } 827 | } 828 | }; 829 | class Sentinel 830 | {}; 831 | friend bool operator!=(Iter const& iter, Sentinel const&) 832 | { 833 | return iter.hasValue(); 834 | } 835 | constexpr ProductView(Bases... bases) 836 | : mBases{std::make_tuple(std::move(bases)...)} 837 | {} 838 | auto begin() const 839 | { 840 | return Iter(*this); 841 | } 842 | auto end() const 843 | { 844 | return Sentinel{}; 845 | } 846 | private: 847 | std::tuple...> mBases; 848 | }; 849 | 850 | template 851 | class ZipWithView 852 | { 853 | public: 854 | class Iter 855 | { 856 | public: 857 | constexpr Iter(ZipWithView const& view) 858 | : mView{view} 859 | , mIters{impl::getBegins(mView.get().mBases)} 860 | { 861 | } 862 | auto& operator++() 863 | { 864 | std::apply([](auto&&... iters) 865 | { 866 | ((++iters), ...); 867 | }, mIters); 868 | return *this; 869 | } 870 | auto operator*() const 871 | { 872 | return std::apply([&](auto&&... iters) 873 | { 874 | return mView.get().mFunc(((*iters))...); 875 | }, mIters); 876 | } 877 | bool hasValue() const 878 | { 879 | return hasValueImpl(); 880 | } 881 | private: 882 | template 883 | bool hasValueImpl() const 884 | { 885 | if constexpr (I == sizeof...(Bases)) 886 | { 887 | return true; 888 | } 889 | else 890 | { 891 | return std::get(mIters) != std::get(mView.get().mBases).end() && hasValueImpl(); 892 | } 893 | } 894 | 895 | std::reference_wrapper mView; 896 | std::decay_t mIters; 897 | }; 898 | class Sentinel 899 | {}; 900 | friend bool operator!=(Iter const& iter, Sentinel const&) 901 | { 902 | return iter.hasValue(); 903 | } 904 | constexpr ZipWithView(Func func, Bases... bases) 905 | : mFunc{std::move(func)} 906 | , mBases{std::make_tuple(std::move(bases)...)} 907 | {} 908 | auto begin() const 909 | { 910 | return Iter(*this); 911 | } 912 | auto end() const 913 | { 914 | return Sentinel{}; 915 | } 916 | private: 917 | Func mFunc; 918 | std::tuple...> mBases; 919 | }; 920 | 921 | template 922 | class ConcatView 923 | { 924 | public: 925 | class Iter 926 | { 927 | public: 928 | constexpr Iter(ConcatView const& view) 929 | : mView{view} 930 | , mIters{impl::getBegins(mView.get().mBases)} 931 | { 932 | } 933 | auto& operator++() 934 | { 935 | next(); 936 | return *this; 937 | } 938 | auto operator*() const 939 | { 940 | return deref(); 941 | } 942 | bool hasValue() const 943 | { 944 | return hasValueImpl(); 945 | } 946 | private: 947 | std::reference_wrapper mView; 948 | std::decay_t mIters; 949 | 950 | template 951 | auto deref() const 952 | { 953 | auto& iter = std::get(mIters); 954 | auto const& view = std::get(mView.get().mBases); 955 | if (iter != view.end()) 956 | { 957 | return *iter; 958 | } 959 | constexpr auto nbIters = std::tuple_size_v>; 960 | if constexpr (I < nbIters-1) 961 | { 962 | return deref(); 963 | } 964 | throw std::runtime_error{"Never reach here!"}; 965 | } 966 | 967 | template 968 | bool hasValueImpl() const 969 | { 970 | if constexpr (I == sizeof...(Bases)) 971 | { 972 | return false; 973 | } 974 | else 975 | { 976 | return std::get(mIters) != std::get(mView.get().mBases).end() || hasValueImpl(); 977 | } 978 | } 979 | 980 | template 981 | void next() 982 | { 983 | auto& iter = std::get(mIters); 984 | auto const view = std::get(mView.get().mBases); 985 | if (iter != view.end()) 986 | { 987 | ++iter; 988 | return; 989 | } 990 | constexpr auto nbIters = std::tuple_size_v>; 991 | if constexpr (I < nbIters-1) 992 | { 993 | next(); 994 | } 995 | } 996 | }; 997 | class Sentinel 998 | {}; 999 | friend bool operator!=(Iter const& iter, Sentinel const&) 1000 | { 1001 | return iter.hasValue(); 1002 | } 1003 | constexpr ConcatView(Bases... bases) 1004 | : mBases{std::make_tuple(std::move(bases)...)} 1005 | {} 1006 | auto begin() const 1007 | { 1008 | return Iter(*this); 1009 | } 1010 | auto end() const 1011 | { 1012 | return Sentinel{}; 1013 | } 1014 | private: 1015 | std::tuple...> mBases; 1016 | }; 1017 | 1018 | template 1019 | class IterateView 1020 | { 1021 | public: 1022 | class Iter 1023 | { 1024 | public: 1025 | constexpr Iter(IterateView const& iterateView) 1026 | : mUnary{iterateView.mUnary} 1027 | , mValue{iterateView.mStart} 1028 | { 1029 | } 1030 | auto& operator++() 1031 | { 1032 | mValue = mUnary(mValue); 1033 | return *this; 1034 | } 1035 | auto operator*() const 1036 | { 1037 | return mValue; 1038 | } 1039 | bool hasValue() const 1040 | { 1041 | return true; 1042 | } 1043 | private: 1044 | Unary mUnary; 1045 | Value mValue; 1046 | }; 1047 | class Sentinel 1048 | {}; 1049 | friend bool operator!=(Iter const& iter, Sentinel const&) 1050 | { 1051 | return iter.hasValue(); 1052 | } 1053 | constexpr IterateView(Unary unary, Value start) 1054 | : mUnary{unary} 1055 | , mStart{start} 1056 | {} 1057 | auto begin() const 1058 | { 1059 | return Iter(*this); 1060 | } 1061 | auto end() const 1062 | { 1063 | return Sentinel{}; 1064 | } 1065 | private: 1066 | Unary mUnary; 1067 | Value mStart; 1068 | }; 1069 | 1070 | template 1071 | class IterRange 1072 | { 1073 | Iter1 mBegin; 1074 | Iter2 mEnd; 1075 | public: 1076 | IterRange(Iter1 begin, Iter2 end) 1077 | : mBegin{begin} 1078 | , mEnd{end} 1079 | {} 1080 | auto begin() const 1081 | { 1082 | return mBegin; 1083 | } 1084 | auto end() const 1085 | { 1086 | return mEnd; 1087 | } 1088 | }; 1089 | 1090 | template 1091 | class GroupByView 1092 | { 1093 | public: 1094 | class Iter 1095 | { 1096 | public: 1097 | constexpr Iter(GroupByView const& groupByView) 1098 | : mView{groupByView} 1099 | , mBaseIter{mView.get().mBase.begin()} 1100 | { 1101 | } 1102 | auto& operator++() 1103 | { 1104 | auto last = *mBaseIter; 1105 | while (++mBaseIter, hasValue() && mView.get().mBinary(*mBaseIter, last)) 1106 | { 1107 | last = *mBaseIter; 1108 | } 1109 | return *this; 1110 | } 1111 | auto operator*() const 1112 | { 1113 | return ownedRange(TakeWhileView{[v = *mBaseIter, bin = mView.get().mBinary](auto x) { return bin(v, x); }, IterRange{mBaseIter, mView.get().mBase.end()}}); 1114 | } 1115 | bool hasValue() const 1116 | { 1117 | return mBaseIter != mView.get().mBase.end(); 1118 | } 1119 | private: 1120 | std::reference_wrapper mView; 1121 | std::decay_t mBaseIter; 1122 | }; 1123 | class Sentinel 1124 | {}; 1125 | friend bool operator!=(Iter const& iter, Sentinel const&) 1126 | { 1127 | return iter.hasValue(); 1128 | } 1129 | constexpr GroupByView(Binary binary, Base base) 1130 | : mBinary{std::move(binary)} 1131 | , mBase{std::move(base)} 1132 | {} 1133 | auto begin() const 1134 | { 1135 | return Iter(*this); 1136 | } 1137 | auto end() const 1138 | { 1139 | return Sentinel{}; 1140 | } 1141 | private: 1142 | Binary mBinary; 1143 | Base mBase; 1144 | }; 1145 | 1146 | } // namespace data 1147 | } // namespace hspp 1148 | 1149 | #endif // HSPP_RANGE_H 1150 | -------------------------------------------------------------------------------- /include/monadTrans.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2022 Bowen Fu 3 | * Distributed Under The Apache-2.0 License 4 | */ 5 | 6 | #ifndef HSPP_MONAD_TRANS_H 7 | #define HSPP_MONAD_TRANS_H 8 | 9 | namespace hspp 10 | { 11 | 12 | /////////// MonadTrans //////////// 13 | // class MonadTrans t where 14 | // -- | Lift a computation from the argument monad to the constructed monad. 15 | // lift :: (Monad m) => m a -> t m a 16 | template class, typename...> class T, typename...> 17 | class MonadTrans; 18 | 19 | template