├── .github └── workflows │ ├── macos.yml │ ├── ubuntu.yml │ └── windows.yml ├── .gitignore ├── .vscode ├── launch.json └── tasks.json ├── LICENSE ├── OpenString.natvis ├── OpenString.sln ├── README.md ├── VisualStudioSolution ├── OpenString.vcxproj ├── OpenString.vcxproj.filters ├── dictionary.txt ├── packages.config ├── test.vcxproj └── test.vcxproj.filters ├── benchmark ├── codeunit_sequence__benchmark.cpp ├── format__benchmark.cpp ├── main.cpp └── pch.h ├── include ├── codeunit_sequence.h ├── codeunit_sequence_view.h ├── common │ ├── adapters.h │ ├── assertion.h │ ├── basic_types.h │ ├── constants.h │ ├── definitions.h │ ├── functions.h │ ├── linear_iterator.h │ ├── platforms.h │ └── sequence.h ├── format.h ├── text.h ├── text_view.h ├── unicode.h └── wide_text.h ├── source ├── codeunit_sequence.cpp ├── format.cpp ├── text.cpp └── wide_text.cpp ├── test ├── gtest_printers_extension.h ├── main.cpp ├── pch.h ├── scoped_memory_leak_detector.h ├── test__codeunit_sequence.cpp ├── test__codeunit_sequence_view.cpp ├── test__format.cpp ├── test__text.cpp ├── test__text_view.cpp └── test__wide_text.cpp └── xmake.lua /.github/workflows/macos.yml: -------------------------------------------------------------------------------- 1 | name: macos 2 | 3 | on: 4 | pull_request: 5 | paths: 6 | - "xmake.lua" 7 | - "include/**" 8 | - "source/**" 9 | - "test/**" 10 | push: 11 | branches: 12 | - "master" 13 | 14 | jobs: 15 | Build: 16 | name: Build with XMake 17 | strategy: 18 | fail-fast: false 19 | matrix: 20 | os: [ macos-latest ] 21 | configuration: [ debug, release ] 22 | runs-on: ${{ matrix.os }} 23 | steps: 24 | - uses: actions/checkout@v2 25 | 26 | - name: Setup XMake 27 | uses: xmake-io/github-action-setup-xmake@v1 28 | with: 29 | xmake-version: latest 30 | actions-cache-folder: ".xmake-cache-macos" 31 | 32 | - name: Build with configuration file 33 | run: | 34 | xmake f -m ${{ matrix.configuration }} -y 35 | xmake -y -v -w 36 | 37 | - name: Upload binary 38 | uses: actions/upload-artifact@v3 39 | with: 40 | name: test-${{ matrix.os }}-${{ matrix.configuration }} 41 | path: build/macosx/x86_64/${{ matrix.configuration }}/test 42 | 43 | 44 | Test: 45 | needs: Build 46 | name: Test 47 | strategy: 48 | fail-fast: false 49 | matrix: 50 | os: [ macos-latest ] 51 | configuration: [ debug, release ] 52 | runs-on: ${{ matrix.os }} 53 | steps: 54 | - name: Download binary 55 | uses: actions/download-artifact@v3 56 | with: 57 | name: test-${{ matrix.os }}-${{ matrix.configuration }} 58 | path: build/ 59 | 60 | - name: Run executable 61 | run: | 62 | chmod 755 ./build/test 63 | ./build/test 64 | 65 | -------------------------------------------------------------------------------- /.github/workflows/ubuntu.yml: -------------------------------------------------------------------------------- 1 | name: ubuntu 2 | 3 | on: 4 | pull_request: 5 | paths: 6 | - "xmake.lua" 7 | - "include/**" 8 | - "source/**" 9 | - "test/**" 10 | push: 11 | branches: 12 | - "master" 13 | 14 | jobs: 15 | Build: 16 | name: Build with XMake 17 | strategy: 18 | fail-fast: false 19 | matrix: 20 | os: [ ubuntu-latest ] 21 | configuration: [ debug, release ] 22 | runs-on: ${{ matrix.os }} 23 | steps: 24 | - uses: actions/checkout@v2 25 | 26 | - name: Setup XMake 27 | uses: xmake-io/github-action-setup-xmake@v1 28 | with: 29 | xmake-version: latest 30 | actions-cache-folder: ".xmake-cache-ubuntu" 31 | 32 | - name: Build with configuration file 33 | run: | 34 | xmake f -m ${{ matrix.configuration }} -y 35 | xmake -y -v -w 36 | 37 | - name: Upload binary 38 | uses: actions/upload-artifact@v3 39 | with: 40 | name: test-${{ matrix.os }}-${{ matrix.configuration }} 41 | path: build/linux/x86_64/${{ matrix.configuration }}/test 42 | 43 | Test: 44 | needs: Build 45 | name: Test 46 | strategy: 47 | fail-fast: false 48 | matrix: 49 | os: [ ubuntu-latest ] 50 | configuration: [ debug, release ] 51 | runs-on: ${{ matrix.os }} 52 | steps: 53 | - name: Download binary 54 | uses: actions/download-artifact@v3 55 | with: 56 | name: test-${{ matrix.os }}-${{ matrix.configuration }} 57 | path: build/ 58 | 59 | - name: Run executable 60 | run: | 61 | chmod 755 ./build/test 62 | ./build/test 63 | 64 | 65 | -------------------------------------------------------------------------------- /.github/workflows/windows.yml: -------------------------------------------------------------------------------- 1 | name: windows 2 | 3 | on: 4 | pull_request: 5 | paths: 6 | - "xmake.lua" 7 | - "include/**" 8 | - "source/**" 9 | - "test/**" 10 | push: 11 | branches: 12 | - "master" 13 | 14 | jobs: 15 | Build: 16 | name: Build with XMake 17 | strategy: 18 | fail-fast: false 19 | matrix: 20 | os: [ windows-latest ] 21 | configuration: [ debug, release ] 22 | runs-on: ${{ matrix.os }} 23 | steps: 24 | - uses: actions/checkout@v2 25 | 26 | - name: Setup XMake 27 | uses: xmake-io/github-action-setup-xmake@v1 28 | with: 29 | xmake-version: latest 30 | actions-cache-folder: ".xmake-cache-windows" 31 | 32 | - name: Build with configuration file 33 | run: | 34 | xmake f -m ${{ matrix.configuration }} -y 35 | xmake -y -v -w 36 | 37 | - name: Upload binary 38 | uses: actions/upload-artifact@v3 39 | with: 40 | name: test-${{ matrix.os }}-${{ matrix.configuration }} 41 | path: build/windows/x64/${{ matrix.configuration }}/test.exe 42 | 43 | Test: 44 | needs: Build 45 | name: Test 46 | strategy: 47 | fail-fast: false 48 | matrix: 49 | os: [ windows-latest ] 50 | configuration: [ debug, release ] 51 | runs-on: ${{ matrix.os }} 52 | steps: 53 | - name: Download binary 54 | uses: actions/download-artifact@v3 55 | with: 56 | name: test-${{ matrix.os }}-${{ matrix.configuration }} 57 | path: build/ 58 | 59 | - name: Run executable 60 | run: start "build/test.exe" 61 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Xmake cache 2 | .xmake/ 3 | build/ 4 | 5 | # Clangd cache 6 | .cache/clangd/ 7 | 8 | # Build files 9 | x64/ 10 | 11 | # nuget packages 12 | packages/ 13 | 14 | # Generated files 15 | compile_commands.json 16 | 17 | # Visual Studio cache/options directory 18 | .vs/ 19 | 20 | # Visual Studio Code cache/options directory 21 | .vscode/settings.json 22 | 23 | # JetBrains Rider cache/options directory 24 | .idea/ 25 | 26 | # JetBrains Rider auto-generated XML file 27 | *.sln.DotSettings 28 | 29 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "name": "Debug test - xmake", 9 | "request": "launch", 10 | "type": "xmake", 11 | "target": "", 12 | "windows": { 13 | "name" : "windows", 14 | "type": "xmake", 15 | "request": "launch", 16 | "target": "${workspaceFolder}/build/windows/x64/debug/test.exe", 17 | }, 18 | "linux": { 19 | "name" : "linux", 20 | "type": "xmake", 21 | "request": "launch", 22 | "target": "${workspaceFolder}/build/linux/x86_64/debug/test", 23 | }, 24 | "args": [], 25 | "cwd": "${workspaceFolder}", 26 | "preLaunchTask": "Build test" 27 | }, 28 | { 29 | "name": "Debug test", 30 | "request": "launch", 31 | "type": "cppdbg", 32 | "program": "", 33 | "windows": { 34 | "name" : "windows", 35 | "type" : "cppvsdbg", 36 | "request": "launch", 37 | "program": "${workspaceFolder}/build/windows/x64/debug/test.exe" 38 | }, 39 | "linux": { 40 | "name" : "linux", 41 | "type" : "cppdbg", 42 | "request": "launch", 43 | "program": "${workspaceFolder}/build/linux/x86_64/debug/test" 44 | }, 45 | "args": [], 46 | "cwd": "${workspaceFolder}", 47 | "visualizerFile": "${workspaceFolder}/OpenString.natvis", 48 | "preLaunchTask": "Build test" 49 | } 50 | ] 51 | } -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | // See https://go.microsoft.com/fwlink/?LinkId=733558 3 | // for the documentation about the tasks.json format 4 | "version": "2.0.0", 5 | "tasks": [ 6 | { 7 | "label": "Build test", 8 | "type": "shell", 9 | "command": "xmake build -w test" 10 | } 11 | ] 12 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 OpenString 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /OpenString.natvis: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | (-∞,+∞) 7 | 8 | 9 | (-∞,{upper_.value}) 10 | 11 | 12 | (-∞,{upper_.value}] 13 | 14 | 15 | ({lower_.value},+∞) 16 | 17 | 18 | [{lower_.value},+∞) 19 | 20 | 21 | ({lower_.value},{upper_.value}) 22 | 23 | 24 | ({lower_.value},{upper_.value}] 25 | 26 | 27 | [{lower_.value},{upper_.value}) 28 | 29 | 30 | [{lower_.value},{upper_.value}] 31 | 32 | 33 | invalid 34 | 35 | 36 | 37 | 38 | {data_,[size_]} 39 | 40 | size_ 41 | 42 | 43 | 44 | 45 | {((easy::codeunit_sequence::sso*)this)->alloc ? ((easy::codeunit_sequence::norm*)this)->data : ((easy::codeunit_sequence::sso*)this)->data._Elems} 46 | 47 | (bool)(((easy::codeunit_sequence::sso*)this)->alloc) 48 | ((easy::codeunit_sequence::sso*)this)->alloc ? ((easy::codeunit_sequence::norm*)this)->size : (easy::u32)((easy::codeunit_sequence::sso*)this)->size 49 | ((easy::codeunit_sequence::sso*)this)->alloc ? ((easy::codeunit_sequence::norm*)this)->capacity : easy::codeunit_sequence::SSO_SIZE_MAX 50 | ((easy::codeunit_sequence::sso*)this)->alloc ? ((easy::codeunit_sequence::norm*)this)->data : (easy::u32)((easy::codeunit_sequence::sso*)this)->data._Elems 51 | 52 | 53 | 54 | -------------------------------------------------------------------------------- /OpenString.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.4.33205.214 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "OpenString", "VisualStudioSolution\OpenString.vcxproj", "{16BBF1B5-AF6E-4B3D-A507-1C24CB0E3285}" 7 | EndProject 8 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "test", "VisualStudioSolution\test.vcxproj", "{5AEA09DB-474B-4770-AEF6-22F4AF32EDC3}" 9 | EndProject 10 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Visualizer", "Visualizer", "{69C45B19-D146-4B14-862B-2C41E82374E0}" 11 | ProjectSection(SolutionItems) = preProject 12 | OpenString.natvis = OpenString.natvis 13 | EndProjectSection 14 | EndProject 15 | Global 16 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 17 | Debug|x64 = Debug|x64 18 | Release|x64 = Release|x64 19 | EndGlobalSection 20 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 21 | {16BBF1B5-AF6E-4B3D-A507-1C24CB0E3285}.Debug|x64.ActiveCfg = Debug|x64 22 | {16BBF1B5-AF6E-4B3D-A507-1C24CB0E3285}.Debug|x64.Build.0 = Debug|x64 23 | {16BBF1B5-AF6E-4B3D-A507-1C24CB0E3285}.Release|x64.ActiveCfg = Release|x64 24 | {16BBF1B5-AF6E-4B3D-A507-1C24CB0E3285}.Release|x64.Build.0 = Release|x64 25 | {5AEA09DB-474B-4770-AEF6-22F4AF32EDC3}.Debug|x64.ActiveCfg = Debug|x64 26 | {5AEA09DB-474B-4770-AEF6-22F4AF32EDC3}.Debug|x64.Build.0 = Debug|x64 27 | {5AEA09DB-474B-4770-AEF6-22F4AF32EDC3}.Release|x64.ActiveCfg = Release|x64 28 | {5AEA09DB-474B-4770-AEF6-22F4AF32EDC3}.Release|x64.Build.0 = Release|x64 29 | EndGlobalSection 30 | GlobalSection(SolutionProperties) = preSolution 31 | HideSolutionNode = FALSE 32 | EndGlobalSection 33 | GlobalSection(ExtensibilityGlobals) = postSolution 34 | SolutionGuid = {CF1AD042-3A9F-47DD-B4F0-51ED3471675B} 35 | EndGlobalSection 36 | EndGlobal 37 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 |

OpenString

3 |
4 | 5 | ci-windows 6 | 7 | 8 | ci-ubuntu 9 | 10 | 11 | ci-macos 12 | 13 |
14 | 15 | A modern user-friendly string library for game developing
16 |
17 | 18 | ## License 19 | 20 | *OpenString* is distributed under the MIT license. 21 | 22 | ## Features 23 | 24 | - User-friendly APIs 25 | - Encoding dependencies are encapsulated and hidden 26 | - Using [{fmt}](https://fmt.dev/latest/index.html)-like formatting method 27 | - High performance 28 | 29 | ## Target Users 30 | 31 | - Ask for a easy-using and high-performance string library. 32 | - Do not prepare to worry about encoding things. 33 | - Deal with strings in common sense and safe way. 34 | 35 | ## Benchmarks 36 | 37 | todo 38 | 39 | ## Q&A 40 | 41 | todo 42 | 43 | ## TODO 44 | 45 | - CMake support 46 | - Regex support 47 | - Folder directory path library 48 | - A ``name`` class for easy copying and comparing 49 | - Localization support 50 | 51 | ## Thanks 52 | 53 | - [{fmt}](https://github.com/fmtlib/fmt): Provides a lot of inspiration for the formatting method. 54 | - [Folly](https://github.com/facebook/folly): FBString provides references for some implementations. 55 | - [xmake](https://github.com/xmake-io/xmake): A powerful cross-platform build utility. -------------------------------------------------------------------------------- /VisualStudioSolution/OpenString.vcxproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | x64 7 | 8 | 9 | Release 10 | x64 11 | 12 | 13 | 14 | 16.0 15 | Win32Proj 16 | {16bbf1b5-af6e-4b3d-a507-1c24cb0e3285} 17 | OpenString 18 | 10.0 19 | 20 | 21 | 22 | StaticLibrary 23 | true 24 | v143 25 | Unicode 26 | 27 | 28 | Application 29 | false 30 | v143 31 | true 32 | Unicode 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | $(SolutionDir)include;$(IncludePath) 48 | $(Platform)\$(Configuration)\OpenString\ 49 | 50 | 51 | 52 | Level3 53 | true 54 | _DEBUG;_CONSOLE;%(PreprocessorDefinitions);WIN32_LEAN_AND_MEAN;NOMINMAX 55 | true 56 | stdcpp17 57 | 58 | 59 | 60 | 61 | Console 62 | true 63 | 64 | 65 | 66 | 67 | Level3 68 | true 69 | true 70 | true 71 | NDEBUG;_CONSOLE;%(PreprocessorDefinitions) 72 | true 73 | 74 | 75 | Console 76 | true 77 | true 78 | true 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | -------------------------------------------------------------------------------- /VisualStudioSolution/OpenString.vcxproj.filters: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | {93995380-89BD-4b04-88EB-625FBE52EBFB} 6 | h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd 7 | 8 | 9 | {4FC737F1-C7A5-4376-A066-2A32D752A2FF} 10 | cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx 11 | 12 | 13 | {4adabc1e-7b75-4860-a3a1-f9098d269d47} 14 | 15 | 16 | 17 | 18 | include 19 | 20 | 21 | include 22 | 23 | 24 | include 25 | 26 | 27 | include 28 | 29 | 30 | include\common 31 | 32 | 33 | include\common 34 | 35 | 36 | include\common 37 | 38 | 39 | include\common 40 | 41 | 42 | include 43 | 44 | 45 | include 46 | 47 | 48 | include\common 49 | 50 | 51 | 52 | 53 | source 54 | 55 | 56 | source 57 | 58 | 59 | source 60 | 61 | 62 | -------------------------------------------------------------------------------- /VisualStudioSolution/dictionary.txt: -------------------------------------------------------------------------------- 1 | Hoshizora 2 | codeunit 3 | cbegin 4 | cend 5 | -------------------------------------------------------------------------------- /VisualStudioSolution/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | -------------------------------------------------------------------------------- /VisualStudioSolution/test.vcxproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | x64 7 | 8 | 9 | Release 10 | x64 11 | 12 | 13 | 14 | 16.0 15 | Win32Proj 16 | {5aea09db-474b-4770-aef6-22f4af32edc3} 17 | test 18 | 10.0 19 | 20 | 21 | 22 | Application 23 | true 24 | v143 25 | Unicode 26 | 27 | 28 | Application 29 | false 30 | v143 31 | true 32 | Unicode 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | $(SolutionDir)include;$(SolutionDir)test\platform;$(IncludePath) 48 | $(Platform)\$(Configuration)\test\ 49 | 50 | 51 | 52 | Level3 53 | true 54 | _DEBUG;_CONSOLE;%(PreprocessorDefinitions);WIN32_LEAN_AND_MEAN;NOMINMAX 55 | true 56 | stdcpp17 57 | $(SolutionDir)include;%(AdditionalIncludeDirectories) 58 | 59 | 60 | Console 61 | true 62 | 63 | 64 | 65 | 66 | Level3 67 | true 68 | true 69 | true 70 | NDEBUG;_CONSOLE;%(PreprocessorDefinitions) 71 | true 72 | 73 | 74 | Console 75 | true 76 | true 77 | true 78 | 79 | 80 | 81 | 82 | {16bbf1b5-af6e-4b3d-a507-1c24cb0e3285} 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 这台计算机上缺少此项目引用的 NuGet 程序包。使用“NuGet 程序包还原”可下载这些程序包。有关更多信息,请参见 http://go.microsoft.com/fwlink/?LinkID=322105。缺少的文件是 {0}。 109 | 110 | 111 | 112 | -------------------------------------------------------------------------------- /VisualStudioSolution/test.vcxproj.filters: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | {93995380-89BD-4b04-88EB-625FBE52EBFB} 6 | h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | common 21 | 22 | 23 | common 24 | 25 | 26 | 27 | common 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /benchmark/codeunit_sequence__benchmark.cpp: -------------------------------------------------------------------------------- 1 | #include "pch.h" 2 | #include "common/basic_types.h" 3 | #include "text.h" 4 | 5 | void std_string_construct(benchmark::State& state) 6 | { 7 | for (auto _ : state) 8 | { 9 | std::string empty_string; 10 | } 11 | } 12 | 13 | void codeunit_sequence_construct(benchmark::State& state) 14 | { 15 | for (auto _ : state) 16 | { 17 | easy::codeunit_sequence empty_sequence; 18 | } 19 | } 20 | 21 | BENCHMARK(std_string_construct); 22 | BENCHMARK(codeunit_sequence_construct); -------------------------------------------------------------------------------- /benchmark/format__benchmark.cpp: -------------------------------------------------------------------------------- 1 | #include "pch.h" 2 | #include "format.h" 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /benchmark/main.cpp: -------------------------------------------------------------------------------- 1 | #include "pch.h" 2 | 3 | BENCHMARK_MAIN(); -------------------------------------------------------------------------------- /benchmark/pch.h: -------------------------------------------------------------------------------- 1 | 2 | #pragma once 3 | 4 | #include 5 | -------------------------------------------------------------------------------- /include/codeunit_sequence.h: -------------------------------------------------------------------------------- 1 | 2 | #pragma once 3 | #include "common/definitions.h" 4 | 5 | #include 6 | 7 | #include "codeunit_sequence_view.h" 8 | 9 | namespace ostr 10 | { 11 | class OPEN_STRING_API codeunit_sequence 12 | { 13 | public: 14 | 15 | // code-region-start: constructors 16 | 17 | codeunit_sequence() noexcept; 18 | explicit codeunit_sequence(u64 size) noexcept; 19 | codeunit_sequence(const codeunit_sequence&) noexcept; 20 | codeunit_sequence(codeunit_sequence&&) noexcept; 21 | codeunit_sequence& operator=(const codeunit_sequence&) noexcept; 22 | codeunit_sequence& operator=(codeunit_sequence&&) noexcept; 23 | 24 | codeunit_sequence& operator=(const codeunit_sequence_view& view) noexcept; 25 | 26 | ~codeunit_sequence() noexcept; 27 | 28 | explicit codeunit_sequence(const char* data) noexcept; 29 | codeunit_sequence(const char* from, const char* last) noexcept; 30 | codeunit_sequence(const char* data, u64 count) noexcept; 31 | 32 | explicit codeunit_sequence(const codeunit_sequence_view& sv) noexcept; 33 | 34 | template 35 | static codeunit_sequence build(const Args&... argument); 36 | template 37 | static codeunit_sequence join(const Container& container, const codeunit_sequence_view& separator) noexcept; 38 | 39 | // code-region-end: constructors 40 | 41 | // code-region-start: iterators 42 | 43 | using const_iterator = codeunit_sequence_view::const_iterator; 44 | 45 | struct iterator 46 | { 47 | iterator() noexcept; 48 | explicit iterator(char* v) noexcept; 49 | [[nodiscard]] char* data() const noexcept; 50 | [[nodiscard]] char& operator*() const noexcept; 51 | [[nodiscard]] i64 operator-(const iterator& rhs) const noexcept; 52 | iterator& operator+=(i64 diff) noexcept; 53 | iterator& operator-=(i64 diff) noexcept; 54 | iterator& operator+=(u64 diff) noexcept; 55 | iterator& operator-=(u64 diff) noexcept; 56 | [[nodiscard]] iterator operator+(i64 diff) const noexcept; 57 | [[nodiscard]] iterator operator-(i64 diff) const noexcept; 58 | [[nodiscard]] iterator operator+(u64 diff) const noexcept; 59 | [[nodiscard]] iterator operator-(u64 diff) const noexcept; 60 | iterator& operator++() noexcept; 61 | iterator operator++(int) noexcept; 62 | iterator& operator--() noexcept; 63 | iterator operator--(int) noexcept; 64 | [[nodiscard]] bool operator==(const iterator& rhs) const noexcept; 65 | [[nodiscard]] bool operator!=(const iterator& rhs) const noexcept; 66 | [[nodiscard]] bool operator<(const iterator& rhs) const noexcept; 67 | [[nodiscard]] bool operator>(const iterator& rhs) const noexcept; 68 | [[nodiscard]] bool operator<=(const iterator& rhs) const noexcept; 69 | [[nodiscard]] bool operator>=(const iterator& rhs) const noexcept; 70 | 71 | char* value; 72 | }; 73 | 74 | [[nodiscard]] iterator begin() noexcept; 75 | [[nodiscard]] const_iterator begin() const noexcept; 76 | [[nodiscard]] iterator end() noexcept; 77 | [[nodiscard]] const_iterator end() const noexcept; 78 | [[nodiscard]] const_iterator cbegin() const noexcept; 79 | [[nodiscard]] const_iterator cend() const noexcept; 80 | 81 | // code-region-end: iterators 82 | 83 | [[nodiscard]] codeunit_sequence_view view() const& noexcept; 84 | /** 85 | * FATAL: You must NOT get view from rvalue of a codeunit sequence 86 | */ 87 | codeunit_sequence_view view() && = delete; 88 | 89 | /** 90 | * @return The length of this codeunit sequence 91 | */ 92 | [[nodiscard]] u64 size() const noexcept; 93 | 94 | /** 95 | * @return Whether this codeunit sequence is empty or not. 96 | */ 97 | [[nodiscard]] bool is_empty() const noexcept; 98 | 99 | /** 100 | * @param rhs Another codeunit sequence 101 | * @return Whether two codeunit sequences are equal. 102 | */ 103 | [[nodiscard]] bool operator==(const codeunit_sequence_view& rhs) const noexcept; 104 | [[nodiscard]] bool operator==(const codeunit_sequence& rhs) const noexcept; 105 | [[nodiscard]] bool operator==(const char* rhs) const noexcept; 106 | 107 | /** 108 | * @param rhs Another codeunit sequence 109 | * @return Whether two codeunit sequences are different. 110 | */ 111 | [[nodiscard]] bool operator!=(const codeunit_sequence_view& rhs) const noexcept; 112 | [[nodiscard]] bool operator!=(const codeunit_sequence& rhs) const noexcept; 113 | [[nodiscard]] bool operator!=(const char* rhs) const noexcept; 114 | 115 | /** 116 | * Append a codeunit sequence back. 117 | * @return ref of this codeunit sequence. 118 | */ 119 | codeunit_sequence& append(const codeunit_sequence_view& rhs) noexcept; 120 | codeunit_sequence& append(const codeunit_sequence& rhs) noexcept; 121 | codeunit_sequence& append(const codepoint& cp) noexcept; 122 | codeunit_sequence& append(const char* rhs) noexcept; 123 | /** 124 | * \brief Append amount of same character after this sequence. 125 | * \param codeunit Character to fill in. 126 | * Specially, if character is '\0', it will still resize the sequence, 127 | * but will not set memory to 0, so you can use like 128 | * c.append('\0', 1024); 129 | * for like buffering file or network streaming in. 130 | * \param count How many character to fill in. 131 | * \return self 132 | */ 133 | codeunit_sequence& append(char codeunit, u64 count = 1) noexcept; 134 | 135 | codeunit_sequence& operator+=(const codeunit_sequence_view& rhs) noexcept; 136 | codeunit_sequence& operator+=(const codeunit_sequence& rhs) noexcept; 137 | codeunit_sequence& operator+=(const codepoint& cp) noexcept; 138 | codeunit_sequence& operator+=(const char* rhs) noexcept; 139 | codeunit_sequence& operator+=(char codeunit) noexcept; 140 | 141 | [[nodiscard]] codeunit_sequence_view subview(u64 from, u64 size = SIZE_MAX) const noexcept; 142 | 143 | /** 144 | * Make this a subsequence from specific range 145 | * 146 | * Example: codeunit_sequence("codeunit_sequence").subsequence(2, 3) == "ar_"; 147 | * 148 | * @param from start index 149 | * @param size size of subsequence 150 | * @return ref of this codeunit sequence 151 | */ 152 | codeunit_sequence& subsequence(u64 from, u64 size = SIZE_MAX) noexcept; 153 | 154 | /** 155 | * Get the index of specific codeunit_sequence. 156 | * 157 | * Example: codeunit_sequence("codeunit_sequence").index_of("ar_") == 2; 158 | * 159 | * @param pattern pattern to search. 160 | * @param from start index to search 161 | * @param size size of search range 162 | * @return the index of where the codeunit_sequence first found 163 | */ 164 | [[nodiscard]] u64 index_of(const codeunit_sequence_view& pattern, u64 from = 0, u64 size = SIZE_MAX) const noexcept; 165 | [[nodiscard]] u64 last_index_of(const codeunit_sequence_view& pattern, u64 from = 0, u64 size = SIZE_MAX) const noexcept; 166 | 167 | [[nodiscard]] u64 count(const codeunit_sequence_view& pattern) const noexcept; 168 | 169 | [[nodiscard]] bool starts_with(const codeunit_sequence_view& pattern) const noexcept; 170 | [[nodiscard]] bool ends_with(const codeunit_sequence_view& pattern) const noexcept; 171 | 172 | /** 173 | * Empty the string without reallocation. 174 | */ 175 | void empty(); 176 | /** 177 | * Empty the string with size reserved. 178 | * @param size the size reserved 179 | */ 180 | void empty(u64 size); 181 | 182 | void reserve(u64 size); 183 | 184 | codeunit_sequence& write_at(u64 index, char codeunit) noexcept; 185 | [[nodiscard]] const char& read_at(u64 index) const noexcept; 186 | 187 | [[nodiscard]] char& operator[](u64 index) noexcept; 188 | [[nodiscard]] const char& operator[](u64 index) const noexcept; 189 | 190 | codeunit_sequence& reverse(u64 from = 0, u64 size = SIZE_MAX) noexcept; 191 | 192 | u32 split(const codeunit_sequence_view& splitter, std::vector& pieces, bool cull_empty = true) const noexcept; 193 | 194 | codeunit_sequence& replace(const codeunit_sequence_view& destination, const codeunit_sequence_view& source, u64 from = 0, u64 size = SIZE_MAX); 195 | codeunit_sequence& replace(const codeunit_sequence_view& destination, u64 from, u64 size = SIZE_MAX); 196 | 197 | codeunit_sequence& self_remove_prefix(const codeunit_sequence_view& prefix) noexcept; 198 | codeunit_sequence& self_remove_suffix(const codeunit_sequence_view& suffix) noexcept; 199 | [[nodiscard]] codeunit_sequence_view view_remove_prefix(const codeunit_sequence_view& prefix) const noexcept; 200 | [[nodiscard]] codeunit_sequence_view view_remove_suffix(const codeunit_sequence_view& suffix) const noexcept; 201 | 202 | codeunit_sequence& self_trim_start(const codeunit_sequence_view& characters = codeunit_sequence_view(" \t")) noexcept; 203 | codeunit_sequence& self_trim_end(const codeunit_sequence_view& characters = codeunit_sequence_view(" \t")) noexcept; 204 | codeunit_sequence& self_trim(const codeunit_sequence_view& characters = codeunit_sequence_view(" \t")) noexcept; 205 | [[nodiscard]] codeunit_sequence_view view_trim_start(const codeunit_sequence_view& characters = codeunit_sequence_view(" \t")) const noexcept; 206 | [[nodiscard]] codeunit_sequence_view view_trim_end(const codeunit_sequence_view& characters = codeunit_sequence_view(" \t")) const noexcept; 207 | [[nodiscard]] codeunit_sequence_view view_trim(const codeunit_sequence_view& characters = codeunit_sequence_view(" \t")) const noexcept; 208 | 209 | [[nodiscard]] char* data() noexcept; 210 | [[nodiscard]] const char* data() const noexcept; 211 | [[nodiscard]] const char* c_str() const noexcept; 212 | 213 | private: 214 | 215 | static constexpr u8 SSO_SIZE_MAX = 14; 216 | static bool is_short_size(u64 size) noexcept; 217 | 218 | struct sso 219 | { 220 | u8 alloc : 1; 221 | u8 size : 7; 222 | std::array data; 223 | }; 224 | 225 | struct norm 226 | { 227 | u32 alloc : 1; 228 | u32 size : 15; 229 | u32 capacity; // character capacity, which is 1 less than memory capacity 230 | char* data; 231 | }; 232 | 233 | [[nodiscard]] sso& as_sso(); 234 | [[nodiscard]] const sso& as_sso() const; 235 | 236 | [[nodiscard]] norm& as_norm(); 237 | [[nodiscard]] const norm& as_norm() const; 238 | 239 | /// @return is this a sequence with less than 15 chars 240 | [[nodiscard]] bool is_short() const; 241 | 242 | [[nodiscard]] u64 get_capacity() const; 243 | 244 | [[nodiscard]] char* last(); 245 | 246 | [[nodiscard]] const char* last() const; 247 | 248 | void deallocate(); 249 | 250 | void set_size(u64 size); 251 | 252 | /** 253 | * \brief Receive and consume data from another string. 254 | * eg. "abc".transfer_data("def"); results in "def" and "" 255 | * \param other Provider. 256 | */ 257 | void transfer_data(codeunit_sequence& other); 258 | 259 | std::array store_{ }; 260 | }; 261 | 262 | namespace details 263 | { 264 | template 265 | [[nodiscard]] constexpr codeunit_sequence_view view_sequence(const T& v) 266 | { 267 | return codeunit_sequence_view{ v }; 268 | } 269 | 270 | template<> 271 | [[nodiscard]] inline codeunit_sequence_view view_sequence(const codeunit_sequence& v) 272 | { 273 | return v.view(); 274 | } 275 | } 276 | 277 | template 278 | codeunit_sequence codeunit_sequence::build(const Args&... argument) 279 | { 280 | u64 size = 0; 281 | std::array arguments{ details::view_sequence(argument)... }; 282 | for(const codeunit_sequence_view& a : arguments) 283 | size += a.size(); 284 | codeunit_sequence result(size); 285 | for(const codeunit_sequence_view& a : arguments) 286 | result.append(a); 287 | return { result }; 288 | } 289 | 290 | template 291 | codeunit_sequence codeunit_sequence::join(const Container& container, const codeunit_sequence_view& separator) noexcept 292 | { 293 | codeunit_sequence result; 294 | for(const auto& element : container) 295 | { 296 | if(!result.is_empty()) 297 | result += separator; 298 | result.append(details::view_sequence(element)); 299 | } 300 | return result; 301 | } 302 | 303 | OPEN_STRING_API [[nodiscard]] bool operator==(const codeunit_sequence_view& lhs, const codeunit_sequence& rhs) noexcept; 304 | } 305 | -------------------------------------------------------------------------------- /include/codeunit_sequence_view.h: -------------------------------------------------------------------------------- 1 | 2 | #pragma once 3 | #include "common/definitions.h" 4 | 5 | #include 6 | #include 7 | 8 | #include "common/basic_types.h" 9 | #include "common/constants.h" 10 | #include "unicode.h" 11 | #include "common/functions.h" 12 | 13 | namespace ostr 14 | { 15 | class OPEN_STRING_API codeunit_sequence_view 16 | { 17 | public: 18 | 19 | // code-region-start: constructors 20 | 21 | constexpr codeunit_sequence_view() noexcept; 22 | constexpr codeunit_sequence_view(const codeunit_sequence_view&) noexcept; 23 | constexpr codeunit_sequence_view(codeunit_sequence_view&&) noexcept; 24 | constexpr codeunit_sequence_view& operator=(const codeunit_sequence_view& other) noexcept; 25 | constexpr codeunit_sequence_view& operator=(codeunit_sequence_view&& other) noexcept; 26 | ~codeunit_sequence_view() noexcept = default; 27 | 28 | constexpr codeunit_sequence_view(const char* data, const u64 count) noexcept; 29 | constexpr codeunit_sequence_view(const char* data, const char* last) noexcept; 30 | explicit constexpr codeunit_sequence_view(const char* str) noexcept; 31 | explicit constexpr codeunit_sequence_view(const char& c) noexcept; 32 | explicit constexpr codeunit_sequence_view(const codepoint& cp) noexcept; 33 | 34 | // code-region-end: constructors 35 | 36 | // code-region-start: iterators 37 | 38 | struct const_iterator 39 | { 40 | constexpr const_iterator() noexcept; 41 | explicit constexpr const_iterator(const char* v) noexcept; 42 | 43 | [[nodiscard]] constexpr const char* data() const noexcept; 44 | [[nodiscard]] constexpr const char& operator*() const noexcept; 45 | 46 | [[nodiscard]] constexpr i64 operator-(const const_iterator& rhs) const noexcept; 47 | 48 | constexpr const_iterator& operator+=(const i64 diff) noexcept; 49 | constexpr const_iterator& operator-=(const i64 diff) noexcept; 50 | constexpr const_iterator& operator+=(const u64 diff) noexcept; 51 | constexpr const_iterator& operator-=(const u64 diff) noexcept; 52 | 53 | [[nodiscard]] constexpr const_iterator operator+(const i64 diff) const noexcept; 54 | [[nodiscard]] constexpr const_iterator operator-(const i64 diff) const noexcept; 55 | [[nodiscard]] constexpr const_iterator operator+(const u64 diff) const noexcept; 56 | [[nodiscard]] constexpr const_iterator operator-(const u64 diff) const noexcept; 57 | 58 | constexpr const_iterator& operator++() noexcept; 59 | constexpr const_iterator operator++(int) noexcept; 60 | constexpr const_iterator& operator--() noexcept; 61 | constexpr const_iterator operator--(int) noexcept; 62 | 63 | [[nodiscard]] constexpr bool operator==(const const_iterator& rhs) const noexcept; 64 | [[nodiscard]] constexpr bool operator!=(const const_iterator& rhs) const noexcept; 65 | [[nodiscard]] constexpr bool operator<(const const_iterator& rhs) const noexcept; 66 | [[nodiscard]] constexpr bool operator>(const const_iterator& rhs) const noexcept; 67 | [[nodiscard]] constexpr bool operator<=(const const_iterator& rhs) const noexcept; 68 | [[nodiscard]] constexpr bool operator>=(const const_iterator& rhs) const noexcept; 69 | 70 | const char* value = nullptr; 71 | }; 72 | 73 | [[nodiscard]] constexpr const_iterator begin() const noexcept; 74 | [[nodiscard]] constexpr const_iterator end() const noexcept; 75 | [[nodiscard]] constexpr const_iterator cbegin() const noexcept; 76 | [[nodiscard]] constexpr const_iterator cend() const noexcept; 77 | 78 | // code-region-end: iterators 79 | 80 | [[nodiscard]] constexpr bool operator==(const codeunit_sequence_view& rhs) const noexcept; 81 | [[nodiscard]] constexpr bool operator==(const char* rhs) const noexcept; 82 | [[nodiscard]] constexpr bool operator!=(const codeunit_sequence_view& rhs) const noexcept; 83 | [[nodiscard]] constexpr bool operator!=(const char* rhs) const noexcept; 84 | 85 | [[nodiscard]] constexpr u64 size() const noexcept; 86 | 87 | // This is not allowed for getting rid of misuse 88 | [[nodiscard]] const char* c_str() const noexcept = delete; 89 | 90 | // Warning: Please make sure that you know what you are doing 91 | // this is not guaranteed to be null-terminated 92 | [[nodiscard]] const char* data() const noexcept; 93 | 94 | /// @return Is this an empty string 95 | [[nodiscard]] constexpr bool is_empty() const noexcept; 96 | 97 | [[nodiscard]] constexpr codeunit_sequence_view subview(const u64 from, const u64 size = SIZE_MAX) const noexcept; 98 | 99 | [[nodiscard]] constexpr const char& read_at(const u64 index) const noexcept; 100 | [[nodiscard]] constexpr const char& read_from_last(const u64 index) const noexcept; 101 | 102 | // [[nodiscard]] constexpr codeunit_sequence_view operator[](const index_interval& range) const noexcept 103 | // { 104 | // return this->subview(range); 105 | // } 106 | 107 | /** 108 | * \brief this is learned from FBString in folly 109 | * see https://github.com/facebook/folly/blob/main/folly/FBString.h 110 | * which is a Boyer-Moore-like trick 111 | * \param pattern string which to search in this string 112 | * \param from search range start index 113 | * \param size search range size 114 | * \return index where searched, return global_constant::INDEX_INVALID_INDEX if not found 115 | */ 116 | [[nodiscard]] constexpr u64 index_of(const codeunit_sequence_view& pattern, const u64 from = 0, const u64 size = SIZE_MAX) const noexcept; 117 | [[nodiscard]] constexpr u64 index_of(const char codeunit, const u64 from = 0, const u64 size = SIZE_MAX) const noexcept; 118 | [[nodiscard]] constexpr u64 last_index_of(const codeunit_sequence_view& pattern, const u64 from = 0, const u64 size = SIZE_MAX) const noexcept; 119 | [[nodiscard]] constexpr u64 index_of_any(const codeunit_sequence_view& units, const u64 from = 0, const u64 size = SIZE_MAX) const noexcept; 120 | [[nodiscard]] constexpr u64 last_index_of_any(const codeunit_sequence_view& units, const u64 from = 0, const u64 size = SIZE_MAX) const noexcept; 121 | 122 | [[nodiscard]] constexpr bool contains(const codeunit_sequence_view& pattern) const noexcept; 123 | [[nodiscard]] constexpr bool contains(const char codeunit) const noexcept; 124 | 125 | [[nodiscard]] constexpr std::array split(const codeunit_sequence_view& splitter) const noexcept; 126 | u32 split(const codeunit_sequence_view& splitter, std::vector& pieces, bool cull_empty = true) const noexcept; 127 | 128 | [[nodiscard]] constexpr u64 count(const codeunit_sequence_view& pattern, u64 from = 0, u64 size = global_constant::SIZE_INVALID) const noexcept; 129 | 130 | [[nodiscard]] constexpr bool starts_with(const codeunit_sequence_view& prefix) const noexcept; 131 | [[nodiscard]] constexpr bool ends_with(const codeunit_sequence_view& suffix) const noexcept; 132 | 133 | [[nodiscard]] constexpr codeunit_sequence_view remove_prefix(const codeunit_sequence_view& prefix) const noexcept; 134 | [[nodiscard]] constexpr codeunit_sequence_view remove_suffix(const codeunit_sequence_view& suffix) const noexcept; 135 | 136 | [[nodiscard]] constexpr codeunit_sequence_view trim_start(const codeunit_sequence_view& units = codeunit_sequence_view(" \t")) const noexcept; 137 | [[nodiscard]] constexpr codeunit_sequence_view trim_end(const codeunit_sequence_view& units = codeunit_sequence_view(" \t")) const noexcept; 138 | [[nodiscard]] constexpr codeunit_sequence_view trim(const codeunit_sequence_view& units = codeunit_sequence_view(" \t")) const noexcept; 139 | 140 | private: 141 | 142 | u64 size_{ 0 }; 143 | const char* data_{ nullptr }; 144 | 145 | }; 146 | 147 | namespace details 148 | { 149 | [[nodiscard]] constexpr u64 get_sequence_length(const char* str) noexcept 150 | { 151 | if(!str) 152 | return 0; 153 | u64 count = 0; 154 | while(str[count] != 0) 155 | ++count; 156 | return count; 157 | } 158 | } 159 | 160 | constexpr codeunit_sequence_view::codeunit_sequence_view() noexcept = default; 161 | constexpr codeunit_sequence_view::codeunit_sequence_view(const codeunit_sequence_view&) noexcept = default; 162 | constexpr codeunit_sequence_view::codeunit_sequence_view(codeunit_sequence_view&&) noexcept = default; 163 | constexpr codeunit_sequence_view& codeunit_sequence_view::operator=(const codeunit_sequence_view& other) noexcept = default; 164 | constexpr codeunit_sequence_view& codeunit_sequence_view::operator=(codeunit_sequence_view&& other) noexcept = default; 165 | 166 | constexpr codeunit_sequence_view::codeunit_sequence_view(const char* data, const u64 count) noexcept 167 | : size_{ count } 168 | , data_{ data } 169 | { } 170 | 171 | constexpr codeunit_sequence_view::codeunit_sequence_view(const char* data, const char* last) noexcept 172 | : size_{ static_cast(last - data) } 173 | , data_{ data } 174 | { } 175 | 176 | constexpr codeunit_sequence_view::codeunit_sequence_view(const char* str) noexcept 177 | : codeunit_sequence_view{ str, details::get_sequence_length(str) } 178 | { } 179 | 180 | constexpr codeunit_sequence_view::codeunit_sequence_view(const char& c) noexcept 181 | : codeunit_sequence_view{ &c, 1 } 182 | { } 183 | 184 | constexpr codeunit_sequence_view::codeunit_sequence_view(const codepoint& cp) noexcept 185 | : codeunit_sequence_view{ cp.raw(), cp.size() } 186 | { } 187 | 188 | constexpr codeunit_sequence_view::const_iterator::const_iterator() noexcept = default; 189 | 190 | constexpr codeunit_sequence_view::const_iterator::const_iterator(const char* v) noexcept 191 | : value{ v } 192 | { } 193 | 194 | constexpr const char* codeunit_sequence_view::const_iterator::data() const noexcept 195 | { 196 | return this->value; 197 | } 198 | 199 | constexpr const char& codeunit_sequence_view::const_iterator::operator*() const noexcept 200 | { 201 | return *this->value; 202 | } 203 | 204 | constexpr i64 codeunit_sequence_view::const_iterator::operator-(const const_iterator& rhs) const noexcept 205 | { 206 | return this->value - rhs.value; 207 | } 208 | 209 | constexpr codeunit_sequence_view::const_iterator& codeunit_sequence_view::const_iterator::operator+=(const i64 diff) noexcept 210 | { 211 | this->value += diff; 212 | return *this; 213 | } 214 | 215 | constexpr codeunit_sequence_view::const_iterator& codeunit_sequence_view::const_iterator::operator-=(const i64 diff) noexcept 216 | { 217 | this->value -= diff; 218 | return *this; 219 | } 220 | 221 | constexpr codeunit_sequence_view::const_iterator& codeunit_sequence_view::const_iterator::operator+=(const u64 diff) noexcept 222 | { 223 | this->value += diff; 224 | return *this; 225 | } 226 | 227 | constexpr codeunit_sequence_view::const_iterator& codeunit_sequence_view::const_iterator::operator-=(const u64 diff) noexcept 228 | { 229 | this->value -= diff; 230 | return *this; 231 | } 232 | 233 | constexpr codeunit_sequence_view::const_iterator codeunit_sequence_view::const_iterator::operator+(const i64 diff) const noexcept 234 | { 235 | const_iterator tmp = *this; 236 | tmp += diff; 237 | return tmp; 238 | } 239 | 240 | constexpr codeunit_sequence_view::const_iterator codeunit_sequence_view::const_iterator::operator-(const i64 diff) const noexcept 241 | { 242 | const_iterator tmp = *this; 243 | tmp -= diff; 244 | return tmp; 245 | } 246 | 247 | constexpr codeunit_sequence_view::const_iterator codeunit_sequence_view::const_iterator::operator+(const u64 diff) const noexcept 248 | { 249 | const_iterator tmp = *this; 250 | tmp += diff; 251 | return tmp; 252 | } 253 | 254 | constexpr codeunit_sequence_view::const_iterator codeunit_sequence_view::const_iterator::operator-(const u64 diff) const noexcept 255 | { 256 | const_iterator tmp = *this; 257 | tmp -= diff; 258 | return tmp; 259 | } 260 | 261 | constexpr codeunit_sequence_view::const_iterator& codeunit_sequence_view::const_iterator::operator++() noexcept 262 | { 263 | ++this->value; 264 | return *this; 265 | } 266 | 267 | constexpr codeunit_sequence_view::const_iterator codeunit_sequence_view::const_iterator::operator++(int) noexcept 268 | { 269 | const const_iterator tmp = *this; 270 | ++*this; 271 | return tmp; 272 | } 273 | 274 | constexpr codeunit_sequence_view::const_iterator& codeunit_sequence_view::const_iterator::operator--() noexcept 275 | { 276 | --this->value; 277 | return *this; 278 | } 279 | 280 | constexpr codeunit_sequence_view::const_iterator codeunit_sequence_view::const_iterator::operator--(int) noexcept 281 | { 282 | const const_iterator tmp = *this; 283 | --*this; 284 | return tmp; 285 | } 286 | 287 | constexpr bool codeunit_sequence_view::const_iterator::operator==(const const_iterator& rhs) const noexcept 288 | { 289 | return this->value == rhs.value; 290 | } 291 | 292 | constexpr bool codeunit_sequence_view::const_iterator::operator!=(const const_iterator& rhs) const noexcept 293 | { 294 | return !(*this == rhs); 295 | } 296 | 297 | constexpr bool codeunit_sequence_view::const_iterator::operator<(const const_iterator& rhs) const noexcept 298 | { 299 | return this->value < rhs.value; 300 | } 301 | 302 | constexpr bool codeunit_sequence_view::const_iterator::operator>(const const_iterator& rhs) const noexcept 303 | { 304 | return rhs < *this; 305 | } 306 | 307 | constexpr bool codeunit_sequence_view::const_iterator::operator<=(const const_iterator& rhs) const noexcept 308 | { 309 | return rhs >= *this; 310 | } 311 | 312 | constexpr bool codeunit_sequence_view::const_iterator::operator>=(const const_iterator& rhs) const noexcept 313 | { 314 | return !(*this < rhs); 315 | } 316 | 317 | constexpr codeunit_sequence_view::const_iterator codeunit_sequence_view::begin() const noexcept 318 | { 319 | return const_iterator{ this->data_ }; 320 | } 321 | 322 | constexpr codeunit_sequence_view::const_iterator codeunit_sequence_view::end() const noexcept 323 | { 324 | return const_iterator{ this->data_ + this->size_ }; 325 | } 326 | 327 | constexpr codeunit_sequence_view::const_iterator codeunit_sequence_view::cbegin() const noexcept 328 | { 329 | return this->begin(); 330 | } 331 | 332 | constexpr codeunit_sequence_view::const_iterator codeunit_sequence_view::cend() const noexcept 333 | { 334 | return this->end(); 335 | } 336 | 337 | constexpr bool codeunit_sequence_view::operator==(const codeunit_sequence_view& rhs) const noexcept 338 | { 339 | if (this->size() != rhs.size()) 340 | return false; 341 | for(u64 i = 0; i < this->size(); ++i) 342 | if(this->read_at(i) != rhs.read_at(i)) 343 | return false; 344 | return true; 345 | } 346 | 347 | constexpr bool codeunit_sequence_view::operator==(const char* rhs) const noexcept 348 | { 349 | return this->operator==(codeunit_sequence_view(rhs)); 350 | } 351 | 352 | constexpr bool codeunit_sequence_view::operator!=(const codeunit_sequence_view& rhs) const noexcept 353 | { 354 | return !this->operator==(rhs); 355 | } 356 | 357 | constexpr bool codeunit_sequence_view::operator!=(const char* rhs) const noexcept 358 | { 359 | return !this->operator==(rhs); 360 | } 361 | 362 | constexpr u64 codeunit_sequence_view::size() const noexcept 363 | { 364 | return this->size_; 365 | } 366 | 367 | inline const char* codeunit_sequence_view::data() const noexcept 368 | { 369 | return this->data_; 370 | } 371 | 372 | constexpr bool codeunit_sequence_view::is_empty() const noexcept 373 | { 374 | return this->size_ == 0; 375 | } 376 | 377 | constexpr codeunit_sequence_view codeunit_sequence_view::subview(const u64 from, const u64 size) const noexcept 378 | { 379 | const u64 self_size = this->size(); 380 | 381 | if(from >= self_size || size == 0) 382 | return { }; 383 | 384 | const char* first_data = this->data_ + from; 385 | const u64 actual_size = minimum(size, self_size - from); 386 | return { first_data, actual_size }; 387 | } 388 | 389 | constexpr const char& codeunit_sequence_view::read_at(const u64 index) const noexcept 390 | { 391 | return this->data_[index]; 392 | } 393 | 394 | constexpr const char& codeunit_sequence_view::read_from_last(const u64 index) const noexcept 395 | { 396 | return this->data_[this->size_ - index - 1]; 397 | } 398 | 399 | constexpr u64 codeunit_sequence_view::index_of(const codeunit_sequence_view& pattern, const u64 from, const u64 size) const noexcept 400 | { 401 | const codeunit_sequence_view view = this->subview(from, size); 402 | if(view.size() < pattern.size()) 403 | return global_constant::INDEX_INVALID; 404 | const char pattern_last = pattern.read_from_last(0); 405 | u64 skip = 1; 406 | while(pattern.size() > skip && pattern.read_from_last(skip) != pattern_last) 407 | ++skip; 408 | 409 | u64 i = 0; 410 | const u64 endpoint = view.size() - pattern.size() + 1; 411 | while(i < endpoint) 412 | { 413 | while(true) 414 | { 415 | if(view.read_at(i + pattern.size() - 1) == pattern_last) 416 | break; 417 | ++i; 418 | if(i == endpoint) 419 | return global_constant::INDEX_INVALID; 420 | } 421 | if(view.subview(i, pattern.size()) == pattern) 422 | return i + from; 423 | i += skip; 424 | } 425 | return global_constant::INDEX_INVALID; 426 | } 427 | 428 | constexpr u64 codeunit_sequence_view::index_of(const char codeunit, const u64 from, const u64 size) const noexcept 429 | { 430 | if(codeunit == 0) 431 | return global_constant::INDEX_INVALID; 432 | const codeunit_sequence_view view = this->subview(from, size); 433 | for(u64 i = 0; i < view.size(); ++i) 434 | if(view.read_at(i) == codeunit) 435 | return i + from; 436 | return global_constant::INDEX_INVALID; 437 | } 438 | 439 | constexpr u64 codeunit_sequence_view::last_index_of(const codeunit_sequence_view& pattern, const u64 from, const u64 size) const noexcept 440 | { 441 | const codeunit_sequence_view view = this->subview(from, size); 442 | if(pattern.is_empty()) 443 | return global_constant::INDEX_INVALID; 444 | if(view.size() < pattern.size()) 445 | return global_constant::INDEX_INVALID; 446 | 447 | const char pattern_first = pattern.read_at(0); 448 | u64 skip = 1; 449 | while(pattern.size() > skip && pattern.read_at(skip) != pattern_first) 450 | ++skip; 451 | 452 | u64 i = view.size() - pattern.size(); 453 | while(true) 454 | { 455 | while(true) 456 | { 457 | if(view.read_at(i) == pattern_first) 458 | break; 459 | if(i == 0) 460 | return global_constant::INDEX_INVALID; 461 | --i; 462 | } 463 | if(view.subview(i, pattern.size()) == pattern) 464 | return i + from; 465 | if(i < skip) 466 | break; 467 | i -= skip; 468 | } 469 | return global_constant::INDEX_INVALID; 470 | } 471 | 472 | constexpr u64 codeunit_sequence_view::index_of_any(const codeunit_sequence_view& units, const u64 from, const u64 size) const noexcept 473 | { 474 | const u64 actual_size = minimum(this->size() - from, size); 475 | const u64 final_index = from + actual_size; 476 | for(u64 i = from; i < final_index; ++i) 477 | if(const char& unit = this->read_at(i); units.contains(unit)) 478 | return i; 479 | return global_constant::INDEX_INVALID; 480 | } 481 | 482 | constexpr u64 codeunit_sequence_view::last_index_of_any(const codeunit_sequence_view& units, const u64 from, const u64 size) const noexcept 483 | { 484 | const u64 actual_size = minimum(this->size() - from, size); 485 | const u64 final_index = from + actual_size; 486 | for(u64 i = final_index - 1; i >= from; --i) 487 | if(const char& unit = this->read_at(i); units.contains(unit)) 488 | return i; 489 | return global_constant::INDEX_INVALID; 490 | } 491 | 492 | constexpr bool codeunit_sequence_view::contains(const codeunit_sequence_view& pattern) const noexcept 493 | { 494 | return this->index_of(pattern) != global_constant::INDEX_INVALID; 495 | } 496 | 497 | constexpr bool codeunit_sequence_view::contains(const char codeunit) const noexcept 498 | { 499 | return this->index_of(codeunit) != global_constant::INDEX_INVALID; 500 | } 501 | 502 | constexpr std::array codeunit_sequence_view::split(const codeunit_sequence_view& splitter) const noexcept 503 | { 504 | const u64 index = this->index_of(splitter); 505 | if(index == global_constant::INDEX_INVALID) 506 | return { *this, { } }; 507 | const codeunit_sequence_view first = this->subview(0, index); 508 | const codeunit_sequence_view second = this->subview(index + splitter.size()); 509 | return { first, second }; 510 | } 511 | 512 | inline u32 codeunit_sequence_view::split(const codeunit_sequence_view& splitter, std::vector& pieces, const bool cull_empty) const noexcept 513 | { 514 | codeunit_sequence_view view{ *this }; 515 | u32 count = 0; 516 | while(true) 517 | { 518 | const auto [ left, right ] = view.split(splitter); 519 | if(!cull_empty || !left.is_empty()) 520 | pieces.push_back(left); 521 | ++count; 522 | if(right.is_empty()) 523 | break; 524 | view = right; 525 | } 526 | return count; 527 | } 528 | 529 | constexpr u64 codeunit_sequence_view::count(const codeunit_sequence_view& pattern, const u64 from, const u64 size) const noexcept 530 | { 531 | codeunit_sequence_view view = this->subview(from, size); 532 | u64 count = 0; 533 | while(true) 534 | { 535 | const u64 index = view.index_of(pattern); 536 | if(index == global_constant::INDEX_INVALID) 537 | break; 538 | ++count; 539 | view = view.subview(index + pattern.size()); 540 | } 541 | return count; 542 | } 543 | 544 | constexpr bool codeunit_sequence_view::starts_with(const codeunit_sequence_view& prefix) const noexcept 545 | { 546 | if(prefix.is_empty()) 547 | return true; 548 | return this->subview(0, prefix.size()) == prefix; 549 | } 550 | 551 | constexpr bool codeunit_sequence_view::ends_with(const codeunit_sequence_view& suffix) const noexcept 552 | { 553 | if(suffix.is_empty()) 554 | return true; 555 | const u64 end_size = suffix.size(); 556 | const u64 this_size = this->size(); 557 | return this->subview(this_size - end_size, end_size) == suffix; 558 | } 559 | 560 | constexpr codeunit_sequence_view codeunit_sequence_view::remove_prefix(const codeunit_sequence_view& prefix) const noexcept 561 | { 562 | return this->starts_with(prefix) ? this->subview(prefix.size()) : *this; 563 | } 564 | 565 | constexpr codeunit_sequence_view codeunit_sequence_view::remove_suffix(const codeunit_sequence_view& suffix) const noexcept 566 | { 567 | return this->ends_with(suffix) ? this->subview(0, this->size() - suffix.size()) : *this; 568 | } 569 | 570 | constexpr codeunit_sequence_view codeunit_sequence_view::trim_start(const codeunit_sequence_view& units) const noexcept 571 | { 572 | if(this->is_empty()) 573 | return { }; 574 | for(u64 i = 0; i < this->size(); ++i) 575 | if(const char codeunit = this->read_at(i); !units.contains(codeunit)) 576 | return this->subview(i); 577 | return { }; 578 | } 579 | 580 | constexpr codeunit_sequence_view codeunit_sequence_view::trim_end(const codeunit_sequence_view& units) const noexcept 581 | { 582 | if(this->is_empty()) 583 | return { }; 584 | 585 | for(u64 i = this->size(); i > 0; --i) 586 | if(const char codeunit = this->read_at(i - 1); !units.contains(codeunit)) 587 | return this->subview(0, i); 588 | return { }; 589 | } 590 | 591 | constexpr codeunit_sequence_view codeunit_sequence_view::trim(const codeunit_sequence_view& units) const noexcept 592 | { 593 | return this->trim_start(units).trim_end(units); 594 | } 595 | } 596 | 597 | inline namespace literal 598 | { 599 | [[nodiscard]] constexpr ostr::codeunit_sequence_view operator""_cuqv(const char* str, const size_t len) noexcept 600 | { 601 | return { str, len }; 602 | } 603 | } 604 | -------------------------------------------------------------------------------- /include/common/adapters.h: -------------------------------------------------------------------------------- 1 | 2 | #pragma once 3 | 4 | #include 5 | #include 6 | 7 | namespace ostr 8 | { 9 | template 10 | struct allocator 11 | { 12 | static T* allocate_single() noexcept 13 | { 14 | return new T; 15 | } 16 | 17 | static T* allocate_array(const size_t count) noexcept 18 | { 19 | return new T[count]; 20 | } 21 | 22 | static void deallocate_single(const T* ptr) noexcept 23 | { 24 | delete ptr; 25 | } 26 | 27 | static void deallocate_array(const T* ptr) noexcept 28 | { 29 | delete[] ptr; 30 | } 31 | 32 | template 33 | static void placement_construct(void* ptr, Args&&...args) noexcept 34 | { 35 | new(ptr) T{ std::forward(args)... }; 36 | } 37 | }; 38 | } 39 | -------------------------------------------------------------------------------- /include/common/assertion.h: -------------------------------------------------------------------------------- 1 | 2 | #pragma once 3 | 4 | #include "platforms.h" 5 | #include "common/definitions.h" 6 | #include "format.h" -------------------------------------------------------------------------------- /include/common/basic_types.h: -------------------------------------------------------------------------------- 1 | 2 | #pragma once 3 | #include 4 | 5 | namespace ostr 6 | { 7 | using i8 = int8_t; 8 | using u8 = uint8_t; 9 | using i16 = int16_t; 10 | using u16 = uint16_t; 11 | using i32 = int32_t; 12 | using u32 = uint32_t; 13 | using i64 = int64_t; 14 | using u64 = uint64_t; 15 | 16 | using f32 = float; 17 | using f64 = double; 18 | 19 | using byte = u8; 20 | } 21 | 22 | inline namespace literal 23 | { 24 | [[nodiscard]] constexpr char operator""_as_char(const unsigned long long value) noexcept 25 | { 26 | return static_cast(value); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /include/common/constants.h: -------------------------------------------------------------------------------- 1 | 2 | #pragma once 3 | 4 | #include "basic_types.h" 5 | #include 6 | 7 | namespace ostr::global_constant 8 | { 9 | static constexpr u64 INDEX_INVALID = std::numeric_limits::max(); 10 | static constexpr u64 SIZE_INVALID = std::numeric_limits::max(); 11 | 12 | static constexpr u64 TOLERANCE_EXPONENT = 3; 13 | static constexpr f64 TOLERANCE = 1e-3; 14 | 15 | static constexpr f64 GOLDEN_RATIO = 1.61803398874989484820458683436563811772030917980576286213544862270526046281890; 16 | } 17 | -------------------------------------------------------------------------------- /include/common/definitions.h: -------------------------------------------------------------------------------- 1 | 2 | #pragma once 3 | 4 | #ifndef OPEN_STRING_API 5 | #define OPEN_STRING_API 6 | #endif 7 | 8 | #ifndef OPEN_STRING_CODE_BLOCK 9 | #define OPEN_STRING_CODE_BLOCK(code) do{code}while(false) 10 | #endif 11 | 12 | #ifndef OPEN_STRING_STRINGIFY 13 | #define OPEN_STRING_STRINGIFY(x) #x 14 | #endif 15 | #ifndef OPEN_STRING_STRINGIFY_EXPANDED 16 | #define OPEN_STRING_STRINGIFY_EXPANDED(x) OPEN_STRING_STRINGIFY(x) 17 | #endif 18 | 19 | #ifndef OPEN_STRING_UNLIKELY 20 | #define OPEN_STRING_UNLIKELY(expression) (!!(expression)) 21 | #endif 22 | 23 | #ifndef OPEN_STRING_CHECK_OR 24 | #define OPEN_STRING_CHECK_OR(result, expression, ...) \ 25 | OPEN_STRING_CODE_BLOCK({ \ 26 | if(OPEN_STRING_UNLIKELY(!(expression))) \ 27 | { \ 28 | OPEN_STRING_PRINT_FORMATTED_DEBUG_MESSAGE("\nCheck failed: {}\n\t[" __FILE__ ":" OPEN_STRING_STRINGIFY_EXPANDED(__LINE__) "] " OPEN_STRING_STRINGIFY(expression) "\n", __VA_ARGS__ ); \ 29 | OPEN_STRING_DEBUG_BREAK(); \ 30 | result; \ 31 | } \ 32 | }) 33 | #endif 34 | 35 | #ifndef OPEN_STRING_CHECK 36 | #define OPEN_STRING_CHECK(expression, ...) OPEN_STRING_CHECK_OR(/* Do nothing */, expression, __VA_ARGS__) 37 | #endif 38 | 39 | #ifndef OPEN_STRING_RESULT_CHECK 40 | #define OPEN_STRING_RESULT_CHECK(result) OPEN_STRING_CODE_BLOCK( \ 41 | HRESULT result__{ result }; \ 42 | OPEN_STRING_CHECK(SUCCEEDED(result__), "Result check failed! {}", _com_error{ result__ }.ErrorMessage()); \ 43 | ) 44 | #endif -------------------------------------------------------------------------------- /include/common/functions.h: -------------------------------------------------------------------------------- 1 | 2 | #pragma once 3 | #include 4 | #include 5 | 6 | namespace ostr 7 | { 8 | [[nodiscard]] constexpr u64 minimum(const u64 a, const u64 b) noexcept 9 | { 10 | return std::min(a, b); 11 | } 12 | 13 | [[nodiscard]] constexpr u64 minimum(const std::initializer_list v) noexcept 14 | { 15 | return std::min(v); 16 | } 17 | 18 | [[nodiscard]] constexpr u64 maximum(const u64 a, const u64 b) noexcept 19 | { 20 | return std::max(a, b); 21 | } 22 | 23 | [[nodiscard]] constexpr u64 maximum(const std::initializer_list v) noexcept 24 | { 25 | return std::max(v); 26 | } 27 | 28 | [[nodiscard]] inline u64 round_ceil(const f64 v) noexcept 29 | { 30 | return static_cast(ceil(v)); 31 | } 32 | 33 | [[nodiscard]] constexpr u64 power(const u64 base, const u64 exponent) noexcept 34 | { 35 | u64 result = 1; 36 | for(u64 i = 0; i < exponent; ++i) 37 | result *= base; 38 | return result; 39 | } 40 | 41 | template 42 | constexpr std::enable_if_t> bitwise_swap(T& a, T& b) noexcept 43 | { 44 | T temp = a; 45 | a = b; 46 | b = temp; 47 | } 48 | 49 | template 50 | std::enable_if_t> bitwise_swap(T& a, T& b) noexcept 51 | { 52 | std::array temp; 53 | std::copy_n(reinterpret_cast(&a), sizeof(T), temp.data()); 54 | std::copy_n(reinterpret_cast(&b), sizeof(T), reinterpret_cast(&a)); 55 | std::copy_n(temp.data(), sizeof(T), reinterpret_cast(&b)); 56 | } 57 | 58 | namespace details 59 | { 60 | [[nodiscard]] constexpr u64 hash_byte_crc64_implementation(const byte b) noexcept 61 | { 62 | u64 result = 0; 63 | u64 c = b; 64 | c <<= 56; 65 | for (u64 j = 0; j < 8; j++) 66 | { 67 | if ((result ^ c) & 0x8000000000000000ull) 68 | { 69 | constexpr u64 crc64_ecma182_poly = 0x42F0E1EBA9EA3693ull; 70 | result = (result << 1) ^ crc64_ecma182_poly; 71 | } 72 | else 73 | { 74 | result <<= 1; 75 | } 76 | c <<= 1; 77 | } 78 | return result; 79 | } 80 | } 81 | 82 | [[nodiscard]] constexpr u64 hash_byte_crc64(const byte b) noexcept 83 | { 84 | constexpr auto h = details::hash_byte_crc64_implementation; 85 | constexpr u64 crc64_table[256] = 86 | { 87 | h(0), h(1), h(2), h(3), h(4), h(5), h(6), h(7), 88 | h(8), h(9), h(10), h(11), h(12), h(13), h(14), h(15), 89 | h(16), h(17), h(18), h(19), h(20), h(21), h(22), h(23), 90 | h(24), h(25), h(26), h(27), h(28), h(29), h(30), h(31), 91 | h(32), h(33), h(34), h(35), h(36), h(37), h(38), h(39), 92 | h(40), h(41), h(42), h(43), h(44), h(45), h(46), h(47), 93 | h(48), h(49), h(50), h(51), h(52), h(53), h(54), h(55), 94 | h(56), h(57), h(58), h(59), h(60), h(61), h(62), h(63), 95 | h(64), h(65), h(66), h(67), h(68), h(69), h(70), h(71), 96 | h(72), h(73), h(74), h(75), h(76), h(77), h(78), h(79), 97 | h(80), h(81), h(82), h(83), h(84), h(85), h(86), h(87), 98 | h(88), h(89), h(90), h(91), h(92), h(93), h(94), h(95), 99 | h(96), h(97), h(98), h(99), h(100), h(101), h(102), h(103), 100 | h(104), h(105), h(106), h(107), h(108), h(109), h(110), h(111), 101 | h(112), h(113), h(114), h(115), h(116), h(117), h(118), h(119), 102 | h(120), h(121), h(122), h(123), h(124), h(125), h(126), h(127), 103 | h(128), h(129), h(130), h(131), h(132), h(133), h(134), h(135), 104 | h(136), h(137), h(138), h(139), h(140), h(141), h(142), h(143), 105 | h(144), h(145), h(146), h(147), h(148), h(149), h(150), h(151), 106 | h(152), h(153), h(154), h(155), h(156), h(157), h(158), h(159), 107 | h(160), h(161), h(162), h(163), h(164), h(165), h(166), h(167), 108 | h(168), h(169), h(170), h(171), h(172), h(173), h(174), h(175), 109 | h(176), h(177), h(178), h(179), h(180), h(181), h(182), h(183), 110 | h(184), h(185), h(186), h(187), h(188), h(189), h(190), h(191), 111 | h(192), h(193), h(194), h(195), h(196), h(197), h(198), h(199), 112 | h(200), h(201), h(202), h(203), h(204), h(205), h(206), h(207), 113 | h(208), h(209), h(210), h(211), h(212), h(213), h(214), h(215), 114 | h(216), h(217), h(218), h(219), h(220), h(221), h(222), h(223), 115 | h(224), h(225), h(226), h(227), h(228), h(229), h(230), h(231), 116 | h(232), h(233), h(234), h(235), h(236), h(237), h(238), h(239), 117 | h(240), h(241), h(242), h(243), h(244), h(245), h(246), h(247), 118 | h(248), h(249), h(250), h(251), h(252), h(253), h(254), h(255) 119 | }; 120 | return crc64_table[b]; 121 | } 122 | 123 | template 124 | [[nodiscard]] constexpr u64 hash_sequence_crc64(const T* data, const u64 length, const u64 seed = 0) noexcept 125 | { 126 | static_assert(sizeof(T) == 1, "CRC64 hash only supports byte-sized types."); 127 | 128 | u64 ans = seed; 129 | for (u64 i = 0; i < length; ++i) 130 | { 131 | const T& value = data[i]; 132 | const byte hash_byte = ((ans >> 56) ^ value) & 0xFF; 133 | const u64 hash_next = hash_byte_crc64(hash_byte); 134 | ans = hash_next ^ (ans << 8); 135 | } 136 | return ans; 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /include/common/linear_iterator.h: -------------------------------------------------------------------------------- 1 | 2 | #pragma once 3 | 4 | #include "common/basic_types.h" 5 | 6 | namespace ostr 7 | { 8 | template 9 | struct linear_iterator 10 | { 11 | constexpr linear_iterator() noexcept = default; 12 | 13 | constexpr explicit linear_iterator(T* v) noexcept 14 | : data_{ v } 15 | { } 16 | 17 | [[nodiscard]] constexpr T* data() const noexcept 18 | { 19 | return this->data_; 20 | } 21 | 22 | [[nodiscard]] constexpr T& operator*() const noexcept 23 | { 24 | return *this->data_; 25 | } 26 | 27 | constexpr linear_iterator& operator+=(const i64 diff) noexcept 28 | { 29 | this->data_ += diff; 30 | return *this; 31 | } 32 | 33 | constexpr linear_iterator& operator-=(const i64 diff) noexcept 34 | { 35 | this->data_ -= diff; 36 | return *this; 37 | } 38 | 39 | constexpr linear_iterator& operator+=(const u64 diff) noexcept 40 | { 41 | this->data_ += diff; 42 | return *this; 43 | } 44 | 45 | constexpr linear_iterator& operator-=(const u64 diff) noexcept 46 | { 47 | this->data_ -= diff; 48 | return *this; 49 | } 50 | 51 | [[nodiscard]] constexpr linear_iterator operator+(const i64 diff) const noexcept 52 | { 53 | linear_iterator tmp = *this; 54 | tmp += diff; 55 | return tmp; 56 | } 57 | 58 | [[nodiscard]] constexpr linear_iterator operator-(const i64 diff) const noexcept 59 | { 60 | linear_iterator tmp = *this; 61 | tmp -= diff; 62 | return tmp; 63 | } 64 | 65 | [[nodiscard]] constexpr linear_iterator operator+(const u64 diff) const noexcept 66 | { 67 | linear_iterator tmp = *this; 68 | tmp += diff; 69 | return tmp; 70 | } 71 | 72 | [[nodiscard]] constexpr linear_iterator operator-(const u64 diff) const noexcept 73 | { 74 | linear_iterator tmp = *this; 75 | tmp -= diff; 76 | return tmp; 77 | } 78 | 79 | constexpr linear_iterator& operator++() noexcept 80 | { 81 | ++this->data_; 82 | return *this; 83 | } 84 | 85 | constexpr linear_iterator operator++(int) noexcept 86 | { 87 | const linear_iterator tmp = *this; 88 | ++*this; 89 | return tmp; 90 | } 91 | 92 | constexpr linear_iterator& operator--() noexcept 93 | { 94 | --this->data_; 95 | return *this; 96 | } 97 | 98 | constexpr linear_iterator operator--(int) noexcept 99 | { 100 | const linear_iterator tmp = *this; 101 | --*this; 102 | return tmp; 103 | } 104 | 105 | private: 106 | T* data_; 107 | }; 108 | 109 | template 110 | [[nodiscard]] constexpr i64 operator-(const linear_iterator& lhs, const linear_iterator& rhs) noexcept 111 | { 112 | return lhs.data() - rhs.data(); 113 | } 114 | 115 | template 116 | [[nodiscard]] constexpr bool operator==(const linear_iterator& lhs, const linear_iterator& rhs) noexcept 117 | { 118 | return lhs.data() == rhs.data(); 119 | } 120 | 121 | template 122 | [[nodiscard]] constexpr bool operator!=(const linear_iterator& lhs, const linear_iterator& rhs) noexcept 123 | { 124 | return !(lhs == rhs); 125 | } 126 | 127 | template 128 | [[nodiscard]] constexpr bool operator<(const linear_iterator& lhs, const linear_iterator& rhs) noexcept 129 | { 130 | return lhs.data() < rhs.data(); 131 | } 132 | 133 | template 134 | [[nodiscard]] constexpr bool operator>(const linear_iterator& lhs, const linear_iterator& rhs) noexcept 135 | { 136 | return rhs < lhs; 137 | } 138 | 139 | template 140 | [[nodiscard]] constexpr bool operator<=(const linear_iterator& lhs, const linear_iterator& rhs) noexcept 141 | { 142 | return rhs >= lhs; 143 | } 144 | 145 | template 146 | [[nodiscard]] constexpr bool operator>=(const linear_iterator& lhs, const linear_iterator& rhs) noexcept 147 | { 148 | return !(lhs < rhs); 149 | } 150 | } 151 | -------------------------------------------------------------------------------- /include/common/platforms.h: -------------------------------------------------------------------------------- 1 | 2 | #pragma once 3 | 4 | #if _WIN64 5 | 6 | #ifndef WIN32_LEAN_AND_MEAN 7 | #define WIN32_LEAN_AND_MEAN 8 | #endif 9 | #ifndef NOMINMAX 10 | #define NOMINMAX 11 | #endif 12 | 13 | #include 14 | #include 15 | #include 16 | 17 | #define OPEN_STRING_PRINT_SIMPLE_DEBUG_MESSAGE(...) \ 18 | { \ 19 | [](const char* message_format, auto&&...args) \ 20 | { \ 21 | const ostr::codeunit_sequence message = ostr::format(message_format, args...); \ 22 | OutputDebugStringA(message.c_str()); \ 23 | }(__VA_ARGS__); \ 24 | } \ 25 | 26 | #define OPEN_STRING_PRINT_FORMATTED_DEBUG_MESSAGE(...) \ 27 | { \ 28 | [](const char* debug_format, const char* message_format, auto&&...args) \ 29 | { \ 30 | const ostr::codeunit_sequence message = ostr::format(message_format, args...); \ 31 | OutputDebugStringA(ostr::format(debug_format, message).c_str()); \ 32 | }(__VA_ARGS__); \ 33 | } \ 34 | 35 | // Visual Studio will not trigger the breakpoint during single-step debugging without __nop() 36 | #define OPEN_STRING_DEBUG_BREAK() (__nop(), __debugbreak()) 37 | 38 | #elif defined(__linux__) || defined(__MACH__) 39 | 40 | #define OPEN_STRING_PRINT_SIMPLE_DEBUG_MESSAGE(...) 41 | #define OPEN_STRING_PRINT_FORMATTED_DEBUG_MESSAGE(...) 42 | #define OPEN_STRING_DEBUG_BREAK() 43 | 44 | #endif 45 | -------------------------------------------------------------------------------- /include/common/sequence.h: -------------------------------------------------------------------------------- 1 | 2 | #pragma once 3 | #include 4 | #include "common/adapters.h" 5 | #include "common/basic_types.h" 6 | #include "common/assertion.h" 7 | #include "common/functions.h" 8 | #include "common/linear_iterator.h" 9 | 10 | namespace ostr 11 | { 12 | template 13 | struct OPEN_STRING_API sequence 14 | { 15 | public: 16 | sequence() noexcept; 17 | 18 | template 19 | explicit sequence(const std::array& array) noexcept; 20 | 21 | template 22 | explicit sequence(std::array&& array) noexcept; 23 | 24 | sequence(std::initializer_list list) noexcept; 25 | 26 | // Copy constructor is not allowed because element inside maybe not copyable. 27 | // TODO: Maybe we can use std::is_copy_constructible_v to check. 28 | sequence(const sequence& other) noexcept = delete; 29 | sequence(sequence&& other) noexcept; 30 | 31 | sequence& operator=(const sequence& other) noexcept = delete; 32 | 33 | // The purpose of this function is not clear. 34 | // Do you want to move the memory ownership from other to this? 35 | // Or do you want to just copy the data from other to this? 36 | // TODO: Two functions for two purposed. 37 | sequence& operator=(sequence&& other) noexcept = delete; 38 | 39 | ~sequence() noexcept; 40 | 41 | using const_iterator = linear_iterator; 42 | using iterator = linear_iterator; 43 | 44 | [[nodiscard]] const_iterator cbegin() const noexcept; 45 | [[nodiscard]] const_iterator cend() const noexcept; 46 | [[nodiscard]] iterator begin() noexcept; 47 | [[nodiscard]] iterator end() noexcept; 48 | [[nodiscard]] const_iterator begin() const noexcept; 49 | [[nodiscard]] const_iterator end() const noexcept; 50 | 51 | [[nodiscard]] bool is_empty() const noexcept; 52 | 53 | [[nodiscard]] bool operator==(const sequence& other) const noexcept; 54 | 55 | [[nodiscard]] u64 size() const noexcept; 56 | [[nodiscard]] u64 capacity() const noexcept; 57 | 58 | [[nodiscard]] T* data() noexcept; 59 | [[nodiscard]] const T* data() const noexcept; 60 | 61 | void empty() noexcept; 62 | void empty(u64 size) noexcept; 63 | 64 | [[nodiscard]] const T& read_at(const u64 index) const noexcept; 65 | [[nodiscard]] const T& read_from_last(const u64 index = 0) const noexcept; 66 | [[nodiscard]] T& access_at(const u64 index) noexcept; 67 | [[nodiscard]] T& access_from_last(const u64 index = 0) noexcept; 68 | 69 | void reserve(const u64 size) noexcept; 70 | 71 | void push_back_uninitialized(const u64 size = 1) noexcept; 72 | 73 | void resize_uninitialized(const u64 size) noexcept; 74 | 75 | template 76 | void resize(const u64 size, Args&&...args) noexcept; 77 | 78 | void push_back(T element) noexcept; 79 | 80 | template 81 | void emplace_back(Args&&...args) noexcept; 82 | 83 | template 84 | T1 pop_back() noexcept; 85 | 86 | void append(const T* const source, const u64 size) noexcept; 87 | 88 | private: 89 | 90 | [[nodiscard]] bool is_short() const noexcept; 91 | 92 | [[nodiscard]] T* data_at(u64 index) noexcept; 93 | [[nodiscard]] const T* data_at(u64 index) const noexcept; 94 | 95 | static constexpr u64 POINTER_SIZE = sizeof(void*); 96 | static constexpr u64 ELEMENT_SIZE = sizeof(T); 97 | static constexpr u64 ELEMENT_SHORT_CAPACITY = maximum({ POINTER_SIZE / ELEMENT_SIZE, I }); 98 | static constexpr u64 STORAGE_SHORT_CAPACITY = maximum({ ELEMENT_SHORT_CAPACITY, 1 }) * ELEMENT_SIZE; 99 | 100 | struct short_sequence_storage 101 | { 102 | std::array stack_storage; 103 | }; 104 | 105 | struct large_sequence_storage 106 | { 107 | T* heap_storage; 108 | }; 109 | 110 | [[nodiscard]] short_sequence_storage& as_small() noexcept 111 | { 112 | return reinterpret_cast(this->storage_); 113 | } 114 | 115 | [[nodiscard]] const short_sequence_storage& as_small() const noexcept 116 | { 117 | return reinterpret_cast(this->storage_); 118 | } 119 | 120 | [[nodiscard]] const large_sequence_storage& as_large() const noexcept 121 | { 122 | return reinterpret_cast(this->storage_); 123 | } 124 | 125 | [[nodiscard]] large_sequence_storage& as_large() noexcept 126 | { 127 | return reinterpret_cast(this->storage_); 128 | } 129 | 130 | struct storage 131 | { 132 | std::array data; 133 | 134 | [[nodiscard]] byte* raw_data() noexcept 135 | { 136 | return *reinterpret_cast(this->data.data()); 137 | } 138 | }; 139 | 140 | private: 141 | u64 capacity_{ ELEMENT_SHORT_CAPACITY }; 142 | u64 size_{ 0 }; 143 | storage storage_{ }; 144 | }; 145 | 146 | template 147 | sequence::sequence() noexcept = default; 148 | 149 | template 150 | template 151 | sequence::sequence(const std::array& array) noexcept 152 | { 153 | this->reserve(array.size()); 154 | std::copy_n(array.data(), array.size(), this->data()); 155 | this->size_ = array.size(); 156 | } 157 | 158 | template 159 | template 160 | sequence::sequence(std::array&& array) noexcept 161 | { 162 | this->reserve(array.size()); 163 | std::move(array.data(), array.end(), this->data()); 164 | this->size_ = array.size(); 165 | } 166 | 167 | template 168 | sequence::sequence(std::initializer_list list) noexcept 169 | { 170 | this->reserve(list.size()); 171 | std::copy_n(list.begin(), list.size(), this->data()); 172 | this->size_ = list.size(); 173 | } 174 | 175 | template 176 | sequence::sequence(sequence&& other) noexcept 177 | : capacity_{ other.capacity_ } 178 | , size_{ other.size_ } 179 | , storage_{ std::move(other.storage_) } 180 | { 181 | other.capacity_ = ELEMENT_SHORT_CAPACITY; 182 | other.size_ = 0; 183 | } 184 | 185 | template 186 | sequence::~sequence() noexcept 187 | { 188 | for(u64 i = 0; i < this->size(); ++i) 189 | std::destroy_at(this->data_at(i)); 190 | this->size_ = 0; 191 | if(!this->is_short()) 192 | { 193 | const byte* data = reinterpret_cast(this->as_large().heap_storage); 194 | allocator::deallocate_array(data); 195 | } 196 | } 197 | 198 | template 199 | typename sequence::const_iterator sequence::cbegin() const noexcept 200 | { 201 | return const_iterator{ this->data() }; 202 | } 203 | 204 | template 205 | typename sequence::const_iterator sequence::cend() const noexcept 206 | { 207 | return const_iterator{ this->data_at(this->size_) }; 208 | } 209 | 210 | template 211 | typename sequence::iterator sequence::begin() noexcept 212 | { 213 | return iterator{ this->data() }; 214 | } 215 | 216 | template 217 | typename sequence::iterator sequence::end() noexcept 218 | { 219 | return iterator{ this->data_at(this->size_) }; 220 | } 221 | 222 | template 223 | typename sequence::const_iterator sequence::begin() const noexcept 224 | { 225 | return this->cbegin(); 226 | } 227 | 228 | template 229 | typename sequence::const_iterator sequence::end() const noexcept 230 | { 231 | return this->cend(); 232 | } 233 | 234 | template 235 | bool sequence::is_empty() const noexcept 236 | { 237 | return this->size_ == 0; 238 | } 239 | 240 | template 241 | bool sequence::operator==(const sequence& other) const noexcept 242 | { 243 | if(this->size() != other.size()) 244 | return false; 245 | for(u64 i = 0; i < this->size(); ++i) 246 | if(this->data_at(i) != other.data_at(i)) 247 | return false; 248 | return true; 249 | } 250 | 251 | template 252 | u64 sequence::size() const noexcept 253 | { 254 | return this->size_; 255 | } 256 | 257 | template 258 | u64 sequence::capacity() const noexcept 259 | { 260 | return this->capacity_; 261 | } 262 | 263 | template 264 | T* sequence::data() noexcept 265 | { 266 | return this->is_short() ? this->as_small().stack_storage.data() : this->as_large().heap_storage; 267 | } 268 | 269 | template 270 | const T* sequence::data() const noexcept 271 | { 272 | return this->is_short() ? this->as_small().stack_storage.data() : this->as_large().heap_storage; 273 | } 274 | 275 | template 276 | void sequence::empty() noexcept 277 | { 278 | for(T& e : *this) 279 | std::destroy_at(&e); 280 | this->size_ = 0; 281 | } 282 | 283 | template 284 | void sequence::empty(u64 size) noexcept 285 | { 286 | this->empty(); 287 | if(size >= this->capacity_) 288 | { 289 | T* allocated = allocator::allocate_array(size); 290 | if(!this->is_short()) 291 | allocator::deallocate_array(this->data()); 292 | this->as_large().heap_storage = allocated; 293 | this->capacity_ = size; 294 | } 295 | } 296 | 297 | template 298 | const T& sequence::read_at(const u64 index) const noexcept 299 | { 300 | OPEN_STRING_CHECK(index < this->size_, "index out of range"); 301 | return *(this->data_at(index)); 302 | } 303 | 304 | template 305 | const T& sequence::read_from_last(const u64 index) const noexcept 306 | { 307 | OPEN_STRING_CHECK(index < this->size_, "index out of range"); 308 | return this->read_at(this->size_ - index - 1); 309 | } 310 | 311 | template 312 | T& sequence::access_at(const u64 index) noexcept 313 | { 314 | OPEN_STRING_CHECK(index < this->size_, "index out of range"); 315 | return *(this->data_at(index)); 316 | } 317 | 318 | template 319 | T& sequence::access_from_last(const u64 index) noexcept 320 | { 321 | OPEN_STRING_CHECK(index < this->size_, "index out of range"); 322 | return this->access_at(this->size_ - index - 1); 323 | } 324 | 325 | template 326 | void sequence::reserve(const u64 size) noexcept 327 | { 328 | if(size <= this->capacity_) 329 | return; 330 | byte* allocated_byte = allocator::allocate_array(size * ELEMENT_SIZE); 331 | T* old_data = this->data(); 332 | const auto source = reinterpret_cast(old_data); 333 | if(!this->is_empty()) 334 | { 335 | const auto source_end = reinterpret_cast(old_data + this->size_); 336 | std::move(source, source_end, allocated_byte); 337 | } 338 | if(!this->is_short()) 339 | allocator::deallocate_array(source); 340 | this->as_large().heap_storage = reinterpret_cast(allocated_byte); 341 | this->capacity_ = size; 342 | } 343 | 344 | template 345 | void sequence::push_back_uninitialized(const u64 size) noexcept 346 | { 347 | const u64 size_required = this->size_ + size; 348 | if(size_required > this->capacity_) 349 | { 350 | // Golden ratio is a good growth factor. 351 | // reference: https://stackoverflow.com/a/5232342 352 | // " I have an intuition (that is currently not backed up by any hard data) 353 | // that a growth rate in line with the golden ratio 354 | // (because of its relationship to the fibonacci sequence) 355 | // will prove to be the most efficient growth rate for real-world loads 356 | // in terms of minimizing both extra space used and time. " 357 | u64 grown_capacity = round_ceil(static_cast(maximum({ this->capacity_, 1 })) * global_constant::GOLDEN_RATIO); 358 | while(grown_capacity < size_required) 359 | grown_capacity = round_ceil(static_cast(grown_capacity) * global_constant::GOLDEN_RATIO); 360 | this->reserve(grown_capacity); 361 | } 362 | this->size_ = size_required; 363 | } 364 | 365 | template 366 | void sequence::resize_uninitialized(const u64 size) noexcept 367 | { 368 | if(const u64 current_size = this->size(); size > current_size) 369 | { 370 | this->push_back_uninitialized(size - current_size); 371 | } 372 | else 373 | { 374 | for(u64 i = size; i < current_size; ++i) 375 | std::destroy_at(this->data_at(i)); 376 | this->size_ = size; 377 | } 378 | } 379 | 380 | template 381 | template 382 | void sequence::resize(const u64 size, Args&&... args) noexcept 383 | { 384 | const u64 origin_size = this->size(); 385 | this->resize_uninitialized(size); 386 | for(u64 i = origin_size; i < size; ++i) 387 | allocator::placement_construct(this->data_at(i), std::forward(args)...); 388 | } 389 | 390 | template 391 | void sequence::push_back(T element) noexcept 392 | { 393 | this->emplace_back(std::move(element)); 394 | } 395 | 396 | template 397 | template 398 | void sequence::emplace_back(Args&&... args) noexcept 399 | { 400 | this->push_back_uninitialized(); 401 | T* const emplace_target = this->data_at(this->size_ - 1); 402 | allocator::placement_construct(emplace_target, std::forward(args)...); 403 | } 404 | 405 | template 406 | template 407 | T1 sequence::pop_back() noexcept 408 | { 409 | OPEN_STRING_CHECK(this->size_ != 0, "Pop failed! Sequence is empty!"); 410 | T result = std::move(this->access_at(this->size_ - 1)); 411 | std::destroy_at(this->data_at(this->size_ - 1)); 412 | --this->size_; 413 | return result; 414 | } 415 | 416 | template 417 | void sequence::append(const T* const source, const u64 size) noexcept 418 | { 419 | this->push_back_uninitialized(size); 420 | std::copy_n(source, size, this->data_at(this->size_ - size)); 421 | } 422 | 423 | template 424 | bool sequence::is_short() const noexcept 425 | { 426 | return this->capacity_ == ELEMENT_SHORT_CAPACITY; 427 | } 428 | 429 | template 430 | T* sequence::data_at(u64 index) noexcept 431 | { 432 | return this->data() + index; 433 | } 434 | 435 | template 436 | const T* sequence::data_at(u64 index) const noexcept 437 | { 438 | return this->data() + index; 439 | } 440 | } 441 | -------------------------------------------------------------------------------- /include/format.h: -------------------------------------------------------------------------------- 1 | 2 | #pragma once 3 | 4 | #include 5 | #include 6 | #include 7 | #include "common/platforms.h" 8 | #include "common/definitions.h" 9 | #include "codeunit_sequence.h" 10 | 11 | namespace ostr 12 | { 13 | namespace details 14 | { 15 | codeunit_sequence OPEN_STRING_API format_integer(const u64& value, const codeunit_sequence_view& specification); 16 | codeunit_sequence OPEN_STRING_API format_integer(const i64& value, const codeunit_sequence_view& specification); 17 | codeunit_sequence OPEN_STRING_API format_float(const f64& value, const codeunit_sequence_view& specification); 18 | } 19 | 20 | template 21 | [[nodiscard]] codeunit_sequence format(const Format& format_mold_literal, const Args&...args); 22 | 23 | template 24 | struct argument_formatter 25 | { 26 | static codeunit_sequence produce(const T& value, const codeunit_sequence_view& specification) 27 | { 28 | constexpr u64 size = sizeof(T); 29 | const auto reader = reinterpret_cast(&value); 30 | codeunit_sequence raw; 31 | for(u64 i = 0; i < size; ++i) 32 | { 33 | if(!raw.is_empty()) 34 | raw += " "_cuqv; 35 | const byte memory = reader[i]; 36 | raw += details::format_integer(static_cast(memory), "02x"_cuqv); 37 | } 38 | 39 | if(specification == "r"_cuqv) // output raw memory bytes 40 | return raw; 41 | 42 | OPEN_STRING_CHECK(false, "Undefined format with raw memory bytes:{}!", raw); 43 | return { }; 44 | } 45 | }; 46 | 47 | namespace details 48 | { 49 | class format_mold_view 50 | { 51 | public: 52 | // code-region-start: constructors 53 | 54 | explicit constexpr format_mold_view(codeunit_sequence_view format_mold) noexcept 55 | : format_mold_(std::move(format_mold)) 56 | { } 57 | 58 | // code-region-end: constructors 59 | 60 | // code-region-start: iterators 61 | 62 | enum class run_type : u8 63 | { 64 | plain_text, 65 | escaped_brace, 66 | formatter, 67 | ending 68 | }; 69 | 70 | struct const_iterator 71 | { 72 | constexpr const_iterator() noexcept = default; 73 | 74 | explicit constexpr const_iterator(codeunit_sequence_view v) noexcept 75 | : format_mold(std::move(v)) 76 | { } 77 | 78 | constexpr const_iterator& initialize() noexcept 79 | { 80 | const auto [ first_type, first_from, first_size ] = this->parse_run(0); 81 | this->type = first_type; 82 | this->from = first_from; 83 | this->size = first_size; 84 | return *this; 85 | } 86 | 87 | [[nodiscard]] constexpr std::tuple operator*() const noexcept 88 | { 89 | return { this->type, this->format_mold.subview(this->from, this->size) }; 90 | } 91 | 92 | constexpr const_iterator& operator++() 93 | { 94 | if(this->from == global_constant::INDEX_INVALID) 95 | { 96 | this->type = run_type::ending; 97 | this->from = global_constant::INDEX_INVALID; 98 | this->size = SIZE_MAX; 99 | return *this; 100 | } 101 | const auto [ next_type, next_from, next_size ] = this->parse_run(this->from + this->size); 102 | this->type = next_type; 103 | this->from = next_from; 104 | this->size = next_size; 105 | return *this; 106 | } 107 | 108 | constexpr const_iterator operator++(int) noexcept 109 | { 110 | const_iterator tmp = *this; 111 | ++*this; 112 | return tmp; 113 | } 114 | 115 | [[nodiscard]] constexpr bool operator==(const const_iterator& rhs) const noexcept 116 | { 117 | return this->type == rhs.type && this->from == rhs.from && this->size == rhs.size; 118 | } 119 | 120 | [[nodiscard]] constexpr bool operator!=(const const_iterator& rhs) const noexcept 121 | { 122 | return !(*this == rhs); 123 | } 124 | 125 | struct result_parse_run 126 | { 127 | run_type type = run_type::plain_text; 128 | u64 from = global_constant::INDEX_INVALID; 129 | u64 size = SIZE_MAX; 130 | }; 131 | [[nodiscard]] constexpr result_parse_run parse_run(const u64 from_index) const 132 | { 133 | const u64 index = this->format_mold.index_of_any("{}"_cuqv, from_index); 134 | if(index == global_constant::INDEX_INVALID) 135 | { 136 | if(from_index == this->format_mold.size()) 137 | return { run_type::ending }; 138 | return { run_type::plain_text, from_index, this->format_mold.size() - from_index }; 139 | } 140 | if(from_index != index) 141 | return { run_type::plain_text, from_index, index - from_index }; 142 | if(format_mold.read_at(index) == format_mold.read_at(index + 1)) 143 | return { run_type::escaped_brace, index, 2 }; 144 | OPEN_STRING_CHECK(format_mold.read_at(index) != '}', "Unclosed right brace is not allowed!"); 145 | const u64 index_next = this->format_mold.index_of_any("{}"_cuqv, index + 1); 146 | OPEN_STRING_CHECK(index_next != global_constant::INDEX_INVALID && format_mold.read_at(index_next) != '{', "Unclosed left brace is not allowed!"); 147 | return { run_type::formatter, index, index_next - index + 1 }; 148 | } 149 | 150 | codeunit_sequence_view format_mold; 151 | run_type type = run_type::ending; 152 | u64 from = global_constant::INDEX_INVALID; 153 | u64 size = SIZE_MAX; 154 | }; 155 | 156 | [[nodiscard]] constexpr const_iterator begin() const noexcept 157 | { 158 | return const_iterator{ this->format_mold_ }.initialize(); 159 | } 160 | 161 | [[nodiscard]] constexpr const_iterator end() const noexcept 162 | { 163 | return const_iterator{ this->format_mold_ }; 164 | } 165 | 166 | [[nodiscard]] constexpr const_iterator cbegin() const noexcept 167 | { 168 | return this->begin(); 169 | } 170 | 171 | [[nodiscard]] constexpr const_iterator cend() const noexcept 172 | { 173 | return this->end(); 174 | } 175 | 176 | // code-region-end: iterators 177 | 178 | private: 179 | codeunit_sequence_view format_mold_{ }; 180 | }; 181 | 182 | template 183 | codeunit_sequence format_argument_value(const void* value, const codeunit_sequence_view specification) 184 | { 185 | return argument_formatter::produce(*static_cast(value), specification); 186 | } 187 | 188 | struct argument_value_package 189 | { 190 | using argument_value_formatter_type = codeunit_sequence(*)(const void* value, codeunit_sequence_view specification); 191 | 192 | const void* value; 193 | argument_value_formatter_type argument_value_formatter; 194 | 195 | template 196 | explicit constexpr argument_value_package(const T& v) noexcept 197 | : value(static_cast(&v)) 198 | , argument_value_formatter(format_argument_value) 199 | { } 200 | 201 | [[nodiscard]] codeunit_sequence produce(const codeunit_sequence_view& specification) const 202 | { 203 | return this->argument_value_formatter(this->value, specification); 204 | } 205 | }; 206 | 207 | template 208 | codeunit_sequence produce_format(const format_mold_view& format_mold, const Args&...args) 209 | { 210 | constexpr u64 argument_count = sizeof...(Args); 211 | const std::array arguments {{ argument_value_package{ args } ... }}; 212 | 213 | enum class indexing_type : u8 214 | { 215 | unknown, 216 | manual, 217 | automatic 218 | }; 219 | indexing_type index_type = indexing_type::unknown; 220 | u64 next_index = 0; 221 | codeunit_sequence result; 222 | for(const auto [ type, run ] : format_mold) 223 | { 224 | switch (type) 225 | { 226 | case format_mold_view::run_type::plain_text: 227 | result += run; 228 | break; 229 | case format_mold_view::run_type::escaped_brace: 230 | result += run.read_at(0); 231 | break; 232 | case format_mold_view::run_type::formatter: 233 | { 234 | const auto [ index_run, specification ] = run.subview(1, run.size() - 2).split(":"_cuqv); 235 | u64 current_index = next_index; 236 | if (index_run.is_empty()) 237 | { 238 | OPEN_STRING_CHECK(index_type != indexing_type::manual, "Manual index is not allowed mixing with automatic index!"); 239 | index_type = indexing_type::automatic; 240 | ++next_index; 241 | } 242 | else 243 | { 244 | OPEN_STRING_CHECK(index_type != indexing_type::automatic, "Automatic index is not allowed mixing with manual index!"); 245 | index_type = indexing_type::manual; 246 | [[maybe_unused]] const auto [ last, error ] = 247 | std::from_chars(index_run.data(), index_run.cend().data(), current_index); 248 | OPEN_STRING_CHECK(last == index_run.cend().data(), "Invalid format index [{}]!", index_run); 249 | } 250 | OPEN_STRING_CHECK(current_index < argument_count, "Invalid format index [{}]: Index should be less than count of argument [{}]!", current_index, argument_count); 251 | result += arguments[current_index].produce(specification); 252 | } 253 | break; 254 | case format_mold_view::run_type::ending: 255 | // Unreachable 256 | break; 257 | } 258 | } 259 | return result; 260 | } 261 | } 262 | 263 | template 264 | [[nodiscard]] codeunit_sequence format(const Format& format_mold_literal, const Args&...args) 265 | { 266 | return details::produce_format(details::format_mold_view{ details::view_sequence(format_mold_literal) }, args...); 267 | } 268 | 269 | // code-region-start: formatter specializations for built-in types 270 | 271 | template 272 | struct argument_formatter && std::is_signed_v>> 273 | { 274 | static codeunit_sequence produce(const T& value, const codeunit_sequence_view& specification) 275 | { 276 | return details::format_integer(static_cast(value), specification); 277 | } 278 | }; 279 | 280 | template 281 | struct argument_formatter && std::is_unsigned_v>> 282 | { 283 | static codeunit_sequence produce(const T& value, const codeunit_sequence_view& specification) 284 | { 285 | return details::format_integer(static_cast(value), specification); 286 | } 287 | }; 288 | 289 | template 290 | struct argument_formatter>> 291 | { 292 | static codeunit_sequence produce(const T& value, const codeunit_sequence_view& specification) 293 | { 294 | return details::format_float(static_cast(value), specification); 295 | } 296 | }; 297 | 298 | template<> 299 | struct argument_formatter 300 | { 301 | static codeunit_sequence produce(const char* value, const codeunit_sequence_view& specification) 302 | { 303 | return codeunit_sequence{value}; 304 | } 305 | }; 306 | 307 | template 308 | struct argument_formatter 309 | { 310 | static codeunit_sequence produce(const char (&value)[N], const codeunit_sequence_view& specification) 311 | { 312 | return codeunit_sequence{value}; 313 | } 314 | }; 315 | 316 | template<> 317 | struct argument_formatter 318 | { 319 | static codeunit_sequence produce(const codeunit_sequence_view& value, const codeunit_sequence_view& specification) 320 | { 321 | return codeunit_sequence{value}; 322 | } 323 | }; 324 | 325 | template<> 326 | struct argument_formatter 327 | { 328 | static const codeunit_sequence& produce(const codeunit_sequence& value, const codeunit_sequence_view& specification) 329 | { 330 | return value; 331 | } 332 | }; 333 | 334 | template<> 335 | struct argument_formatter 336 | { 337 | static codeunit_sequence produce(std::nullptr_t, const codeunit_sequence_view& specification) 338 | { 339 | return codeunit_sequence{"nullptr"_cuqv}; 340 | } 341 | }; 342 | 343 | template 344 | struct argument_formatter 345 | { 346 | static codeunit_sequence produce(const T* value, const codeunit_sequence_view& specification) 347 | { 348 | return details::format_integer(reinterpret_cast(value), "#016x"_cuqv); 349 | } 350 | }; 351 | 352 | // code-region-end: formatter specializations for built-in types 353 | } 354 | -------------------------------------------------------------------------------- /include/text.h: -------------------------------------------------------------------------------- 1 | 2 | #pragma once 3 | 4 | #include "text_view.h" 5 | #include "codeunit_sequence.h" 6 | #include "common/sequence.h" 7 | 8 | namespace ostr 9 | { 10 | class OPEN_STRING_API text 11 | { 12 | public: 13 | 14 | text() noexcept; 15 | text(const text&) noexcept; 16 | text(text&&) noexcept; 17 | text& operator=(const text&) noexcept; 18 | text& operator=(text&&) noexcept; 19 | ~text(); 20 | 21 | text(const char* str) noexcept; 22 | text(const text_view& view) noexcept; 23 | text(codeunit_sequence sequence) noexcept; 24 | text(const codeunit_sequence_view& sequence) noexcept; 25 | 26 | static text from_utf8(const char* string_utf8) noexcept; 27 | static text from_utf16(const char16_t* string_utf16) noexcept; 28 | static text from_utf32(const char32_t* string_utf32) noexcept; 29 | static text from_wide(const wchar_t* wide_string) noexcept; 30 | 31 | template 32 | static text build(const Args&... argument); 33 | template 34 | static text join(const Container& container, const text_view& separator) noexcept; 35 | 36 | // code-region-start: iterator 37 | 38 | using const_iterator = text_view::const_iterator; 39 | 40 | struct OPEN_STRING_API iterator 41 | { 42 | iterator() noexcept = default; 43 | iterator(text& t, u64 from, u64 size) noexcept; 44 | 45 | struct OPEN_STRING_API codepoint_accessor 46 | { 47 | explicit codepoint_accessor(iterator& iter) noexcept; 48 | 49 | codepoint_accessor& operator=(char c) noexcept; 50 | codepoint_accessor& operator=(char32_t cp) noexcept; 51 | codepoint_accessor& operator=(const codepoint& cp) noexcept; 52 | codepoint_accessor& operator=(const text_view& tv) noexcept; 53 | codepoint_accessor& operator=(const text& t) noexcept; 54 | 55 | [[nodiscard]] codepoint get_codepoint() const noexcept; 56 | [[nodiscard]] operator codepoint() const noexcept; 57 | 58 | private: 59 | 60 | void assign(const codeunit_sequence_view& sequence_view) const noexcept; 61 | 62 | iterator& it_; 63 | }; 64 | 65 | [[nodiscard]] bool is_valid() const noexcept; 66 | [[nodiscard]] u64 raw_size() const noexcept; 67 | [[nodiscard]] codepoint get_codepoint() const noexcept; 68 | [[nodiscard]] codepoint operator*() const noexcept; 69 | [[nodiscard]] codepoint_accessor operator*() noexcept; 70 | iterator& operator++() noexcept; 71 | iterator operator++(int) noexcept; 72 | iterator& operator--() noexcept; 73 | iterator operator--(int) noexcept; 74 | [[nodiscard]] bool operator==(const iterator& rhs) const noexcept; 75 | [[nodiscard]] bool operator!=(const iterator& rhs) const noexcept; 76 | 77 | u64 from = global_constant::INDEX_INVALID; 78 | u64 size = SIZE_MAX; 79 | text* owner = nullptr; 80 | }; 81 | 82 | [[nodiscard]] iterator begin() noexcept; 83 | [[nodiscard]] const_iterator begin() const noexcept; 84 | [[nodiscard]] iterator end() noexcept; 85 | [[nodiscard]] const_iterator end() const noexcept; 86 | [[nodiscard]] const_iterator cbegin() const noexcept; 87 | [[nodiscard]] const_iterator cend() const noexcept; 88 | 89 | // code-region-end: iterator 90 | 91 | [[nodiscard]] codeunit_sequence& raw() & noexcept; 92 | [[nodiscard]] const codeunit_sequence& raw() const& noexcept; 93 | [[nodiscard]] codeunit_sequence raw() && noexcept; 94 | 95 | [[nodiscard]] text_view view() const noexcept; 96 | 97 | [[nodiscard]] u64 size() const noexcept; 98 | [[nodiscard]] bool is_empty() const noexcept; 99 | 100 | [[nodiscard]] bool operator==(const text_view& rhs) const noexcept; 101 | [[nodiscard]] bool operator==(const text& rhs) const noexcept; 102 | [[nodiscard]] bool operator==(const char* rhs) const noexcept; 103 | [[nodiscard]] bool operator!=(const text_view& rhs) const noexcept; 104 | [[nodiscard]] bool operator!=(const text& rhs) const noexcept; 105 | [[nodiscard]] bool operator!=(const char* rhs) const noexcept; 106 | 107 | text& append(const text_view& rhs) noexcept; 108 | text& append(const text& rhs) noexcept; 109 | text& append(const codepoint& cp) noexcept; 110 | text& append(const char* rhs) noexcept; 111 | text& append(char codeunit, u64 count = 1) noexcept; 112 | 113 | text& operator+=(const text_view& rhs) noexcept; 114 | text& operator+=(const text& rhs) noexcept; 115 | text& operator+=(const codepoint& cp) noexcept; 116 | text& operator+=(const char* rhs) noexcept; 117 | text& operator+=(char codeunit) noexcept; 118 | 119 | [[nodiscard]] text_view subview(u64 from, u64 size = global_constant::SIZE_INVALID) const noexcept; 120 | text& subtext(u64 from, u64 size = global_constant::SIZE_INVALID) noexcept; 121 | 122 | [[nodiscard]] u64 index_of(const text_view& pattern, u64 from = 0, u64 size = global_constant::SIZE_INVALID) const noexcept; 123 | [[nodiscard]] u64 last_index_of(const text_view& pattern, u64 from = 0, u64 size = global_constant::SIZE_INVALID) const noexcept; 124 | 125 | [[nodiscard]] u64 count(const text_view& pattern, u64 from = 0, u64 size = global_constant::SIZE_INVALID) const noexcept; 126 | 127 | [[nodiscard]] bool starts_with(const text_view& prefix) const noexcept; 128 | [[nodiscard]] bool ends_with(const text_view& suffix) const noexcept; 129 | 130 | /** 131 | * Empty the text without reallocation. 132 | */ 133 | void empty() noexcept; 134 | 135 | // How to reserve for unknown size? 136 | // If you got a sequence of code units, 137 | // please use t.raw().reserve(size); 138 | text& reserve(u64) noexcept = delete; 139 | 140 | text& write_at(u64 index, codepoint cp) noexcept; 141 | [[nodiscard]] codepoint read_at(u64 index) const noexcept; 142 | 143 | /// @note: Please use operator[] for read-only access, or use write_at method for write access. 144 | [[nodiscard]] codepoint operator[](u64 index) const noexcept; 145 | 146 | text& reverse(u64 from = 0, u64 size = SIZE_MAX) noexcept; 147 | 148 | u32 split(const text_view& splitter, sequence& pieces, bool cull_empty = true) const noexcept; 149 | 150 | text& replace(const text_view& destination, const text_view& source, u64 from = 0, u64 size = SIZE_MAX); 151 | text& replace(const text_view& destination, u64 from = 0, u64 size = SIZE_MAX); 152 | 153 | text& self_remove_prefix(const text_view& prefix) noexcept; 154 | text& self_remove_suffix(const text_view& suffix) noexcept; 155 | [[nodiscard]] text_view view_remove_prefix(const text_view& prefix) const noexcept; 156 | [[nodiscard]] text_view view_remove_suffix(const text_view& suffix) const noexcept; 157 | 158 | text& self_trim_start(const text_view& characters = text_view(" \t")) noexcept; 159 | text& self_trim_end(const text_view& characters = text_view(" \t")) noexcept; 160 | text& self_trim(const text_view& characters = text_view(" \t")) noexcept; 161 | 162 | [[nodiscard]] text_view view_trim_start(const text_view& characters = text_view(" \t")) const noexcept; 163 | [[nodiscard]] text_view view_trim_end(const text_view& characters = text_view(" \t")) const noexcept; 164 | [[nodiscard]] text_view view_trim(const text_view& characters = text_view(" \t")) const noexcept; 165 | 166 | [[nodiscard]] const char* c_str() const noexcept; 167 | 168 | private: 169 | 170 | codeunit_sequence sequence_{ }; 171 | 172 | }; 173 | 174 | namespace details 175 | { 176 | template<> 177 | [[nodiscard]] constexpr codeunit_sequence_view view_sequence(const text_view& v) 178 | { 179 | return v.raw(); 180 | } 181 | template<> 182 | [[nodiscard]] inline codeunit_sequence_view view_sequence(const text& v) 183 | { 184 | return v.view().raw(); 185 | } 186 | } 187 | 188 | template 189 | text text::build(const Args&... argument) 190 | { 191 | return { codeunit_sequence::build(argument...) }; 192 | } 193 | 194 | template 195 | text text::join(const Container& container, const text_view& separator) noexcept 196 | { 197 | return { codeunit_sequence::join(container, separator.raw()) }; 198 | } 199 | 200 | OPEN_STRING_API [[nodiscard]] bool operator==(const text_view& lhs, const text& rhs) noexcept; 201 | 202 | template<> 203 | struct argument_formatter 204 | { 205 | static codeunit_sequence produce(const text_view& value, const codeunit_sequence_view& specification) 206 | { 207 | return codeunit_sequence{ value.raw() }; 208 | } 209 | }; 210 | 211 | template<> 212 | struct argument_formatter 213 | { 214 | static codeunit_sequence produce(const text& value, const codeunit_sequence_view& specification) 215 | { 216 | return value.raw(); 217 | } 218 | }; 219 | } 220 | -------------------------------------------------------------------------------- /include/text_view.h: -------------------------------------------------------------------------------- 1 | 2 | #pragma once 3 | #include "common/definitions.h" 4 | 5 | #include 6 | #include "codeunit_sequence_view.h" 7 | 8 | namespace ostr 9 | { 10 | // UTF-8 encoded string 11 | class OPEN_STRING_API text_view 12 | { 13 | public: 14 | 15 | // code-region-start: constructors 16 | 17 | constexpr text_view() noexcept = default; 18 | constexpr text_view(const text_view&) noexcept = default; 19 | constexpr text_view(text_view&&) noexcept = default; 20 | constexpr text_view& operator=(const text_view&) noexcept = default; 21 | constexpr text_view& operator=(text_view&&) noexcept = default; 22 | ~text_view() noexcept = default; 23 | 24 | constexpr text_view(const char* data, const u64 count) noexcept 25 | : view_{ data, count } 26 | { } 27 | constexpr text_view(const char* str) noexcept 28 | : view_{ str } 29 | { } 30 | constexpr text_view(codeunit_sequence_view view) noexcept 31 | : view_{ std::move(view) } 32 | { } 33 | constexpr text_view(const codepoint& cp) noexcept 34 | : view_{ cp.raw(), cp.size() } 35 | { } 36 | 37 | constexpr text_view& operator=(const char* str) noexcept 38 | { 39 | *this = text_view(str); 40 | return *this; 41 | } 42 | 43 | // code-region-end: constructors 44 | 45 | // code-region-start: iterators 46 | 47 | struct const_iterator 48 | { 49 | constexpr const_iterator() noexcept = default; 50 | 51 | explicit constexpr const_iterator(codeunit_sequence_view::const_iterator v) noexcept 52 | : value(v) 53 | { } 54 | 55 | [[nodiscard]] constexpr u64 raw_size() const noexcept 56 | { 57 | return unicode::parse_utf8_length(*this->value); 58 | } 59 | 60 | [[nodiscard]] constexpr codepoint get_codepoint() const noexcept 61 | { 62 | return codepoint{ this->value.data() }; 63 | } 64 | 65 | [[nodiscard]] constexpr codepoint operator*() const noexcept 66 | { 67 | return this->get_codepoint(); 68 | } 69 | 70 | [[nodiscard]] constexpr i64 operator-(const const_iterator& rhs) const noexcept 71 | { 72 | if(rhs > *this) 73 | return - (rhs - *this); 74 | 75 | const_iterator cur = rhs; 76 | i64 diff = 0; 77 | while(cur != *this) 78 | { 79 | ++cur; 80 | ++diff; 81 | } 82 | return diff; 83 | } 84 | 85 | constexpr const_iterator& operator+=(const i64 diff) noexcept 86 | { 87 | if(diff != 0) 88 | { 89 | using self_operator = const_iterator& (const_iterator::*)(); 90 | const auto& op = diff > 0 ? static_cast(&const_iterator::operator++) : static_cast(&const_iterator::operator--); 91 | const u64 diff_abs = abs(diff); 92 | for(u64 i = 0; i < diff_abs; ++i) 93 | (this->*op)(); 94 | } 95 | return *this; 96 | } 97 | 98 | constexpr const_iterator& operator-=(const i64 diff) noexcept 99 | { 100 | return this->operator+=(-diff); 101 | } 102 | 103 | [[nodiscard]] constexpr const_iterator operator+(const i64 diff) const noexcept 104 | { 105 | const_iterator tmp = *this; 106 | tmp += diff; 107 | return tmp; 108 | } 109 | 110 | [[nodiscard]] constexpr const_iterator operator-(const i64 diff) const noexcept 111 | { 112 | const_iterator tmp = *this; 113 | tmp -= diff; 114 | return tmp; 115 | } 116 | 117 | constexpr const_iterator& operator++() noexcept 118 | { 119 | this->value += static_cast(this->raw_size()); 120 | return *this; 121 | } 122 | 123 | constexpr const_iterator operator++(int) noexcept 124 | { 125 | const const_iterator tmp = *this; 126 | ++*this; 127 | return tmp; 128 | } 129 | 130 | constexpr const_iterator& operator--() noexcept 131 | { 132 | --this->value; 133 | while(this->raw_size() == 0) 134 | --this->value; 135 | return *this; 136 | } 137 | 138 | constexpr const_iterator operator--(int) noexcept 139 | { 140 | const const_iterator tmp = *this; 141 | --*this; 142 | return tmp; 143 | } 144 | 145 | [[nodiscard]] constexpr bool operator==(const const_iterator& rhs) const noexcept 146 | { 147 | return this->value == rhs.value; 148 | } 149 | 150 | [[nodiscard]] constexpr bool operator!=(const const_iterator& rhs) const noexcept 151 | { 152 | return !(*this == rhs); 153 | } 154 | 155 | [[nodiscard]] constexpr bool operator<(const const_iterator& rhs) const noexcept 156 | { 157 | return this->value < rhs.value; 158 | } 159 | 160 | [[nodiscard]] constexpr bool operator>(const const_iterator& rhs) const noexcept 161 | { 162 | return rhs < *this; 163 | } 164 | 165 | [[nodiscard]] constexpr bool operator<=(const const_iterator& rhs) const noexcept 166 | { 167 | return rhs >= *this; 168 | } 169 | 170 | [[nodiscard]] constexpr bool operator>=(const const_iterator& rhs) const noexcept 171 | { 172 | return !(*this < rhs); 173 | } 174 | 175 | codeunit_sequence_view::const_iterator value; 176 | }; 177 | 178 | [[nodiscard]] constexpr const_iterator begin() const noexcept 179 | { 180 | return const_iterator{ this->view_.cbegin() }; 181 | } 182 | 183 | [[nodiscard]] constexpr const_iterator end() const noexcept 184 | { 185 | return const_iterator{ this->view_.cend() }; 186 | } 187 | 188 | [[nodiscard]] constexpr const_iterator cbegin() const noexcept 189 | { 190 | return this->begin(); 191 | } 192 | 193 | [[nodiscard]] constexpr const_iterator cend() const noexcept 194 | { 195 | return this->end(); 196 | } 197 | 198 | // code-region-end: iterators 199 | 200 | [[nodiscard]] constexpr bool operator==(const text_view& rhs) const noexcept 201 | { 202 | return this->view_ == rhs.view_; 203 | } 204 | 205 | [[nodiscard]] constexpr bool operator!=(const text_view& rhs) const noexcept 206 | { 207 | return !this->operator==(rhs); 208 | } 209 | 210 | [[nodiscard]] constexpr bool operator==(const char* rhs) const noexcept 211 | { 212 | return this->view_ == rhs; 213 | } 214 | 215 | [[nodiscard]] constexpr bool operator!=(const char* rhs) const noexcept 216 | { 217 | return this->view_ != rhs; 218 | } 219 | 220 | [[nodiscard]] constexpr u64 size() const noexcept 221 | { 222 | return this->get_codepoint_index( this->view_.size() ); 223 | } 224 | 225 | [[nodiscard]] constexpr codeunit_sequence_view raw() const noexcept 226 | { 227 | return this->view_; 228 | } 229 | 230 | // This is not allowed for getting rid of misuse. 231 | [[nodiscard]] const char* c_str() const noexcept = delete; 232 | 233 | /// @return Is this an empty string 234 | [[nodiscard]] constexpr bool is_empty() const noexcept 235 | { 236 | return this->view_.is_empty(); 237 | } 238 | 239 | [[nodiscard]] constexpr text_view subview(const u64 from, const u64 size = SIZE_MAX) const noexcept 240 | { 241 | u64 raw_from = from; u64 raw_size = size; 242 | this->get_codeunit_range(raw_from, raw_size); 243 | return { this->view_.subview(raw_from, raw_size) }; 244 | } 245 | 246 | [[nodiscard]] constexpr codepoint read_at(const u64 index) const noexcept 247 | { 248 | const char* data = &this->view_.read_at(this->get_codeunit_index(index)); 249 | return codepoint{ data }; 250 | } 251 | 252 | [[nodiscard]] constexpr codepoint operator[](const u64 index) const noexcept 253 | { 254 | return this->read_at(index); 255 | } 256 | 257 | [[nodiscard]] constexpr u64 index_of(const text_view& pattern, const u64 from = 0, const u64 size = SIZE_MAX) const noexcept 258 | { 259 | if(pattern.is_empty()) 260 | return global_constant::INDEX_INVALID; 261 | u64 raw_from = from; u64 raw_size = size; 262 | this->get_codeunit_range(raw_from, raw_size); 263 | const u64 found_raw_index = this->view_.index_of(pattern.view_, raw_from, raw_size); 264 | if(found_raw_index == global_constant::INDEX_INVALID) 265 | return global_constant::INDEX_INVALID; 266 | return this->get_codepoint_index(found_raw_index); 267 | } 268 | 269 | [[nodiscard]] constexpr u64 index_of(const codepoint& pattern, const u64 from = 0, const u64 size = SIZE_MAX) const noexcept 270 | { 271 | return this->index_of(text_view{ pattern }, from, size); 272 | } 273 | 274 | [[nodiscard]] constexpr u64 last_index_of(const text_view& pattern, const u64 from = 0, const u64 size = SIZE_MAX) const noexcept 275 | { 276 | if(pattern.is_empty()) 277 | return global_constant::INDEX_INVALID; 278 | u64 raw_from = from; u64 raw_size = size; 279 | this->get_codeunit_range(raw_from, raw_size); 280 | const u64 found_raw_index = this->view_.last_index_of(pattern.view_, raw_from, raw_size); 281 | if(found_raw_index == global_constant::INDEX_INVALID) 282 | return global_constant::INDEX_INVALID; 283 | return this->get_codepoint_index(found_raw_index); 284 | } 285 | 286 | [[nodiscard]] u64 count(const text_view& pattern, const u64 from, const u64 size) const noexcept 287 | { 288 | return this->view_.count(pattern.view_, from, size); 289 | } 290 | 291 | [[nodiscard]] constexpr bool contains(const text_view& pattern) const noexcept 292 | { 293 | return this->view_.contains(pattern.view_); 294 | } 295 | 296 | [[nodiscard]] constexpr bool contains(const codepoint& pattern) const noexcept 297 | { 298 | return this->contains(text_view{ pattern }); 299 | } 300 | 301 | [[nodiscard]] constexpr std::array split(const text_view& splitter) const noexcept 302 | { 303 | const auto [ first, second ] = this->view_.split(splitter.view_); 304 | return { text_view{ first }, text_view{ second } }; 305 | } 306 | 307 | [[nodiscard]] constexpr bool starts_with(const text_view& prefix) const noexcept 308 | { 309 | return this->view_.starts_with(prefix.view_); 310 | } 311 | 312 | [[nodiscard]] constexpr bool ends_with(const text_view& suffix) const noexcept 313 | { 314 | return this->view_.ends_with(suffix.view_); 315 | } 316 | 317 | [[nodiscard]] constexpr text_view remove_prefix(const text_view& prefix) const noexcept 318 | { 319 | return text_view{ this->view_.remove_prefix(prefix.view_) }; 320 | } 321 | 322 | [[nodiscard]] constexpr text_view remove_suffix(const text_view& suffix) const noexcept 323 | { 324 | return text_view{ this->view_.remove_suffix(suffix.view_) }; 325 | } 326 | 327 | [[nodiscard]] constexpr text_view trim_start(const text_view& text_set = text_view(" \t")) const noexcept 328 | { 329 | if(this->is_empty()) 330 | return { }; 331 | if(text_set.is_empty()) 332 | return *this; 333 | auto iterator = this->begin(); 334 | const u64 size = this->size(); 335 | for(u64 i = 0; i < size; ++i) 336 | { 337 | if(const auto cp = *iterator; !text_set.contains(cp)) 338 | return this->subview(i); 339 | ++iterator; 340 | } 341 | return { }; 342 | } 343 | 344 | [[nodiscard]] constexpr text_view trim_end(const text_view& text_set = text_view(" \t")) const noexcept 345 | { 346 | if(this->is_empty()) 347 | return { }; 348 | if(text_set.is_empty()) 349 | return *this; 350 | auto iterator = this->end(); 351 | const u64 size = this->size(); 352 | for(u64 i = size; i > 0; --i) 353 | { 354 | --iterator; 355 | if(const auto cp = *iterator; !text_set.contains(cp)) 356 | return this->subview(0, i); 357 | } 358 | return { }; 359 | } 360 | 361 | [[nodiscard]] constexpr text_view trim(const text_view& text_set = text_view(" \t")) const noexcept 362 | { 363 | return this->trim_start(text_set).trim_end(text_set); 364 | } 365 | 366 | [[nodiscard]] constexpr u64 get_codepoint_index(const u64 codeunit_index) const noexcept 367 | { 368 | const u64 view_size = this->view_.size(); 369 | u64 index = 0; 370 | u64 offset = 0; 371 | while(offset < codeunit_index && offset < view_size) 372 | { 373 | const u8 sequence_length = unicode::parse_utf8_length(this->view_.read_at(offset)); 374 | const u8 offset_delta = sequence_length == 0 ? 1 : sequence_length; 375 | offset += offset_delta; 376 | ++index; 377 | } 378 | return index; 379 | } 380 | 381 | [[nodiscard]] constexpr u64 get_codeunit_index(const u64 codepoint_index) const noexcept 382 | { 383 | const u64 view_size = this->view_.size(); 384 | u64 index = 0; 385 | u64 offset = 0; 386 | while(index < codepoint_index && offset < view_size) 387 | { 388 | const u8 sequence_length = unicode::parse_utf8_length(this->view_.read_at(offset)); 389 | offset += sequence_length; 390 | ++index; 391 | } 392 | return offset; 393 | } 394 | 395 | constexpr void get_codeunit_range(u64& from, u64& size) const noexcept 396 | { 397 | const u64 origin_from = from; 398 | const u64 actual_size = minimum(size, this->size() - from); 399 | const u64 last = this->get_codeunit_index(origin_from + actual_size); 400 | from = this->get_codeunit_index(origin_from); 401 | size = last - from; 402 | } 403 | 404 | // todo: a better conversion approach 405 | [[nodiscard]] constexpr std::optional try_as_integer() const 406 | { 407 | if(this->is_empty()) 408 | return { }; 409 | i32 result = 0; 410 | i32 signal = 1; 411 | codeunit_sequence_view raw_view = this->raw(); 412 | if(raw_view.starts_with("+"_cuqv)) 413 | { 414 | raw_view = raw_view.subview(1); 415 | } 416 | if(raw_view.starts_with("-"_cuqv)) 417 | { 418 | raw_view = raw_view.subview(1); 419 | signal = -1; 420 | } 421 | for(const char c : raw_view) 422 | { 423 | if(c < '0' || c > '9') 424 | return { }; 425 | result *= 10; 426 | result += (c - '0'); 427 | } 428 | return result * signal; 429 | } 430 | 431 | private: 432 | 433 | codeunit_sequence_view view_{ }; 434 | 435 | }; 436 | } 437 | 438 | inline namespace literal 439 | { 440 | [[nodiscard]] constexpr ostr::text_view operator""_txtv(const char* str, const size_t len) noexcept 441 | { 442 | return { str, len }; 443 | } 444 | } 445 | -------------------------------------------------------------------------------- /include/unicode.h: -------------------------------------------------------------------------------- 1 | 2 | #pragma once 3 | 4 | #include "common/basic_types.h" 5 | #include 6 | 7 | namespace ostr 8 | { 9 | namespace unicode 10 | { 11 | static constexpr u64 UTF8_SEQUENCE_MAXIMUM_LENGTH = 4; 12 | 13 | [[nodiscard]] constexpr char32_t get_utf8_maximum_codepoint(const u64 codeunit_count) 14 | { 15 | constexpr std::array maximum_codepoints 16 | { 17 | 0x7F, 18 | 0x7FF, 19 | 0xFFFF, 20 | 0x1FFFFF, 21 | }; 22 | return maximum_codepoints.at(codeunit_count - 1); 23 | } 24 | 25 | namespace utf16 26 | { 27 | static constexpr u64 SEQUENCE_MAXIMUM_LENGTH = 2; 28 | 29 | static constexpr char32_t LEADING_SURROGATE_HEADER = 0xD800; 30 | static constexpr char32_t TRAILING_SURROGATE_HEADER = 0xDC00; 31 | static constexpr char32_t LEADING_SURROGATE_MINIMUM = 0xD800; 32 | static constexpr char32_t TRAILING_SURROGATE_MINIMUM = 0xDC00; 33 | static constexpr char32_t LEADING_SURROGATE_MAXIMUM = 0xDBFF; 34 | static constexpr char32_t TRAILING_SURROGATE_MAXIMUM = 0xDFFF; 35 | static constexpr char32_t SURROGATE_MASK = 0x03FF; 36 | static constexpr char32_t SINGLE_UNIT_MAXIMUM_VALUE = 0xFFFF; 37 | 38 | [[nodiscard]] constexpr bool is_leading_surrogate(const char16_t c) noexcept 39 | { 40 | return c >= LEADING_SURROGATE_MINIMUM && c <= LEADING_SURROGATE_MAXIMUM; 41 | } 42 | 43 | [[nodiscard]] constexpr bool is_trailing_surrogate(const char16_t c) noexcept 44 | { 45 | return c >= TRAILING_SURROGATE_MINIMUM && c <= TRAILING_SURROGATE_MAXIMUM; 46 | } 47 | 48 | [[nodiscard]] constexpr bool is_surrogate(const char16_t c) noexcept 49 | { 50 | return c >= LEADING_SURROGATE_MINIMUM && c <= TRAILING_SURROGATE_MAXIMUM; 51 | } 52 | 53 | [[nodiscard]] constexpr u64 parse_utf16_length(const char16_t c) noexcept 54 | { 55 | return is_leading_surrogate(c) ? SEQUENCE_MAXIMUM_LENGTH : 1; 56 | } 57 | } 58 | 59 | /** 60 | * @param length length of a utf8 sequence 61 | * @return mask of utf8 code unit 62 | */ 63 | [[nodiscard]] constexpr char get_utf8_mask(const u64 length) noexcept 64 | { 65 | constexpr char masks[] = 66 | { 67 | 0b00111111_as_char, 68 | 0b01111111_as_char, 69 | 0b00011111_as_char, 70 | 0b00001111_as_char, 71 | 0b00000111_as_char, 72 | }; 73 | return masks[length]; 74 | } 75 | 76 | /** 77 | * @param c start of a utf8 sequence 78 | * @return length of utf8 sequence, return 0 if this is not start of a utf8 sequence 79 | */ 80 | [[nodiscard]] constexpr u8 parse_utf8_length(const char c) noexcept 81 | { 82 | if (c == 0) 83 | return 1; 84 | constexpr char mask = 0b10000000_as_char; 85 | u8 size = 0; 86 | u8 v = c; 87 | while (v & mask) 88 | { 89 | ++size; 90 | v <<= 1; 91 | } 92 | // 1 byte when start with 0 93 | // 0 byte when start with 10 94 | // 2 bytes when start with 110 95 | // n bytes when start with 1..(n)..0 96 | return size > 1 ? size : 1 - size; 97 | } 98 | 99 | [[nodiscard]] constexpr u64 parse_utf8_length(const char16_t utf16) noexcept 100 | { 101 | if (utf16 <= get_utf8_maximum_codepoint(1)) 102 | return 1; 103 | if (utf16 <= get_utf8_maximum_codepoint(2)) 104 | return 2; 105 | if (utf16::is_surrogate(utf16)) 106 | return 4; 107 | return 3; 108 | } 109 | 110 | [[nodiscard]] constexpr u64 parse_utf8_length(const char32_t utf32) noexcept 111 | { 112 | if (utf32 <= get_utf8_maximum_codepoint(1)) 113 | return 1; 114 | if (utf32 <= get_utf8_maximum_codepoint(2)) 115 | return 2; 116 | if (utf32 <= get_utf8_maximum_codepoint(3)) 117 | return 3; 118 | return 4; 119 | } 120 | 121 | /** 122 | * @param utf8 input utf-8 code unit sequence 123 | * @param length length of utf-8 code unit sequence 124 | * @return codepoint of input utf8 code unit sequence 125 | */ 126 | [[nodiscard]] constexpr char32_t utf8_to_utf32(char const* const utf8, const u64 length) noexcept 127 | { 128 | if (!utf8) 129 | return 0; 130 | const char c0 = utf8[0]; 131 | const char lead_mask = get_utf8_mask(length); 132 | char32_t utf32 = c0 & lead_mask; 133 | constexpr char following_mask = get_utf8_mask(0); 134 | for (u64 i = 1; i < length; ++i) 135 | { 136 | constexpr u64 bits_following = 6; 137 | const char ci = utf8[i]; 138 | utf32 <<= bits_following; 139 | utf32 |= ci & following_mask; 140 | } 141 | return utf32; 142 | } 143 | 144 | /** 145 | * @param utf32 input utf-32 code unit 146 | * @return decoded utf-8 code unit sequence from utf-32 code unit 147 | */ 148 | [[nodiscard]] constexpr std::array utf32_to_utf8(const char32_t utf32) noexcept 149 | { 150 | if (utf32 <= get_utf8_maximum_codepoint(1)) return 151 | { static_cast(utf32), 152 | 0, 0, 0 }; 153 | if (utf32 <= get_utf8_maximum_codepoint(2)) return 154 | { static_cast((utf32 >> 6) | 0xc0), 155 | static_cast((utf32 & 0x3f) | 0x80), 156 | 0, 0 }; 157 | if (utf32 <= get_utf8_maximum_codepoint(3)) return 158 | { static_cast((utf32 >> 12) | 0xe0), 159 | static_cast(((utf32 >> 6) & 0x3f) | 0x80), 160 | static_cast((utf32 & 0x3f) | 0x80), 161 | 0 }; 162 | return 163 | { static_cast((utf32 >> 18) | 0xf0), 164 | static_cast(((utf32 >> 12) & 0x3f) | 0x80), 165 | static_cast(((utf32 >> 6) & 0x3f) | 0x80), 166 | static_cast((utf32 & 0x3f) | 0x80) }; 167 | } 168 | 169 | [[nodiscard]] constexpr char32_t utf16_to_utf32(char16_t const* const utf16, const u64 length) noexcept 170 | { 171 | // 1 0 172 | // 9876543210 9876543210 173 | // |||||||||| |||||||||| 174 | // [110110]9876543210 |||||||||| high surrogate 175 | // [110111]9876543210 low surrogate 176 | return length == 1 ? utf16[0] : ((utf16[0] & utf16::SURROGATE_MASK) << 10) + (utf16[1] & utf16::SURROGATE_MASK); 177 | } 178 | 179 | [[nodiscard]] constexpr std::array utf32_to_utf16(char32_t const utf32) noexcept 180 | { 181 | return utf32 <= utf16::SINGLE_UNIT_MAXIMUM_VALUE ? 182 | std::array{ static_cast(utf32) } : 183 | std::array{ 184 | static_cast((utf32 >> 10) + utf16::LEADING_SURROGATE_HEADER), 185 | static_cast((utf32 & utf16::SURROGATE_MASK) + utf16::TRAILING_SURROGATE_HEADER) }; 186 | } 187 | 188 | [[nodiscard]] constexpr std::array utf8_to_utf16(char const* const utf8, const u64 length) noexcept 189 | { 190 | return utf32_to_utf16(utf8_to_utf32(utf8, length)); 191 | } 192 | 193 | [[nodiscard]] constexpr std::array utf16_to_utf8(char16_t const* const utf16, const u64 length) noexcept 194 | { 195 | return utf32_to_utf8(utf16_to_utf32(utf16, length)); 196 | } 197 | } 198 | 199 | struct codepoint 200 | { 201 | explicit constexpr codepoint(const char v0 = 0, const char v1 = 0, const char v2 = 0, const char v3 = 0) noexcept 202 | : sequence{ v0, v1, v2, v3 } 203 | { } 204 | 205 | explicit constexpr codepoint(const char* const v, const u8 length) noexcept 206 | : sequence{ } 207 | { 208 | for(u8 i = 0; i < length; ++i) 209 | sequence[i] = v[i]; 210 | } 211 | 212 | explicit constexpr codepoint(const char* v) noexcept 213 | : codepoint{ v, unicode::parse_utf8_length(v[0]) } 214 | { } 215 | 216 | explicit constexpr codepoint(const char16_t* sp) noexcept 217 | : sequence{ unicode::utf16_to_utf8(sp, unicode::utf16::parse_utf16_length(sp[0])) } 218 | { } 219 | 220 | explicit constexpr codepoint(const char32_t cp) noexcept 221 | : sequence{ unicode::utf32_to_utf8(cp) } 222 | { } 223 | 224 | struct const_iterator 225 | { 226 | constexpr const_iterator() noexcept = default; 227 | 228 | explicit constexpr const_iterator(const char* v) noexcept 229 | : value{ v } 230 | { } 231 | 232 | [[nodiscard]] constexpr const char& operator*() const noexcept 233 | { 234 | return *this->value; 235 | } 236 | 237 | [[nodiscard]] constexpr const char* raw() const noexcept 238 | { 239 | return this->value; 240 | } 241 | 242 | [[nodiscard]] constexpr i64 operator-(const const_iterator& rhs) const noexcept 243 | { 244 | return this->value - rhs.value; 245 | } 246 | 247 | constexpr const_iterator& operator+=(const i64 diff) noexcept 248 | { 249 | this->value += diff; 250 | return *this; 251 | } 252 | 253 | constexpr const_iterator& operator-=(const i64 diff) noexcept 254 | { 255 | return this->operator+=(-diff); 256 | } 257 | 258 | [[nodiscard]] constexpr const_iterator operator+(const i64 diff) const noexcept 259 | { 260 | const_iterator tmp = *this; 261 | tmp += diff; 262 | return tmp; 263 | } 264 | 265 | [[nodiscard]] constexpr const_iterator operator-(const i64 diff) const noexcept 266 | { 267 | const_iterator tmp = *this; 268 | tmp -= diff; 269 | return tmp; 270 | } 271 | 272 | constexpr const_iterator& operator++() noexcept 273 | { 274 | ++this->value; 275 | return *this; 276 | } 277 | 278 | constexpr const_iterator operator++(int) noexcept 279 | { 280 | const const_iterator tmp = *this; 281 | ++*this; 282 | return tmp; 283 | } 284 | 285 | constexpr const_iterator& operator--() noexcept 286 | { 287 | --this->value; 288 | return *this; 289 | } 290 | 291 | constexpr const_iterator operator--(int) noexcept 292 | { 293 | const const_iterator tmp = *this; 294 | --*this; 295 | return tmp; 296 | } 297 | 298 | [[nodiscard]] constexpr bool operator==(const const_iterator& rhs) const noexcept 299 | { 300 | return this->value == rhs.value; 301 | } 302 | 303 | [[nodiscard]] constexpr bool operator!=(const const_iterator& rhs) const noexcept 304 | { 305 | return !(*this == rhs); 306 | } 307 | 308 | [[nodiscard]] constexpr bool operator<(const const_iterator& rhs) const noexcept 309 | { 310 | return this->value < rhs.value; 311 | } 312 | 313 | [[nodiscard]] constexpr bool operator>(const const_iterator& rhs) const noexcept 314 | { 315 | return rhs < *this; 316 | } 317 | 318 | [[nodiscard]] constexpr bool operator<=(const const_iterator& rhs) const noexcept 319 | { 320 | return rhs >= *this; 321 | } 322 | 323 | [[nodiscard]] constexpr bool operator>=(const const_iterator& rhs) const noexcept 324 | { 325 | return !(*this < rhs); 326 | } 327 | 328 | const char* value = nullptr; 329 | }; 330 | 331 | [[nodiscard]] constexpr const_iterator begin() const noexcept 332 | { 333 | return const_iterator{ this->sequence.data() }; 334 | } 335 | 336 | [[nodiscard]] constexpr const_iterator end() const noexcept 337 | { 338 | return const_iterator{ this->sequence.data() + this->size() }; 339 | } 340 | 341 | [[nodiscard]] constexpr const_iterator cbegin() const noexcept 342 | { 343 | return this->begin(); 344 | } 345 | 346 | [[nodiscard]] constexpr const_iterator cend() const noexcept 347 | { 348 | return this->end(); 349 | } 350 | 351 | [[nodiscard]] constexpr bool operator==(const codepoint& rhs) const noexcept 352 | { 353 | const u64 size_l = this->size(); 354 | if(size_l != rhs.size()) 355 | return false; 356 | for(u64 i = 0; i < size_l; ++i) 357 | if(this->sequence[i] != rhs.sequence[i]) 358 | return false; 359 | return true; 360 | } 361 | 362 | [[nodiscard]] constexpr bool operator==(const char* rhs) const noexcept 363 | { 364 | for(u64 i = 0; i < this->size(); ++i) 365 | if(this->sequence[i] != rhs[i]) 366 | return false; 367 | return true; 368 | } 369 | 370 | [[nodiscard]] constexpr bool operator==(const char32_t rhs) const noexcept 371 | { 372 | return this->get_codepoint() == rhs; 373 | } 374 | 375 | [[nodiscard]] constexpr bool operator!=(const codepoint& rhs) const noexcept 376 | { 377 | return !(*this == rhs); 378 | } 379 | 380 | [[nodiscard]] constexpr const char* raw() const noexcept 381 | { 382 | return this->sequence.data(); 383 | } 384 | 385 | [[nodiscard]] constexpr u64 size() const noexcept 386 | { 387 | for(u64 i = 0; i < unicode::UTF8_SEQUENCE_MAXIMUM_LENGTH; ++i) 388 | if(this->sequence[i] == 0) 389 | return i; 390 | return unicode::UTF8_SEQUENCE_MAXIMUM_LENGTH; 391 | } 392 | 393 | [[nodiscard]] constexpr char32_t get_codepoint() const noexcept 394 | { 395 | return unicode::utf8_to_utf32(this->sequence.data(), this->size()); 396 | } 397 | 398 | [[nodiscard]] explicit constexpr operator char32_t() const noexcept 399 | { 400 | return this->get_codepoint(); 401 | } 402 | 403 | std::array sequence; 404 | }; 405 | 406 | [[nodiscard]] constexpr bool operator==(const char* lhs, const codepoint& rhs) noexcept 407 | { 408 | return rhs == lhs; 409 | } 410 | } 411 | 412 | inline namespace literal 413 | { 414 | [[nodiscard]] constexpr ostr::codepoint operator""_cp(const char c) noexcept 415 | { 416 | return ostr::codepoint{ c }; 417 | } 418 | 419 | [[nodiscard]] constexpr ostr::codepoint operator""_cp(const char32_t c) noexcept 420 | { 421 | return ostr::codepoint{ c }; 422 | } 423 | } -------------------------------------------------------------------------------- /include/wide_text.h: -------------------------------------------------------------------------------- 1 | 2 | #pragma once 3 | 4 | #include "codeunit_sequence.h" 5 | #include "codeunit_sequence_view.h" 6 | #include "common/sequence.h" 7 | 8 | namespace ostr 9 | { 10 | class OPEN_STRING_API wide_text 11 | { 12 | public: 13 | explicit wide_text(const wchar_t* wide_str) noexcept; 14 | explicit wide_text(const codeunit_sequence_view& view) noexcept; 15 | wide_text& operator=(const wchar_t* wide_str) noexcept; 16 | wide_text& operator=(const codeunit_sequence_view& view) noexcept; 17 | 18 | [[nodiscard]] const wchar_t* data() const noexcept; 19 | void decode(codeunit_sequence& out) const noexcept; 20 | private: 21 | sequence sequence_{ }; 22 | }; 23 | 24 | template<> 25 | struct argument_formatter 26 | { 27 | static codeunit_sequence produce(const wide_text& value, const codeunit_sequence_view& specification) 28 | { 29 | codeunit_sequence result; 30 | value.decode(result); 31 | return result; 32 | } 33 | }; 34 | 35 | template<> 36 | struct argument_formatter 37 | { 38 | static codeunit_sequence produce(const wchar_t* value, const codeunit_sequence_view& specification) 39 | { 40 | return argument_formatter::produce(wide_text{ value }, specification); 41 | } 42 | }; 43 | 44 | template 45 | struct argument_formatter 46 | { 47 | static codeunit_sequence produce(const wchar_t (&value)[N], const codeunit_sequence_view& specification) 48 | { 49 | return argument_formatter::produce(wide_text{ value }, specification); 50 | } 51 | }; 52 | } 53 | -------------------------------------------------------------------------------- /source/format.cpp: -------------------------------------------------------------------------------- 1 | 2 | #include "common/definitions.h" 3 | #include "format.h" 4 | #include "text.h" 5 | 6 | namespace ostr 7 | { 8 | namespace details 9 | { 10 | [[nodiscard]] constexpr u64 get_integer_digit_count(const i64 value, const i64 base) 11 | { 12 | if(value == 0) 13 | return 1; 14 | i64 remaining = value; 15 | u64 count = 0; 16 | while(remaining != 0) 17 | { 18 | remaining /= base; 19 | ++count; 20 | } 21 | return count; 22 | } 23 | 24 | [[nodiscard]] constexpr u64 get_integer_digit_count(const u64 value, const u64 base) 25 | { 26 | if(value == 0) 27 | return 1; 28 | u64 remaining = value; 29 | u64 count = 0; 30 | while(remaining != 0) 31 | { 32 | remaining /= base; 33 | ++count; 34 | } 35 | return count; 36 | } 37 | 38 | [[nodiscard]] constexpr char from_digit(const u64 digit) 39 | { 40 | constexpr char digits[] = "0123456789abcdef"; 41 | return digits[digit]; 42 | } 43 | 44 | codeunit_sequence format_integer(const u64& value, const codeunit_sequence_view& specification) 45 | { 46 | char type = 'd'; 47 | char holder = '0'; 48 | u64 holding = global_constant::SIZE_INVALID; 49 | bool with_prefix = false; 50 | if (!specification.is_empty()) 51 | { 52 | codeunit_sequence_view parsing = specification; 53 | if ("bcdox"_cuqv.contains(parsing.read_from_last(0))) 54 | { 55 | type = parsing.read_from_last(0); 56 | parsing = parsing.subview(0, parsing.size() - 1); 57 | } 58 | if(!parsing.is_empty()) 59 | { 60 | if(const u64 holder_index = parsing.index_of_any("0 "_cuqv); holder_index != global_constant::INDEX_INVALID) 61 | { 62 | holder = parsing.read_at(holder_index); 63 | const codeunit_sequence_view holding_view = parsing.subview(holder_index + 1); 64 | const auto [ last, error ] = std::from_chars(holding_view.data(), holding_view.cend().data(), holding); 65 | OPEN_STRING_CHECK(last == holding_view.cend().data(), "Invalid format specification [{}]!", specification); 66 | parsing = parsing.subview(0, holder_index); 67 | } 68 | } 69 | if(!parsing.is_empty()) 70 | { 71 | if(parsing.read_from_last(0) == '#') 72 | { 73 | with_prefix = true; 74 | parsing = parsing.subview(0, parsing.size() - 1); 75 | } 76 | } 77 | OPEN_STRING_CHECK(parsing.is_empty(), "Invalid format specification [{}]!", specification); 78 | } 79 | u64 base = 10; 80 | codeunit_sequence_view prefix; 81 | switch (type) 82 | { 83 | case 'b': 84 | { 85 | base = 2; 86 | prefix = "0b"_cuqv; 87 | } 88 | break; 89 | case 'c': 90 | { 91 | codeunit_sequence result; 92 | result.append(static_cast(value)); 93 | return result; 94 | } 95 | case 'd': 96 | break; 97 | case 'o': 98 | { 99 | base = 8; 100 | prefix = "0o"_cuqv; 101 | } 102 | break; 103 | case 'x': 104 | { 105 | base = 16; 106 | prefix = "0x"_cuqv; 107 | } 108 | break; 109 | default: 110 | break; 111 | } 112 | if(!with_prefix) 113 | prefix = ""_cuqv; 114 | 115 | const u64 digit_count = get_integer_digit_count(value, base); 116 | const u64 preserve = holding == global_constant::SIZE_INVALID ? digit_count : maximum(holding, digit_count); 117 | const u64 holder_count = preserve - digit_count; 118 | codeunit_sequence result( prefix.size() + preserve ); 119 | result 120 | .append(prefix) 121 | .append(holder, holder_count); 122 | u64 remaining = value; 123 | for(u64 i = 0; i < digit_count; ++i) 124 | { 125 | const char digit = from_digit(remaining % base); 126 | remaining /= base; 127 | result.append(digit); 128 | } 129 | result.reverse(); 130 | return result; 131 | } 132 | 133 | codeunit_sequence format_integer(const i64& value, const codeunit_sequence_view& specification) 134 | { 135 | char type = 'd'; 136 | u64 holding = global_constant::SIZE_INVALID; 137 | bool with_prefix = false; 138 | if (!specification.is_empty()) 139 | { 140 | codeunit_sequence_view parsing = specification; 141 | if ("bcdox"_cuqv.contains(parsing.read_from_last(0))) 142 | { 143 | type = parsing.read_from_last(0); 144 | parsing = parsing.subview(0, parsing.size() - 1); 145 | } 146 | if(!parsing.is_empty()) 147 | { 148 | if(const u64 zero_index = parsing.index_of('0'); zero_index != global_constant::INDEX_INVALID) 149 | { 150 | const codeunit_sequence_view holding_view = parsing.subview(zero_index + 1); 151 | const auto [ last, error ] = std::from_chars(holding_view.data(), holding_view.cend().data(), holding); 152 | OPEN_STRING_CHECK(last == holding_view.cend().data(), "Invalid format specification [{}]!", specification); 153 | parsing = parsing.subview(0, zero_index); 154 | } 155 | } 156 | if(!parsing.is_empty()) 157 | { 158 | if(parsing.read_from_last(0) == '#') 159 | { 160 | with_prefix = true; 161 | parsing = parsing.subview(0, parsing.size() - 1); 162 | } 163 | } 164 | OPEN_STRING_CHECK(parsing.is_empty(), "Invalid format specification [{}]!", specification); 165 | } 166 | i64 base = 10; 167 | codeunit_sequence_view prefix; 168 | switch (type) 169 | { 170 | case 'b': 171 | { 172 | base = 2; 173 | prefix = "0b"_cuqv; 174 | } 175 | break; 176 | case 'c': 177 | { 178 | codeunit_sequence result; 179 | result.append(static_cast(value)); 180 | return result; 181 | } 182 | case 'd': 183 | break; 184 | case 'o': 185 | { 186 | base = 8; 187 | prefix = "0o"_cuqv; 188 | } 189 | break; 190 | case 'x': 191 | { 192 | base = 16; 193 | prefix = "0x"_cuqv; 194 | } 195 | break; 196 | default: 197 | break; 198 | } 199 | if(!with_prefix) 200 | prefix = ""_cuqv; 201 | 202 | const codeunit_sequence_view sign = (value < 0) ? "-"_cuqv : ""_cuqv; 203 | const u64 digit_count = get_integer_digit_count(value, base); 204 | const u64 preserve = holding == global_constant::SIZE_INVALID ? digit_count : maximum(holding, digit_count); 205 | const u64 zero_count = preserve - digit_count; 206 | codeunit_sequence result( sign.size() + prefix.size() + preserve ); 207 | result 208 | .append(sign) 209 | .append(prefix) 210 | .append('0', zero_count); 211 | const u64 digits_start = result.size(); 212 | i64 remaining = value >= 0 ? value : -value; 213 | for(u64 i = 0; i < digit_count; ++i) 214 | { 215 | const char digit = from_digit(remaining % base); 216 | remaining /= base; 217 | result.append(digit); 218 | } 219 | result.reverse(digits_start); 220 | return result; 221 | } 222 | 223 | codeunit_sequence format_float(const f64& value, const codeunit_sequence_view& specification) 224 | { 225 | if (std::isinf(value)) 226 | return codeunit_sequence{ value < 0 ? "-inf"_cuqv : "inf"_cuqv }; 227 | if (std::isnan(value)) 228 | return codeunit_sequence{ "nan"_cuqv }; 229 | u64 precision = global_constant::SIZE_INVALID; 230 | // char type = 'g'; 231 | if (!specification.is_empty()) 232 | { 233 | codeunit_sequence_view parsing = specification; 234 | if("aefg"_cuqv.contains( parsing.read_from_last(0) )) 235 | { 236 | // type = parsing.read_from_last(0); 237 | parsing = parsing.subview(0, parsing.size() - 1); 238 | } 239 | if(!parsing.is_empty()) 240 | { 241 | if(const u64 dot_index = parsing.index_of('.'); dot_index != global_constant::INDEX_INVALID) 242 | { 243 | const codeunit_sequence_view precision_view = parsing.subview(dot_index + 1); 244 | const auto [ last, error ] = std::from_chars(precision_view.data(), precision_view.cend().data(), precision); 245 | OPEN_STRING_CHECK(last == precision_view.cend().data(), "Invalid format specification [{}]!", specification); 246 | parsing = parsing.subview(0, dot_index); 247 | } 248 | } 249 | OPEN_STRING_CHECK(parsing.is_empty(), "Invalid format specification [{}]!", specification); 250 | } 251 | // switch (type) 252 | // { 253 | // case 'a': 254 | // break; 255 | // case 'e': 256 | // break; 257 | // case 'f': 258 | // break; 259 | // case 'g': 260 | // break; 261 | // default: 262 | // break; 263 | // } 264 | static constexpr u64 max_precision = 9; 265 | OPEN_STRING_CHECK(precision == global_constant::SIZE_INVALID || precision <= max_precision, "Too high precision for float type [{}]!", precision); 266 | codeunit_sequence result; 267 | const bool negative = value < 0; 268 | if(negative) 269 | result.append("-"); 270 | f64 remaining = negative ? -value : value; 271 | const u64 decimal = static_cast(remaining); 272 | remaining -= static_cast(decimal); 273 | if(precision == global_constant::SIZE_INVALID) 274 | { 275 | static constexpr u64 max_floating = power(10, max_precision + 1); 276 | u64 floating = 1; 277 | u64 count_nine = 0; 278 | while(floating <= max_floating) 279 | { 280 | remaining *= 10; 281 | floating *= 10; 282 | const u64 ones = static_cast(remaining); 283 | floating += ones; 284 | remaining -= static_cast(ones); 285 | if(ones == 9) 286 | { 287 | ++count_nine; 288 | if(count_nine == global_constant::TOLERANCE_EXPONENT) 289 | { 290 | floating += 1; 291 | floating = floating / power(10, global_constant::TOLERANCE_EXPONENT); 292 | break; 293 | } 294 | } 295 | else 296 | { 297 | count_nine = 0; 298 | } 299 | if(remaining < global_constant::TOLERANCE) 300 | break; 301 | } 302 | if(floating > 10) 303 | { 304 | const codeunit_sequence decimal_part = format_integer(decimal, { }); 305 | const u64 dot_position = result.size() + decimal_part.size(); 306 | const codeunit_sequence floating_part = format_integer(floating, { }); 307 | result.reserve(dot_position + floating_part.size()); 308 | result .append(decimal_part) 309 | .append(floating_part) 310 | .write_at(dot_position, '.'); 311 | } 312 | if(floating == 2) 313 | { 314 | result.append(format_integer(decimal + 1, { })); 315 | } 316 | } 317 | else 318 | { 319 | u64 floating = 1; 320 | for(u64 i = 0; i < precision; ++i) 321 | { 322 | remaining *= 10; 323 | floating *= 10; 324 | const u64 ones = static_cast(remaining); 325 | floating += ones; 326 | remaining -= static_cast(ones); 327 | } 328 | const codeunit_sequence decimal_part = format_integer(decimal, { }); 329 | const u64 dot_position = result.size() + decimal_part.size(); 330 | const codeunit_sequence floating_part = format_integer(floating, { }); 331 | result.reserve(dot_position + floating_part.size()); 332 | result .append(decimal_part) 333 | .append(floating_part) 334 | .write_at(dot_position, '.'); 335 | } 336 | return result; 337 | } 338 | } 339 | } 340 | -------------------------------------------------------------------------------- /source/text.cpp: -------------------------------------------------------------------------------- 1 | 2 | #include "text.h" 3 | 4 | #include 5 | #include "common/functions.h" 6 | #include "wide_text.h" 7 | 8 | namespace ostr 9 | { 10 | text::text() noexcept = default; 11 | text::text(const text&) noexcept = default; 12 | text::text(text&&) noexcept = default; 13 | text& text::operator=(const text&) noexcept = default; 14 | text& text::operator=(text&&) noexcept = default; 15 | text::~text() = default; 16 | 17 | text::text(const char* str) noexcept 18 | : sequence_{ str } 19 | { } 20 | 21 | text::text(const text_view& view) noexcept 22 | : sequence_{ view.raw() } 23 | { } 24 | 25 | text::text(codeunit_sequence sequence) noexcept 26 | : sequence_{ std::move(sequence) } 27 | { } 28 | 29 | text::text(const codeunit_sequence_view& sequence) noexcept 30 | : sequence_{ sequence } 31 | { } 32 | 33 | text text::from_utf8(const char* string_utf8) noexcept 34 | { 35 | return text{ codeunit_sequence_view{ string_utf8 } }; 36 | } 37 | 38 | text text::from_utf16(const char16_t* string_utf16) noexcept 39 | { 40 | const char16_t* p = string_utf16; 41 | u64 size = 0; 42 | while(*p != 0) 43 | { 44 | size += unicode::parse_utf8_length(*p); 45 | ++p; 46 | } 47 | codeunit_sequence sequence{ size + 1 }; 48 | p = string_utf16; 49 | while(*p != 0) 50 | { 51 | const u64 surrogate_sequence_length = unicode::utf16::parse_utf16_length(*p); 52 | sequence.append(codepoint{ p }); 53 | p += surrogate_sequence_length; 54 | } 55 | return text{ std::move(sequence) }; 56 | } 57 | 58 | text text::from_utf32(const char32_t* string_utf32) noexcept 59 | { 60 | const char32_t* p = string_utf32; 61 | u64 size = 0; 62 | while(*p != 0) 63 | { 64 | size += unicode::parse_utf8_length(*p); 65 | ++p; 66 | } 67 | codeunit_sequence sequence{ size }; 68 | p = string_utf32; 69 | while(*p != 0) 70 | { 71 | sequence.append(codepoint{ *p }); 72 | ++p; 73 | } 74 | return text{ std::move(sequence) }; 75 | } 76 | 77 | text text::from_wide(const wchar_t* wide_string) noexcept 78 | { 79 | const wide_text wt{ wide_string }; 80 | codeunit_sequence decoded; 81 | wt.decode(decoded); 82 | return text{ std::move(decoded) }; 83 | } 84 | 85 | text::iterator::iterator(text& t, const u64 from, const u64 size) noexcept 86 | : from{ from } 87 | , size{ size } 88 | , owner{ &t } 89 | { } 90 | 91 | text::iterator::codepoint_accessor::codepoint_accessor(iterator& iter) noexcept 92 | : it_{ iter } 93 | { } 94 | 95 | text::iterator::codepoint_accessor& text::iterator::codepoint_accessor::operator=(const char c) noexcept 96 | { 97 | return *this = codepoint{ c }; 98 | } 99 | 100 | text::iterator::codepoint_accessor& text::iterator::codepoint_accessor::operator=(const char32_t cp) noexcept 101 | { 102 | return *this = codepoint{ cp }; 103 | } 104 | 105 | text::iterator::codepoint_accessor& text::iterator::codepoint_accessor::operator=(const codepoint& cp) noexcept 106 | { 107 | this->assign(codeunit_sequence_view{ cp }); 108 | return *this; 109 | } 110 | 111 | text::iterator::codepoint_accessor& text::iterator::codepoint_accessor::operator=(const text_view& tv) noexcept 112 | { 113 | this->assign(tv.raw()); 114 | return *this; 115 | } 116 | 117 | text::iterator::codepoint_accessor& text::iterator::codepoint_accessor::operator=(const text& t) noexcept 118 | { 119 | return *this = t.view(); 120 | } 121 | 122 | codepoint text::iterator::codepoint_accessor::get_codepoint() const noexcept 123 | { 124 | return this->it_.get_codepoint(); 125 | } 126 | 127 | text::iterator::codepoint_accessor::operator codepoint() const noexcept 128 | { 129 | return this->get_codepoint(); 130 | } 131 | 132 | void text::iterator::codepoint_accessor::assign(const codeunit_sequence_view& sequence_view) const noexcept 133 | { 134 | if(this->it_.is_valid()) 135 | { 136 | this->it_.owner->sequence_.replace(sequence_view, it_.from, it_.size); 137 | this->it_.size = sequence_view.size(); 138 | } 139 | } 140 | 141 | bool text::iterator::is_valid() const noexcept 142 | { 143 | return this->from != global_constant::INDEX_INVALID; 144 | } 145 | 146 | u64 text::iterator::raw_size() const noexcept 147 | { 148 | return this->size; 149 | } 150 | 151 | codepoint text::iterator::get_codepoint() const noexcept 152 | { 153 | const codeunit_sequence_view subview = this->owner->sequence_.subview( this->from, this->size ); 154 | return codepoint{ subview.data(), static_cast(subview.size()) }; 155 | } 156 | 157 | codepoint text::iterator::operator*() const noexcept 158 | { 159 | return this->get_codepoint(); 160 | } 161 | 162 | text::iterator::codepoint_accessor text::iterator::operator*() noexcept 163 | { 164 | return codepoint_accessor{ *this }; 165 | } 166 | 167 | text::iterator& text::iterator::operator++() noexcept 168 | { 169 | const u64 next_start = this->from + this->size; 170 | const char c = this->owner->sequence_.read_at(next_start); 171 | if(const u8 code_size = unicode::parse_utf8_length(c); code_size != 0) 172 | { 173 | this->from = next_start; 174 | this->size = code_size; 175 | } 176 | else 177 | { 178 | this->from = global_constant::INDEX_INVALID; 179 | this->size = SIZE_MAX; 180 | } 181 | return *this; 182 | } 183 | 184 | text::iterator text::iterator::operator++(int) noexcept 185 | { 186 | const iterator tmp = *this; 187 | ++*this; 188 | return tmp; 189 | } 190 | 191 | text::iterator& text::iterator::operator--() noexcept 192 | { 193 | u64 current_from = this->from; 194 | u8 code_size = 0; 195 | while(code_size == 0) 196 | { 197 | --current_from; 198 | code_size = unicode::parse_utf8_length(this->owner->sequence_.read_at(current_from)); 199 | } 200 | this->from = current_from; 201 | this->size = code_size; 202 | return *this; 203 | } 204 | 205 | text::iterator text::iterator::operator--(int) noexcept 206 | { 207 | const iterator tmp = *this; 208 | --*this; 209 | return tmp; 210 | } 211 | 212 | bool text::iterator::operator==(const iterator& rhs) const noexcept 213 | { 214 | return this->owner == rhs.owner && this->from == rhs.from && this->size == rhs.size; 215 | } 216 | 217 | bool text::iterator::operator!=(const iterator& rhs) const noexcept 218 | { 219 | return !(*this == rhs); 220 | } 221 | 222 | text::iterator text::begin() noexcept 223 | { 224 | return iterator{ *this, 0, this->cbegin().raw_size() }; 225 | } 226 | 227 | text::const_iterator text::begin() const noexcept 228 | { 229 | return this->view().begin(); 230 | } 231 | 232 | text::iterator text::end() noexcept 233 | { 234 | return iterator{ *this, global_constant::INDEX_INVALID, global_constant::SIZE_INVALID }; 235 | } 236 | 237 | text::const_iterator text::end() const noexcept 238 | { 239 | return this->view().end(); 240 | } 241 | 242 | text::const_iterator text::cbegin() const noexcept 243 | { 244 | return this->view().cbegin(); 245 | } 246 | 247 | text::const_iterator text::cend() const noexcept 248 | { 249 | return this->view().cend(); 250 | } 251 | 252 | codeunit_sequence& text::raw() & noexcept 253 | { 254 | return this->sequence_; 255 | } 256 | 257 | const codeunit_sequence& text::raw() const& noexcept 258 | { 259 | return this->sequence_; 260 | } 261 | 262 | codeunit_sequence text::raw() && noexcept 263 | { 264 | return std::forward(this->sequence_); 265 | } 266 | 267 | text_view text::view() const noexcept 268 | { 269 | return this->sequence_.view(); 270 | } 271 | 272 | u64 text::size() const noexcept 273 | { 274 | return this->view().size(); 275 | } 276 | 277 | bool text::is_empty() const noexcept 278 | { 279 | return this->sequence_.is_empty(); 280 | } 281 | 282 | bool text::operator==(const text_view& rhs) const noexcept 283 | { 284 | return this->view() == rhs; 285 | } 286 | 287 | bool text::operator==(const text& rhs) const noexcept 288 | { 289 | return this->view() == rhs.view(); 290 | } 291 | 292 | bool text::operator==(const char* rhs) const noexcept 293 | { 294 | return this->view() == rhs; 295 | } 296 | 297 | bool text::operator!=(const text_view& rhs) const noexcept 298 | { 299 | return this->view() != rhs; 300 | } 301 | 302 | bool text::operator!=(const text& rhs) const noexcept 303 | { 304 | return this->view() != rhs.view(); 305 | } 306 | 307 | bool text::operator!=(const char* rhs) const noexcept 308 | { 309 | return this->view() != rhs; 310 | } 311 | 312 | text& text::append(const text_view& rhs) noexcept 313 | { 314 | this->sequence_.append(rhs.raw()); 315 | return *this; 316 | } 317 | 318 | text& text::append(const text& rhs) noexcept 319 | { 320 | return this->append(rhs.view()); 321 | } 322 | 323 | text& text::append(const codepoint& cp) noexcept 324 | { 325 | this->sequence_.append(cp); 326 | return *this; 327 | } 328 | 329 | text& text::append(const char* rhs) noexcept 330 | { 331 | this->sequence_.append(rhs); 332 | return *this; 333 | } 334 | 335 | text& text::append(const char codeunit, const u64 count) noexcept 336 | { 337 | this->sequence_.append(codeunit, count); 338 | return *this; 339 | } 340 | 341 | text& text::operator+=(const text_view& rhs) noexcept 342 | { 343 | return this->append(rhs); 344 | } 345 | 346 | text& text::operator+=(const text& rhs) noexcept 347 | { 348 | return this->append(rhs); 349 | } 350 | 351 | text& text::operator+=(const codepoint& cp) noexcept 352 | { 353 | return this->append(cp); 354 | } 355 | 356 | text& text::operator+=(const char* rhs) noexcept 357 | { 358 | return this->append(rhs); 359 | } 360 | 361 | text& text::operator+=(const char codeunit) noexcept 362 | { 363 | return this->append(codeunit); 364 | } 365 | 366 | text_view text::subview(const u64 from, const u64 size) const noexcept 367 | { 368 | return this->view().subview(from, size); 369 | } 370 | 371 | text& text::subtext(const u64 from, const u64 size) noexcept 372 | { 373 | const u64 self_size = this->size(); 374 | if(from >= self_size || size == 0) 375 | { 376 | this->empty(); 377 | return *this; 378 | } 379 | if(from == 0 && size >= self_size) 380 | // Do nothing 381 | return *this; 382 | const u64 actual_size = minimum({ size, self_size - from }); 383 | const u64 lower_bound = this->view().get_codepoint_index( from ); 384 | const u64 upper_bound = this->view().get_codepoint_index( from + actual_size ); 385 | this->sequence_.subsequence(lower_bound, upper_bound - lower_bound); 386 | return *this; 387 | } 388 | 389 | u64 text::index_of(const text_view& pattern, const u64 from, const u64 size) const noexcept 390 | { 391 | return this->view().index_of(pattern, from, size); 392 | } 393 | 394 | u64 text::last_index_of(const text_view& pattern, const u64 from, const u64 size) const noexcept 395 | { 396 | return this->view().last_index_of(pattern, from, size); 397 | } 398 | 399 | u64 text::count(const text_view& pattern, const u64 from, const u64 size) const noexcept 400 | { 401 | return this->view().count(pattern, from, size); 402 | } 403 | 404 | bool text::starts_with(const text_view& prefix) const noexcept 405 | { 406 | return this->view().starts_with(prefix); 407 | } 408 | 409 | bool text::ends_with(const text_view& suffix) const noexcept 410 | { 411 | return this->view().ends_with(suffix); 412 | } 413 | 414 | void text::empty() noexcept 415 | { 416 | this->sequence_.empty(); 417 | } 418 | 419 | text& text::write_at(const u64 index, const codepoint cp) noexcept 420 | { 421 | this->replace(text_view{ cp }, index, 1); 422 | return *this; 423 | } 424 | 425 | codepoint text::read_at(const u64 index) const noexcept 426 | { 427 | return this->view().read_at(index); 428 | } 429 | 430 | codepoint text::operator[](const u64 index) const noexcept 431 | { 432 | return this->view().read_at(index); 433 | } 434 | 435 | text& text::reverse(const u64 from, const u64 size) noexcept 436 | { 437 | u64 raw_from = from; 438 | u64 raw_size = size; 439 | this->view().get_codeunit_range(raw_from, raw_size); 440 | this->sequence_.reverse(raw_from, raw_size); 441 | for(u64 i = 0; i < raw_size; ++i) 442 | if(const u8 code_size = unicode::parse_utf8_length( this->sequence_.read_at(raw_from + i) ); code_size != 0) 443 | this->sequence_.reverse(raw_from + i + 1 - code_size, code_size); 444 | return *this; 445 | } 446 | 447 | u32 text::split(const text_view& splitter, sequence& pieces, const bool cull_empty) const noexcept 448 | { 449 | text_view view = this->view(); 450 | u32 count = 0; 451 | while(true) 452 | { 453 | const auto [ left, right ] = view.split(splitter); 454 | if(!cull_empty || !left.is_empty()) 455 | pieces.push_back(left); 456 | ++count; 457 | if(right.is_empty()) 458 | break; 459 | view = right; 460 | } 461 | return count; 462 | } 463 | 464 | text& text::replace(const text_view& destination, const text_view& source, const u64 from, const u64 size) 465 | { 466 | u64 raw_from = from; 467 | u64 raw_size = size; 468 | this->view().get_codeunit_range(raw_from, raw_size); 469 | this->sequence_.replace(destination.raw(), source.raw(), raw_from, raw_size); 470 | return *this; 471 | } 472 | 473 | text& text::replace(const text_view& destination, const u64 from, const u64 size) 474 | { 475 | u64 raw_from = from; 476 | u64 raw_size = size; 477 | this->view().get_codeunit_range(raw_from, raw_size); 478 | this->sequence_.replace(destination.raw(), raw_from, raw_size); 479 | return *this; 480 | } 481 | 482 | text& text::self_remove_prefix(const text_view& prefix) noexcept 483 | { 484 | this->sequence_.self_remove_prefix(prefix.raw()); 485 | return *this; 486 | } 487 | 488 | text& text::self_remove_suffix(const text_view& suffix) noexcept 489 | { 490 | this->sequence_.self_remove_suffix(suffix.raw()); 491 | return *this; 492 | } 493 | 494 | text_view text::view_remove_prefix(const text_view& prefix) const noexcept 495 | { 496 | return this->view().remove_prefix(prefix); 497 | } 498 | 499 | text_view text::view_remove_suffix(const text_view& suffix) const noexcept 500 | { 501 | return this->view().remove_suffix(suffix); 502 | } 503 | 504 | text& text::self_trim_start(const text_view& characters) noexcept 505 | { 506 | if(this->is_empty()) 507 | return *this; 508 | u64 codeunit_index = 0; 509 | for(auto it = this->cbegin(); it != this->cend(); ++it) 510 | { 511 | if(!characters.contains(it.get_codepoint())) 512 | break; 513 | codeunit_index += it.raw_size(); 514 | } 515 | this->sequence_.subsequence(codeunit_index); 516 | return *this; 517 | } 518 | 519 | text& text::self_trim_end(const text_view& characters) noexcept 520 | { 521 | if(this->is_empty()) 522 | return *this; 523 | u64 codeunit_index = this->raw().size(); 524 | auto it = this->cend(); 525 | while(true) 526 | { 527 | --it; 528 | if(!characters.contains(it.get_codepoint())) 529 | break; 530 | codeunit_index -= it.raw_size(); 531 | if(it == this->cbegin()) 532 | break; 533 | } 534 | this->sequence_.subsequence(0, codeunit_index); 535 | return *this; 536 | } 537 | 538 | text& text::self_trim(const text_view& characters) noexcept 539 | { 540 | return this->self_trim_end(characters).self_trim_start(characters); 541 | } 542 | 543 | text_view text::view_trim_start(const text_view& characters) const noexcept 544 | { 545 | return this->view().trim_start(characters); 546 | } 547 | 548 | text_view text::view_trim_end(const text_view& characters) const noexcept 549 | { 550 | return this->view().trim_end(characters); 551 | } 552 | 553 | text_view text::view_trim(const text_view& characters) const noexcept 554 | { 555 | return this->view().trim(characters); 556 | } 557 | 558 | const char* text::c_str() const noexcept 559 | { 560 | return this->sequence_.c_str(); 561 | } 562 | 563 | bool operator==(const text_view& lhs, const text& rhs) noexcept 564 | { 565 | return rhs == lhs; 566 | } 567 | } 568 | 569 | -------------------------------------------------------------------------------- /source/wide_text.cpp: -------------------------------------------------------------------------------- 1 | 2 | #include "wide_text.h" 3 | 4 | #include "text.h" 5 | #include "text_view.h" 6 | 7 | namespace ostr 8 | { 9 | namespace details 10 | { 11 | [[nodiscard]] constexpr u64 get_sequence_length(const wchar_t* str) noexcept 12 | { 13 | if(!str) 14 | return 0; 15 | u64 count = 0; 16 | while(str[count] != 0) 17 | ++count; 18 | return count; 19 | } 20 | } 21 | 22 | wide_text::wide_text(const wchar_t* wide_str) noexcept 23 | { 24 | this->operator=(wide_str); 25 | } 26 | 27 | wide_text::wide_text(const codeunit_sequence_view& view) noexcept 28 | { 29 | this->operator=(view); 30 | } 31 | 32 | wide_text& wide_text::operator=(const wchar_t* wide_str) noexcept 33 | { 34 | this->sequence_.empty(); 35 | this->sequence_.append(wide_str, details::get_sequence_length(wide_str) + 1); 36 | return *this; 37 | } 38 | 39 | wide_text& wide_text::operator=(const codeunit_sequence_view& view) noexcept 40 | { 41 | const text_view tv{ view }; 42 | this->sequence_.empty(); 43 | this->sequence_.reserve(tv.size() + 1); 44 | for(const codepoint cp : tv) 45 | { 46 | #if _WIN64 47 | const auto utf16_pair = unicode::utf32_to_utf16(cp.get_codepoint()); 48 | this->sequence_.push_back(utf16_pair.at(0)); 49 | if(utf16_pair.at(1) != 0) 50 | this->sequence_.push_back(utf16_pair.at(1)); 51 | #elif __linux__ || __MACH__ 52 | this->sequence_.push_back(cp.get_codepoint()); 53 | #endif 54 | } 55 | this->sequence_.push_back(L'\0'); 56 | return *this; 57 | } 58 | 59 | const wchar_t* wide_text::data() const noexcept 60 | { 61 | return this->sequence_.data(); 62 | } 63 | 64 | void wide_text::decode(codeunit_sequence& out) const noexcept 65 | { 66 | #if _WIN64 67 | out = text::from_utf16(reinterpret_cast(this->sequence_.data())).raw(); 68 | #elif __linux__ || __MACH__ 69 | out = text::from_utf32(reinterpret_cast(this->sequence_.data())).raw(); 70 | #endif 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /test/gtest_printers_extension.h: -------------------------------------------------------------------------------- 1 | 2 | #pragma once 3 | 4 | #include 5 | #include "unicode.h" 6 | #include "text.h" 7 | #include "format.h" 8 | 9 | #define DEFINE_PRINT_TO(T) inline void PrintTo(const T& v, std::ostream* os) { *os << v; } 10 | 11 | namespace ostr 12 | { 13 | inline std::ostream& operator<<(std::ostream& os, const codeunit_sequence_view& v) 14 | { 15 | for(const auto c : v) 16 | os << c; 17 | return os; 18 | } 19 | 20 | inline std::ostream& operator<<(std::ostream& os, const codeunit_sequence& v) 21 | { 22 | return os << v.view(); 23 | } 24 | 25 | inline std::ostream& operator<<(std::ostream& os, const codepoint& v) 26 | { 27 | const codeunit_sequence s = codeunit_sequence::join(v, ", "_cuqv); 28 | os << "[ " << s << " ] = " << v.get_codepoint(); 29 | return os; 30 | } 31 | 32 | inline std::ostream& operator<<(std::ostream& os, const text_view& v) 33 | { 34 | os << v.raw(); 35 | return os; 36 | } 37 | 38 | inline std::ostream& operator<<(std::ostream& os, const text& v) 39 | { 40 | os << v.view(); 41 | return os; 42 | } 43 | 44 | DEFINE_PRINT_TO(codeunit_sequence_view) 45 | DEFINE_PRINT_TO(codeunit_sequence) 46 | DEFINE_PRINT_TO(text_view) 47 | DEFINE_PRINT_TO(text) 48 | } 49 | -------------------------------------------------------------------------------- /test/main.cpp: -------------------------------------------------------------------------------- 1 | 2 | #include "gtest/gtest.h" 3 | 4 | int main(int argc, char **argv) { 5 | ::testing::InitGoogleTest(&argc, argv); 6 | return RUN_ALL_TESTS(); 7 | } -------------------------------------------------------------------------------- /test/pch.h: -------------------------------------------------------------------------------- 1 | 2 | #pragma once 3 | 4 | #include "scoped_memory_leak_detector.h" 5 | #include "gtest/gtest.h" 6 | #include "gtest_printers_extension.h" 7 | #include "common/definitions.h" 8 | -------------------------------------------------------------------------------- /test/scoped_memory_leak_detector.h: -------------------------------------------------------------------------------- 1 | 2 | #pragma once 3 | 4 | #ifdef _WIN64 5 | 6 | #define _CRTDBG_MAP_ALLOC 7 | #include 8 | #include 9 | 10 | class scoped_memory_leak_detector 11 | { 12 | public: 13 | scoped_memory_leak_detector(const char* filename, const int line) 14 | : filename_(filename) 15 | , line_(line) 16 | { 17 | _CrtMemCheckpoint(&memory_state_); 18 | } 19 | 20 | ~scoped_memory_leak_detector() { 21 | [[maybe_unused]] _CrtMemState state_ending; 22 | [[maybe_unused]] _CrtMemState state_difference; 23 | _CrtMemCheckpoint(&state_ending); 24 | if (_CrtMemDifference(&state_difference, &memory_state_, &state_ending)) 25 | { 26 | _CrtMemDumpAllObjectsSince( &memory_state_ ); 27 | std::cerr << "Detector@ " << filename_ << " (" << line_ << "): Memory leak detected!" << std::endl; 28 | } 29 | } 30 | 31 | scoped_memory_leak_detector(const scoped_memory_leak_detector&) = delete; 32 | scoped_memory_leak_detector(scoped_memory_leak_detector&&) = delete; 33 | scoped_memory_leak_detector& operator=(const scoped_memory_leak_detector&) = delete; 34 | scoped_memory_leak_detector& operator=(scoped_memory_leak_detector&&) = delete; 35 | 36 | private: 37 | [[maybe_unused]] _CrtMemState memory_state_ { }; 38 | const char* filename_; 39 | int line_; 40 | }; 41 | 42 | #define SCOPED_DETECT_MEMORY_LEAK() scoped_memory_leak_detector detector{ __FILE__, __LINE__ }; 43 | 44 | #else 45 | 46 | #define SCOPED_DETECT_MEMORY_LEAK() 47 | 48 | #endif 49 | -------------------------------------------------------------------------------- /test/test__codeunit_sequence.cpp: -------------------------------------------------------------------------------- 1 | 2 | // ReSharper disable StringLiteralTypo 3 | #include "pch.h" 4 | 5 | using namespace ostr; 6 | 7 | // this is a struct that has same memory layout as struct codeunit_sequence 8 | // convert the pointer of struct codeunit_sequence to this struct and access the private members. 9 | struct codeunit_sequence_accessor 10 | { 11 | static constexpr u64 SSO_SIZE_MAX = 14; 12 | 13 | struct sso 14 | { 15 | u8 alloc : 1; 16 | u8 size : 7; 17 | std::array data; 18 | }; 19 | 20 | struct norm 21 | { 22 | u32 alloc : 1; 23 | u32 size : 15; 24 | u32 capacity; 25 | char* data; 26 | }; 27 | 28 | [[nodiscard]] sso& as_sso() 29 | { 30 | return reinterpret_cast(store); 31 | } 32 | [[nodiscard]] const sso& as_sso() const 33 | { 34 | return reinterpret_cast(store); 35 | } 36 | 37 | [[nodiscard]] norm& as_norm() 38 | { 39 | return reinterpret_cast(store); 40 | } 41 | [[nodiscard]] const norm& as_norm() const 42 | { 43 | return reinterpret_cast(store); 44 | } 45 | 46 | /// @return is this a sequence with less than 15 chars 47 | [[nodiscard]] bool is_short() const 48 | { 49 | return !as_sso().alloc; 50 | } 51 | 52 | std::array store; 53 | }; 54 | 55 | #define ACCESS(cuq) ((codeunit_sequence_accessor*)(&(cuq))) 56 | 57 | TEST(codeunit_sequence, construct) 58 | { 59 | SCOPED_DETECT_MEMORY_LEAK() 60 | 61 | const codeunit_sequence cuq_default_ctor; 62 | const codeunit_sequence cuq_ctor_supplement("𪚥😁"); 63 | const codeunit_sequence cuq_ctor_long("👌𪚥1😁 你好"); 64 | 65 | EXPECT_EQ(0, cuq_default_ctor.size()); 66 | EXPECT_EQ(8, cuq_ctor_supplement.size()); 67 | EXPECT_EQ(20, cuq_ctor_long.size()); 68 | } 69 | 70 | TEST(codeunit_sequence, concatenate) 71 | { 72 | SCOPED_DETECT_MEMORY_LEAK() 73 | { 74 | const codeunit_sequence cuq_ai("😘"); 75 | 76 | const codeunit_sequence combine = codeunit_sequence::build("我", cuq_ai, cuq_ai, codeunit_sequence("あなた"), "!"_cuqv); 77 | const codeunit_sequence correct("我😘😘あなた!"); 78 | 79 | EXPECT_EQ(combine, correct); 80 | } 81 | } 82 | 83 | TEST(codeunit_sequence, split) 84 | { 85 | SCOPED_DETECT_MEMORY_LEAK() 86 | { 87 | const codeunit_sequence s("This is a long string"_cuqv); 88 | std::vector result; 89 | s.split(" "_cuqv, result); 90 | const std::vector answer { "This"_cuqv, "is"_cuqv, "a"_cuqv, "long"_cuqv, "string"_cuqv }; 91 | EXPECT_EQ(result, answer); 92 | } 93 | } 94 | 95 | TEST(codeunit_sequence, trim) 96 | { 97 | SCOPED_DETECT_MEMORY_LEAK() 98 | { 99 | { 100 | codeunit_sequence cuq_src(" 123 1234 "); 101 | cuq_src.self_trim_start(); 102 | EXPECT_EQ(cuq_src, "123 1234 "); 103 | } 104 | { 105 | codeunit_sequence cuq_src(" 123 1234 "); 106 | cuq_src.self_trim_end(); 107 | EXPECT_EQ(cuq_src, " 123 1234"); 108 | } 109 | { 110 | codeunit_sequence cuq_src(" 123 1234 "); 111 | cuq_src.self_trim(); 112 | EXPECT_EQ(cuq_src, "123 1234"); 113 | } 114 | } 115 | 116 | { 117 | { 118 | const codeunit_sequence cuq_origin(" 你 好 😙 \t "); 119 | codeunit_sequence cuq = cuq_origin; 120 | EXPECT_EQ(cuq, " 你 好 😙 \t "); 121 | cuq.self_trim_start(); 122 | EXPECT_EQ(cuq, "你 好 😙 \t "); 123 | } 124 | { 125 | codeunit_sequence cuq(" 你 好 😙 \t "); 126 | EXPECT_EQ(cuq.self_trim_end(), " 你 好 😙"); 127 | } 128 | { 129 | codeunit_sequence cuq(" 你 好 😙 \t "_cuqv); 130 | EXPECT_EQ(cuq.self_trim(), "你 好 😙"); 131 | } 132 | } 133 | 134 | { 135 | codeunit_sequence cuq("你 好 😙"); 136 | EXPECT_EQ(cuq.self_trim_start(), "你 好 😙"); 137 | EXPECT_EQ(cuq.self_trim_end(), "你 好 😙"); 138 | EXPECT_EQ(cuq.self_trim(), "你 好 😙"); 139 | } 140 | { 141 | { 142 | codeunit_sequence cuq(" "); 143 | EXPECT_EQ(cuq.self_trim_start(), ""); 144 | EXPECT_TRUE(cuq.is_empty()); 145 | } 146 | { 147 | codeunit_sequence cuq(" "); 148 | EXPECT_EQ(cuq.self_trim_end(), ""); 149 | EXPECT_TRUE(cuq.is_empty()); 150 | } 151 | { 152 | codeunit_sequence cuq(" "); 153 | EXPECT_EQ(cuq.self_trim(), ""); 154 | EXPECT_TRUE(cuq.is_empty()); 155 | } 156 | } 157 | { 158 | { 159 | codeunit_sequence cuq; 160 | EXPECT_EQ(cuq.self_trim_start(), ""); 161 | EXPECT_TRUE(cuq.is_empty()); 162 | } 163 | { 164 | codeunit_sequence cuq(" "); 165 | EXPECT_EQ(cuq.self_trim_end(), ""); 166 | EXPECT_TRUE(cuq.is_empty()); 167 | } 168 | { 169 | codeunit_sequence cuq(" "); 170 | EXPECT_EQ(cuq.self_trim(), ""); 171 | EXPECT_TRUE(cuq.is_empty()); 172 | } 173 | } 174 | { 175 | { 176 | codeunit_sequence cuq("你 好 😙"_cuqv); 177 | EXPECT_EQ(cuq.self_trim_start("你 😙"_cuqv), "好 😙"_cuqv); 178 | } 179 | { 180 | codeunit_sequence cuq("你 好 😙"_cuqv); 181 | EXPECT_EQ(cuq.self_trim_end("你 😙"_cuqv), "你 \xE5\xA5"_cuqv); 182 | } 183 | { 184 | codeunit_sequence cuq("你 好 😙"_cuqv); 185 | EXPECT_EQ(cuq.self_trim("你 😙"_cuqv), "\xE5\xA5"_cuqv); 186 | } 187 | } 188 | } 189 | 190 | TEST(codeunit_sequence, empty) 191 | { 192 | SCOPED_DETECT_MEMORY_LEAK() 193 | { 194 | codeunit_sequence cuq("12345"); 195 | cuq.empty(50); 196 | const codeunit_sequence_accessor* accessor = ACCESS(cuq); 197 | EXPECT_TRUE(!accessor->is_short()); 198 | EXPECT_EQ(0, accessor->as_norm().size); 199 | EXPECT_EQ(63, accessor->as_norm().capacity); 200 | } 201 | { 202 | codeunit_sequence cuq("This is a sentence with 33 words."_cuqv); 203 | const codeunit_sequence_accessor* accessor = ACCESS(cuq); 204 | cuq.empty(100); 205 | EXPECT_TRUE(!accessor->is_short()); 206 | EXPECT_EQ(0, cuq.size()); 207 | EXPECT_EQ(127, accessor->as_norm().capacity); 208 | cuq.empty(20); // Do not reallocate 209 | EXPECT_TRUE(!accessor->is_short()); 210 | EXPECT_EQ(0, accessor->as_norm().size); 211 | EXPECT_EQ(127, accessor->as_norm().capacity); 212 | cuq.empty(10); // Do not reallocate 213 | EXPECT_TRUE(!accessor->is_short()); 214 | EXPECT_EQ(0, accessor->as_sso().size); 215 | } 216 | } 217 | 218 | TEST(codeunit_sequence, reserve) 219 | { 220 | SCOPED_DETECT_MEMORY_LEAK() 221 | { 222 | codeunit_sequence cuq("12345"); 223 | const codeunit_sequence_accessor* accessor = ACCESS(cuq); 224 | cuq.reserve(10); // Do nothing 225 | EXPECT_TRUE(accessor->is_short()); 226 | EXPECT_EQ(5, accessor->as_sso().size); 227 | EXPECT_EQ("12345"_cuqv, cuq); 228 | cuq.reserve(50); 229 | EXPECT_TRUE(!accessor->is_short()); 230 | EXPECT_EQ(5, accessor->as_norm().size); 231 | EXPECT_EQ(63, accessor->as_norm().capacity); 232 | } 233 | { 234 | codeunit_sequence cuq("This is a sentence with 33 words."_cuqv); 235 | const codeunit_sequence_accessor* accessor = ACCESS(cuq); 236 | cuq.reserve(10); // Do nothing 237 | EXPECT_TRUE(!accessor->is_short()); 238 | EXPECT_EQ(33, accessor->as_norm().size); 239 | EXPECT_EQ(63, accessor->as_norm().capacity); 240 | EXPECT_EQ("This is a sentence with 33 words."_cuqv, cuq); 241 | cuq.reserve(100); 242 | EXPECT_EQ(33, accessor->as_norm().size); 243 | EXPECT_EQ(127, accessor->as_norm().capacity); 244 | EXPECT_EQ("This is a sentence with 33 words."_cuqv, cuq); 245 | } 246 | } 247 | 248 | TEST(codeunit_sequence, index) 249 | { 250 | SCOPED_DETECT_MEMORY_LEAK() 251 | { 252 | const codeunit_sequence cuq("abbabbabbabbab"); 253 | EXPECT_EQ(cuq.index_of("ab"_cuqv), 0); 254 | EXPECT_EQ(cuq.index_of("ab"_cuqv, 3, 5), 3); 255 | EXPECT_EQ(cuq.index_of("ab"_cuqv, 5, 3), 6); 256 | EXPECT_EQ(cuq.index_of("ab"_cuqv, 5, 2), global_constant::INDEX_INVALID); 257 | } 258 | { 259 | const codeunit_sequence cuq("abcab"); 260 | EXPECT_EQ(cuq.last_index_of("ab"_cuqv), 3); 261 | EXPECT_EQ(cuq.last_index_of("a"_cuqv, 0, 2), 0); 262 | } 263 | } 264 | 265 | TEST(codeunit_sequence, replace) 266 | { 267 | SCOPED_DETECT_MEMORY_LEAK() 268 | // same size 269 | { 270 | { 271 | codeunit_sequence cuq("abbabbabbabbab"); 272 | EXPECT_EQ(cuq.replace("ba"_cuqv, "ab"_cuqv), "babbabbabbabba"_cuqv); 273 | } 274 | { 275 | codeunit_sequence cuq("abbabbabbabbab"); 276 | EXPECT_EQ(cuq.replace("ba"_cuqv, "ab"_cuqv, 4, 6), "abbabbbababbab"_cuqv); 277 | } 278 | } 279 | // smaller size 280 | { 281 | { 282 | codeunit_sequence cuq("abbabbabbabbab"); 283 | EXPECT_EQ(cuq.replace("a"_cuqv, "ab"_cuqv), "ababababa"_cuqv); 284 | } 285 | { 286 | codeunit_sequence cuq("abbabbabbabbab"); 287 | EXPECT_EQ(cuq.replace("a"_cuqv, "ab"_cuqv, 4, 6), "abbabbababbab"_cuqv); 288 | } 289 | } 290 | // greater size 291 | { 292 | { 293 | { 294 | codeunit_sequence cuq("abbabbabbabbab"); 295 | EXPECT_EQ(cuq.replace("aabb"_cuqv, "ab"_cuqv), "aabbbaabbbaabbbaabbbaabb"_cuqv); 296 | EXPECT_EQ(cuq.replace("aabb"_cuqv, "b"_cuqv), "aaaabbaabbaabbaaaabbaabbaabbaaaabbaabbaabbaaaabbaabbaabbaaaabbaabb"_cuqv); 297 | } 298 | { 299 | codeunit_sequence cuq("abbabbabbabbab"); 300 | EXPECT_EQ(cuq.replace("aabb"_cuqv, "ab"_cuqv, 4, 6), "abbabbaabbbabbab"_cuqv); 301 | EXPECT_EQ(cuq.replace("aabb"_cuqv, "b"_cuqv, 6, 100), "abbabbaaaabbaabbaabbaaabbaabbaaabb"_cuqv); 302 | } 303 | } 304 | { 305 | { 306 | codeunit_sequence cuq("This is a long string."); 307 | EXPECT_EQ(cuq.replace("short"_cuqv, "long"_cuqv), "This is a short string."_cuqv); 308 | EXPECT_EQ(cuq.replace("isn't"_cuqv, "is"_cuqv), "Thisn't isn't a short string."_cuqv); 309 | } 310 | { 311 | codeunit_sequence cuq("This is a long string."); 312 | EXPECT_EQ(cuq.replace("short"_cuqv, "is"_cuqv, 3, 3), "This is a long string."_cuqv); 313 | EXPECT_EQ(cuq.replace("short"_cuqv, "is"_cuqv, 3, 4), "This short a long string."_cuqv); 314 | } 315 | } 316 | } 317 | } 318 | 319 | TEST(codeunit_sequence, join) 320 | { 321 | SCOPED_DETECT_MEMORY_LEAK() 322 | { 323 | const sequence views = { "This"_cuqv, "is"_cuqv, "a"_cuqv, "very"_cuqv, "very"_cuqv, "long"_cuqv, "text"_cuqv }; 324 | const codeunit_sequence joined_1 = codeunit_sequence::join(views, "/**/"_cuqv); 325 | EXPECT_EQ(joined_1, "This/**/is/**/a/**/very/**/very/**/long/**/text"_cuqv); 326 | const codeunit_sequence joined_2 = codeunit_sequence::join(views, ""_cuqv); 327 | EXPECT_EQ(joined_2, "Thisisaveryverylongtext"_cuqv); 328 | } 329 | } 330 | -------------------------------------------------------------------------------- /test/test__codeunit_sequence_view.cpp: -------------------------------------------------------------------------------- 1 | 2 | #include "pch.h" 3 | 4 | using namespace ostr; 5 | 6 | TEST(codeunit_sequence_view, subview) 7 | { 8 | SCOPED_DETECT_MEMORY_LEAK() 9 | { 10 | constexpr auto view = "sequence view"_cuqv; 11 | constexpr codeunit_sequence_view subview = view.subview(4, 6); 12 | EXPECT_EQ(subview, "ence v"_cuqv); 13 | } 14 | { 15 | constexpr auto view = "你好❤a𪚥"_cuqv; 16 | constexpr codeunit_sequence_view subview = view.subview(2, 11); 17 | EXPECT_EQ(subview, "\xA0好❤a\xF0\xAA\x9A"_cuqv); 18 | } 19 | } 20 | 21 | TEST(codeunit_sequence_view, index_of) 22 | { 23 | SCOPED_DETECT_MEMORY_LEAK() 24 | { 25 | constexpr auto view = "This is a long string"_cuqv; 26 | constexpr u64 index_1 = view.index_of(" "_cuqv); 27 | EXPECT_EQ(index_1, 4); 28 | } 29 | { 30 | constexpr auto view = "Hello world!"_cuqv; 31 | constexpr u64 index = view.index_of("?"_cuqv); 32 | EXPECT_EQ(index, global_constant::INDEX_INVALID); 33 | } 34 | { 35 | constexpr auto view = "{:r}"_cuqv; 36 | constexpr u64 index_1 = view.index_of_any("{}"_cuqv); 37 | EXPECT_EQ(index_1, 0); 38 | constexpr u64 index_2 = view.index_of_any("{}"_cuqv, 1); 39 | EXPECT_EQ(index_2, 3); 40 | } 41 | { 42 | constexpr codeunit_sequence_view view("long long ago long"); 43 | EXPECT_EQ(view.index_of("long"_cuqv), 0); 44 | EXPECT_EQ(view.index_of("long"_cuqv, 3), 5); 45 | EXPECT_EQ(view.last_index_of("long"_cuqv), 14); 46 | EXPECT_EQ(view.index_of_any("like"_cuqv), 0); 47 | EXPECT_EQ(view.index_of_any("like"_cuqv, 3), 5); 48 | EXPECT_EQ(view.last_index_of_any("like"_cuqv), 14); 49 | EXPECT_EQ(view.last_index_of_any("like"_cuqv, 3, 4), 5); 50 | } 51 | { 52 | constexpr codeunit_sequence_view view("你好❤a𪚥"); 53 | EXPECT_EQ(view.index_of("𪚥"_cuqv), 10); 54 | EXPECT_EQ(view.index_of("\xAA"_cuqv), 11); 55 | EXPECT_EQ(view.index_of("\xA5"_cuqv), 4); 56 | EXPECT_EQ(view.last_index_of("\xA5"_cuqv), 13); 57 | } 58 | } 59 | 60 | TEST(codeunit_sequence_view, split) 61 | { 62 | SCOPED_DETECT_MEMORY_LEAK() 63 | { 64 | constexpr auto view = "This is a long string"_cuqv; 65 | constexpr auto subviews = view.split(" "_cuqv); 66 | constexpr std::array answer { "This"_cuqv, "is a long string"_cuqv }; 67 | EXPECT_EQ(subviews, answer); 68 | } 69 | { 70 | constexpr auto view = "This is a long string"_cuqv; 71 | constexpr auto subviews = view.split(" "_cuqv); 72 | constexpr std::array answer { "This is"_cuqv, "a long string"_cuqv }; 73 | EXPECT_EQ(subviews, answer); 74 | } 75 | { 76 | constexpr auto view = "Hello world!"_cuqv; 77 | constexpr auto subviews = view.split("?"_cuqv); 78 | constexpr std::array answer { "Hello world!"_cuqv, ""_cuqv }; 79 | EXPECT_EQ(subviews, answer); 80 | } 81 | } 82 | 83 | TEST(codeunit_sequence_view, count) 84 | { 85 | SCOPED_DETECT_MEMORY_LEAK() 86 | { 87 | constexpr auto view = "This is a long string "_cuqv; 88 | EXPECT_EQ(view.count(" "_cuqv), 5); 89 | EXPECT_EQ(view.count(" "_cuqv, 5), 4); 90 | EXPECT_EQ(view.count(" "_cuqv, 5, 5), 2); 91 | } 92 | { 93 | constexpr auto view = "aaaaaaaaa"_cuqv; 94 | EXPECT_EQ(view.count("aa"_cuqv), 4); 95 | EXPECT_EQ(view.count("aa"_cuqv, 3), 3); 96 | EXPECT_EQ(view.count("aa"_cuqv, 3, 3), 1); 97 | } 98 | } 99 | 100 | TEST(codeunit_sequence_view, iterate) 101 | { 102 | SCOPED_DETECT_MEMORY_LEAK() 103 | { 104 | static constexpr char sequence[] = "sequence view"; 105 | constexpr codeunit_sequence_view view(sequence); 106 | u32 index = 0; 107 | for(auto u : view) 108 | { 109 | EXPECT_EQ(u, sequence[index]); 110 | ++index; 111 | } 112 | } 113 | { 114 | static constexpr char sequence[] = "你好❤a𪚥"; 115 | constexpr codeunit_sequence_view view(sequence); 116 | constexpr codeunit_sequence_view subview = view.subview(2, 11); 117 | u32 index = 2; 118 | for(auto u : subview) 119 | { 120 | EXPECT_EQ(u, sequence[index]); 121 | ++index; 122 | } 123 | EXPECT_EQ(index, 13); 124 | } 125 | } 126 | 127 | TEST(codeunit_sequence_view, equal) 128 | { 129 | SCOPED_DETECT_MEMORY_LEAK() 130 | { 131 | constexpr codeunit_sequence_view view_default_ctor; 132 | EXPECT_EQ(view_default_ctor, ""); 133 | } 134 | 135 | { 136 | constexpr codeunit_sequence_view view("a"); 137 | EXPECT_EQ(view, "a"); 138 | } 139 | 140 | { 141 | constexpr auto view = "你好"_cuqv; 142 | EXPECT_EQ(view, "你好"); 143 | } 144 | 145 | { 146 | constexpr auto view = "😀"_cuqv; 147 | EXPECT_EQ(view, "😀"); 148 | } 149 | } 150 | 151 | TEST(codeunit_sequence_view, trim) 152 | { 153 | SCOPED_DETECT_MEMORY_LEAK() 154 | { 155 | constexpr codeunit_sequence_view view(" 123 1234 \t "); 156 | 157 | constexpr codeunit_sequence_view view_trim_start = view.trim_start(); 158 | EXPECT_EQ(view_trim_start, "123 1234 \t "_cuqv); 159 | 160 | constexpr codeunit_sequence_view view_trim_end = view.trim_end(); 161 | EXPECT_EQ(view_trim_end, " 123 1234"_cuqv); 162 | 163 | constexpr codeunit_sequence_view view_trim = view.trim(); 164 | EXPECT_EQ(view_trim, "123 1234"_cuqv); 165 | } 166 | 167 | { 168 | constexpr auto view = " 你 好 😙 \t "_cuqv; 169 | EXPECT_EQ(view.trim_start(), "你 好 😙 \t "_cuqv); 170 | EXPECT_EQ(view.trim_end(), " 你 好 😙"_cuqv); 171 | EXPECT_EQ(view.trim(), "你 好 😙"_cuqv); 172 | } 173 | 174 | { 175 | constexpr auto view = "你 好 😙"_cuqv; 176 | EXPECT_EQ(view.trim_start("你 😙"_cuqv), "好 😙"_cuqv); 177 | EXPECT_EQ(view.trim_end("你 😙"_cuqv), "你 \xE5\xA5"_cuqv); 178 | EXPECT_EQ(view.trim("你 😙"_cuqv), "\xE5\xA5"_cuqv); 179 | } 180 | 181 | { 182 | constexpr auto view = " \t "_cuqv; 183 | EXPECT_EQ(view.trim_start(), ""_cuqv); 184 | EXPECT_TRUE(view.trim_start().is_empty()); 185 | 186 | EXPECT_EQ(view.trim_end(), ""_cuqv); 187 | 188 | EXPECT_EQ(view.trim(), ""_cuqv); 189 | } 190 | 191 | { 192 | constexpr codeunit_sequence_view view; 193 | EXPECT_EQ(view, ""_cuqv); 194 | EXPECT_TRUE(view.is_empty()); 195 | EXPECT_EQ(view.trim_start(), ""_cuqv); 196 | EXPECT_TRUE(view.trim_start().is_empty()); 197 | EXPECT_EQ(view.trim_end(), ""_cuqv); 198 | EXPECT_EQ(view.trim(), ""_cuqv); 199 | } 200 | } 201 | 202 | TEST(codeunit_sequence_view, starts_ends_with) 203 | { 204 | SCOPED_DETECT_MEMORY_LEAK() 205 | { 206 | constexpr codeunit_sequence_view view(" 123 1234 \t "); 207 | EXPECT_TRUE(view.starts_with(" "_cuqv)); 208 | EXPECT_TRUE(view.starts_with(" "_cuqv)); 209 | EXPECT_TRUE(view.starts_with(" 1"_cuqv)); 210 | EXPECT_TRUE(view.starts_with(""_cuqv)); 211 | EXPECT_TRUE(view.ends_with(" \t "_cuqv)); 212 | EXPECT_TRUE(view.ends_with("\t "_cuqv)); 213 | EXPECT_TRUE(view.ends_with(" "_cuqv)); 214 | EXPECT_TRUE(view.ends_with(""_cuqv)); 215 | EXPECT_FALSE(view.ends_with("\t"_cuqv)); 216 | } 217 | 218 | { 219 | constexpr auto view = "你好😙"_cuqv; 220 | EXPECT_TRUE(view.starts_with("\xE4"_cuqv)); 221 | EXPECT_TRUE(view.starts_with("你"_cuqv)); 222 | EXPECT_TRUE(view.starts_with("你好"_cuqv)); 223 | EXPECT_TRUE(view.starts_with("你好😙"_cuqv)); 224 | EXPECT_TRUE(view.ends_with("\x99"_cuqv)); 225 | EXPECT_TRUE(view.ends_with("😙"_cuqv)); 226 | EXPECT_TRUE(view.ends_with("好😙"_cuqv)); 227 | EXPECT_TRUE(view.ends_with("你好😙"_cuqv)); 228 | } 229 | 230 | { 231 | constexpr codeunit_sequence_view view; 232 | EXPECT_TRUE(view.starts_with(""_cuqv)); 233 | EXPECT_TRUE(view.ends_with(""_cuqv)); 234 | } 235 | } -------------------------------------------------------------------------------- /test/test__format.cpp: -------------------------------------------------------------------------------- 1 | 2 | #include "pch.h" 3 | 4 | #include "format.h" 5 | 6 | #include 7 | 8 | using namespace ostr; 9 | 10 | #define EXPECT_CHECKED_WITH_MESSAGE(statement, expected_message) 11 | 12 | TEST(format, built_in_types) 13 | { 14 | SCOPED_DETECT_MEMORY_LEAK() 15 | 16 | // escaped braces 17 | EXPECT_EQ("{}"_cuqv, format("{{}}"_cuqv)); 18 | 19 | // integer 20 | EXPECT_EQ("0"_cuqv, format("{}"_cuqv, 0)); 21 | EXPECT_EQ("00255"_txtv, format("{:05d}"_txtv, 255)); 22 | EXPECT_EQ("ff"_cuqv, format("{:x}"_cuqv, 255)); 23 | EXPECT_EQ("-0xff"_cuqv, format("{:#x}"_cuqv, -255)); 24 | EXPECT_EQ("_1762757171"_cuqv, format("_{}"_cuqv, 1762757171ull)); 25 | 26 | // float 27 | EXPECT_EQ("3.14"_cuqv, format("{}"_cuqv, 3.14f)); 28 | EXPECT_EQ("3.1"_cuqv, format("{:.1f}"_cuqv, 3.14f)); 29 | EXPECT_EQ("-3.14000"_cuqv, format("{:.5f}"_cuqv, -3.14f)); 30 | EXPECT_EQ("-100"_cuqv, format("{}"_cuqv, -99.999999999)); 31 | EXPECT_EQ("60.004"_cuqv, format("{}"_cuqv, 60.004)); 32 | EXPECT_EQ("inf"_cuqv, format("{}"_cuqv, std::numeric_limits::infinity())); 33 | EXPECT_EQ("-inf"_txtv, format("{}"_txtv, -std::numeric_limits::infinity())); 34 | EXPECT_EQ("nan"_cuqv, format("{}"_cuqv, std::numeric_limits::quiet_NaN())); 35 | EXPECT_CHECKED_WITH_MESSAGE(format("{:.10f}"_cuqv, 3.14f), "Too high precision for float type [10]!"); 36 | 37 | // pointer 38 | EXPECT_EQ("nullptr"_cuqv, format("{}"_cuqv, nullptr)); 39 | EXPECT_EQ("0x00000000075bcd15"_cuqv, format("{}"_cuqv, reinterpret_cast(123456789))); 40 | 41 | // string 42 | EXPECT_EQ("繁星明 😀"_cuqv, format("{}{}明{}"_cuqv, "繁", "星"_cuqv, codeunit_sequence(" 😀"))); 43 | } 44 | 45 | TEST(format, undefined_type) 46 | { 47 | SCOPED_DETECT_MEMORY_LEAK() 48 | 49 | struct test_struct 50 | { 51 | int a; 52 | int b; 53 | }; 54 | [[maybe_unused]] constexpr test_struct ts1 { 123456, 654321 }; 55 | EXPECT_CHECKED_WITH_MESSAGE(format("{}"_cuqv, ts1), "Undefined format with raw memory bytes: 40 e2 01 00 f1 fb 09 00!"); 56 | constexpr test_struct ts2 { 17627, 57171 }; 57 | EXPECT_EQ(format("{:r}"_cuqv, ts2), "db 44 00 00 53 df 00 00"_cuqv); 58 | } 59 | 60 | TEST(format, manual_index) 61 | { 62 | SCOPED_DETECT_MEMORY_LEAK() 63 | 64 | EXPECT_EQ("My name is 繁星明 and I'm 25 years old."_cuqv, format("My name is {1} and I'm {0} years old."_cuqv, 25, "繁星明"_cuqv)); 65 | } 66 | 67 | TEST(format, format_exception) 68 | { 69 | SCOPED_DETECT_MEMORY_LEAK() 70 | 71 | EXPECT_CHECKED_WITH_MESSAGE(format("{}{ {}"_cuqv, 3), "Unclosed left brace is not allowed!"); 72 | EXPECT_CHECKED_WITH_MESSAGE(format("{}} "_cuqv, 3), "Unclosed right brace is not allowed!"); 73 | EXPECT_CHECKED_WITH_MESSAGE(format("{}{}"_cuqv, 3), "Invalid format index [1]: Index should be less than count of argument [1]!"); 74 | EXPECT_CHECKED_WITH_MESSAGE(format("{}{}}"_cuqv, 3, 4), "Unclosed right brace is not allowed!"); 75 | EXPECT_CHECKED_WITH_MESSAGE(format("{} {0}"_cuqv, 3), "Automatic index is not allowed mixing with manual index!"); 76 | EXPECT_CHECKED_WITH_MESSAGE(format("{0} {}"_cuqv, 3), "Manual index is not allowed mixing with automatic index!"); 77 | 78 | EXPECT_CHECKED_WITH_MESSAGE(format("{abc}"_cuqv, 123), "Invalid format index [abc]!"); // named argument is not allowed. 79 | EXPECT_CHECKED_WITH_MESSAGE(format("{:.1fa}"_cuqv, 3.14f), "Invalid format specification [.1fa]!"); 80 | } 81 | -------------------------------------------------------------------------------- /test/test__text.cpp: -------------------------------------------------------------------------------- 1 | 2 | #include "pch.h" 3 | 4 | #include "text.h" 5 | 6 | using namespace ostr; 7 | 8 | TEST(text, construct) 9 | { 10 | SCOPED_DETECT_MEMORY_LEAK() 11 | { 12 | const text t; 13 | EXPECT_TRUE(t.is_empty()); 14 | } 15 | { 16 | const text t("Hello 🌏!"_cuqv); 17 | EXPECT_FALSE(t.is_empty()); 18 | EXPECT_EQ(t.size(), 8); 19 | } 20 | { 21 | const text t = "This is a very long text."; 22 | EXPECT_FALSE(t.is_empty()); 23 | EXPECT_EQ(t.size(), 25); 24 | } 25 | { 26 | const text t = text::from_utf32(U"Hello 🌏!"); 27 | EXPECT_FALSE(t.is_empty()); 28 | EXPECT_EQ(t.size(), 8); 29 | } 30 | } 31 | 32 | TEST(text, concatenate) 33 | { 34 | SCOPED_DETECT_MEMORY_LEAK() 35 | { 36 | const text result = text::build("Hello ", "World!"_txtv); 37 | EXPECT_EQ(result, "Hello World!"); 38 | } 39 | { 40 | const text txt_ai("😘"); 41 | 42 | const text combine = text::build("我", " very "_txtv, txt_ai, "あなた"_txtv, "!"); 43 | const text correct("我 very 😘あなた!"); 44 | 45 | EXPECT_EQ(combine, correct); 46 | } 47 | } 48 | 49 | TEST(text, join) 50 | { 51 | SCOPED_DETECT_MEMORY_LEAK() 52 | { 53 | const sequence views = { "This"_txtv, "is"_txtv, "a"_txtv, "very"_txtv, "very"_txtv, "long"_txtv, "text"_txtv }; 54 | const text joined_1 = text::join(views, "/**/"_txtv); 55 | EXPECT_EQ(joined_1, "This/**/is/**/a/**/very/**/very/**/long/**/text"_txtv); 56 | const text joined_2 = text::join(views, ""_txtv); 57 | EXPECT_EQ(joined_2, "Thisisaveryverylongtext"_txtv); 58 | } 59 | } 60 | 61 | TEST(text, reverse) 62 | { 63 | SCOPED_DETECT_MEMORY_LEAK() 64 | { 65 | text t("你好a😙😙你"); 66 | t.reverse(); 67 | EXPECT_EQ(t, "你😙😙a好你"_txtv); 68 | t.reverse(1, 3); 69 | EXPECT_EQ(t, "你a😙😙好你"_txtv); 70 | } 71 | } 72 | 73 | TEST(text, replace) 74 | { 75 | SCOPED_DETECT_MEMORY_LEAK() 76 | { 77 | text t("你好😙😙你"); 78 | t.write_at(2, 'a'_cp); 79 | EXPECT_EQ(t, "你好a😙你"_txtv); 80 | } 81 | { 82 | const text t("0123456789"); 83 | u64 n = 0; 84 | for(const auto cp : t) 85 | { 86 | EXPECT_EQ(cp.size(), 1); 87 | EXPECT_EQ(n, cp.get_codepoint() - U'0'); 88 | ++n; 89 | } 90 | } 91 | { 92 | text t("0123456789"); 93 | u64 n = 0; 94 | for(auto cp : t) 95 | { 96 | if(n == 6) 97 | { 98 | cp = "你好😀"_txtv; 99 | break; 100 | } 101 | ++n; 102 | } 103 | EXPECT_EQ(t, "012345你好😀789"_txtv); 104 | } 105 | } 106 | 107 | TEST(text, remove_prefix_suffix) 108 | { 109 | SCOPED_DETECT_MEMORY_LEAK() 110 | { 111 | text t = "She says: 你好😙"_txtv; 112 | t.self_remove_prefix("He says:"); 113 | EXPECT_EQ(t, "She says: 你好😙"_txtv); 114 | t.self_remove_prefix("She says: "); 115 | EXPECT_EQ(t, "你好😙"_txtv); 116 | t.self_remove_suffix("😙"_txtv); 117 | EXPECT_EQ(t, "你好"_txtv); 118 | } 119 | } 120 | 121 | TEST(text, trim) 122 | { 123 | SCOPED_DETECT_MEMORY_LEAK() 124 | { 125 | text t(""); 126 | EXPECT_TRUE(t.self_trim_start().is_empty()); 127 | } 128 | { 129 | text t(""); 130 | EXPECT_TRUE(t.self_trim_end().is_empty()); 131 | } 132 | { 133 | text t(""); 134 | EXPECT_EQ(t.self_trim(), ""_txtv); 135 | } 136 | 137 | { 138 | text t(" \t "); 139 | EXPECT_TRUE(t.self_trim_start().is_empty()); 140 | } 141 | { 142 | text t(" \t "); 143 | EXPECT_TRUE(t.self_trim_end().is_empty()); 144 | } 145 | { 146 | text t(" \t "); 147 | EXPECT_EQ(t.self_trim(), ""_txtv); 148 | } 149 | 150 | { 151 | text t(" 123 1234 "); 152 | EXPECT_EQ(t.self_trim_start(), "123 1234 "_txtv); 153 | } 154 | { 155 | text t(" 123 1234 "); 156 | EXPECT_EQ(t.self_trim_end(), " 123 1234"_txtv); 157 | } 158 | { 159 | text t(" 123 1234 "); 160 | EXPECT_EQ(t.self_trim(), "123 1234"_txtv); 161 | } 162 | 163 | { 164 | text t(" 你 好 😙 \t "_txtv); 165 | EXPECT_EQ(t.self_trim_start(), "你 好 😙 \t "_txtv); 166 | } 167 | { 168 | text t(" 你 好 😙 \t "_txtv); 169 | EXPECT_EQ(t.self_trim_end(), " 你 好 😙"_txtv); 170 | } 171 | { 172 | text t(" 你 好 😙 \t "_txtv); 173 | EXPECT_EQ(t.self_trim(), "你 好 😙"_txtv); 174 | } 175 | 176 | { 177 | text t(" 你 好 😙 \t "_txtv); 178 | EXPECT_EQ(t.self_trim_start("你 😙"_txtv), "好 😙 \t "_txtv); 179 | } 180 | { 181 | text t(" 你 好 😙 \t "_txtv); 182 | EXPECT_EQ(t.self_trim_end("你 😙"_txtv), " 你 好 😙 \t"_txtv); 183 | } 184 | { 185 | text t(" 你 好 😙 \t "_txtv); 186 | EXPECT_EQ(t.self_trim("你 😙"_txtv), "好 😙 \t"_txtv); 187 | } 188 | 189 | { 190 | text t(" 你 好 😙 \t "_txtv); 191 | EXPECT_EQ(t.self_trim_start("你 😙\t"_txtv), "好 😙 \t "_txtv); 192 | } 193 | { 194 | text t(" 你 好 😙 \t "_txtv); 195 | EXPECT_EQ(t.self_trim_end("你 😙\t"_txtv), " 你 好"_txtv); 196 | } 197 | { 198 | text t(" 你 好 😙 \t "_txtv); 199 | EXPECT_EQ(t.self_trim("你 😙\t"_txtv), "好"_txtv); 200 | } 201 | } 202 | -------------------------------------------------------------------------------- /test/test__text_view.cpp: -------------------------------------------------------------------------------- 1 | 2 | #include "pch.h" 3 | 4 | #include "text_view.h" 5 | 6 | using namespace ostr; 7 | 8 | TEST(text_view, iterate) 9 | { 10 | SCOPED_DETECT_MEMORY_LEAK() 11 | { 12 | constexpr auto view = "你 好 😙"_txtv; 13 | constexpr u64 size = view.size(); 14 | EXPECT_EQ(size, 5); 15 | constexpr codepoint cp[] = { U'你'_cp, U' '_cp, U'好'_cp, U' '_cp, U'😙'_cp }; 16 | u64 index = 0; 17 | for(const auto c : view) 18 | { 19 | EXPECT_EQ(c, cp[index]); 20 | ++index; 21 | } 22 | } 23 | } 24 | 25 | TEST(text_view, subtext) 26 | { 27 | SCOPED_DETECT_MEMORY_LEAK() 28 | { 29 | constexpr auto view = "text view"_txtv; 30 | constexpr text_view subview = view.subview(3, 4); 31 | EXPECT_EQ(subview, "t vi"_txtv); 32 | } 33 | { 34 | constexpr auto view = "你好❤a𪚥"_txtv; 35 | constexpr text_view subview = view.subview(1, 3); 36 | EXPECT_EQ(subview, "好❤a"_txtv); 37 | } 38 | } 39 | 40 | TEST(text_view, trim) 41 | { 42 | SCOPED_DETECT_MEMORY_LEAK() 43 | { 44 | constexpr text_view view(" 123 1234 "); 45 | 46 | constexpr text_view view_trim_start = view.trim_start(); 47 | EXPECT_EQ(view_trim_start, "123 1234 "_txtv); 48 | 49 | constexpr text_view view_trim_end = view.trim_end(); 50 | EXPECT_EQ(view_trim_end, " 123 1234"_txtv); 51 | 52 | constexpr text_view view_trim = view.trim(); 53 | EXPECT_EQ(view_trim, "123 1234"_txtv); 54 | } 55 | 56 | { 57 | constexpr auto view = " 你 好 😙 \t "_txtv; 58 | EXPECT_EQ(view.trim_start(), "你 好 😙 \t "_txtv); 59 | EXPECT_EQ(view.trim_end(), " 你 好 😙"_txtv); 60 | EXPECT_EQ(view.trim(), "你 好 😙"_txtv); 61 | } 62 | 63 | { 64 | constexpr text_view view("你 好 😙"_txtv); 65 | EXPECT_EQ(view.trim_start("你 😙"_txtv), "好 😙"_txtv); 66 | EXPECT_EQ(view.trim_end("你 😙"_txtv), "你 好"_txtv); 67 | EXPECT_EQ(view.trim("你 😙"_txtv), "好"_txtv); 68 | } 69 | 70 | { 71 | constexpr auto view = " \t "_txtv; 72 | EXPECT_EQ(view.trim_start(), ""_txtv); 73 | EXPECT_TRUE(view.trim_start().is_empty()); 74 | EXPECT_EQ(view.trim_end(), ""_txtv); 75 | EXPECT_EQ(view.trim(), ""_txtv); 76 | } 77 | 78 | { 79 | constexpr text_view view; 80 | EXPECT_EQ(view, ""_txtv); 81 | EXPECT_TRUE(view.is_empty()); 82 | EXPECT_EQ(view.trim_start(), ""_txtv); 83 | EXPECT_TRUE(view.trim_start().is_empty()); 84 | EXPECT_EQ(view.trim_end(), ""_txtv); 85 | EXPECT_EQ(view.trim(), ""_txtv); 86 | } 87 | } 88 | 89 | TEST(text_view, index_of) 90 | { 91 | SCOPED_DETECT_MEMORY_LEAK() 92 | { 93 | constexpr auto view = "你❤好❤a𪚥"_txtv; 94 | EXPECT_EQ(view.index_of("❤"_txtv), 1); 95 | EXPECT_EQ(view.index_of("a"_txtv), 4); 96 | EXPECT_EQ(view.last_index_of("𪚥"_txtv), 5); 97 | EXPECT_EQ(view.last_index_of("❤"_txtv), 3); 98 | } 99 | { 100 | const text_view view("This is a very long text."); 101 | EXPECT_EQ(view.index_of("is"_txtv), 2); 102 | EXPECT_EQ(view.index_of("is"_txtv, 3, 5), 5); 103 | EXPECT_EQ(view.index_of("is"_txtv, 6), global_constant::INDEX_INVALID); 104 | EXPECT_EQ(view.last_index_of("is"_txtv), 5); 105 | EXPECT_EQ(view.last_index_of("is"_txtv, 0, 4), 2); 106 | } 107 | { 108 | const text_view view("你好😙😙你"); 109 | EXPECT_EQ(view.index_of("你"_txtv), 0); 110 | EXPECT_EQ(view.index_of(U'你'_cp, 1), 4); 111 | EXPECT_EQ(view.index_of("你"_txtv, 1, 3), global_constant::INDEX_INVALID); 112 | EXPECT_EQ(view.index_of("😙"_txtv), 2); 113 | EXPECT_EQ(view.last_index_of("😙"_txtv), 3); 114 | EXPECT_EQ(view.last_index_of("😙"_txtv, 0, 3), 2); 115 | } 116 | } 117 | 118 | TEST(text_view, access) 119 | { 120 | SCOPED_DETECT_MEMORY_LEAK() 121 | { 122 | constexpr auto view = "你❤好❤a𪚥"_txtv; 123 | EXPECT_EQ(view[3], U'❤'); 124 | } 125 | } 126 | 127 | TEST(text_view, starts_ends_with) 128 | { 129 | SCOPED_DETECT_MEMORY_LEAK() 130 | { 131 | constexpr text_view view(" 123 1234 \t "); 132 | EXPECT_TRUE(view.starts_with(" "_txtv)); 133 | EXPECT_TRUE(view.starts_with(" "_txtv)); 134 | EXPECT_TRUE(view.starts_with(" 1"_txtv)); 135 | EXPECT_TRUE(view.starts_with(""_txtv)); 136 | EXPECT_TRUE(view.ends_with(" \t "_txtv)); 137 | EXPECT_TRUE(view.ends_with("\t "_txtv)); 138 | EXPECT_TRUE(view.ends_with(" "_txtv)); 139 | EXPECT_TRUE(view.ends_with(""_txtv)); 140 | EXPECT_FALSE(view.ends_with("\t"_txtv)); 141 | } 142 | 143 | { 144 | constexpr auto view = "你好😙"_txtv; 145 | EXPECT_TRUE(view.starts_with("你"_txtv)); 146 | EXPECT_TRUE(view.starts_with("你好"_txtv)); 147 | EXPECT_TRUE(view.starts_with("你好😙"_txtv)); 148 | EXPECT_TRUE(view.ends_with("😙"_txtv)); 149 | EXPECT_TRUE(view.ends_with("好😙"_txtv)); 150 | EXPECT_TRUE(view.ends_with("你好😙"_txtv)); 151 | } 152 | 153 | { 154 | constexpr text_view view; 155 | EXPECT_TRUE(view.starts_with(""_txtv)); 156 | EXPECT_TRUE(view.ends_with(""_txtv)); 157 | } 158 | } 159 | -------------------------------------------------------------------------------- /test/test__wide_text.cpp: -------------------------------------------------------------------------------- 1 | 2 | #include "pch.h" 3 | 4 | #include "wide_text.h" 5 | 6 | using namespace ostr; 7 | 8 | TEST(wide_text, construct) 9 | { 10 | SCOPED_DETECT_MEMORY_LEAK() 11 | { 12 | constexpr text_view tv{ "你 好 😙"_txtv }; 13 | const wide_text wt{ tv.raw() }; 14 | u64 index = 0; 15 | for(const codepoint cp : tv) 16 | { 17 | #if _WIN64 18 | const auto* data = reinterpret_cast(wt.data()); 19 | const u64 size = unicode::utf16::parse_utf16_length(data[index]); 20 | EXPECT_EQ(cp.get_codepoint(), unicode::utf16_to_utf32(data + index, size)); 21 | index += size; 22 | #elif __linux__ || __MACH__ 23 | const auto* data = reinterpret_cast(wt.data()); 24 | EXPECT_EQ(cp.get_codepoint(), data[index]); 25 | ++index; 26 | #endif 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /xmake.lua: -------------------------------------------------------------------------------- 1 | add_rules("mode.debug", "mode.release") 2 | set_warnings("all", "error") 3 | set_languages("c++17") 4 | 5 | add_requires("gtest") 6 | -- add_requires("benchmark") 7 | 8 | add_cxxflags("cl::/utf-8", {force = true}) 9 | 10 | target("OpenString") 11 | set_kind("static") 12 | add_includedirs("include") 13 | add_files("source/*.cpp") 14 | target_end() 15 | 16 | target("test") 17 | set_kind("binary") 18 | add_packages("gtest") 19 | add_includedirs("include") 20 | add_files("test/*.cpp") 21 | add_deps("OpenString") 22 | target_end() 23 | 24 | -- target("benchmark") 25 | -- set_kind("binary") 26 | -- add_cxflags(cxflags, {force = true}) 27 | -- add_packages("benchmark") 28 | -- add_includedirs("include") 29 | -- add_deps("OpenString") 30 | -- add_files("benchmark/*.cpp") 31 | 32 | -- 33 | -- If you want to known more usage about xmake, please see https://xmake.io 34 | -- 35 | -- ## FAQ 36 | -- 37 | -- You can enter the project directory firstly before building project. 38 | -- 39 | -- $ cd projectdir 40 | -- 41 | -- 1. How to build project? 42 | -- 43 | -- $ xmake 44 | -- 45 | -- 2. How to configure project? 46 | -- 47 | -- $ xmake f -p [macosx|linux|iphoneos ..] -a [x86_64|i386|arm64 ..] -m [debug|release] 48 | -- 49 | -- 3. Where is the build output directory? 50 | -- 51 | -- The default output directory is `./build` and you can configure the output directory. 52 | -- 53 | -- $ xmake f -o outputdir 54 | -- $ xmake 55 | -- 56 | -- 4. How to run and debug target after building project? 57 | -- 58 | -- $ xmake run [targetname] 59 | -- $ xmake run -d [targetname] 60 | -- 61 | -- 5. How to install target to the system directory or other output directory? 62 | -- 63 | -- $ xmake install 64 | -- $ xmake install -o installdir 65 | -- 66 | -- 6. Add some frequently-used compilation flags in xmake.lua 67 | -- 68 | -- @code 69 | -- -- add debug and release modes 70 | -- add_rules("mode.debug", "mode.release") 71 | -- 72 | -- -- add macro defination 73 | -- add_defines("NDEBUG", "_GNU_SOURCE=1") 74 | -- 75 | -- -- set warning all as error 76 | -- set_warnings("all", "error") 77 | -- 78 | -- -- set language: c99, c++11 79 | -- set_languages("c99", "c++11") 80 | -- 81 | -- -- set optimization: none, faster, fastest, smallest 82 | -- set_optimize("fastest") 83 | -- 84 | -- -- add include search directories 85 | -- add_includedirs("/usr/include", "/usr/local/include") 86 | -- 87 | -- -- add link libraries and search directories 88 | -- add_links("tbox") 89 | -- add_linkdirs("/usr/local/lib", "/usr/lib") 90 | -- 91 | -- -- add system link libraries 92 | -- add_syslinks("z", "pthread") 93 | -- 94 | -- -- add compilation and link flags 95 | -- add_cxflags("-stdnolib", "-fno-strict-aliasing") 96 | -- add_ldflags("-L/usr/local/lib", "-lpthread", {force = true}) 97 | -- 98 | -- @endcode 99 | -- 100 | 101 | --------------------------------------------------------------------------------