├── .github └── workflows │ └── ci-build.yml ├── AUTHORS ├── CMakeLists.txt ├── LICENSE ├── README.md ├── cmake ├── FindFmt.cmake ├── check_char8_types.cpp ├── check_deprecated_attr.cpp ├── check_filesystem.cpp ├── check_fs_path_u8_ctor.cpp ├── check_int64.cpp ├── check_nodiscard.cpp ├── check_string_view.cpp ├── string_theory-config-version.cmake.in └── string_theory-config.cmake.in ├── doc └── index.md ├── include ├── st_assert.h ├── st_charbuffer.h ├── st_codecs.h ├── st_codecs_priv.h ├── st_config.h.in ├── st_format.h ├── st_format_numeric.h ├── st_format_priv.h ├── st_formatter.h ├── st_iostream.h ├── st_stdio.h ├── st_string.h ├── st_string_priv.h ├── st_stringstream.h ├── st_utf_conv.h ├── st_utf_conv_priv.h └── string_theory │ ├── assert │ ├── char_buffer │ ├── codecs │ ├── exceptions │ ├── format │ ├── format_numeric │ ├── formatter │ ├── iostream │ ├── stdio │ ├── string │ ├── string_stream │ └── utf_conversion └── test ├── CMakeLists.txt ├── profile.cpp ├── test_buffer.cpp ├── test_codecs.cpp ├── test_format.cpp ├── test_iostream.cpp ├── test_regress.cpp ├── test_sstream.cpp ├── test_stdio.cpp ├── test_string.cpp └── test_winheaders.cpp /.github/workflows/ci-build.yml: -------------------------------------------------------------------------------- 1 | name: CI-Build 2 | on: 3 | push: 4 | branches: [master] 5 | pull_request: 6 | 7 | jobs: 8 | build-ubuntu: 9 | runs-on: ${{ matrix.cfg.os }} 10 | strategy: 11 | matrix: 12 | cfg: 13 | - { os: ubuntu-22.04, compiler: gcc-9 } 14 | - { os: ubuntu-22.04, compiler: gcc } # GCC 11 15 | - { os: ubuntu-22.04, compiler: clang-11 } 16 | - { os: ubuntu-22.04, compiler: clang-15 } 17 | - { os: ubuntu-24.04, compiler: gcc-14 } 18 | - { os: ubuntu-24.04, compiler: clang } # Clang 18 19 | steps: 20 | - uses: actions/checkout@v4 21 | with: 22 | submodules: true 23 | - name: Install Compiler 24 | run: | 25 | sudo apt update 26 | if [[ "${{ matrix.cfg.compiler }}" =~ ^gcc.*$ ]]; then 27 | gxx=$(echo "${{ matrix.cfg.compiler }}" | sed -e 's,gcc,g++,') 28 | sudo apt install "$gxx" 29 | else 30 | sudo apt install "${{ matrix.cfg.compiler }}" 31 | fi 32 | - name: Build and Test 33 | run: | 34 | export CC="${{ matrix.cfg.compiler }}" 35 | export CXX="$(echo "${{ matrix.cfg.compiler }}" | sed -e 's,gcc,g++,;s,clang,clang++,')" 36 | mkdir -p build && cd build 37 | cmake -DCMAKE_BUILD_TYPE=Debug -DST_BUILD_TESTS=ON .. 38 | make test 39 | 40 | build-macos: 41 | runs-on: ${{ matrix.cfg.os }} 42 | strategy: 43 | matrix: 44 | cfg: 45 | - { os: macOS-13, xcode: Xcode_14.3.1 } 46 | - { os: macOS-14, xcode: Xcode_15.3 } 47 | - { os: macOS-15, xcode: Xcode_16.2 } 48 | steps: 49 | - uses: actions/checkout@v4 50 | with: 51 | submodules: true 52 | - name: Build and Test 53 | run: | 54 | export PATH="/Applications/${{ matrix.cfg.xcode }}.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin:$PATH" 55 | export CC=clang 56 | export CXX=clang++ 57 | mkdir -p build && cd build 58 | cmake -DCMAKE_BUILD_TYPE=Debug -DST_BUILD_TESTS=ON .. 59 | make test 60 | 61 | build-msvc: 62 | runs-on: ${{ matrix.cfg.os }} 63 | strategy: 64 | matrix: 65 | cfg: 66 | - { os: windows-2022, msvc: Visual Studio 17 2022, arch: Win32 } 67 | - { os: windows-2022, msvc: Visual Studio 17 2022, arch: x64 } 68 | steps: 69 | - uses: actions/checkout@v4 70 | with: 71 | submodules: true 72 | - name: Build and Test 73 | run: | 74 | mkdir build 75 | cd build 76 | cmake -G "${{ matrix.cfg.msvc }}" -A ${{ matrix.cfg.arch }} -DST_BUILD_TESTS=ON .. 77 | cmake --build . --config Debug --target test 78 | 79 | build-mingw: 80 | runs-on: windows-2019 81 | steps: 82 | - uses: actions/checkout@v4 83 | with: 84 | submodules: true 85 | - name: Build and Test 86 | run: | 87 | mkdir build 88 | cd build 89 | cmake -G "MinGW Makefiles" -DCMAKE_BUILD_TYPE=Debug -DST_BUILD_TESTS=ON .. 90 | mingw32-make test 91 | 92 | build-msys2: 93 | runs-on: windows-latest 94 | strategy: 95 | matrix: 96 | cfg: 97 | - { mingw: mingw32, arch: i686 } 98 | - { mingw: mingw64, arch: x86_64 } 99 | steps: 100 | - uses: actions/checkout@v4 101 | with: 102 | submodules: true 103 | - name: Install compiler and tools 104 | run: | 105 | $Env:Path = "C:\msys64\usr\bin;$Env:Path" 106 | pacman -Sy --noconfirm mingw-w64-${{ matrix.cfg.arch }}-gcc ` 107 | mingw-w64-${{ matrix.cfg.arch }}-cmake ` 108 | mingw-w64-${{ matrix.cfg.arch }}-ninja 109 | - name: Build and Test 110 | run: | 111 | mkdir build 112 | cd build 113 | $Env:Path = "C:\msys64\${{ matrix.cfg.mingw }}\bin;C:\msys64\usr\bin;$Env:Path" 114 | cmake -G Ninja -DCMAKE_BUILD_TYPE=Debug -DST_BUILD_TESTS=ON .. 115 | ninja test 116 | 117 | build-solaris: 118 | runs-on: ubuntu-latest 119 | steps: 120 | - name: Enable KVM group perms 121 | run: | 122 | echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules 123 | sudo udevadm control --reload-rules 124 | sudo udevadm trigger --name-match=kvm 125 | - uses: actions/checkout@v4 126 | with: 127 | submodules: true 128 | - name: Build and Test 129 | uses: vmactions/solaris-vm@v1 130 | with: 131 | prepare: pkg install -q cmake gcc 132 | run: | 133 | mkdir -p build && cd build 134 | cmake -DCMAKE_BUILD_TYPE=Debug -DST_BUILD_TESTS=ON .. 135 | make test 136 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | string_theory was originally ported from the plString and plFormat classes 2 | from http://github.com/H-uru/Plasma. It has since been extracted into its 3 | own independent library (string_theory) by Michael Hansen. 4 | 5 | Contributors to H-uru/Plasma's string library: 6 | - Michael Hansen @zrax 7 | - Darryl Pogue @dpogue 8 | - Anne Marije van der Meer @Lunanne 9 | - Adam Johnson @Hoikas 10 | - Bartek Bok @boq 11 | - Joseph Davies @Deledrius (regex support - not ported to string_theory) 12 | 13 | Authors and contributors to string_theory: 14 | - Michael Hansen @zrax 15 | - Adam Johnson @Hoikas 16 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2016 Michael Hansen 2 | # 3 | # Permission is hereby granted, free of charge, to any person obtaining a 4 | # copy of this software and associated documentation files (the "Software"), 5 | # to deal in the Software without restriction, including without limitation 6 | # the rights to use, copy, modify, merge, publish, distribute, sublicense, 7 | # and/or sell copies of the Software, and to permit persons to whom the 8 | # Software is furnished to do so, subject to the following conditions: 9 | # 10 | # The above copyright notice and this permission notice shall be included in 11 | # all copies or substantial portions of the Software. 12 | # 13 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 18 | # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 19 | # DEALINGS IN THE SOFTWARE. 20 | 21 | cmake_minimum_required(VERSION 3.14 FATAL_ERROR) 22 | project(string_theory) 23 | 24 | # We will detect and use optional features in newer C++ standards, 25 | # but C++11 is required at minimum 26 | set(CMAKE_CXX_STANDARD 20) 27 | set(CMAKE_CXX_STANDARD_REQUIRED OFF) 28 | set(CMAKE_CXX_EXTENSIONS OFF) 29 | 30 | if(POLICY CMP0067) 31 | # Honor CMAKE_CXX_STANDARD in try_compile() commands 32 | cmake_policy(SET CMP0067 NEW) 33 | endif() 34 | 35 | set(ST_MAJOR_VERSION 3) 36 | set(ST_MINOR_VERSION 10) 37 | set(ST_VERSION ${ST_MAJOR_VERSION}.${ST_MINOR_VERSION}) 38 | 39 | set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${PROJECT_SOURCE_DIR}/cmake") 40 | 41 | if(CMAKE_COMPILER_IS_GNUCXX OR "${CMAKE_CXX_COMPILER_ID}" MATCHES ".*Clang") 42 | set(CMAKE_CXX_FLAGS "-Wall -Wextra ${CMAKE_CXX_FLAGS}") 43 | endif() 44 | 45 | option(ST_ENABLE_STL_STRINGS "Enable std::*string and std::*string_view support" ON) 46 | option(ST_ENABLE_STL_FILESYSTEM "Enable std::filesystem::path support" ON) 47 | 48 | option(ST_BUILD_TEST_COVERAGE "Enable code coverage in string_theory and tests" OFF) 49 | if(ST_BUILD_TEST_COVERAGE) 50 | if(CMAKE_COMPILER_IS_GNUCXX) 51 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fprofile-arcs -ftest-coverage") 52 | elseif("${CMAKE_CXX_COMPILER_ID}" MATCHES ".*Clang") 53 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fprofile-instr-generate -fcoverage-mapping") 54 | else() 55 | message(FATAL_ERROR "Don't know how to generate profile data for this compiler") 56 | endif() 57 | endif() 58 | 59 | try_compile(ST_HAVE_CXX20_CHAR8_TYPES "${PROJECT_BINARY_DIR}" 60 | "${PROJECT_SOURCE_DIR}/cmake/check_char8_types.cpp") 61 | try_compile(ST_HAVE_INT64 "${PROJECT_BINARY_DIR}" 62 | "${PROJECT_SOURCE_DIR}/cmake/check_int64.cpp") 63 | try_compile(ST_HAVE_DEPRECATED_ATTR "${PROJECT_BINARY_DIR}" 64 | "${PROJECT_SOURCE_DIR}/cmake/check_deprecated_attr.cpp") 65 | try_compile(ST_HAVE_NODISCARD_ATTR "${PROJECT_BINARY_DIR}" 66 | "${PROJECT_SOURCE_DIR}/cmake/check_nodiscard.cpp") 67 | 68 | try_compile(ST_HAVE_CXX17_STRING_VIEW "${PROJECT_BINARY_DIR}" 69 | "${PROJECT_SOURCE_DIR}/cmake/check_string_view.cpp") 70 | 71 | if(ST_ENABLE_STL_FILESYSTEM) 72 | try_compile(ST_HAVE_CXX17_FILESYSTEM_NOLIBS "${PROJECT_BINARY_DIR}" 73 | "${PROJECT_SOURCE_DIR}/cmake/check_filesystem.cpp") 74 | if(ST_HAVE_CXX17_FILESYSTEM_NOLIBS) 75 | set(ST_HAVE_CXX17_FILESYSTEM 1) 76 | else() 77 | if(CMAKE_COMPILER_IS_GNUCXX) 78 | set(ST_CXXFS_LIBS stdc++fs) 79 | endif() 80 | try_compile(ST_HAVE_CXX17_FILESYSTEM "${PROJECT_BINARY_DIR}" 81 | "${PROJECT_SOURCE_DIR}/cmake/check_filesystem.cpp" 82 | LINK_LIBRARIES ${ST_CXXFS_LIBS}) 83 | endif() 84 | 85 | try_compile(ST_HAVE_CXX20_U8_FSPATH "${PROJECT_BINARY_DIR}" 86 | "${PROJECT_SOURCE_DIR}/cmake/check_fs_path_u8_ctor.cpp" 87 | LINK_LIBRARIES ${ST_CXXFS_LIBS}) 88 | endif() 89 | 90 | configure_file("${PROJECT_SOURCE_DIR}/include/st_config.h.in" 91 | "${PROJECT_BINARY_DIR}/include/st_config.h") 92 | include_directories("${PROJECT_BINARY_DIR}/include") 93 | 94 | set(ST_HEADERS_PRIV 95 | include/st_assert.h 96 | include/st_charbuffer.h 97 | include/st_codecs.h 98 | include/st_codecs_priv.h 99 | include/st_format.h 100 | include/st_format_numeric.h 101 | include/st_format_priv.h 102 | include/st_formatter.h 103 | include/st_iostream.h 104 | include/st_stdio.h 105 | include/st_string.h 106 | include/st_string_priv.h 107 | include/st_stringstream.h 108 | include/st_utf_conv.h 109 | include/st_utf_conv_priv.h 110 | "${PROJECT_BINARY_DIR}/include/st_config.h" 111 | ) 112 | set(ST_HEADERS_PUB 113 | include/string_theory/assert 114 | include/string_theory/char_buffer 115 | include/string_theory/codecs 116 | include/string_theory/exceptions 117 | include/string_theory/formatter 118 | include/string_theory/format 119 | include/string_theory/iostream 120 | include/string_theory/stdio 121 | include/string_theory/string 122 | include/string_theory/string_stream 123 | include/string_theory/utf_conversion 124 | ) 125 | 126 | set(ST_INSTALL_BIN_DIR "bin" CACHE PATH "Path to install DLLs on Windows") 127 | set(ST_INSTALL_LIB_DIR "lib" CACHE PATH "Path to install shared libraries") 128 | set(ST_INSTALL_INCLUDE_DIR "include" CACHE PATH "Path to install headers") 129 | set(ST_INSTALL_CMAKE_DIR "${ST_INSTALL_LIB_DIR}/cmake" CACHE PATH "Path to install CMake files") 130 | 131 | add_library(string_theory INTERFACE) 132 | 133 | target_include_directories(string_theory INTERFACE 134 | $ 135 | $) 136 | 137 | install(TARGETS string_theory 138 | EXPORT string_theory-targets 139 | ) 140 | install(FILES ${ST_HEADERS_PRIV} ${ST_HEADERS_PUB} 141 | DESTINATION "${ST_INSTALL_INCLUDE_DIR}/string_theory" COMPONENT devel) 142 | 143 | export(TARGETS string_theory 144 | FILE "${PROJECT_BINARY_DIR}/string_theory-targets.cmake") 145 | 146 | configure_file(cmake/string_theory-config.cmake.in 147 | "${PROJECT_BINARY_DIR}/string_theory-config.cmake" @ONLY) 148 | configure_file(cmake/string_theory-config-version.cmake.in 149 | "${PROJECT_BINARY_DIR}/string_theory-config-version.cmake" @ONLY) 150 | 151 | install(FILES 152 | "${PROJECT_BINARY_DIR}/string_theory-config.cmake" 153 | "${PROJECT_BINARY_DIR}/string_theory-config-version.cmake" 154 | DESTINATION "${ST_INSTALL_CMAKE_DIR}/string_theory" COMPONENT devel) 155 | install(EXPORT string_theory-targets 156 | NAMESPACE string_theory:: 157 | DESTINATION "${ST_INSTALL_CMAKE_DIR}/string_theory" COMPONENT devel) 158 | 159 | option(ST_BUILD_TESTS "Build string_theory test suite (recommended)" ON) 160 | if(ST_BUILD_TESTS) 161 | add_subdirectory(test) 162 | endif() 163 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 Michael Hansen 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. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | String Theory 2 | ============= 3 | 4 | 5 | GitHub Build Status 7 | 8 | 9 | Coverity Scan Build Status 11 | 12 | 13 | Introduction 14 | ------------ 15 | 16 | String Theory is a flexible modern C++ library for string manipulation and 17 | storage. It stores data internally as UTF-8, for ease of use with existing 18 | C/C++ APIs. It can also handle conversion to and from UTF-16, UTF-32, and 19 | Latin-1, and has a variety of methods to simplify text manipulation. 20 | 21 | In addition, String Theory includes a powerful and fast type-safe string 22 | formatter (`ST::format`), which can be extended with custom type formatters 23 | by end-user code. 24 | 25 | You can find the [full documentation](https://github.com/zrax/string_theory/wiki) 26 | online at https://github.com/zrax/string_theory/wiki. 27 | 28 | Why another string library? 29 | --------------------------- 30 | 31 | String Theory was originally developed to replace the half-dozen or so string 32 | types and string manipulation mini-libraries in the [Plasma game 33 | engine](http://github.com/H-uru/Plasma). Because of the state of the code, it 34 | was designed primarily to reduce coding errors, provide an easy to use set of 35 | manipulation functionality with minimal surprises, handle Unicode text without 36 | a lot of overhead, and have reasonable performance. Many existing string 37 | libraries provide some subset of those features, but were hard to integrate 38 | well with Plasma, or didn't meet all of our needs. Therefore, plString (and 39 | later plFormat) were born. After it had matured a while, it seemed that other 40 | projects could benefit from the string library, and so it was ported out into 41 | its own stand-alone library, which is String Theory. 42 | 43 | String Theory's features 44 | ------------------------ 45 | 46 | String Theory is designed to provide: 47 | - Minimal surprises. Strings are immutable objects, so you never have to worry 48 | whether your `.replace()` will create a copy or modify the original -- it 49 | will always return a copy even if the new string is identical. 50 | - UTF-8 by default. You don't have to remember what encoding your string data 51 | came in as; by the time `ST::string` is constructed, its data is assumed to 52 | already be in the UTF-8 encoding. This also allows easy re-use by other 53 | character-based APIs, since you don't have to first down-convert the string 54 | data from UTF-16 or UTF-32 in order to use it. 55 | - Easy conversion to Unicode formats. String theory provides conversion 56 | between UTF-8, UTF-16, UTF-32 and Latin-1. In addition, it can check raw 57 | character input for validity with several mechanisms (C++ exceptions, 58 | replacement of invalid characters, or just ignore). 59 | - Type-safe formatting. `sprintf` and friends are notoriously unsafe, and 60 | are one of the most common sources of bugs in string code. `ST::format` uses 61 | C++11's variadic templates to provide a type-safe way to format strings. 62 | String Theory also provides a mechanism to create custom formatters for 63 | end-user code, in order to extend `ST::format`'s capabilities. 64 | - Good performance. String theory is optimized to be reasonably fast on a 65 | variety of compilers and systems. For `ST::string`, this ends up being 66 | slightly slower than C++'s `std::string` due to the extra encoding work. 67 | However, in my tests `ST::string_stream` tends to be faster or at least on 68 | par with `std::stringstream`, and `ST::format` is in the same order of 69 | magnitude as an equivalent `snprintf`. 70 | - Reentrance. Another side-effect of immutable strings is that ST::string is 71 | a fully reentrant string object with no locking necessary. 72 | - Cross Platform. String Theory is supported on any platform that provides a 73 | reasonably modern C++ compiler. Additional features from newer compilers 74 | are detected and enabled when supported, but not required. 75 | - Minimal dependencies. Currently, String Theory has no run-time dependencies 76 | aside from the C/C++ standard libraries and runtime. Additional tools may 77 | however be necessary for building String Theory or its tests. 78 | - Well tested. String Theory comes with an extensive suite of unit tests to 79 | ensure it works as designed. 80 | 81 | What String Theory is NOT 82 | ------------------------- 83 | 84 | - A full Unicode library. If you need more Unicode support than just basic 85 | UTF data conversion, you probably want to use something like 86 | [ICU](http://icu-project.org) instead. 87 | - A faster version of `std::string`. String Theory was never designed to be 88 | faster than STL, and because of its design goal to always use UTF-8 data 89 | internally, it may be slower for some use cases. However, practical tests 90 | have shown that `ST::string` performs at least on par with STL in many use 91 | cases, and `ST::format` is usually significantly faster than many other 92 | type-safe alternatives such as `boost::format`. 93 | - A regular expression library. C++11 provides a regex library which should 94 | be usable with `ST::string`, and I don't have a compelling reason at this 95 | point to introduce another regular expression library to String Theory. 96 | - A library for working with theoretical physics. Just in case you got this 97 | far and were still uncertain :). 98 | 99 | Platform Support 100 | ---------------- 101 | 102 | string_theory supports a variety of platforms and compilers. As of March 103 | 2023, string_theory is tested and working on: 104 | - GCC 12 (Arch Linux x86_64 and ARMv7) 105 | - GCC 11 (Ubuntu 22.04 x86_64) 106 | - GCC 9 (Ubuntu 20.04 x86_64) 107 | - GCC 7 (Ubuntu 18.04 x86_64) 108 | - Clang 15 (Arch Linux x86_64 and ARMv7) 109 | - Clang 14 (Ubuntu 22.04 x86_64) 110 | - Clang 10 (Ubuntu 20.04 x86_64) 111 | - Clang 6 (Ubuntu 18.04 x86_64) 112 | - AppleClang 14.0 (macOS Monterey) 113 | - AppleClang 13.1 (macOS Monterey) 114 | - AppleClang 12.0 (macOS Big Sur) 115 | - MSVC 2022 (x64 and x86) 116 | - MSVC 2019 (x64 and x86) 117 | - MSVC 2017 (x64 and x86) 118 | - MinGW-w64 GCC 12 (x86_64 and i686) 119 | - MinGW-w64 GCC 8 (x86_64) 120 | 121 | As of string_theory 3.0, support for some older compilers has been dropped. 122 | You'll need a compiler that supports most of C++11. 123 | 124 | Contributing to String Theory 125 | ----------------------------- 126 | 127 | String Theory is Open Source software, licensed under the 128 | [MIT license](https://opensource.org/licenses/MIT). Contributions are welcome, 129 | and may be submitted as issues and/or pull requests on GitHub: 130 | http://github.com/zrax/string_theory. 131 | 132 | Some ideas for areas to contribute: 133 | - Report and/or fix bugs (See http://github.com/zrax/string_theory/issues) 134 | - Write and/or fix [documentation](https://github.com/zrax/string_theory/wiki) 135 | - Translate documentation to other languages 136 | - Write more unit tests 137 | -------------------------------------------------------------------------------- /cmake/FindFmt.cmake: -------------------------------------------------------------------------------- 1 | find_path(Fmt_INCLUDE_DIR fmt/format.h) 2 | find_library(Fmt_LIBRARY NAMES fmt) 3 | 4 | mark_as_advanced(Fmt_INCLUDE_DIR Fmt_LIBRARY) 5 | 6 | include(FindPackageHandleStandardArgs) 7 | find_package_handle_standard_args(Fmt 8 | REQUIRED_VARS Fmt_INCLUDE_DIR Fmt_LIBRARY 9 | ) 10 | 11 | if(Fmt_FOUND AND NOT TARGET Fmt::Fmt) 12 | add_library(Fmt::Fmt UNKNOWN IMPORTED) 13 | set_target_properties(Fmt::Fmt PROPERTIES 14 | IMPORTED_LINK_INTERFACE_LANGUAGES "CXX" 15 | IMPORTED_LOCATION "${Fmt_LIBRARY}" 16 | INTERFACE_INCLUDE_DIRECTORIES "${Fmt_INCLUDE_DIR}" 17 | ) 18 | endif() 19 | -------------------------------------------------------------------------------- /cmake/check_char8_types.cpp: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2019 Michael Hansen 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a 4 | copy of this software and associated documentation files (the "Software"), 5 | to deal in the Software without restriction, including without limitation 6 | the rights to use, copy, modify, merge, publish, distribute, sublicense, 7 | and/or sell copies of the Software, and to permit persons to whom the 8 | Software is furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 18 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 19 | DEALINGS IN THE SOFTWARE. */ 20 | 21 | #include 22 | 23 | int main() 24 | { 25 | char8_t foo = u8'x'; 26 | (void)foo; 27 | 28 | std::u8string bar = u8"foo"; 29 | (void)bar; 30 | 31 | return 0; 32 | } 33 | -------------------------------------------------------------------------------- /cmake/check_deprecated_attr.cpp: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2016 Michael Hansen 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a 4 | copy of this software and associated documentation files (the "Software"), 5 | to deal in the Software without restriction, including without limitation 6 | the rights to use, copy, modify, merge, publish, distribute, sublicense, 7 | and/or sell copies of the Software, and to permit persons to whom the 8 | Software is furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 18 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 19 | DEALINGS IN THE SOFTWARE. */ 20 | 21 | #if defined(__GNUC__) 22 | #pragma GCC diagnostic error "-Wattributes" 23 | #elif defined(_MSC_VER) 24 | #pragma warning(error: 5030) 25 | #endif 26 | 27 | struct Test 28 | { 29 | [[deprecated("Don't use this")]] 30 | int foo() { return 4; } 31 | }; 32 | 33 | [[deprecated("Don't use this either")]] 34 | int foo() { return 5; } 35 | 36 | int main() 37 | { 38 | return 0; 39 | } 40 | -------------------------------------------------------------------------------- /cmake/check_filesystem.cpp: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2016 Michael Hansen 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a 4 | copy of this software and associated documentation files (the "Software"), 5 | to deal in the Software without restriction, including without limitation 6 | the rights to use, copy, modify, merge, publish, distribute, sublicense, 7 | and/or sell copies of the Software, and to permit persons to whom the 8 | Software is furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 18 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 19 | DEALINGS IN THE SOFTWARE. */ 20 | 21 | #include 22 | 23 | int main(int, char *argv[]) 24 | { 25 | std::filesystem::path p(argv[0]); 26 | (void)p; 27 | 28 | return 0; 29 | } 30 | -------------------------------------------------------------------------------- /cmake/check_fs_path_u8_ctor.cpp: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2019 Michael Hansen 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a 4 | copy of this software and associated documentation files (the "Software"), 5 | to deal in the Software without restriction, including without limitation 6 | the rights to use, copy, modify, merge, publish, distribute, sublicense, 7 | and/or sell copies of the Software, and to permit persons to whom the 8 | Software is furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 18 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 19 | DEALINGS IN THE SOFTWARE. */ 20 | 21 | #include 22 | #include 23 | 24 | int main() 25 | { 26 | const std::u8string pathname(u8"/path/to/file.txt"); 27 | std::filesystem::path p(pathname.c_str(), pathname.c_str() + pathname.size()); 28 | (void)p; 29 | 30 | return 0; 31 | } 32 | -------------------------------------------------------------------------------- /cmake/check_int64.cpp: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2016 Michael Hansen 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a 4 | copy of this software and associated documentation files (the "Software"), 5 | to deal in the Software without restriction, including without limitation 6 | the rights to use, copy, modify, merge, publish, distribute, sublicense, 7 | and/or sell copies of the Software, and to permit persons to whom the 8 | Software is furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 18 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 19 | DEALINGS IN THE SOFTWARE. */ 20 | 21 | #include 22 | 23 | int main() 24 | { 25 | int64_t x = -1LL; 26 | uint64_t y = 1ULL; 27 | (void)x; 28 | (void)y; 29 | 30 | return 0; 31 | } 32 | -------------------------------------------------------------------------------- /cmake/check_nodiscard.cpp: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2020 Michael Hansen 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a 4 | copy of this software and associated documentation files (the "Software"), 5 | to deal in the Software without restriction, including without limitation 6 | the rights to use, copy, modify, merge, publish, distribute, sublicense, 7 | and/or sell copies of the Software, and to permit persons to whom the 8 | Software is furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 18 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 19 | DEALINGS IN THE SOFTWARE. */ 20 | 21 | #if defined(__GNUC__) 22 | #pragma GCC diagnostic error "-Wattributes" 23 | #elif defined(_MSC_VER) 24 | #pragma warning(error: 5030) 25 | #endif 26 | 27 | struct Test 28 | { 29 | [[nodiscard]] 30 | int answer() { return 42; } 31 | }; 32 | 33 | int main() 34 | { 35 | return 0; 36 | } 37 | -------------------------------------------------------------------------------- /cmake/check_string_view.cpp: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2016 Michael Hansen 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a 4 | copy of this software and associated documentation files (the "Software"), 5 | to deal in the Software without restriction, including without limitation 6 | the rights to use, copy, modify, merge, publish, distribute, sublicense, 7 | and/or sell copies of the Software, and to permit persons to whom the 8 | Software is furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 18 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 19 | DEALINGS IN THE SOFTWARE. */ 20 | 21 | #include 22 | 23 | int main(int, char *argv[]) 24 | { 25 | std::string_view p(argv[0]); 26 | (void)p; 27 | 28 | return 0; 29 | } 30 | -------------------------------------------------------------------------------- /cmake/string_theory-config-version.cmake.in: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2016 Michael Hansen 2 | # 3 | # Permission is hereby granted, free of charge, to any person obtaining a 4 | # copy of this software and associated documentation files (the "Software"), 5 | # to deal in the Software without restriction, including without limitation 6 | # the rights to use, copy, modify, merge, publish, distribute, sublicense, 7 | # and/or sell copies of the Software, and to permit persons to whom the 8 | # Software is furnished to do so, subject to the following conditions: 9 | # 10 | # The above copyright notice and this permission notice shall be included in 11 | # all copies or substantial portions of the Software. 12 | # 13 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 18 | # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 19 | # DEALINGS IN THE SOFTWARE. 20 | 21 | set(PACKAGE_VERSION "@ST_VERSION@") 22 | 23 | if("${PACKAGE_VERSION}" VERSION_LESS "${PACKAGE_FIND_VERSION}") 24 | set(PACKAGE_VERSION_COMPATIBLE FALSE) 25 | else() 26 | set(PACKAGE_VERSION_COMPATIBLE TRUE) 27 | if("${PACKAGE_VERSION}" VERSION_EQUAL "${PACKAGE_FIND_VERSION}") 28 | set(PACKAGE_VERSION_EXACT TRUE) 29 | endif() 30 | endif() 31 | -------------------------------------------------------------------------------- /cmake/string_theory-config.cmake.in: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2016 Michael Hansen 2 | # 3 | # Permission is hereby granted, free of charge, to any person obtaining a 4 | # copy of this software and associated documentation files (the "Software"), 5 | # to deal in the Software without restriction, including without limitation 6 | # the rights to use, copy, modify, merge, publish, distribute, sublicense, 7 | # and/or sell copies of the Software, and to permit persons to whom the 8 | # Software is furnished to do so, subject to the following conditions: 9 | # 10 | # The above copyright notice and this permission notice shall be included in 11 | # all copies or substantial portions of the Software. 12 | # 13 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 18 | # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 19 | # DEALINGS IN THE SOFTWARE. 20 | 21 | include("${CMAKE_CURRENT_LIST_DIR}/string_theory-targets.cmake") 22 | 23 | get_target_property(STRING_THEORY_INCLUDE_DIRS string_theory::string_theory 24 | INTERFACE_INCLUDE_DIRECTORIES) 25 | set(STRING_THEORY_LIBRARIES string_theory::string_theory) 26 | 27 | # Backwards-compatibility target. CMake <3.11 doesn't allow ALIAS libraries to 28 | # imported targets, so this is another IMPORTED target. 29 | if(NOT TARGET string_theory) 30 | add_library(string_theory INTERFACE IMPORTED) 31 | target_link_libraries(string_theory INTERFACE string_theory::string_theory) 32 | endif() 33 | -------------------------------------------------------------------------------- /doc/index.md: -------------------------------------------------------------------------------- 1 | # String Theory Documentation 2 | 3 | **NOTE:** string_theory documentation has moved to the 4 | [Github Wiki](https://github.com/zrax/string_theory/wiki). 5 | 6 | Documentation for string_theory 1.x can still be found on the 7 | [string_theory-1 branch](https://github.com/zrax/string_theory/blob/string_theory-1/doc/index.md) 8 | -------------------------------------------------------------------------------- /include/st_assert.h: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2016 Michael Hansen 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a 4 | copy of this software and associated documentation files (the "Software"), 5 | to deal in the Software without restriction, including without limitation 6 | the rights to use, copy, modify, merge, publish, distribute, sublicense, 7 | and/or sell copies of the Software, and to permit persons to whom the 8 | Software is furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 18 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 19 | DEALINGS IN THE SOFTWARE. */ 20 | 21 | #ifndef _ST_ASSERT_H 22 | #define _ST_ASSERT_H 23 | 24 | #include "st_config.h" 25 | 26 | #include // For std::runtime_error 27 | #include // For std::fprintf 28 | #include // For std::abort 29 | 30 | namespace ST 31 | { 32 | class unicode_error : public std::runtime_error 33 | { 34 | public: 35 | explicit unicode_error(const char *message) noexcept 36 | : std::runtime_error(message) 37 | { } 38 | }; 39 | 40 | class codec_error : public std::runtime_error 41 | { 42 | public: 43 | explicit codec_error(const char *message) noexcept 44 | : std::runtime_error(message) 45 | { } 46 | }; 47 | 48 | class bad_format : public std::invalid_argument 49 | { 50 | public: 51 | explicit bad_format(const char *message) noexcept 52 | : std::invalid_argument(message) 53 | { } 54 | }; 55 | } 56 | 57 | namespace _ST_PRIVATE 58 | { 59 | inline void assert_handler(const char *filename, int line, const char *message) 60 | { 61 | std::fprintf(stderr, "%s:%d: %s\n", filename, line, message); 62 | std::abort(); 63 | } 64 | } 65 | 66 | #define ST_ASSERT(condition, message) \ 67 | do { \ 68 | if (!(condition)) \ 69 | _ST_PRIVATE::assert_handler(__FILE__, __LINE__, message); \ 70 | } while (0) 71 | 72 | #endif // _ST_ASSERT_H 73 | -------------------------------------------------------------------------------- /include/st_charbuffer.h: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2016 Michael Hansen 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a 4 | copy of this software and associated documentation files (the "Software"), 5 | to deal in the Software without restriction, including without limitation 6 | the rights to use, copy, modify, merge, publish, distribute, sublicense, 7 | and/or sell copies of the Software, and to permit persons to whom the 8 | Software is furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 18 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 19 | DEALINGS IN THE SOFTWARE. */ 20 | 21 | #ifndef _ST_CHARBUFFER_H 22 | #define _ST_CHARBUFFER_H 23 | 24 | #include "st_assert.h" 25 | 26 | #include // Needed for ptrdiff_t 27 | #include // Needed for reverse_iterator 28 | #include // Needed for char_traits 29 | #include // For std::move 30 | #include // For std::min 31 | 32 | #if defined(ST_ENABLE_STL_STRINGS) && defined(ST_HAVE_CXX17_STRING_VIEW) 33 | # include 34 | #endif 35 | 36 | #define ST_AUTO_SIZE (static_cast(-1)) 37 | 38 | namespace ST 39 | { 40 | // For optimized construction of empty objects 41 | struct null_t 42 | { 43 | constexpr null_t() noexcept { } 44 | }; 45 | 46 | ST_DEPRECATED_IN_3_4("Use empty initializer {} instead.") 47 | static constexpr null_t null; 48 | 49 | template 50 | class buffer 51 | { 52 | public: 53 | // STL-compatible typedefs 54 | typedef size_t size_type; 55 | typedef ptrdiff_t difference_type; 56 | typedef char_T value_type; 57 | typedef value_type *pointer; 58 | typedef const value_type *const_pointer; 59 | typedef value_type &reference; 60 | typedef const value_type &const_reference; 61 | 62 | // This should satisfy ContiguousIterator if std::array is any indication 63 | typedef value_type *iterator; 64 | typedef const value_type *const_iterator; 65 | typedef std::reverse_iterator reverse_iterator; 66 | typedef std::reverse_iterator const_reverse_iterator; 67 | 68 | private: 69 | enum 70 | { 71 | local_length = (ST_MAX_SSO_LENGTH * sizeof(char_T)) > ST_MAX_SSO_SIZE 72 | ? (ST_MAX_SSO_SIZE / sizeof(char_T)) 73 | : ST_MAX_SSO_LENGTH 74 | }; 75 | 76 | char_T *m_chars; 77 | size_t m_size; 78 | char_T m_data[local_length]; 79 | 80 | typedef std::char_traits traits_t; 81 | 82 | inline bool is_reffed() const noexcept 83 | { 84 | return m_size >= local_length; 85 | } 86 | 87 | public: 88 | constexpr buffer() noexcept 89 | : m_chars(m_data), m_size(), m_data() { } 90 | 91 | ST_DEPRECATED_IN_3_4("Use empty initializer {} instead.") 92 | constexpr buffer(const null_t &) noexcept 93 | : m_chars(m_data), m_size(), m_data() { } 94 | 95 | buffer(const buffer ©) 96 | : m_size() 97 | { 98 | if (copy.is_reffed()) { 99 | m_chars = new char_T[copy.m_size + 1]; 100 | traits_t::copy(m_chars, copy.m_chars, copy.m_size); 101 | m_chars[copy.m_size] = 0; 102 | } else { 103 | traits_t::copy(m_data, copy.m_data, local_length); 104 | m_chars = m_data; 105 | } 106 | m_size = copy.m_size; 107 | } 108 | 109 | buffer(buffer &&move) noexcept 110 | : m_size(move.m_size) 111 | { 112 | m_chars = is_reffed() ? move.m_chars : m_data; 113 | traits_t::copy(m_data, move.m_data, local_length); 114 | move.m_size = 0; 115 | } 116 | 117 | buffer(const char_T *data, size_t size) 118 | : m_size(size), m_data() 119 | { 120 | ST_ASSERT(data || (size == 0), 121 | "buffer cannot be constructed with non-zero size and NULL data"); 122 | m_chars = is_reffed() ? new char_T[m_size + 1] : m_data; 123 | if (data) 124 | traits_t::move(m_chars, data, m_size); 125 | m_chars[m_size] = 0; 126 | } 127 | 128 | buffer(size_t count, char_T fill) 129 | : m_size(count), m_data() 130 | { 131 | m_chars = is_reffed() ? new char_T[m_size + 1] : m_data; 132 | traits_t::assign(m_chars, m_size, fill); 133 | m_chars[m_size] = 0; 134 | } 135 | 136 | ~buffer() noexcept 137 | { 138 | #if defined(__GNUC__) 139 | // See https://gcc.gnu.org/bugzilla/show_bug.cgi?id=98753 140 | # pragma GCC diagnostic push 141 | # pragma GCC diagnostic ignored "-Wfree-nonheap-object" 142 | #endif 143 | if (is_reffed()) 144 | delete[] m_chars; 145 | #if defined(__GNUC__) 146 | # pragma GCC diagnostic pop 147 | #endif 148 | } 149 | 150 | void clear() noexcept 151 | { 152 | if (is_reffed()) 153 | delete[] m_chars; 154 | 155 | m_chars = m_data; 156 | m_size = 0; 157 | traits_t::assign(m_data, local_length, 0); 158 | } 159 | 160 | ST_DEPRECATED_IN_3_4("Use clear() instead") 161 | buffer &operator=(const null_t &) noexcept ST_LIFETIME_BOUND 162 | { 163 | clear(); 164 | return *this; 165 | } 166 | 167 | buffer &operator=(const buffer ©) ST_LIFETIME_BOUND 168 | { 169 | if (this == ©) 170 | return *this; 171 | 172 | if (is_reffed()) { 173 | delete[] m_chars; 174 | m_size = 0; 175 | } 176 | 177 | if (copy.is_reffed()) { 178 | m_chars = new char_T[copy.m_size + 1]; 179 | traits_t::copy(m_chars, copy.m_chars, copy.m_size); 180 | m_chars[copy.m_size] = 0; 181 | } else { 182 | traits_t::copy(m_data, copy.m_data, local_length); 183 | m_chars = m_data; 184 | } 185 | m_size = copy.m_size; 186 | return *this; 187 | } 188 | 189 | buffer &operator=(buffer &&move) noexcept ST_LIFETIME_BOUND 190 | { 191 | std::swap(m_chars, move.m_chars); 192 | std::swap(m_size, move.m_size); 193 | traits_t::copy(m_data, move.m_data, local_length); 194 | if (!is_reffed()) 195 | m_chars = m_data; 196 | return *this; 197 | } 198 | 199 | ST_NODISCARD 200 | static int compare(const char_T *left, size_t lsize, 201 | const char_T *right, size_t rsize) noexcept 202 | { 203 | const size_t cmplen = std::min(lsize, rsize); 204 | const int cmp = traits_t::compare(left, right, cmplen); 205 | return cmp ? cmp : static_cast(lsize - rsize); 206 | } 207 | 208 | ST_NODISCARD 209 | static int compare(const char_T *left, size_t lsize, 210 | const char_T *right, size_t rsize, size_t maxlen) noexcept 211 | { 212 | lsize = std::min(lsize, maxlen); 213 | rsize = std::min(rsize, maxlen); 214 | return compare(left, lsize, right, rsize); 215 | } 216 | 217 | ST_NODISCARD 218 | int compare(const buffer &other) const noexcept 219 | { 220 | return compare(data(), size(), other.data(), other.size()); 221 | } 222 | 223 | ST_NODISCARD 224 | int compare(const char_T *str) const noexcept 225 | { 226 | const size_t rsize = str ? traits_t::length(str) : 0; 227 | const char_T empty[] = {0}; 228 | return compare(data(), size(), str ? str : empty, rsize); 229 | } 230 | 231 | ST_NODISCARD 232 | int compare_n(const buffer &other, size_t count) const noexcept 233 | { 234 | return compare(data(), size(), other.data(), other.size(), count); 235 | } 236 | 237 | ST_NODISCARD 238 | int compare_n(const char_T *str, size_t count) const noexcept 239 | { 240 | const size_t rsize = str ? traits_t::length(str) : 0; 241 | const char_T empty[] = {0}; 242 | return compare(data(), size(), str ? str : empty, rsize, count); 243 | } 244 | 245 | ST_NODISCARD 246 | ST_DEPRECATED_IN_3_4("Use empty() instead") 247 | bool operator==(const null_t &) const noexcept 248 | { 249 | return empty(); 250 | } 251 | 252 | ST_NODISCARD 253 | bool operator==(const buffer &other) const noexcept 254 | { 255 | return compare(other) == 0; 256 | } 257 | 258 | ST_NODISCARD 259 | ST_DEPRECATED_IN_3_4("Use !empty() instead") 260 | bool operator!=(const null_t &) const noexcept 261 | { 262 | return !empty(); 263 | } 264 | 265 | ST_NODISCARD 266 | bool operator!=(const buffer &other) const noexcept 267 | { 268 | return compare(other) != 0; 269 | } 270 | 271 | ST_NODISCARD 272 | bool operator<(const buffer &other) const noexcept 273 | { 274 | return compare(other) < 0; 275 | } 276 | 277 | ST_NODISCARD 278 | char_T *data() noexcept ST_LIFETIME_BOUND { return m_chars; } 279 | 280 | ST_NODISCARD 281 | const char_T *data() const noexcept ST_LIFETIME_BOUND { return m_chars; } 282 | 283 | ST_NODISCARD 284 | const char_T *c_str() const noexcept ST_LIFETIME_BOUND { return m_chars; } 285 | 286 | ST_NODISCARD 287 | const char_T *c_str(const char_T *substitute ST_LIFETIME_BOUND) 288 | const noexcept ST_LIFETIME_BOUND 289 | { 290 | return empty() ? substitute : m_chars; 291 | } 292 | 293 | ST_NODISCARD 294 | size_t size() const noexcept { return m_size; } 295 | 296 | ST_NODISCARD 297 | bool empty() const noexcept { return m_size == 0; } 298 | 299 | ST_NODISCARD 300 | char_T &at(size_t index) ST_LIFETIME_BOUND 301 | { 302 | if (index >= size()) 303 | throw std::out_of_range("Character index out of range"); 304 | return m_chars[index]; 305 | } 306 | 307 | ST_NODISCARD 308 | const char_T &at(size_t index) const ST_LIFETIME_BOUND 309 | { 310 | if (index >= size()) 311 | throw std::out_of_range("Character index out of range"); 312 | return m_chars[index]; 313 | } 314 | 315 | ST_NODISCARD 316 | char_T &operator[](size_t index) noexcept ST_LIFETIME_BOUND 317 | { 318 | return m_chars[index]; 319 | } 320 | 321 | ST_NODISCARD 322 | const char_T &operator[](size_t index) const noexcept ST_LIFETIME_BOUND 323 | { 324 | return m_chars[index]; 325 | } 326 | 327 | ST_NODISCARD 328 | char_T &front() noexcept ST_LIFETIME_BOUND 329 | { 330 | return m_chars[0]; 331 | } 332 | 333 | ST_NODISCARD 334 | const char_T &front() const noexcept ST_LIFETIME_BOUND 335 | { 336 | return m_chars[0]; 337 | } 338 | 339 | ST_NODISCARD 340 | char_T &back() noexcept ST_LIFETIME_BOUND 341 | { 342 | return empty() ? m_chars[0] : m_chars[m_size - 1]; 343 | } 344 | 345 | ST_NODISCARD 346 | const char_T &back() const noexcept ST_LIFETIME_BOUND 347 | { 348 | return empty() ? m_chars[0] : m_chars[m_size - 1]; 349 | } 350 | 351 | ST_NODISCARD 352 | iterator begin() noexcept ST_LIFETIME_BOUND { return m_chars; } 353 | 354 | ST_NODISCARD 355 | const_iterator begin() const noexcept ST_LIFETIME_BOUND { return m_chars; } 356 | 357 | ST_NODISCARD 358 | const_iterator cbegin() const noexcept ST_LIFETIME_BOUND { return m_chars; } 359 | 360 | ST_NODISCARD 361 | iterator end() noexcept ST_LIFETIME_BOUND { return m_chars + m_size; } 362 | 363 | ST_NODISCARD 364 | const_iterator end() const noexcept ST_LIFETIME_BOUND 365 | { 366 | return m_chars + m_size; 367 | } 368 | 369 | ST_NODISCARD 370 | const_iterator cend() const noexcept ST_LIFETIME_BOUND 371 | { 372 | return m_chars + m_size; 373 | } 374 | 375 | ST_NODISCARD 376 | reverse_iterator rbegin() noexcept ST_LIFETIME_BOUND 377 | { 378 | return reverse_iterator(end()); 379 | } 380 | 381 | ST_NODISCARD 382 | const_reverse_iterator rbegin() const noexcept ST_LIFETIME_BOUND 383 | { 384 | return const_reverse_iterator(end()); 385 | } 386 | 387 | ST_NODISCARD 388 | const_reverse_iterator crbegin() const noexcept ST_LIFETIME_BOUND 389 | { 390 | return const_reverse_iterator(cend()); 391 | } 392 | 393 | ST_NODISCARD 394 | reverse_iterator rend() noexcept ST_LIFETIME_BOUND 395 | { 396 | return reverse_iterator(begin()); 397 | } 398 | 399 | ST_NODISCARD 400 | const_reverse_iterator rend() const noexcept ST_LIFETIME_BOUND 401 | { 402 | return const_reverse_iterator(begin()); 403 | } 404 | 405 | ST_NODISCARD 406 | const_reverse_iterator crend() const noexcept ST_LIFETIME_BOUND 407 | { 408 | return const_reverse_iterator(cbegin()); 409 | } 410 | 411 | void allocate(size_t size) 412 | { 413 | if (is_reffed()) 414 | delete[] m_chars; 415 | else 416 | traits_t::assign(m_data, local_length, 0); 417 | 418 | m_size = size; 419 | m_chars = is_reffed() ? new char_T[m_size + 1] : m_data; 420 | m_chars[m_size] = 0; 421 | } 422 | 423 | void allocate(size_t size, char_T fill) 424 | { 425 | allocate(size); 426 | traits_t::assign(m_chars, size, fill); 427 | } 428 | 429 | ST_NODISCARD 430 | static inline size_t strlen(const char_T *buffer) 431 | { 432 | ST_ASSERT(buffer, "buffer::strlen passed null buffer"); 433 | return traits_t::length(buffer); 434 | } 435 | 436 | #if defined(ST_ENABLE_STL_STRINGS) 437 | ST_NODISCARD 438 | std::basic_string to_std_string() const 439 | { 440 | return std::basic_string(data(), size()); 441 | } 442 | 443 | #if defined(ST_HAVE_CXX17_STRING_VIEW) 444 | ST_NODISCARD 445 | std::basic_string_view view(size_t start = 0, 446 | size_t length = ST_AUTO_SIZE) const & 447 | ST_LIFETIME_BOUND 448 | { 449 | if (length == ST_AUTO_SIZE) 450 | length = size() - start; 451 | return std::basic_string_view(data() + start, length); 452 | } 453 | 454 | void view(size_t start = 0, size_t length = ST_AUTO_SIZE) const && = delete; 455 | #endif 456 | 457 | #endif /* defined(ST_ENABLE_STL_STRINGS) */ 458 | }; 459 | 460 | typedef buffer char_buffer; 461 | typedef buffer wchar_buffer; 462 | typedef buffer utf16_buffer; 463 | typedef buffer utf32_buffer; 464 | 465 | template 466 | ST_NODISCARD 467 | ST_DEPRECATED_IN_3_4("Use buffer::empty() instead") 468 | bool operator==(const null_t &, const buffer &right) noexcept 469 | { 470 | return right.empty(); 471 | } 472 | 473 | template 474 | ST_NODISCARD 475 | ST_DEPRECATED_IN_3_4("Use !buffer::empty() instead") 476 | bool operator!=(const null_t &, const buffer &right) noexcept 477 | { 478 | return !right.empty(); 479 | } 480 | } 481 | 482 | #define ST_CHAR_LITERAL(str) \ 483 | ST::char_buffer("" str "", sizeof(str) - 1) 484 | 485 | #define ST_WCHAR_LITERAL(str) \ 486 | ST::wchar_buffer(L"" str L"", (sizeof(L"" str L"") / sizeof(wchar_t)) - 1) 487 | 488 | #define ST_UTF16_LITERAL(str) \ 489 | ST::utf16_buffer(u"" str u"", (sizeof(u"" str u"") / sizeof(char16_t)) - 1) 490 | 491 | #define ST_UTF32_LITERAL(str) \ 492 | ST::utf32_buffer(U"" str U"", (sizeof(U"" str U"") / sizeof(char32_t)) - 1) 493 | 494 | namespace ST { namespace literals 495 | { 496 | ST_NODISCARD 497 | inline ST::char_buffer operator"" _stbuf(const char *str, size_t size) 498 | { 499 | return ST::char_buffer(str, size); 500 | } 501 | 502 | ST_NODISCARD 503 | inline ST::utf16_buffer operator"" _stbuf(const char16_t *str, size_t size) 504 | { 505 | return ST::utf16_buffer(str, size); 506 | } 507 | 508 | ST_NODISCARD 509 | inline ST::utf32_buffer operator"" _stbuf(const char32_t *str, size_t size) 510 | { 511 | return ST::utf32_buffer(str, size); 512 | } 513 | 514 | ST_NODISCARD 515 | inline ST::wchar_buffer operator"" _stbuf(const wchar_t *str, size_t size) 516 | { 517 | return ST::wchar_buffer(str, size); 518 | } 519 | 520 | #ifdef ST_HAVE_CXX20_CHAR8_TYPES 521 | ST_NODISCARD 522 | inline ST::char_buffer operator"" _stbuf(const char8_t *str, size_t size) 523 | { 524 | return ST::char_buffer(reinterpret_cast(str), size); 525 | } 526 | #endif 527 | }} 528 | 529 | #endif // _ST_CHARBUFFER_H 530 | -------------------------------------------------------------------------------- /include/st_codecs.h: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2016 Michael Hansen 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a 4 | copy of this software and associated documentation files (the "Software"), 5 | to deal in the Software without restriction, including without limitation 6 | the rights to use, copy, modify, merge, publish, distribute, sublicense, 7 | and/or sell copies of the Software, and to permit persons to whom the 8 | Software is furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 18 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 19 | DEALINGS IN THE SOFTWARE. */ 20 | 21 | #ifndef _ST_CODECS_H 22 | #define _ST_CODECS_H 23 | 24 | #include "st_string.h" 25 | 26 | #include "st_codecs_priv.h" 27 | 28 | namespace ST 29 | { 30 | inline string hex_encode(const void *data, size_t size) 31 | { 32 | if (size == 0) 33 | return ST::string(); 34 | 35 | if (!data) 36 | throw std::invalid_argument("null data pointer passed to hex_encode"); 37 | 38 | ST::char_buffer buffer; 39 | buffer.allocate(size * 2); 40 | _ST_PRIVATE::hex_encode(buffer.data(), data, size); 41 | return ST::string::from_validated(std::move(buffer)); 42 | } 43 | 44 | inline string hex_encode(const char_buffer &data) 45 | { 46 | return hex_encode(data.data(), data.size()); 47 | } 48 | 49 | inline ST_ssize_t hex_decode(const string &hex, void *output, 50 | size_t output_size) noexcept 51 | { 52 | return _ST_PRIVATE::hex_decode(hex, output, output_size); 53 | } 54 | 55 | inline char_buffer hex_decode(const string &hex) 56 | { 57 | if ((hex.size() % 2) != 0) 58 | throw codec_error("Invalid hex input length"); 59 | 60 | size_t decode_size = hex.size() / 2; 61 | ST::char_buffer result; 62 | result.allocate(decode_size); 63 | ST_ssize_t written = _ST_PRIVATE::hex_decode(hex, result.data(), decode_size); 64 | if (written < 0) 65 | throw codec_error("Invalid character in hex input"); 66 | 67 | ST_ASSERT(static_cast(written) == decode_size, 68 | "Conversion didn't match expected length"); 69 | return result; 70 | } 71 | 72 | inline string base64_encode(const void *data, size_t size) 73 | { 74 | if (size == 0) 75 | return ST::string(); 76 | 77 | if (!data) 78 | throw std::invalid_argument("null data pointer passed to base64_encode"); 79 | 80 | ST::char_buffer buffer; 81 | buffer.allocate(_ST_PRIVATE::b64_encode_size(size)); 82 | _ST_PRIVATE::b64_encode(buffer.data(), data, size); 83 | return ST::string::from_validated(std::move(buffer)); 84 | } 85 | 86 | inline string base64_encode(const char_buffer &data) 87 | { 88 | return base64_encode(data.data(), data.size()); 89 | } 90 | 91 | inline ST_ssize_t base64_decode(const string &base64, void *output, 92 | size_t output_size) noexcept 93 | { 94 | return _ST_PRIVATE::b64_decode(base64, output, output_size); 95 | } 96 | 97 | inline char_buffer base64_decode(const string &base64) 98 | { 99 | ST_ssize_t decode_size = _ST_PRIVATE::b64_decode_size(base64.size(), base64.c_str()); 100 | if (decode_size < 0) 101 | throw codec_error("Invalid base64 input length"); 102 | 103 | ST::char_buffer result; 104 | result.allocate(decode_size); 105 | ST_ssize_t written = _ST_PRIVATE::b64_decode(base64, result.data(), decode_size); 106 | if (written < 0) 107 | throw codec_error("Invalid character in base64 input"); 108 | 109 | ST_ASSERT(written == decode_size, "Conversion didn't match expected length"); 110 | return result; 111 | } 112 | } 113 | 114 | #endif // _ST_CODECS_H 115 | -------------------------------------------------------------------------------- /include/st_codecs_priv.h: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2019 Michael Hansen 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a 4 | copy of this software and associated documentation files (the "Software"), 5 | to deal in the Software without restriction, including without limitation 6 | the rights to use, copy, modify, merge, publish, distribute, sublicense, 7 | and/or sell copies of the Software, and to permit persons to whom the 8 | Software is furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 18 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 19 | DEALINGS IN THE SOFTWARE. */ 20 | 21 | #ifndef _ST_CODECS_PRIV_H 22 | #define _ST_CODECS_PRIV_H 23 | 24 | namespace _ST_PRIVATE 25 | { 26 | inline void hex_encode(char *output, const void *data, size_t size) noexcept 27 | { 28 | static constexpr const char hex_chars[] = "0123456789abcdef"; 29 | static_assert(sizeof(hex_chars) - 1 == 16, "Missing hex characters"); 30 | 31 | auto sp = static_cast(data); 32 | while (size) { 33 | unsigned char byte = *sp++; 34 | *output++ = hex_chars[(byte >> 4) & 0x0F]; 35 | *output++ = hex_chars[(byte ) & 0x0F]; 36 | --size; 37 | } 38 | } 39 | 40 | inline ST_ssize_t hex_decode(const ST::string &hex, void *output, 41 | size_t output_size) noexcept 42 | { 43 | static constexpr const int hex_values[] = { 44 | /* 00 */ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 45 | /* 10 */ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 46 | /* 20 */ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 47 | /* 30 */ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, -1, -1, -1, -1, -1, -1, 48 | /* 40 */ -1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1, 49 | /* 50 */ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 50 | /* 60 */ -1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1, 51 | /* 70 */ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 52 | /* 80 */ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 53 | /* 90 */ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 54 | /* A0 */ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 55 | /* B0 */ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 56 | /* C0 */ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 57 | /* D0 */ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 58 | /* E0 */ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 59 | /* F0 */ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 60 | }; 61 | static_assert(sizeof(hex_values) / sizeof(int) == 0x100, "Missing hex values"); 62 | 63 | if ((hex.size() % 2) != 0) 64 | return -1; 65 | 66 | size_t decode_size = hex.size() / 2; 67 | if (!output) 68 | return decode_size; 69 | 70 | if (static_cast(decode_size) > output_size) 71 | return -1; 72 | 73 | char *outp = reinterpret_cast(output); 74 | char *endp = outp + decode_size; 75 | auto sp = reinterpret_cast(hex.c_str()); 76 | 77 | while (outp < endp) { 78 | int bits[2] = { hex_values[sp[0]], hex_values[sp[1]] }; 79 | if (bits[0] < 0 || bits[1] < 0) 80 | return -1; 81 | 82 | *outp++ = (bits[0] << 4 | bits[1]); 83 | sp += 2; 84 | } 85 | 86 | return outp - reinterpret_cast(output); 87 | } 88 | 89 | inline size_t b64_encode_size(size_t size) 90 | { 91 | return ((size + 2) / 3) * 4; 92 | } 93 | 94 | inline ST_ssize_t b64_decode_size(size_t size, const char *data) 95 | { 96 | if ((size % 4) != 0) 97 | return -1; 98 | 99 | size_t result = (size / 4) * 3; 100 | if (size > 0 && data[size - 1] == '=') 101 | result -= 1; 102 | if (size > 1 && data[size - 2] == '=') 103 | result -= 1; 104 | 105 | return static_cast(result); 106 | } 107 | 108 | inline void b64_encode(char *output, const void *data, size_t size) noexcept 109 | { 110 | static constexpr const char b64_chars[] = 111 | "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; 112 | static_assert(sizeof(b64_chars) - 1 == 64, "Missing base64 characters"); 113 | 114 | auto sp = static_cast(data); 115 | while (size > 2) { 116 | *output++ = b64_chars[sp[0] >> 2]; 117 | *output++ = b64_chars[((sp[0] & 0x03) << 4) | ((sp[1] & 0xF0) >> 4)]; 118 | *output++ = b64_chars[((sp[1] & 0x0F) << 2) | ((sp[2] & 0xC0) >> 6)]; 119 | *output++ = b64_chars[sp[2] & 0x3F]; 120 | size -= 3; 121 | sp += 3; 122 | } 123 | 124 | // Final bytes treated specially 125 | switch (size) { 126 | case 2: 127 | *output++ = b64_chars[sp[0] >> 2]; 128 | *output++ = b64_chars[((sp[0] & 0x03) << 4) | ((sp[1] & 0xF0) >> 4)]; 129 | *output++ = b64_chars[((sp[1] & 0x0F) << 2)]; 130 | *output++ = '='; 131 | break; 132 | case 1: 133 | *output++ = b64_chars[sp[0] >> 2]; 134 | *output++ = b64_chars[((sp[0] & 0x03) << 4)]; 135 | *output++ = '='; 136 | *output++ = '='; 137 | break; 138 | case 0: 139 | break; 140 | default: 141 | ST_ASSERT(false, "Unexpected bytes left after encoding loop"); 142 | break; 143 | } 144 | } 145 | 146 | inline ST_ssize_t b64_decode(const ST::string &base64, void *output, 147 | size_t output_size) noexcept 148 | { 149 | static constexpr const int b64_values[] = { 150 | /* 00 */ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 151 | /* 10 */ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 152 | /* 20 */ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63, 153 | /* 30 */ 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1, 154 | /* 40 */ -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 155 | /* 50 */ 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1, 156 | /* 60 */ -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 157 | /* 70 */ 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1, 158 | /* 80 */ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 159 | /* 90 */ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 160 | /* A0 */ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 161 | /* B0 */ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 162 | /* C0 */ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 163 | /* D0 */ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 164 | /* E0 */ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 165 | /* F0 */ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 166 | }; 167 | static_assert(sizeof(b64_values) / sizeof(int) == 0x100, "Missing base64 values"); 168 | 169 | ST_ssize_t decode_size = b64_decode_size(base64.size(), base64.c_str()); 170 | if (!output) 171 | return decode_size; 172 | 173 | if (decode_size < 0 || static_cast(decode_size) > output_size) 174 | return -1; 175 | 176 | if (decode_size == 0) 177 | return 0; 178 | 179 | char *outp = reinterpret_cast(output); 180 | char *endp = outp + decode_size; 181 | auto sp = reinterpret_cast(base64.c_str()); 182 | 183 | while (outp + 3 < endp) { 184 | int bits[4] = { 185 | b64_values[sp[0]], b64_values[sp[1]], 186 | b64_values[sp[2]], b64_values[sp[3]] 187 | }; 188 | if (bits[0] < 0 || bits[1] < 0 || bits[2] < 0 || bits[3] < 0) 189 | return -1; 190 | 191 | *outp++ = (bits[0] << 2) | ((bits[1] >> 4) & 0x03); 192 | *outp++ = ((bits[1] << 4) & 0xF0) | ((bits[2] >> 2) & 0x0F); 193 | *outp++ = ((bits[2] << 6) & 0xC0) | (bits[3] & 0x3F); 194 | 195 | sp += 4; 196 | } 197 | 198 | // Final chars treated specially 199 | int bits[4] = { 200 | b64_values[sp[0]], b64_values[sp[1]], 201 | b64_values[sp[2]], b64_values[sp[3]] 202 | }; 203 | 204 | if (bits[0] < 0 || bits[1] < 0) 205 | return -1; 206 | 207 | *outp++ = (bits[0] << 2) | ((bits[1] >> 4) & 0x03); 208 | if (sp[2] != '=') { 209 | if (bits[2] < 0) 210 | return -1; 211 | *outp++ = ((bits[1] << 4) & 0xF0) | ((bits[2] >> 2) & 0x0F); 212 | } 213 | if (sp[3] != '=') { 214 | if (bits[2] < 0 || bits[3] < 0) 215 | return -1; 216 | *outp++ = ((bits[2] << 6) & 0xC0) | (bits[3] & 0x3F); 217 | } 218 | 219 | return outp - reinterpret_cast(output); 220 | } 221 | } 222 | 223 | #endif // _ST_CODECS_PRIV_H 224 | -------------------------------------------------------------------------------- /include/st_config.h.in: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2016 Michael Hansen 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a 4 | copy of this software and associated documentation files (the "Software"), 5 | to deal in the Software without restriction, including without limitation 6 | the rights to use, copy, modify, merge, publish, distribute, sublicense, 7 | and/or sell copies of the Software, and to permit persons to whom the 8 | Software is furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 18 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 19 | DEALINGS IN THE SOFTWARE. */ 20 | 21 | #ifndef _ST_CONFIG_H 22 | #define _ST_CONFIG_H 23 | 24 | #define ST_MAJOR_VERSION @ST_MAJOR_VERSION@ 25 | #define ST_MINOR_VERSION @ST_MINOR_VERSION@ 26 | #define ST_VERSION ((ST_MAJOR_VERSION * 100) + ST_MINOR_VERSION) 27 | #define ST_VERSION_STR "@ST_VERSION@" 28 | 29 | #cmakedefine ST_HAVE_INT64 30 | #if (__cplusplus > 201103L) || (defined(_MSVC_LANG) && (_MSVC_LANG > 201103L)) 31 | #cmakedefine ST_HAVE_DEPRECATED_ATTR 32 | #endif 33 | #if (__cplusplus > 201402L) || (defined(_MSVC_LANG) && (_MSVC_LANG > 201402L)) 34 | #cmakedefine ST_HAVE_NODISCARD_ATTR 35 | #cmakedefine ST_HAVE_CXX17_STRING_VIEW 36 | #cmakedefine ST_HAVE_CXX17_FILESYSTEM 37 | #if (__cplusplus > 201703L) || (defined(_MSVC_LANG) && (_MSVC_LANG > 201703L)) 38 | #cmakedefine ST_HAVE_CXX20_U8_FSPATH 39 | #endif 40 | #endif 41 | #if (__cplusplus > 201703L) || (defined(_MSVC_LANG) && (_MSVC_LANG > 201703L)) 42 | #cmakedefine ST_HAVE_CXX20_CHAR8_TYPES 43 | #endif 44 | 45 | #cmakedefine ST_ENABLE_STL_STRINGS 46 | #cmakedefine ST_ENABLE_STL_FILESYSTEM 47 | 48 | #define ST_ENUM_CONSTANT(type, name) constexpr type name = type::name 49 | 50 | #ifndef __has_feature 51 | # define __has_feature(x) 0 52 | #endif 53 | #if __has_feature(attribute_availability_with_version_underscores) || (__has_feature(attribute_availability_with_message) && __clang__ && __clang_major__ >= 7) 54 | # define ST_AVAILABILITY(...) __attribute__((availability( __VA_ARGS__ ))) 55 | #else 56 | # define ST_AVAILABILITY(...) 57 | #endif 58 | 59 | #define ST_FILESYSTEM_AVAILABILITY \ 60 | ST_AVAILABILITY(macosx,strict,introduced=10.15) \ 61 | ST_AVAILABILITY(ios,strict,introduced=13.0) \ 62 | ST_AVAILABILITY(tvos,strict,introduced=13.0) \ 63 | ST_AVAILABILITY(watchos,strict,introduced=6.0) 64 | 65 | #if defined(_MSC_VER) 66 | # define ST_DEPRECATED(message) __declspec(deprecated(message)) 67 | #elif defined(__GNUC__) 68 | # define ST_DEPRECATED(message) __attribute__((deprecated(message))) 69 | #elif defined(ST_HAVE_DEPRECATED_ATTR) 70 | # define ST_DEPRECATED(message) [[deprecated(message)]] 71 | #else 72 | # define ST_DEPRECATED(message) 73 | #endif 74 | 75 | #ifndef ST_DEPRECATED_VERSION 76 | # if defined(ST_NO_3_0_DEPRECATION) 77 | # define ST_DEPRECATED_VERSION 299 78 | # else 79 | # define ST_DEPRECATED_VERSION ST_VERSION 80 | # endif 81 | #endif 82 | 83 | #if ST_DEPRECATED_VERSION >= 300 84 | # define ST_DEPRECATED_IN_3_0(message) ST_DEPRECATED(message) 85 | #else 86 | # define ST_DEPRECATED_IN_3_0(message) 87 | #endif 88 | 89 | #if ST_DEPRECATED_VERSION >= 304 90 | # define ST_DEPRECATED_IN_3_4(message) ST_DEPRECATED(message) 91 | #else 92 | # define ST_DEPRECATED_IN_3_4(message) 93 | #endif 94 | 95 | #if ST_DEPRECATED_VERSION >= 400 96 | # define ST_DEPRECATED_IN_4_0(message) ST_DEPRECATED(message) 97 | #else 98 | # define ST_DEPRECATED_IN_4_0(message) 99 | #endif 100 | 101 | #if defined(ST_HAVE_NODISCARD_ATTR) 102 | # define ST_NODISCARD [[nodiscard]] 103 | #else 104 | # define ST_NODISCARD 105 | #endif 106 | 107 | #ifndef __has_cpp_attribute 108 | # define ST_LIFETIME_BOUND 109 | #elif __has_cpp_attribute(msvc::lifetimebound) 110 | # define ST_LIFETIME_BOUND [[msvc::lifetimebound]] 111 | #elif __has_cpp_attribute(clang::lifetimebound) 112 | # define ST_LIFETIME_BOUND [[clang::lifetimebound]] 113 | #elif __has_cpp_attribute(lifetimebound) 114 | # define ST_LIFETIME_BOUND [[lifetimebound]] 115 | #else 116 | # define ST_LIFETIME_BOUND 117 | #endif 118 | 119 | #define ST_MAX_SSO_LENGTH (16) 120 | #define ST_MAX_SSO_SIZE (48) 121 | #define ST_STACK_STRING_SIZE (256) 122 | 123 | // MSVC doesn't provide ssize_t 124 | #ifdef _MSC_VER 125 | # ifdef _WIN64 126 | typedef __int64 ST_ssize_t; 127 | # else 128 | typedef int ST_ssize_t; 129 | # endif 130 | #else 131 | # include 132 | typedef ssize_t ST_ssize_t; 133 | #endif 134 | 135 | #endif // _ST_CONFIG_H 136 | -------------------------------------------------------------------------------- /include/st_format.h: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2016 Michael Hansen 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a 4 | copy of this software and associated documentation files (the "Software"), 5 | to deal in the Software without restriction, including without limitation 6 | the rights to use, copy, modify, merge, publish, distribute, sublicense, 7 | and/or sell copies of the Software, and to permit persons to whom the 8 | Software is furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 18 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 19 | DEALINGS IN THE SOFTWARE. */ 20 | 21 | #ifndef _ST_FORMAT_H 22 | #define _ST_FORMAT_H 23 | 24 | #include "st_formatter.h" 25 | #include "st_stringstream.h" 26 | 27 | namespace _ST_PRIVATE 28 | { 29 | class string_format_writer : public ST::format_writer 30 | { 31 | public: 32 | explicit string_format_writer(const char *format_str) 33 | : ST::format_writer(format_str) { } 34 | 35 | string_format_writer &append(const char *data, size_t size) 36 | ST_LIFETIME_BOUND override 37 | { 38 | m_output.append(data, size); 39 | return *this; 40 | } 41 | 42 | string_format_writer &append_char(char ch, size_t count = 1) 43 | ST_LIFETIME_BOUND override 44 | { 45 | m_output.append_char(ch, count); 46 | return *this; 47 | } 48 | 49 | ST_NODISCARD 50 | ST::string to_string(bool utf8_encoded, ST::utf_validation_t validation) 51 | { 52 | return m_output.to_string(utf8_encoded, validation); 53 | } 54 | 55 | private: 56 | ST::string_stream m_output; 57 | }; 58 | } 59 | 60 | namespace ST 61 | { 62 | template 63 | ST_NODISCARD 64 | string format(const char *fmt_str, args_T &&...args) 65 | { 66 | _ST_PRIVATE::string_format_writer data(fmt_str); 67 | apply_format(data, std::forward(args)...); 68 | return data.to_string(true, ST_DEFAULT_VALIDATION); 69 | } 70 | 71 | template 72 | ST_NODISCARD 73 | string format(utf_validation_t validation, const char *fmt_str, 74 | args_T &&...args) 75 | { 76 | _ST_PRIVATE::string_format_writer data(fmt_str); 77 | apply_format(data, std::forward(args)...); 78 | return data.to_string(true, validation); 79 | } 80 | 81 | template 82 | ST_NODISCARD 83 | string format_latin_1(const char *fmt_str, args_T &&...args) 84 | { 85 | _ST_PRIVATE::string_format_writer data(fmt_str); 86 | apply_format(data, std::forward(args)...); 87 | return data.to_string(false, assume_valid); 88 | } 89 | } 90 | 91 | namespace _ST_PRIVATE 92 | { 93 | class udl_formatter 94 | { 95 | public: 96 | explicit udl_formatter(const char *fmt_str) 97 | : m_format(fmt_str) { } 98 | 99 | template 100 | ST_NODISCARD 101 | ST::string operator()(args_T &&...args) 102 | { 103 | return ST::format(m_format, std::forward(args)...); 104 | } 105 | 106 | private: 107 | const char *m_format; 108 | }; 109 | } 110 | 111 | namespace ST { namespace literals 112 | { 113 | ST_NODISCARD 114 | inline _ST_PRIVATE::udl_formatter operator"" _stfmt(const char *fmt_str, size_t) 115 | { 116 | return _ST_PRIVATE::udl_formatter(fmt_str); 117 | } 118 | }} 119 | 120 | #endif // _ST_FORMAT_H 121 | -------------------------------------------------------------------------------- /include/st_format_numeric.h: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2018 Michael Hansen 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a 4 | copy of this software and associated documentation files (the "Software"), 5 | to deal in the Software without restriction, including without limitation 6 | the rights to use, copy, modify, merge, publish, distribute, sublicense, 7 | and/or sell copies of the Software, and to permit persons to whom the 8 | Software is furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 18 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 19 | DEALINGS IN THE SOFTWARE. */ 20 | 21 | #ifndef _ST_FORMAT_NUMERIC_H 22 | #define _ST_FORMAT_NUMERIC_H 23 | 24 | #include 25 | 26 | namespace _ST_PRIVATE 27 | { 28 | ST_NODISCARD 29 | inline size_t format_double(char *buffer, size_t size, double value, char format) 30 | { 31 | const char format_spec[] = { '%', format, 0 }; 32 | 33 | int format_size = snprintf(buffer, size, format_spec, value); 34 | ST_ASSERT(format_size > 0, "Your libc doesn't support reporting format size"); 35 | ST_ASSERT(static_cast(format_size) < size, "Format buffer too small"); 36 | 37 | return static_cast(format_size); 38 | } 39 | } 40 | 41 | namespace ST 42 | { 43 | template 44 | class uint_formatter 45 | { 46 | typedef std::numeric_limits uint_limits; 47 | static_assert(uint_limits::is_integer && !uint_limits::is_signed, 48 | "uint_formatter can only be used for unsigned integral types"); 49 | 50 | public: 51 | uint_formatter() noexcept : m_start(nullptr) { } 52 | 53 | void format(uint_T value, int radix, bool upper_case = false) noexcept 54 | { 55 | m_buffer[uint_limits::digits] = 0; 56 | m_start = &m_buffer[uint_limits::digits]; 57 | if (value == 0) { 58 | *--m_start = '0'; 59 | return; 60 | } 61 | 62 | while (value) { 63 | unsigned int digit = (value % radix); 64 | value /= radix; 65 | --m_start; 66 | 67 | if (digit < 10) 68 | *m_start = '0' + digit; 69 | else if (upper_case) 70 | *m_start = 'A' + digit - 10; 71 | else 72 | *m_start = 'a' + digit - 10; 73 | } 74 | } 75 | 76 | ST_NODISCARD 77 | const char *text() const noexcept ST_LIFETIME_BOUND { return m_start; } 78 | 79 | ST_NODISCARD 80 | size_t size() const noexcept 81 | { 82 | return m_buffer + uint_limits::digits - m_start; 83 | } 84 | 85 | private: 86 | char m_buffer[uint_limits::digits + 1]; 87 | char *m_start; 88 | }; 89 | 90 | template 91 | class float_formatter 92 | { 93 | static_assert(!std::numeric_limits::is_integer, 94 | "float_formatter can only be used for floating point types"); 95 | 96 | public: 97 | float_formatter() noexcept : m_size() { } 98 | 99 | void format(float_T value, char format) 100 | { 101 | static const char valid_formats[] = "efgEFG"; 102 | if (!std::char_traits::find(valid_formats, sizeof(valid_formats) - 1, format)) 103 | throw ST::bad_format("Unsupported floating-point format specifier"); 104 | 105 | m_size = _ST_PRIVATE::format_double(m_buffer, sizeof(m_buffer), value, format); 106 | } 107 | 108 | ST_NODISCARD 109 | const char *text() const noexcept ST_LIFETIME_BOUND { return m_buffer; } 110 | 111 | ST_NODISCARD 112 | size_t size() const noexcept { return m_size; } 113 | 114 | private: 115 | char m_buffer[64]; 116 | size_t m_size; 117 | }; 118 | } 119 | 120 | #endif // _ST_FORMAT_NUMERIC_H 121 | -------------------------------------------------------------------------------- /include/st_format_priv.h: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2019 Michael Hansen 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a 4 | copy of this software and associated documentation files (the "Software"), 5 | to deal in the Software without restriction, including without limitation 6 | the rights to use, copy, modify, merge, publish, distribute, sublicense, 7 | and/or sell copies of the Software, and to permit persons to whom the 8 | Software is furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 18 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 19 | DEALINGS IN THE SOFTWARE. */ 20 | 21 | #ifndef _ST_FORMAT_PRIV_H 22 | #define _ST_FORMAT_PRIV_H 23 | 24 | #include "st_utf_conv_priv.h" 25 | 26 | namespace _ST_PRIVATE 27 | { 28 | enum numeric_type 29 | { 30 | numeric_positive, 31 | numeric_negative, 32 | numeric_zero 33 | }; 34 | 35 | ST_NODISCARD 36 | inline size_t pad_size(const ST::format_spec &format, size_t size, 37 | numeric_type ntype) 38 | { 39 | ST_ssize_t pad_size = format.minimum_length - size; 40 | 41 | if (ntype == numeric_negative || format.always_signed) 42 | --pad_size; 43 | 44 | if (ntype != numeric_zero && format.class_prefix) { 45 | switch (format.digit_class) { 46 | case ST::digit_hex: 47 | case ST::digit_hex_upper: 48 | case ST::digit_bin: 49 | pad_size -= 2; 50 | break; 51 | case ST::digit_oct: 52 | pad_size -= 1; 53 | break; 54 | default: 55 | break; 56 | } 57 | } 58 | 59 | return (pad_size > 0) ? static_cast(pad_size) : 0; 60 | } 61 | 62 | inline void format_numeric_prefix(const ST::format_spec &format, 63 | ST::format_writer &output, 64 | numeric_type ntype) 65 | { 66 | if (ntype == numeric_negative) 67 | output.append_char('-'); 68 | else if (format.always_signed) 69 | output.append_char('+'); 70 | 71 | if (ntype != numeric_zero && format.class_prefix) { 72 | switch (format.digit_class) { 73 | case ST::digit_hex: 74 | output.append("0x", 2); 75 | break; 76 | case ST::digit_hex_upper: 77 | output.append("0X", 2); 78 | break; 79 | case ST::digit_bin: 80 | output.append("0b", 2); 81 | break; 82 | case ST::digit_oct: 83 | output.append_char('0'); 84 | break; 85 | default: 86 | break; 87 | } 88 | } 89 | } 90 | 91 | inline void format_numeric_string(const ST::format_spec &format, 92 | ST::format_writer &output, 93 | const char *text, size_t size, 94 | numeric_type ntype) 95 | { 96 | const char pad = format.pad ? format.pad : ' '; 97 | const size_t psize = pad_size(format, size, ntype); 98 | 99 | if (format.numeric_pad) { 100 | format_numeric_prefix(format, output, ntype); 101 | 102 | // numeric padding is always right-aligned 103 | output.append_char(pad, psize); 104 | output.append(text, size); 105 | } else { 106 | ST::alignment_t align = 107 | (format.alignment == ST::align_default) 108 | ? ST::align_right : format.alignment; 109 | 110 | if (align == ST::align_right) { 111 | output.append_char(pad, psize); 112 | format_numeric_prefix(format, output, ntype); 113 | output.append(text, size); 114 | } else { 115 | format_numeric_prefix(format, output, ntype); 116 | output.append(text, size); 117 | output.append_char(pad, psize); 118 | } 119 | } 120 | } 121 | 122 | template 123 | void format_numeric_s(const ST::format_spec &format, 124 | ST::format_writer &output, int_T value) 125 | { 126 | static_assert(std::is_signed::value, 127 | "Use _format_numeric_u for unsigned numerics"); 128 | 129 | int radix = 10; 130 | bool upper_case = false; 131 | switch (format.digit_class) { 132 | case ST::digit_hex_upper: 133 | upper_case = true; 134 | /* fall through */ 135 | case ST::digit_hex: 136 | radix = 16; 137 | break; 138 | case ST::digit_oct: 139 | radix = 8; 140 | break; 141 | case ST::digit_bin: 142 | radix = 2; 143 | break; 144 | case ST::digit_dec: 145 | case ST::digit_default: 146 | break; 147 | default: 148 | ST_ASSERT(false, "Invalid digit class for _format_numeric_s"); 149 | } 150 | 151 | typedef typename std::make_unsigned::type uint_T; 152 | ST::uint_formatter formatter; 153 | formatter.format(static_cast(std::abs(value)), radix, upper_case); 154 | 155 | const numeric_type ntype = (value == 0) ? numeric_zero 156 | : (value < 0) ? numeric_negative 157 | : numeric_positive; 158 | 159 | format_numeric_string(format, output, formatter.text(), formatter.size(), ntype); 160 | } 161 | 162 | template 163 | void format_numeric_u(const ST::format_spec &format, 164 | ST::format_writer &output, uint_T value) 165 | { 166 | static_assert(std::is_unsigned::value, 167 | "Use _format_numeric_s for signed numerics"); 168 | 169 | int radix = 10; 170 | bool upper_case = false; 171 | switch (format.digit_class) { 172 | case ST::digit_hex_upper: 173 | upper_case = true; 174 | /* fall through */ 175 | case ST::digit_hex: 176 | radix = 16; 177 | break; 178 | case ST::digit_oct: 179 | radix = 8; 180 | break; 181 | case ST::digit_bin: 182 | radix = 2; 183 | break; 184 | case ST::digit_dec: 185 | case ST::digit_default: 186 | break; 187 | default: 188 | ST_ASSERT(false, "Invalid digit class for _format_numeric_u"); 189 | } 190 | 191 | ST::uint_formatter formatter; 192 | formatter.format(value, radix, upper_case); 193 | 194 | const numeric_type ntype = (value == 0) ? numeric_zero : numeric_positive; 195 | 196 | format_numeric_string(format, output, formatter.text(), formatter.size(), ntype); 197 | } 198 | 199 | inline void format_char(const ST::format_spec &format, 200 | ST::format_writer &output, int ch) 201 | { 202 | if (format.minimum_length != 0 || format.pad != 0) 203 | ST_ASSERT(false, "Char formatting does not currently support padding"); 204 | 205 | // Don't need to nul-terminate this, since we just write a fixed length 206 | char utf8[4]; 207 | char *dest = utf8; 208 | conversion_error_t error = write_utf8(dest, ch); 209 | if (error != conversion_error_t::success) 210 | append_chars(dest, badchar_substitute_utf8, badchar_substitute_utf8_len); 211 | 212 | ST_ASSERT(size_t(dest - utf8) <= sizeof(utf8), "Destination buffer too small"); 213 | 214 | output.append(utf8, dest - utf8); 215 | } 216 | } 217 | 218 | #endif // _ST_FORMAT_PRIV_H 219 | -------------------------------------------------------------------------------- /include/st_formatter.h: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2016 Michael Hansen 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a 4 | copy of this software and associated documentation files (the "Software"), 5 | to deal in the Software without restriction, including without limitation 6 | the rights to use, copy, modify, merge, publish, distribute, sublicense, 7 | and/or sell copies of the Software, and to permit persons to whom the 8 | Software is furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 18 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 19 | DEALINGS IN THE SOFTWARE. */ 20 | 21 | #ifndef _ST_FORMATTER_H 22 | #define _ST_FORMATTER_H 23 | 24 | #include "st_string.h" 25 | 26 | #include 27 | #include 28 | 29 | #if defined(ST_ENABLE_STL_STRINGS) 30 | # include 31 | #endif 32 | 33 | namespace ST 34 | { 35 | enum class alignment_t 36 | { 37 | align_default, 38 | align_left, 39 | align_right 40 | }; 41 | ST_ENUM_CONSTANT(alignment_t, align_default); 42 | ST_ENUM_CONSTANT(alignment_t, align_left); 43 | ST_ENUM_CONSTANT(alignment_t, align_right); 44 | 45 | enum class digit_class_t 46 | { 47 | digit_default, 48 | digit_dec, 49 | digit_hex, 50 | digit_hex_upper, 51 | digit_oct, 52 | digit_bin, 53 | digit_char 54 | }; 55 | ST_ENUM_CONSTANT(digit_class_t, digit_default); 56 | ST_ENUM_CONSTANT(digit_class_t, digit_dec); 57 | ST_ENUM_CONSTANT(digit_class_t, digit_hex); 58 | ST_ENUM_CONSTANT(digit_class_t, digit_hex_upper); 59 | ST_ENUM_CONSTANT(digit_class_t, digit_oct); 60 | ST_ENUM_CONSTANT(digit_class_t, digit_bin); 61 | ST_ENUM_CONSTANT(digit_class_t, digit_char); 62 | 63 | enum class float_class_t 64 | { 65 | float_default, 66 | float_fixed, 67 | float_exp, 68 | float_exp_upper 69 | }; 70 | ST_ENUM_CONSTANT(float_class_t, float_default); 71 | ST_ENUM_CONSTANT(float_class_t, float_fixed); 72 | ST_ENUM_CONSTANT(float_class_t, float_exp); 73 | ST_ENUM_CONSTANT(float_class_t, float_exp_upper); 74 | 75 | struct format_spec 76 | { 77 | format_spec() noexcept 78 | : minimum_length(), precision(-1), arg_index(-1), alignment(), 79 | digit_class(), float_class(), pad(), always_signed(), 80 | class_prefix(), numeric_pad() { } 81 | 82 | int minimum_length; 83 | int precision; 84 | int arg_index; 85 | alignment_t alignment; 86 | digit_class_t digit_class; 87 | float_class_t float_class; 88 | char pad; 89 | bool always_signed; 90 | bool class_prefix; 91 | bool numeric_pad; 92 | }; 93 | 94 | static_assert(std::is_standard_layout::value, 95 | "ST::format_spec must be standard-layout to pass across the DLL boundary"); 96 | 97 | class format_writer 98 | { 99 | public: 100 | format_writer(const char *format) : m_format_str(format) 101 | { 102 | if (!m_format_str) 103 | throw std::invalid_argument("Passed a null format string!"); 104 | } 105 | 106 | format_writer(const format_writer&) = delete; 107 | format_writer& operator=(const format_writer&) = delete; 108 | 109 | format_writer(format_writer&&) = default; 110 | format_writer& operator=(format_writer&&) ST_LIFETIME_BOUND = default; 111 | 112 | virtual ~format_writer() noexcept { } 113 | 114 | virtual format_writer &append(const char *data, size_t size) ST_LIFETIME_BOUND = 0; 115 | virtual format_writer &append_char(char ch, size_t count = 1) ST_LIFETIME_BOUND = 0; 116 | 117 | template 118 | format_writer &append(const char (&literal)[size]) ST_LIFETIME_BOUND 119 | { 120 | return append(literal, size - 1); 121 | } 122 | 123 | ST_NODISCARD 124 | bool next_format() 125 | { 126 | switch (fetch_prefix()) { 127 | case 0: 128 | return false; 129 | case '{': 130 | return true; 131 | default: 132 | throw ST::bad_format("Error parsing format string"); 133 | } 134 | } 135 | 136 | ST_NODISCARD 137 | ST::format_spec parse_format() 138 | { 139 | ST_ASSERT(*m_format_str == '{', "parse_format() called with no format"); 140 | 141 | ST::format_spec spec; 142 | for ( ;; ) { 143 | switch (*++m_format_str) { 144 | case 0: 145 | throw ST::bad_format("Unterminated format specifier"); 146 | case '}': 147 | // Done with format spec 148 | ++m_format_str; 149 | return spec; 150 | 151 | case '<': 152 | spec.alignment = ST::align_left; 153 | break; 154 | case '>': 155 | spec.alignment = ST::align_right; 156 | break; 157 | case '_': 158 | spec.pad = *(m_format_str + 1); 159 | spec.numeric_pad = false; 160 | if (!spec.pad) 161 | throw ST::bad_format("Unterminated format specifier"); 162 | ++m_format_str; 163 | break; 164 | case '0': 165 | // For easier porting from %08X-style printf strings 166 | spec.pad = '0'; 167 | spec.numeric_pad = true; 168 | break; 169 | case '#': 170 | spec.class_prefix = true; 171 | break; 172 | case 'x': 173 | spec.digit_class = ST::digit_hex; 174 | break; 175 | case 'X': 176 | spec.digit_class = ST::digit_hex_upper; 177 | break; 178 | case '+': 179 | spec.always_signed = true; 180 | break; 181 | case 'd': 182 | spec.digit_class = ST::digit_dec; 183 | break; 184 | case 'o': 185 | spec.digit_class = ST::digit_oct; 186 | break; 187 | case 'b': 188 | spec.digit_class = ST::digit_bin; 189 | break; 190 | case 'c': 191 | spec.digit_class = ST::digit_char; 192 | break; 193 | case 'f': 194 | spec.float_class = ST::float_fixed; 195 | break; 196 | case 'e': 197 | spec.float_class = ST::float_exp; 198 | break; 199 | case 'E': 200 | spec.float_class = ST::float_exp_upper; 201 | break; 202 | case '1': case '2': case '3': case '4': case '5': 203 | case '6': case '7': case '8': case '9': 204 | { 205 | char *end = nullptr; 206 | spec.minimum_length = static_cast(strtol(m_format_str, &end, 10)); 207 | m_format_str = end - 1; 208 | break; 209 | } 210 | case '.': 211 | { 212 | if (*++m_format_str == 0) 213 | throw ST::bad_format("Unterminated format specifier"); 214 | char *end = nullptr; 215 | spec.precision = static_cast(strtol(m_format_str, &end, 10)); 216 | m_format_str = end - 1; 217 | break; 218 | } 219 | case '&': 220 | { 221 | if (*++m_format_str == 0) 222 | throw ST::bad_format("Unterminated format specifier"); 223 | char *end = nullptr; 224 | spec.arg_index = static_cast(strtol(m_format_str, &end, 10)); 225 | m_format_str = end - 1; 226 | break; 227 | } 228 | default: 229 | throw ST::bad_format("Unexpected character in format string"); 230 | } 231 | } 232 | } 233 | 234 | private: 235 | const char *m_format_str; 236 | 237 | ST_NODISCARD 238 | char fetch_prefix() 239 | { 240 | const char *next = m_format_str; 241 | while (*next) { 242 | if (*next == '{') { 243 | if (*(next + 1) != '{') 244 | break; 245 | 246 | append(m_format_str, next - m_format_str); 247 | m_format_str = ++next; 248 | } else if (*next == '}') { 249 | if (*(next + 1) == '}') { 250 | append(m_format_str, next - m_format_str); 251 | m_format_str = ++next; 252 | } 253 | } 254 | ++next; 255 | } 256 | if (next != m_format_str) 257 | append(m_format_str, next - m_format_str); 258 | m_format_str = next; 259 | 260 | return *m_format_str; 261 | } 262 | }; 263 | 264 | inline void format_string(const format_spec &format, format_writer &output, 265 | const char *text, size_t size, 266 | alignment_t default_alignment = align_left) 267 | { 268 | char pad = format.pad ? format.pad : ' '; 269 | 270 | if (format.precision >= 0 && size > static_cast(format.precision)) 271 | size = static_cast(format.precision); 272 | 273 | if (format.minimum_length > static_cast(size)) { 274 | ST::alignment_t align = 275 | (format.alignment == ST::align_default) 276 | ? default_alignment : format.alignment; 277 | 278 | if (align == ST::align_right) { 279 | output.append_char(pad, format.minimum_length - size); 280 | output.append(text, size); 281 | } else { 282 | output.append(text, size); 283 | output.append_char(pad, format.minimum_length - size); 284 | } 285 | } else { 286 | output.append(text, size); 287 | } 288 | } 289 | 290 | #ifdef ST_HAVE_CXX20_CHAR8_TYPES 291 | inline void format_string(const format_spec &format, format_writer &output, 292 | const char8_t *text, size_t size, 293 | alignment_t default_alignment = align_left) 294 | { 295 | format_string(format, output, reinterpret_cast(text), 296 | size, default_alignment); 297 | } 298 | #endif 299 | 300 | typedef std::function 301 | formatter_ref_t; 302 | 303 | template 304 | ST_NODISCARD 305 | formatter_ref_t make_formatter_ref(type_T value) 306 | { 307 | return [value](const ST::format_spec &format, ST::format_writer &output) { 308 | format_type(format, output, value); 309 | }; 310 | } 311 | 312 | template 313 | void apply_format(ST::format_writer &data, arg0_T &&arg0, args_T &&...args) 314 | { 315 | enum { num_formatters = 1 + sizeof...(args) }; 316 | formatter_ref_t formatters[num_formatters] = { 317 | make_formatter_ref(std::forward(arg0)), 318 | make_formatter_ref(std::forward(args))... 319 | }; 320 | size_t index = 0; 321 | while (data.next_format()) { 322 | ST::format_spec spec = data.parse_format(); 323 | size_t formatter_id = (spec.arg_index >= 0) 324 | ? spec.arg_index - 1 325 | : index++; 326 | if (formatter_id >= num_formatters) 327 | throw std::out_of_range("Parameter index out of range"); 328 | formatters[formatter_id](spec, data); 329 | } 330 | } 331 | 332 | inline void apply_format(ST::format_writer &data) 333 | { 334 | if (data.next_format()) 335 | throw std::out_of_range("Parameter index out of range"); 336 | } 337 | } 338 | 339 | #include "st_format_priv.h" 340 | 341 | namespace ST 342 | { 343 | inline void format_type(const ST::format_spec &format, ST::format_writer &output, 344 | char value) 345 | { 346 | if (format.digit_class == ST::digit_char) 347 | _ST_PRIVATE::format_char(format, output, value); 348 | else if (std::numeric_limits::is_signed) 349 | _ST_PRIVATE::format_numeric_s(format, output, static_cast(value)); 350 | else 351 | _ST_PRIVATE::format_numeric_u(format, output, static_cast(value)); 352 | } 353 | 354 | inline void format_type(const ST::format_spec &format, ST::format_writer &output, 355 | wchar_t value) 356 | { 357 | if (format.digit_class == ST::digit_char) 358 | _ST_PRIVATE::format_char(format, output, static_cast(value)); 359 | else if (std::numeric_limits::is_signed) 360 | _ST_PRIVATE::format_numeric_s(format, output, static_cast(value)); 361 | else 362 | _ST_PRIVATE::format_numeric_u(format, output, static_cast(value)); 363 | } 364 | 365 | inline void format_type(const ST::format_spec &format, ST::format_writer &output, 366 | char16_t value) 367 | { 368 | if (format.digit_class == ST::digit_char) 369 | _ST_PRIVATE::format_char(format, output, static_cast(value)); 370 | else 371 | _ST_PRIVATE::format_numeric_u(format, output, static_cast(value)); 372 | } 373 | 374 | inline void format_type(const ST::format_spec &format, ST::format_writer &output, 375 | char32_t value) 376 | { 377 | if (format.digit_class == ST::digit_char) 378 | _ST_PRIVATE::format_char(format, output, static_cast(value)); 379 | else 380 | _ST_PRIVATE::format_numeric_u(format, output, static_cast(value)); 381 | } 382 | 383 | #ifdef ST_HAVE_CXX20_CHAR8_TYPES 384 | inline void format_type(const ST::format_spec &format, ST::format_writer &output, 385 | char8_t value) 386 | { 387 | // Special case: This is a UTF-8 code unit, not a whole character. 388 | // Therefore, if we're formatting it as a character, just leave it 389 | // as-is in the output. 390 | if (format.digit_class == ST::digit_char) { 391 | if (format.minimum_length != 0 || format.pad != 0) 392 | ST_ASSERT(false, "Char formatting does not currently support padding"); 393 | 394 | output.append_char(static_cast(value)); 395 | } else { 396 | _ST_PRIVATE::format_numeric_u(format, output, static_cast(value)); 397 | } 398 | } 399 | #endif 400 | 401 | # define _ST_FORMAT_INT_TYPE(int_T, uint_T) \ 402 | inline void format_type(const ST::format_spec &format, ST::format_writer &output, \ 403 | int_T value) \ 404 | { \ 405 | if (format.digit_class == ST::digit_char) \ 406 | _ST_PRIVATE::format_char(format, output, static_cast(value)); \ 407 | else \ 408 | _ST_PRIVATE::format_numeric_s(format, output, value); \ 409 | } \ 410 | \ 411 | inline void format_type(const ST::format_spec &format, ST::format_writer &output, \ 412 | uint_T value) \ 413 | { \ 414 | if (format.digit_class == ST::digit_char) \ 415 | _ST_PRIVATE::format_char(format, output, static_cast(value)); \ 416 | else \ 417 | _ST_PRIVATE::format_numeric_u(format, output, value); \ 418 | } 419 | 420 | _ST_FORMAT_INT_TYPE(signed char, unsigned char) 421 | _ST_FORMAT_INT_TYPE(short, unsigned short) 422 | _ST_FORMAT_INT_TYPE(int, unsigned int) 423 | _ST_FORMAT_INT_TYPE(long, unsigned long) 424 | _ST_FORMAT_INT_TYPE(long long, unsigned long long) 425 | 426 | # undef _ST_FORMAT_INT_TYPE 427 | 428 | inline void format_type(const ST::format_spec &format, ST::format_writer &output, 429 | double value) 430 | { 431 | char pad = format.pad ? format.pad : ' '; 432 | 433 | // Cheating a bit here -- just pass it along to cstdio 434 | char format_buffer[32]; 435 | size_t end = 0; 436 | 437 | format_buffer[end++] = '%'; 438 | 439 | if (format.always_signed) 440 | format_buffer[end++] = '+'; 441 | 442 | if (format.precision >= 0) { 443 | format_buffer[end++] = '.'; 444 | ST::uint_formatter prec; 445 | prec.format(format.precision, 10); 446 | std::char_traits::move(format_buffer + end, prec.text(), prec.size()); 447 | 448 | // Ensure one more space (excluding \0) is available for the format specifier 449 | ST_ASSERT(prec.size() > 0 && prec.size() + end + 2 < sizeof(format_buffer), 450 | "Not enough space for format string"); 451 | end += prec.size(); 452 | } 453 | 454 | format_buffer[end++] = 455 | (format.float_class == ST::float_exp) ? 'e' : 456 | (format.float_class == ST::float_exp_upper) ? 'E' : 457 | (format.float_class == ST::float_fixed) ? 'f' : 'g'; 458 | format_buffer[end] = 0; 459 | 460 | char out_buffer[64]; 461 | int format_size = snprintf(out_buffer, sizeof(out_buffer), format_buffer, value); 462 | ST_ASSERT(format_size > 0, "Your libc doesn't support reporting format size"); 463 | ST_ASSERT(static_cast(format_size) < sizeof(out_buffer), "Format buffer too small"); 464 | 465 | if (format.minimum_length > format_size) { 466 | if (format.alignment == ST::align_left) { 467 | output.append(out_buffer, format_size); 468 | output.append_char(pad, format.minimum_length - format_size); 469 | } else { 470 | output.append_char(pad, format.minimum_length - format_size); 471 | output.append(out_buffer, format_size); 472 | } 473 | } else { 474 | output.append(out_buffer, format_size); 475 | } 476 | } 477 | 478 | inline void format_type(const ST::format_spec &format, ST::format_writer &output, 479 | float value) 480 | { 481 | format_type(format, output, double(value)); 482 | } 483 | 484 | template 485 | void format_type(const ST::format_spec &format, ST::format_writer &output, 486 | const std::complex &value) 487 | { 488 | format_type(format, output, value.real()); 489 | output.append_char('+'); 490 | format_type(format, output, value.imag()); 491 | output.append_char('i'); 492 | } 493 | 494 | inline void format_type(const ST::format_spec &format, ST::format_writer &output, 495 | const char *text) 496 | { 497 | if (text) 498 | ST::format_string(format, output, text, std::char_traits::length(text)); 499 | } 500 | 501 | inline void format_type(const ST::format_spec &format, ST::format_writer &output, 502 | const wchar_t *wtext) 503 | { 504 | if (wtext) { 505 | ST::char_buffer utf8 = ST::string::from_wchar(wtext).to_utf8(); 506 | ST::format_string(format, output, utf8.data(), utf8.size()); 507 | } 508 | } 509 | 510 | inline void format_type(const ST::format_spec &format, ST::format_writer &output, 511 | const char16_t *text) 512 | { 513 | if (text) { 514 | ST::char_buffer utf8 = ST::string::from_utf16(text).to_utf8(); 515 | ST::format_string(format, output, utf8.data(), utf8.size()); 516 | } 517 | } 518 | 519 | inline void format_type(const ST::format_spec &format, ST::format_writer &output, 520 | const char32_t *text) 521 | { 522 | if (text) { 523 | ST::char_buffer utf8 = ST::string::from_utf32(text).to_utf8(); 524 | ST::format_string(format, output, utf8.data(), utf8.size()); 525 | } 526 | } 527 | 528 | #ifdef ST_HAVE_CXX20_CHAR8_TYPES 529 | inline void format_type(const ST::format_spec &format, ST::format_writer &output, 530 | const char8_t *text) 531 | { 532 | ST::format_string(format, output, reinterpret_cast(text), 533 | std::char_traits::length(text)); 534 | } 535 | #endif 536 | 537 | inline void format_type(const ST::format_spec &format, ST::format_writer &output, 538 | const ST::string &str) 539 | { 540 | ST::format_string(format, output, str.c_str(), str.size()); 541 | } 542 | 543 | #if defined(ST_ENABLE_STL_STRINGS) 544 | 545 | inline void format_type(const ST::format_spec &format, ST::format_writer &output, 546 | const std::string &str) 547 | { 548 | ST::format_string(format, output, str.c_str(), str.size()); 549 | } 550 | 551 | inline void format_type(const ST::format_spec &format, ST::format_writer &output, 552 | const std::wstring &str) 553 | { 554 | ST::char_buffer utf8 = ST::string::from_wchar(str.c_str(), str.size()).to_utf8(); 555 | ST::format_string(format, output, utf8.data(), utf8.size()); 556 | } 557 | 558 | inline void format_type(const ST::format_spec &format, ST::format_writer &output, 559 | const std::u16string &str) 560 | { 561 | ST::char_buffer utf8 = ST::string::from_utf16(str.c_str(), str.size()).to_utf8(); 562 | ST::format_string(format, output, utf8.data(), utf8.size()); 563 | } 564 | 565 | inline void format_type(const ST::format_spec &format, ST::format_writer &output, 566 | const std::u32string &str) 567 | { 568 | ST::char_buffer utf8 = ST::string::from_utf32(str.c_str(), str.size()).to_utf8(); 569 | ST::format_string(format, output, utf8.data(), utf8.size()); 570 | } 571 | 572 | #ifdef ST_HAVE_CXX20_CHAR8_TYPES 573 | inline void format_type(const ST::format_spec &format, ST::format_writer &output, 574 | const std::u8string &str) 575 | { 576 | ST::format_string(format, output, reinterpret_cast(str.c_str()), str.size()); 577 | } 578 | #endif 579 | 580 | #ifdef ST_HAVE_CXX17_STRING_VIEW 581 | inline void format_type(const ST::format_spec &format, ST::format_writer &output, 582 | const std::string_view &view) 583 | { 584 | ST::format_string(format, output, view.data(), view.size()); 585 | } 586 | 587 | inline void format_type(const ST::format_spec &format, ST::format_writer &output, 588 | const std::wstring_view &view) 589 | { 590 | ST::char_buffer utf8 = ST::string::from_wchar(view.data(), view.size()).to_utf8(); 591 | ST::format_string(format, output, utf8.data(), utf8.size()); 592 | } 593 | 594 | inline void format_type(const ST::format_spec &format, ST::format_writer &output, 595 | const std::u16string_view &view) 596 | { 597 | ST::char_buffer utf8 = ST::string::from_utf16(view.data(), view.size()).to_utf8(); 598 | ST::format_string(format, output, utf8.data(), utf8.size()); 599 | } 600 | 601 | inline void format_type(const ST::format_spec &format, ST::format_writer &output, 602 | const std::u32string_view &view) 603 | { 604 | ST::char_buffer utf8 = ST::string::from_utf32(view.data(), view.size()).to_utf8(); 605 | ST::format_string(format, output, utf8.data(), utf8.size()); 606 | } 607 | #endif 608 | 609 | #ifdef ST_HAVE_CXX20_CHAR8_TYPES 610 | inline void format_type(const ST::format_spec &format, ST::format_writer &output, 611 | const std::u8string_view &view) 612 | { 613 | ST::format_string(format, output, reinterpret_cast(view.data()), view.size()); 614 | } 615 | #endif 616 | 617 | #endif // defined(ST_ENABLE_STL_STRINGS) 618 | 619 | #if defined(ST_ENABLE_STL_FILESYSTEM) && defined(ST_HAVE_CXX17_FILESYSTEM) 620 | inline void format_type(const ST::format_spec &format, ST::format_writer &output, 621 | const std::filesystem::path &path) ST_FILESYSTEM_AVAILABILITY 622 | { 623 | auto u8path = path.u8string(); 624 | ST::format_string(format, output, u8path.c_str(), u8path.size()); 625 | } 626 | #endif 627 | 628 | inline void format_type(const ST::format_spec &format, ST::format_writer &output, 629 | bool value) 630 | { 631 | if (value) 632 | ST::format_string(format, output, "true", 4); 633 | else 634 | ST::format_string(format, output, "false", 5); 635 | } 636 | } 637 | 638 | /* These macros are maintained for backwards compatibility. They should be 639 | considered deprecated, and may be removed from a future version of 640 | string_theory (4.0 or later) */ 641 | #define ST_DECL_FORMAT_TYPE(type_T) \ 642 | void format_type(const ST::format_spec &, ST::format_writer &, type_T) 643 | 644 | #define ST_FORMAT_TYPE(type_T) \ 645 | void format_type(const ST::format_spec &format, ST::format_writer &output, type_T value) 646 | 647 | #define ST_FORMAT_FORWARD(fwd_value) \ 648 | format_type(format, output, (fwd_value)) 649 | 650 | #define ST_INVOKE_FORMATTER format_type 651 | 652 | #endif // _ST_FORMATTER_H 653 | -------------------------------------------------------------------------------- /include/st_iostream.h: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2018 Michael Hansen 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a 4 | copy of this software and associated documentation files (the "Software"), 5 | to deal in the Software without restriction, including without limitation 6 | the rights to use, copy, modify, merge, publish, distribute, sublicense, 7 | and/or sell copies of the Software, and to permit persons to whom the 8 | Software is furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 18 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 19 | DEALINGS IN THE SOFTWARE. */ 20 | 21 | #ifndef _ST_IOSTREAM_H 22 | #define _ST_IOSTREAM_H 23 | 24 | #include "st_string.h" 25 | #include "st_formatter.h" 26 | 27 | #include 28 | #include 29 | 30 | namespace _ST_PRIVATE 31 | { 32 | template 33 | class ostream_format_writer : public ST::format_writer 34 | { 35 | public: 36 | ostream_format_writer(const char *format_str, 37 | std::basic_ostream &stream) 38 | : ST::format_writer(format_str), m_stream(stream) { } 39 | 40 | template 41 | typename std::enable_if::value, void>::type 42 | write_data(const char *data, size_t size) 43 | { 44 | m_stream.write(data, size); 45 | } 46 | 47 | template 48 | typename std::enable_if::value, void>::type 49 | write_data(const char *data, size_t size) 50 | { 51 | ST::wchar_buffer wide = ST::utf8_to_wchar(data, size); 52 | m_stream.write(wide.data(), wide.size()); 53 | } 54 | 55 | template 56 | typename std::enable_if::value, void>::type 57 | write_data(const char *data, size_t size) 58 | { 59 | ST::utf16_buffer utf16 = ST::utf8_to_utf16(data, size); 60 | m_stream.write(utf16.data(), utf16.size()); 61 | } 62 | 63 | template 64 | typename std::enable_if::value, void>::type 65 | write_data(const char *data, size_t size) 66 | { 67 | ST::utf32_buffer utf32 = ST::utf8_to_utf32(data, size); 68 | m_stream.write(utf32.data(), utf32.size()); 69 | } 70 | 71 | ostream_format_writer &append(const char *data, size_t size) 72 | ST_LIFETIME_BOUND override 73 | { 74 | write_data(data, size); 75 | return *this; 76 | } 77 | 78 | ostream_format_writer &append_char(char ch, size_t count = 1) 79 | ST_LIFETIME_BOUND override 80 | { 81 | while (count) { 82 | m_stream.put(char_T(ch)); 83 | --count; 84 | } 85 | return *this; 86 | } 87 | 88 | private: 89 | std::basic_ostream &m_stream; 90 | }; 91 | } 92 | 93 | namespace ST 94 | { 95 | template 96 | void writef(std::basic_ostream &stream, 97 | const char *fmt_str, args_T &&...args) 98 | { 99 | _ST_PRIVATE::ostream_format_writer data(fmt_str, stream); 100 | apply_format(data, std::forward(args)...); 101 | } 102 | } 103 | 104 | template 105 | std::basic_ostream &operator<<( 106 | std::basic_ostream &stream ST_LIFETIME_BOUND, 107 | const ST::string &str) 108 | { 109 | ST::buffer buffer; 110 | str.to_buffer(buffer); 111 | return stream << std::basic_string(buffer.data(), buffer.size()); 112 | } 113 | 114 | template 115 | std::basic_istream &operator>>( 116 | std::basic_istream &stream ST_LIFETIME_BOUND, 117 | ST::string &str) 118 | { 119 | std::basic_string stl_string; 120 | stream >> stl_string; 121 | str.set(stl_string.c_str(), stl_string.size()); 122 | return stream; 123 | } 124 | 125 | #endif // _ST_IOSTREAM_H 126 | -------------------------------------------------------------------------------- /include/st_stdio.h: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2016 Michael Hansen 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a 4 | copy of this software and associated documentation files (the "Software"), 5 | to deal in the Software without restriction, including without limitation 6 | the rights to use, copy, modify, merge, publish, distribute, sublicense, 7 | and/or sell copies of the Software, and to permit persons to whom the 8 | Software is furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 18 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 19 | DEALINGS IN THE SOFTWARE. */ 20 | 21 | #ifndef _ST_STDIO_H 22 | #define _ST_STDIO_H 23 | 24 | #include "st_formatter.h" 25 | 26 | namespace _ST_PRIVATE 27 | { 28 | class stdio_format_writer : public ST::format_writer 29 | { 30 | public: 31 | stdio_format_writer(const char *format_str, FILE *stream) 32 | : ST::format_writer(format_str), m_stream(stream) { } 33 | 34 | stdio_format_writer &append(const char *data, size_t size) 35 | ST_LIFETIME_BOUND override 36 | { 37 | (void)fwrite(data, sizeof(char), size, m_stream); 38 | return *this; 39 | } 40 | 41 | stdio_format_writer &append_char(char ch, size_t count = 1) 42 | ST_LIFETIME_BOUND override 43 | { 44 | while (count) { 45 | fputc(ch, m_stream); 46 | --count; 47 | } 48 | return *this; 49 | } 50 | 51 | private: 52 | FILE *m_stream; 53 | }; 54 | } 55 | 56 | namespace ST 57 | { 58 | template 59 | void printf(const char *fmt_str, args_T &&...args) 60 | { 61 | _ST_PRIVATE::stdio_format_writer data(fmt_str, stdout); 62 | apply_format(data, std::forward(args)...); 63 | } 64 | 65 | template 66 | void printf(FILE *out_file, const char *fmt_str, 67 | args_T &&...args) 68 | { 69 | _ST_PRIVATE::stdio_format_writer data(fmt_str, out_file); 70 | apply_format(data, std::forward(args)...); 71 | } 72 | } 73 | 74 | #endif // _ST_STDIO_H 75 | -------------------------------------------------------------------------------- /include/st_string_priv.h: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2019 Michael Hansen 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a 4 | copy of this software and associated documentation files (the "Software"), 5 | to deal in the Software without restriction, including without limitation 6 | the rights to use, copy, modify, merge, publish, distribute, sublicense, 7 | and/or sell copies of the Software, and to permit persons to whom the 8 | Software is furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 18 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 19 | DEALINGS IN THE SOFTWARE. */ 20 | 21 | #ifndef _ST_STRING_PRIV_H 22 | #define _ST_STRING_PRIV_H 23 | 24 | #include "st_charbuffer.h" 25 | #include "st_format_numeric.h" 26 | 27 | namespace _ST_PRIVATE 28 | { 29 | ST_NODISCARD 30 | inline char cl_fast_lower(char ch) 31 | { 32 | // In the C locale, the only characters that get converted are ['A'..'Z'] 33 | if (ch >= 'A' && ch <= 'Z') 34 | return ch + 32; 35 | return ch; 36 | } 37 | 38 | ST_NODISCARD 39 | inline char cl_fast_upper(char ch) 40 | { 41 | // In the C locale, the only characters that get converted are ['a'..'z'] 42 | if (ch >= 'a' && ch <= 'z') 43 | return ch - 32; 44 | return ch; 45 | } 46 | 47 | ST_NODISCARD 48 | inline int compare_cs(const char *left, const char *right, size_t fsize) noexcept 49 | { 50 | return std::char_traits::compare(left, right, fsize); 51 | } 52 | 53 | ST_NODISCARD 54 | inline int compare_cs(const char *left, size_t lsize, 55 | const char *right, size_t rsize) noexcept 56 | { 57 | return ST::char_buffer::compare(left, lsize, right, rsize); 58 | } 59 | 60 | ST_NODISCARD 61 | inline int compare_cs(const char *left, size_t lsize, 62 | const char *right, size_t rsize, size_t maxlen) noexcept 63 | { 64 | return ST::char_buffer::compare(left, lsize, right, rsize, maxlen); 65 | } 66 | 67 | ST_NODISCARD 68 | inline int compare_ci(const char *left, const char *right, size_t fsize) noexcept 69 | { 70 | while (fsize--) { 71 | const char cl = cl_fast_lower(*left++); 72 | const char cr = cl_fast_lower(*right++); 73 | if (cl != cr) 74 | return cl - cr; 75 | } 76 | return 0; 77 | } 78 | 79 | ST_NODISCARD 80 | inline int compare_ci(const char *left, size_t lsize, 81 | const char *right, size_t rsize) noexcept 82 | { 83 | const size_t cmplen = std::min(lsize, rsize); 84 | const int cmp = compare_ci(left, right, cmplen); 85 | return cmp ? cmp : static_cast(lsize - rsize); 86 | } 87 | 88 | ST_NODISCARD 89 | inline int compare_ci(const char *left, size_t lsize, 90 | const char *right, size_t rsize, size_t maxlen) noexcept 91 | { 92 | lsize = std::min(lsize, maxlen); 93 | rsize = std::min(rsize, maxlen); 94 | return compare_ci(left, lsize, right, rsize); 95 | } 96 | 97 | ST_NODISCARD 98 | inline const char *find_cs(const char *haystack ST_LIFETIME_BOUND, size_t size, char ch) 99 | { 100 | return std::char_traits::find(haystack, size, ch); 101 | } 102 | 103 | ST_NODISCARD 104 | inline const char *find_ci(const char *haystack ST_LIFETIME_BOUND, size_t size, char ch) 105 | { 106 | const char *cp = haystack; 107 | const char *ep = haystack + size; 108 | const int lch = cl_fast_lower(static_cast(ch)); 109 | while (cp < ep) { 110 | if (cl_fast_lower(*cp) == lch) 111 | return cp; 112 | ++cp; 113 | } 114 | return nullptr; 115 | } 116 | 117 | ST_NODISCARD 118 | inline const char *find_cs(const char *haystack ST_LIFETIME_BOUND, size_t size, 119 | const char *needle, size_t needle_size) 120 | { 121 | const char *cp = haystack; 122 | const char *ep = haystack + size; 123 | for ( ;; ) { 124 | cp = find_cs(cp, ep - cp, needle[0]); 125 | if (!cp || cp + needle_size > ep) 126 | return nullptr; 127 | if (compare_cs(cp, needle, needle_size) == 0) 128 | return cp; 129 | ++cp; 130 | } 131 | } 132 | 133 | ST_NODISCARD 134 | inline const char *find_ci(const char *haystack ST_LIFETIME_BOUND, size_t size, 135 | const char *needle, size_t needle_size) 136 | { 137 | const char *cp = haystack; 138 | const char *ep = haystack + size; 139 | for ( ;; ) { 140 | cp = find_ci(cp, ep - cp, needle[0]); 141 | if (!cp || cp + needle_size > ep) 142 | return nullptr; 143 | if (compare_ci(cp, needle, needle_size) == 0) 144 | return cp; 145 | ++cp; 146 | } 147 | } 148 | 149 | template 150 | ST_NODISCARD 151 | ST::char_buffer mini_format_int_s(int radix, bool upper_case, int_T value) 152 | { 153 | typedef typename std::make_unsigned::type uint_T; 154 | ST::uint_formatter formatter; 155 | uint_T abs_value = value < 0 ? 0 - static_cast(value) 156 | : static_cast(value); 157 | formatter.format(abs_value, radix, upper_case); 158 | 159 | ST::char_buffer result; 160 | if (value < 0) { 161 | result.allocate(formatter.size() + 1); 162 | std::char_traits::copy(result.data() + 1, formatter.text(), formatter.size()); 163 | result[0] = '-'; 164 | } else { 165 | result.allocate(formatter.size()); 166 | std::char_traits::copy(result.data(), formatter.text(), formatter.size()); 167 | } 168 | 169 | return result; 170 | } 171 | 172 | template 173 | ST_NODISCARD 174 | ST::char_buffer mini_format_int_u(int radix, bool upper_case, uint_T value) 175 | { 176 | ST::uint_formatter formatter; 177 | formatter.format(value, radix, upper_case); 178 | 179 | ST::char_buffer result; 180 | result.allocate(formatter.size()); 181 | std::char_traits::copy(result.data(), formatter.text(), formatter.size()); 182 | 183 | return result; 184 | } 185 | 186 | template 187 | ST_NODISCARD 188 | static ST::char_buffer mini_format_float(float_T value, char format) 189 | { 190 | ST::float_formatter formatter; 191 | formatter.format(value, format); 192 | 193 | ST::char_buffer result; 194 | result.allocate(formatter.size()); 195 | std::char_traits::copy(result.data(), formatter.text(), formatter.size()); 196 | 197 | return result; 198 | } 199 | 200 | template 201 | struct fnv_constants { }; 202 | 203 | template 204 | struct fnv_constants 205 | { 206 | static constexpr SizeType offset_basis = 0x811c9dc5UL; 207 | static constexpr SizeType prime = 0x01000193UL; 208 | }; 209 | 210 | template 211 | struct fnv_constants 212 | { 213 | static constexpr SizeType offset_basis = 0xcbf29ce484222325ULL; 214 | static constexpr SizeType prime = 0x00000100000001b3ULL; 215 | }; 216 | } 217 | 218 | #endif // _ST_STRING_PRIV_H 219 | -------------------------------------------------------------------------------- /include/st_stringstream.h: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2016 Michael Hansen 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a 4 | copy of this software and associated documentation files (the "Software"), 5 | to deal in the Software without restriction, including without limitation 6 | the rights to use, copy, modify, merge, publish, distribute, sublicense, 7 | and/or sell copies of the Software, and to permit persons to whom the 8 | Software is furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 18 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 19 | DEALINGS IN THE SOFTWARE. */ 20 | 21 | #ifndef _ST_STRINGSTREAM_H 22 | #define _ST_STRINGSTREAM_H 23 | 24 | #include "st_string.h" 25 | #include "st_format_numeric.h" 26 | 27 | namespace ST 28 | { 29 | class string_stream 30 | { 31 | public: 32 | string_stream() noexcept 33 | : m_chars(m_stack), m_alloc(ST_STACK_STRING_SIZE), m_size() { } 34 | 35 | string_stream(const string_stream&) = delete; 36 | string_stream& operator=(const string_stream&) = delete; 37 | 38 | ~string_stream() noexcept 39 | { 40 | if (is_heap()) 41 | delete[] m_chars; 42 | } 43 | 44 | string_stream(string_stream &&move) noexcept 45 | : m_alloc(move.m_alloc), m_size(move.m_size) 46 | { 47 | m_chars = is_heap() ? move.m_chars : m_stack; 48 | std::char_traits::copy(m_stack, move.m_stack, ST_STACK_STRING_SIZE); 49 | move.m_alloc = 0; 50 | } 51 | 52 | string_stream &operator=(string_stream &&move) noexcept ST_LIFETIME_BOUND 53 | { 54 | if (is_heap()) 55 | delete[] m_chars; 56 | 57 | m_alloc = move.m_alloc; 58 | m_size = move.m_size; 59 | m_chars = is_heap() ? move.m_chars : m_stack; 60 | std::char_traits::copy(m_stack, move.m_stack, ST_STACK_STRING_SIZE); 61 | move.m_alloc = 0; 62 | return *this; 63 | } 64 | 65 | string_stream &append(const char *data, size_t size = ST_AUTO_SIZE) 66 | ST_LIFETIME_BOUND 67 | { 68 | if (size == 0) 69 | return *this; 70 | 71 | if (size == ST_AUTO_SIZE) 72 | size = data ? std::char_traits::length(data) : 0; 73 | 74 | expand_buffer(size); 75 | 76 | std::char_traits::move(m_chars + m_size, data, size); 77 | m_size += size; 78 | return *this; 79 | } 80 | 81 | string_stream &append_char(char ch, size_t count = 1) 82 | ST_LIFETIME_BOUND 83 | { 84 | if (count == 0) 85 | return *this; 86 | 87 | expand_buffer(count); 88 | 89 | std::char_traits::assign(m_chars + m_size, count, ch); 90 | m_size += count; 91 | return *this; 92 | } 93 | 94 | string_stream &operator<<(const char *text) ST_LIFETIME_BOUND 95 | { 96 | return append(text); 97 | } 98 | 99 | string_stream &operator<<(const wchar_t *text) ST_LIFETIME_BOUND 100 | { 101 | if (text) { 102 | const auto length = std::char_traits::length(text); 103 | ST::char_buffer utf8 = ST::wchar_to_utf8(text, length); 104 | return append(utf8.data(), utf8.size()); 105 | } 106 | return *this; 107 | } 108 | 109 | string_stream &operator<<(const char16_t *text) ST_LIFETIME_BOUND 110 | { 111 | if (text) { 112 | const auto length = std::char_traits::length(text); 113 | ST::char_buffer utf8 = ST::utf16_to_utf8(text, length); 114 | return append(utf8.data(), utf8.size()); 115 | } 116 | return *this; 117 | } 118 | 119 | string_stream &operator<<(const char32_t *text) ST_LIFETIME_BOUND 120 | { 121 | if (text) { 122 | const auto length = std::char_traits::length(text); 123 | ST::char_buffer utf8 = ST::utf32_to_utf8(text, length); 124 | return append(utf8.data(), utf8.size()); 125 | } 126 | return *this; 127 | } 128 | 129 | #ifdef ST_HAVE_CXX20_CHAR8_TYPES 130 | string_stream &operator<<(const char8_t *text) ST_LIFETIME_BOUND 131 | { 132 | return append(reinterpret_cast(text)); 133 | } 134 | #endif 135 | 136 | string_stream &operator<<(int num) ST_LIFETIME_BOUND 137 | { 138 | ST::uint_formatter formatter; 139 | formatter.format(std::abs(num), 10, false); 140 | if (num < 0) 141 | append_char('-'); 142 | return append(formatter.text(), formatter.size()); 143 | } 144 | 145 | string_stream &operator<<(unsigned int num) ST_LIFETIME_BOUND 146 | { 147 | ST::uint_formatter formatter; 148 | formatter.format(num, 10, false); 149 | return append(formatter.text(), formatter.size()); 150 | } 151 | 152 | string_stream &operator<<(long num) ST_LIFETIME_BOUND 153 | { 154 | ST::uint_formatter formatter; 155 | formatter.format(std::abs(num), 10, false); 156 | if (num < 0) 157 | append_char('-'); 158 | return append(formatter.text(), formatter.size()); 159 | } 160 | 161 | string_stream &operator<<(unsigned long num) ST_LIFETIME_BOUND 162 | { 163 | ST::uint_formatter formatter; 164 | formatter.format(num, 10, false); 165 | return append(formatter.text(), formatter.size()); 166 | } 167 | 168 | string_stream &operator<<(long long num) ST_LIFETIME_BOUND 169 | { 170 | ST::uint_formatter formatter; 171 | formatter.format(std::abs(num), 10, false); 172 | if (num < 0) 173 | append_char('-'); 174 | return append(formatter.text(), formatter.size()); 175 | } 176 | 177 | string_stream &operator<<(unsigned long long num) ST_LIFETIME_BOUND 178 | { 179 | ST::uint_formatter formatter; 180 | formatter.format(num, 10, false); 181 | return append(formatter.text(), formatter.size()); 182 | } 183 | 184 | string_stream &operator<<(float num) ST_LIFETIME_BOUND 185 | { 186 | ST::float_formatter formatter; 187 | formatter.format(num, 'g'); 188 | return append(formatter.text(), formatter.size()); 189 | } 190 | 191 | string_stream &operator<<(double num) ST_LIFETIME_BOUND 192 | { 193 | ST::float_formatter formatter; 194 | formatter.format(num, 'g'); 195 | return append(formatter.text(), formatter.size()); 196 | } 197 | 198 | string_stream &operator<<(char ch) ST_LIFETIME_BOUND 199 | { 200 | return append_char(ch); 201 | } 202 | 203 | // Some systems alias int8_t as char rather than signed char. 204 | // We don't want to remove the char overload since it's often better 205 | // than streaming a string or calling .append_char(), so instead we 206 | // can disable the use of these overloads on "sane" platforms to 207 | // try to catch their misuse. 208 | // HINT: If you need to stream an int8_t or uint8_t, cast it to 209 | // an int or unsigned int to get the correct behavior... 210 | string_stream &operator<<(signed char) = delete; 211 | string_stream &operator<<(unsigned char) = delete; 212 | 213 | string_stream &operator<<(const string &text) ST_LIFETIME_BOUND 214 | { 215 | return append(text.c_str(), text.size()); 216 | } 217 | 218 | #if defined(ST_ENABLE_STL_STRINGS) 219 | 220 | string_stream &operator<<(const std::string &text) ST_LIFETIME_BOUND 221 | { 222 | return append(text.c_str(), text.size()); 223 | } 224 | 225 | string_stream &operator<<(const std::wstring &text) ST_LIFETIME_BOUND 226 | { 227 | ST::char_buffer utf8 = ST::wchar_to_utf8(text.c_str(), text.size()); 228 | return append(utf8.data(), utf8.size()); 229 | } 230 | 231 | string_stream &operator<<(const std::u16string &text) ST_LIFETIME_BOUND 232 | { 233 | ST::char_buffer utf8 = ST::utf16_to_utf8(text.c_str(), text.size()); 234 | return append(utf8.data(), utf8.size()); 235 | } 236 | 237 | string_stream &operator<<(const std::u32string &text) ST_LIFETIME_BOUND 238 | { 239 | ST::char_buffer utf8 = ST::utf32_to_utf8(text.c_str(), text.size()); 240 | return append(utf8.data(), utf8.size()); 241 | } 242 | 243 | #ifdef ST_HAVE_CXX20_CHAR8_TYPES 244 | string_stream &operator<<(const std::u8string &text) ST_LIFETIME_BOUND 245 | { 246 | return append(reinterpret_cast(text.c_str()), text.size()); 247 | } 248 | #endif 249 | 250 | #ifdef ST_HAVE_CXX17_STRING_VIEW 251 | string_stream &operator<<(const std::string_view &text) ST_LIFETIME_BOUND 252 | { 253 | return append(text.data(), text.size()); 254 | } 255 | 256 | string_stream &operator<<(const std::wstring_view &text) ST_LIFETIME_BOUND 257 | { 258 | ST::char_buffer utf8 = ST::wchar_to_utf8(text.data(), text.size()); 259 | return append(utf8.data(), utf8.size()); 260 | } 261 | 262 | string_stream &operator<<(const std::u16string_view &text) ST_LIFETIME_BOUND 263 | { 264 | ST::char_buffer utf8 = ST::utf16_to_utf8(text.data(), text.size()); 265 | return append(utf8.data(), utf8.size()); 266 | } 267 | 268 | string_stream &operator<<(const std::u32string_view &text) ST_LIFETIME_BOUND 269 | { 270 | ST::char_buffer utf8 = ST::utf32_to_utf8(text.data(), text.size()); 271 | return append(utf8.data(), utf8.size()); 272 | } 273 | #endif 274 | 275 | #ifdef ST_HAVE_CXX20_CHAR8_TYPES 276 | string_stream &operator<<(const std::u8string_view &text) ST_LIFETIME_BOUND 277 | { 278 | return append(reinterpret_cast(text.data()), text.size()); 279 | } 280 | #endif 281 | 282 | #endif // defined(ST_ENABLE_STL_STRINGS) 283 | 284 | #if defined(ST_ENABLE_STL_FILESYSTEM) && defined(ST_HAVE_CXX17_FILESYSTEM) 285 | string_stream &operator<<(const std::filesystem::path &path) 286 | ST_LIFETIME_BOUND ST_FILESYSTEM_AVAILABILITY 287 | { 288 | auto u8path = path.u8string(); 289 | return append(reinterpret_cast(u8path.c_str()), u8path.size()); 290 | } 291 | #endif 292 | 293 | ST_NODISCARD 294 | const char *raw_buffer() const noexcept ST_LIFETIME_BOUND { return m_chars; } 295 | 296 | ST_NODISCARD 297 | size_t size() const noexcept { return m_size; } 298 | 299 | ST_NODISCARD 300 | string to_string(bool utf8_encoded = true, 301 | utf_validation_t validation = ST_DEFAULT_VALIDATION) const 302 | { 303 | if (utf8_encoded) 304 | return string::from_utf8(raw_buffer(), size(), validation); 305 | else 306 | return string::from_latin_1(raw_buffer(), size()); 307 | } 308 | 309 | void truncate(size_t size = 0) noexcept 310 | { 311 | if (size < m_size) 312 | m_size = size; 313 | } 314 | 315 | void erase(size_t count) noexcept 316 | { 317 | if (count < m_size) 318 | m_size -= count; 319 | else 320 | m_size = 0; 321 | } 322 | 323 | private: 324 | char *m_chars; 325 | size_t m_alloc, m_size; 326 | char m_stack[ST_STACK_STRING_SIZE]; 327 | 328 | ST_NODISCARD 329 | bool is_heap() const noexcept 330 | { 331 | return m_alloc > ST_STACK_STRING_SIZE; 332 | } 333 | 334 | void expand_buffer(size_t added_size) 335 | { 336 | if (m_size + added_size > m_alloc) { 337 | size_t big_size = m_alloc; 338 | do { 339 | big_size *= 2; 340 | } while (m_size + added_size > big_size); 341 | 342 | char *bigger = new char[big_size]; 343 | std::char_traits::copy(bigger, m_chars, m_alloc); 344 | if (is_heap()) 345 | delete[] m_chars; 346 | m_chars = bigger; 347 | m_alloc = big_size; 348 | } 349 | } 350 | }; 351 | } 352 | 353 | #endif // _ST_STRINGSTREAM_H 354 | -------------------------------------------------------------------------------- /include/string_theory/assert: -------------------------------------------------------------------------------- 1 | #include "st_assert.h" 2 | -------------------------------------------------------------------------------- /include/string_theory/char_buffer: -------------------------------------------------------------------------------- 1 | #include "st_charbuffer.h" 2 | -------------------------------------------------------------------------------- /include/string_theory/codecs: -------------------------------------------------------------------------------- 1 | #include "st_codecs.h" 2 | -------------------------------------------------------------------------------- /include/string_theory/exceptions: -------------------------------------------------------------------------------- 1 | #include "st_assert.h" 2 | -------------------------------------------------------------------------------- /include/string_theory/format: -------------------------------------------------------------------------------- 1 | #include "st_format.h" 2 | -------------------------------------------------------------------------------- /include/string_theory/format_numeric: -------------------------------------------------------------------------------- 1 | #include "st_format_numeric.h" 2 | -------------------------------------------------------------------------------- /include/string_theory/formatter: -------------------------------------------------------------------------------- 1 | #include "st_formatter.h" 2 | -------------------------------------------------------------------------------- /include/string_theory/iostream: -------------------------------------------------------------------------------- 1 | #include "st_iostream.h" 2 | -------------------------------------------------------------------------------- /include/string_theory/stdio: -------------------------------------------------------------------------------- 1 | #include "st_stdio.h" 2 | -------------------------------------------------------------------------------- /include/string_theory/string: -------------------------------------------------------------------------------- 1 | #include "st_string.h" 2 | -------------------------------------------------------------------------------- /include/string_theory/string_stream: -------------------------------------------------------------------------------- 1 | #include "st_stringstream.h" 2 | -------------------------------------------------------------------------------- /include/string_theory/utf_conversion: -------------------------------------------------------------------------------- 1 | #include "st_utf_conv.h" 2 | -------------------------------------------------------------------------------- /test/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2016 Michael Hansen 2 | # 3 | # Permission is hereby granted, free of charge, to any person obtaining a 4 | # copy of this software and associated documentation files (the "Software"), 5 | # to deal in the Software without restriction, including without limitation 6 | # the rights to use, copy, modify, merge, publish, distribute, sublicense, 7 | # and/or sell copies of the Software, and to permit persons to whom the 8 | # Software is furnished to do so, subject to the following conditions: 9 | # 10 | # The above copyright notice and this permission notice shall be included in 11 | # all copies or substantial portions of the Software. 12 | # 13 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 18 | # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 19 | # DEALINGS IN THE SOFTWARE. 20 | 21 | # Why is this OFF by default? It doesn't work with CMake's defaults, and 22 | # should really only be off if the project has specifically been set up to 23 | # use the static MSVC CRT 24 | set(gtest_force_shared_crt ON CACHE INTERNAL "Override gtest default") 25 | 26 | # Force other gtest settings to line up correctly -- we're compiling 27 | # string_theory, not gtest... 28 | set(gtest_build_samples OFF CACHE INTERNAL "Override gtest default") 29 | set(gtest_build_tests OFF CACHE INTERNAL "Override gtest default") 30 | set(gtest_disable_pthreads OFF CACHE INTERNAL "Override gtest default") 31 | set(gtest_hide_internal_symbols ON CACHE INTERNAL "Override gtest default") 32 | 33 | set(BUILD_GMOCK OFF CACHE INTERNAL "Override gtest default" FORCE) 34 | set(INSTALL_GTEST OFF CACHE INTERNAL "Override gtest default" FORCE) 35 | 36 | option(ST_USE_SYSTEM_GTEST "Find the GoogleTest library from the system rather than fetching it" OFF) 37 | if(ST_USE_SYSTEM_GTEST) 38 | find_package(GTest REQUIRED) 39 | else() 40 | include(FetchContent) 41 | FetchContent_Declare(gtest 42 | GIT_REPOSITORY https://github.com/google/googletest.git 43 | GIT_TAG v1.15.2 44 | ) 45 | FetchContent_MakeAvailable(gtest) 46 | endif() 47 | 48 | add_executable(st_gtests "") 49 | target_link_libraries(st_gtests PRIVATE GTest::gtest GTest::gtest_main string_theory) 50 | target_include_directories(st_gtests PRIVATE 51 | ${GTEST_INCLUDE_DIR} 52 | ${gtest_SOURCE_DIR}/include 53 | ${gtest_SOURCE_DIR} 54 | ) 55 | 56 | if(WIN32 AND BUILD_SHARED_LIBS) 57 | add_custom_command(TARGET st_gtests POST_BUILD 58 | COMMAND ${CMAKE_COMMAND} -E copy_if_different 59 | "$" 60 | "$" 61 | ) 62 | add_custom_command(TARGET st_gtests POST_BUILD 63 | COMMAND ${CMAKE_COMMAND} -E copy_if_different 64 | "$" 65 | "$" 66 | ) 67 | endif() 68 | 69 | target_sources(st_gtests PRIVATE 70 | test_buffer.cpp 71 | test_string.cpp 72 | test_codecs.cpp 73 | test_iostream.cpp 74 | test_sstream.cpp 75 | test_format.cpp 76 | test_stdio.cpp 77 | test_regress.cpp 78 | ) 79 | 80 | if(WIN32) 81 | target_sources(st_gtests PRIVATE test_winheaders.cpp) 82 | endif() 83 | 84 | if(ST_HAVE_CXX17_FILESYSTEM AND ST_CXXFS_LIBS) 85 | target_link_libraries(st_gtests PRIVATE ${ST_CXXFS_LIBS}) 86 | endif() 87 | 88 | option(ST_BUILD_PROFILE_TEST "Build string profile test application" OFF) 89 | if(ST_BUILD_PROFILE_TEST) 90 | find_package(Boost) 91 | if(Boost_FOUND) 92 | add_definitions(-DST_PROFILE_HAVE_BOOST) 93 | include_directories(${Boost_INCLUDE_DIRS}) 94 | endif() 95 | 96 | find_package(Qt5Core) 97 | if(Qt5Core_FOUND) 98 | add_definitions(-DST_PROFILE_HAVE_QSTRING) 99 | endif() 100 | 101 | find_package(PkgConfig) 102 | if(PkgConfig_FOUND) 103 | pkg_check_modules(GLIBMM glibmm-2.4) 104 | if(GLIBMM_FOUND) 105 | add_definitions(-DST_PROFILE_HAVE_GLIBMM) 106 | include_directories(${GLIBMM_INCLUDE_DIRS}) 107 | endif() 108 | endif() 109 | 110 | find_package(Fmt) 111 | if(Fmt_FOUND) 112 | add_definitions(-DST_PROFILE_HAVE_FMT) 113 | endif() 114 | 115 | add_executable(string_profile EXCLUDE_FROM_ALL profile.cpp) 116 | target_link_libraries(string_profile PRIVATE string_theory) 117 | if(Qt5Core_FOUND) 118 | target_link_libraries(string_profile PRIVATE Qt5::Core) 119 | endif() 120 | if(GLIBMM_FOUND) 121 | target_link_libraries(string_profile PRIVATE ${GLIBMM_LIBRARIES}) 122 | endif() 123 | if(Fmt_FOUND) 124 | target_link_libraries(string_profile PRIVATE Fmt::Fmt) 125 | endif() 126 | 127 | if(ST_HAVE_CXX17_FILESYSTEM AND ST_CXXFS_LIBS) 128 | target_link_libraries(string_profile PRIVATE ${ST_CXXFS_LIBS}) 129 | endif() 130 | endif() 131 | 132 | cmake_policy(PUSH) 133 | add_custom_target(test "$") 134 | cmake_policy(POP) 135 | -------------------------------------------------------------------------------- /test/test_buffer.cpp: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2018 Michael Hansen 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a 4 | copy of this software and associated documentation files (the "Software"), 5 | to deal in the Software without restriction, including without limitation 6 | the rights to use, copy, modify, merge, publish, distribute, sublicense, 7 | and/or sell copies of the Software, and to permit persons to whom the 8 | Software is furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 18 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 19 | DEALINGS IN THE SOFTWARE. */ 20 | 21 | #include "st_string.h" 22 | 23 | #include 24 | 25 | namespace ST 26 | { 27 | // Teach GTest how to print an ST::buffer 28 | static void PrintTo(const ST::char_buffer &str, std::ostream *os) 29 | { 30 | *os << "ST::char_buffer{\"" << str.data() << "\"}"; 31 | } 32 | 33 | static void PrintTo(const ST::wchar_buffer &str, std::ostream *os) 34 | { 35 | ST::string u8str = str; 36 | *os << "ST::wchar_buffer{\"" << u8str.c_str() << "\"}"; 37 | } 38 | 39 | static void PrintTo(const ST::utf16_buffer &str, std::ostream *os) 40 | { 41 | ST::string u8str = str; 42 | *os << "ST::utf16_buffer{\"" << u8str.c_str() << "\"}"; 43 | } 44 | 45 | static void PrintTo(const ST::utf32_buffer &str, std::ostream *os) 46 | { 47 | ST::string u8str = str; 48 | *os << "ST::utf32_buffer{\"" << u8str.c_str() << "\"}"; 49 | } 50 | } 51 | 52 | /* Utility for comparing char16_t/char32_t buffers */ 53 | template 54 | static int T_strcmp(const char_T *left, const char_T *right) 55 | { 56 | for ( ;; ) { 57 | if (*left != *right) 58 | return *left - *right; 59 | if (*left == 0) 60 | return 0; 61 | 62 | ++left; 63 | ++right; 64 | } 65 | } 66 | 67 | TEST(char_buffer, helpers) 68 | { 69 | /* Ensure the utilities for testing the module function properly */ 70 | EXPECT_EQ(0, T_strcmp("abc", "abc")); 71 | EXPECT_LT(0, T_strcmp("abc", "aba")); 72 | EXPECT_GT(0, T_strcmp("abc", "abe")); 73 | EXPECT_LT(0, T_strcmp("abc", "ab")); 74 | EXPECT_GT(0, T_strcmp("abc", "abcd")); 75 | EXPECT_EQ(0, T_strcmp("", "")); 76 | EXPECT_GT(0, T_strcmp("", "a")); 77 | EXPECT_LT(0, T_strcmp("a", "")); 78 | } 79 | 80 | TEST(char_buffer, utility) 81 | { 82 | // Literal constructors 83 | EXPECT_EQ(ST::char_buffer(), ST_CHAR_LITERAL("")); 84 | EXPECT_EQ(ST::wchar_buffer(), ST_WCHAR_LITERAL("")); 85 | EXPECT_EQ(ST::utf16_buffer(), ST_UTF16_LITERAL("")); 86 | EXPECT_EQ(ST::utf32_buffer(), ST_UTF32_LITERAL("")); 87 | EXPECT_EQ(ST::char_buffer("abc", 3), ST_CHAR_LITERAL("abc")); 88 | EXPECT_EQ(ST::wchar_buffer(L"abc", 3), ST_WCHAR_LITERAL("abc")); 89 | EXPECT_EQ(ST::utf16_buffer(u"abc", 3), ST_UTF16_LITERAL("abc")); 90 | EXPECT_EQ(ST::utf32_buffer(U"abc", 3), ST_UTF32_LITERAL("abc")); 91 | 92 | EXPECT_EQ(0U, ST::char_buffer().size()); 93 | EXPECT_TRUE(ST::char_buffer().empty()); 94 | EXPECT_EQ(0U, ST::wchar_buffer().size()); 95 | EXPECT_TRUE(ST::wchar_buffer().empty()); 96 | EXPECT_EQ(0U, ST::utf16_buffer().size()); 97 | EXPECT_TRUE(ST::utf16_buffer().empty()); 98 | EXPECT_EQ(0U, ST::utf32_buffer().size()); 99 | EXPECT_TRUE(ST::utf32_buffer().empty()); 100 | } 101 | 102 | TEST(char_buffer, stack_construction) 103 | { 104 | // If these change, this test may need to be updated to match 105 | ASSERT_EQ(16, ST_MAX_SSO_LENGTH); 106 | ASSERT_EQ(48, ST_MAX_SSO_SIZE); 107 | 108 | char empty[] = {0}; 109 | wchar_t emptyw[] = {0}; 110 | char16_t empty16[] = {0}; 111 | char32_t empty32[] = {0}; 112 | 113 | // Stack allocated 114 | char shortstr[] = "Short"; 115 | wchar_t shortw[] = L"Short"; 116 | char16_t short16[] = {'S', 'h', 'o', 'r', 't', 0}; 117 | char32_t short32[] = {'S', 'h', 'o', 'r', 't', 0}; 118 | 119 | // Heap allocated 120 | char longstr[] = "0123456789abcdefghij"; 121 | wchar_t longw[] = L"0123456789abcdefghij"; 122 | char16_t long16[] = {'0','1','2','3','4','5','6','7','8','9', 123 | 'a','b','c','d','e','f','g','h','i','j',0}; 124 | char32_t long32[] = {'0','1','2','3','4','5','6','7','8','9', 125 | 'a','b','c','d','e','f','g','h','i','j',0}; 126 | 127 | ST::char_buffer cb_empty(empty, 0); 128 | EXPECT_EQ(0, T_strcmp(cb_empty.data(), empty)); 129 | EXPECT_EQ(0U, cb_empty.size()); 130 | EXPECT_TRUE(cb_empty.empty()); 131 | ST::wchar_buffer wcb_empty(emptyw, 0); 132 | EXPECT_EQ(0, T_strcmp(wcb_empty.data(), emptyw)); 133 | EXPECT_EQ(0U, wcb_empty.size()); 134 | EXPECT_TRUE(wcb_empty.empty()); 135 | ST::utf16_buffer cb16_empty(empty16, 0); 136 | EXPECT_EQ(0, T_strcmp(cb16_empty.data(), empty16)); 137 | EXPECT_EQ(0U, cb16_empty.size()); 138 | EXPECT_TRUE(cb16_empty.empty()); 139 | ST::utf32_buffer cb32_empty(empty32, 0); 140 | EXPECT_EQ(0, T_strcmp(cb32_empty.data(), empty32)); 141 | EXPECT_EQ(0U, cb32_empty.size()); 142 | EXPECT_TRUE(cb32_empty.empty()); 143 | 144 | ST::char_buffer cb_short(shortstr, 5); 145 | EXPECT_EQ(0, T_strcmp(cb_short.data(), shortstr)); 146 | EXPECT_EQ(5U, cb_short.size()); 147 | EXPECT_FALSE(cb_short.empty()); 148 | cb_short.clear(); 149 | EXPECT_EQ(0, T_strcmp(cb_short.data(), empty)); 150 | EXPECT_EQ(0U, cb_short.size()); 151 | EXPECT_TRUE(cb_short.empty()); 152 | 153 | ST::wchar_buffer wcb_short(shortw, 5); 154 | EXPECT_EQ(0, T_strcmp(wcb_short.data(), shortw)); 155 | EXPECT_EQ(5U, wcb_short.size()); 156 | EXPECT_FALSE(wcb_short.empty()); 157 | wcb_short.clear(); 158 | EXPECT_EQ(0, T_strcmp(wcb_short.data(), emptyw)); 159 | EXPECT_EQ(0U, wcb_short.size()); 160 | EXPECT_TRUE(wcb_short.empty()); 161 | 162 | ST::utf16_buffer cb16_short(short16, 5); 163 | EXPECT_EQ(0, T_strcmp(cb16_short.data(), short16)); 164 | EXPECT_EQ(5U, cb16_short.size()); 165 | EXPECT_FALSE(cb16_short.empty()); 166 | cb16_short.clear(); 167 | EXPECT_EQ(0, T_strcmp(cb16_short.data(), empty16)); 168 | EXPECT_EQ(0U, cb16_short.size()); 169 | EXPECT_TRUE(cb16_short.empty()); 170 | 171 | ST::utf32_buffer cb32_short(short32, 5); 172 | EXPECT_EQ(0, T_strcmp(cb32_short.data(), short32)); 173 | EXPECT_EQ(5U, cb32_short.size()); 174 | EXPECT_FALSE(cb32_short.empty()); 175 | cb32_short.clear(); 176 | EXPECT_EQ(0, T_strcmp(cb32_short.data(), empty32)); 177 | EXPECT_EQ(0U, cb32_short.size()); 178 | EXPECT_TRUE(cb32_short.empty()); 179 | 180 | ST::char_buffer cb_long(longstr, 20); 181 | EXPECT_EQ(0, T_strcmp(cb_long.data(), longstr)); 182 | EXPECT_EQ(20U, cb_long.size()); 183 | EXPECT_FALSE(cb_long.empty()); 184 | cb_long.clear(); 185 | EXPECT_EQ(0, T_strcmp(cb_long.data(), empty)); 186 | EXPECT_EQ(0U, cb_long.size()); 187 | EXPECT_TRUE(cb_long.empty()); 188 | 189 | ST::wchar_buffer wcb_long(longw, 20); 190 | EXPECT_EQ(0, T_strcmp(wcb_long.data(), longw)); 191 | EXPECT_EQ(20U, wcb_long.size()); 192 | EXPECT_FALSE(wcb_long.empty()); 193 | wcb_long.clear(); 194 | EXPECT_EQ(0, T_strcmp(wcb_long.data(), emptyw)); 195 | EXPECT_EQ(0U, wcb_long.size()); 196 | EXPECT_TRUE(wcb_long.empty()); 197 | 198 | ST::utf16_buffer cb16_long(long16, 20); 199 | EXPECT_EQ(0, T_strcmp(cb16_long.data(), long16)); 200 | EXPECT_EQ(20U, cb16_long.size()); 201 | EXPECT_FALSE(cb16_long.empty()); 202 | cb16_long.clear(); 203 | EXPECT_EQ(0, T_strcmp(cb16_long.data(), empty16)); 204 | EXPECT_EQ(0U, cb16_long.size()); 205 | EXPECT_TRUE(cb16_long.empty()); 206 | 207 | ST::utf32_buffer cb32_long(long32, 20); 208 | EXPECT_EQ(0, T_strcmp(cb32_long.data(), long32)); 209 | EXPECT_EQ(20U, cb32_long.size()); 210 | EXPECT_FALSE(cb32_long.empty()); 211 | cb32_long.clear(); 212 | EXPECT_EQ(0, T_strcmp(cb32_long.data(), empty32)); 213 | EXPECT_EQ(0U, cb32_long.size()); 214 | EXPECT_TRUE(cb32_long.empty()); 215 | } 216 | 217 | // Only testing char and wchar_t for simplicity on compilers that 218 | // don't support utf-16 and utf-32 literals yet 219 | TEST(char_buffer, copy) 220 | { 221 | // If these change, this test may need to be updated to match 222 | ASSERT_EQ(16, ST_MAX_SSO_LENGTH); 223 | ASSERT_EQ(48, ST_MAX_SSO_SIZE); 224 | 225 | ST::char_buffer cb1("Test", 4); 226 | ST::wchar_buffer wcb1(L"Test", 4); 227 | 228 | ST::char_buffer dest(cb1); 229 | EXPECT_EQ(0, T_strcmp(dest.data(), "Test")); 230 | EXPECT_EQ(4U, dest.size()); 231 | ST::wchar_buffer wdest(wcb1); 232 | EXPECT_EQ(0, T_strcmp(wdest.data(), L"Test")); 233 | EXPECT_EQ(4U, wdest.size()); 234 | 235 | ST::char_buffer cb2("operator=", 9); 236 | ST::wchar_buffer wcb2(L"operator=", 9); 237 | 238 | dest = cb2; 239 | EXPECT_EQ(0, T_strcmp(dest.data(), "operator=")); 240 | EXPECT_EQ(9U, dest.size()); 241 | wdest = wcb2; 242 | EXPECT_EQ(0, T_strcmp(wdest.data(), L"operator=")); 243 | EXPECT_EQ(9U, wdest.size()); 244 | 245 | ST::char_buffer cb3("0123456789abcdefghij", 20); 246 | ST::wchar_buffer wcb3(L"0123456789abcdefghij", 20); 247 | 248 | ST::char_buffer dest2(cb3); 249 | EXPECT_EQ(0, T_strcmp(dest2.data(), "0123456789abcdefghij")); 250 | EXPECT_EQ(20U, dest2.size()); 251 | ST::wchar_buffer wdest2(wcb3); 252 | EXPECT_EQ(0, T_strcmp(wdest2.data(), L"0123456789abcdefghij")); 253 | EXPECT_EQ(20U, wdest2.size()); 254 | 255 | ST::char_buffer cb4("9876543210zyxwvutsrqponm", 24); 256 | ST::wchar_buffer wcb4(L"9876543210zyxwvutsrqponm", 24); 257 | 258 | dest2 = cb4; 259 | EXPECT_EQ(0, T_strcmp(dest2.data(), "9876543210zyxwvutsrqponm")); 260 | EXPECT_EQ(24U, dest2.size()); 261 | wdest2 = wcb4; 262 | EXPECT_EQ(0, T_strcmp(wdest2.data(), L"9876543210zyxwvutsrqponm")); 263 | EXPECT_EQ(24U, wdest2.size()); 264 | } 265 | 266 | TEST(char_buffer, move) 267 | { 268 | // If these change, this test may need to be updated to match 269 | ASSERT_EQ(16, ST_MAX_SSO_LENGTH); 270 | ASSERT_EQ(48, ST_MAX_SSO_SIZE); 271 | 272 | ST::char_buffer cb1("Test", 4); 273 | ST::wchar_buffer wcb1(L"Test", 4); 274 | 275 | ST::char_buffer dest(std::move(cb1)); 276 | EXPECT_EQ(0, T_strcmp(dest.data(), "Test")); 277 | EXPECT_EQ(4U, dest.size()); 278 | ST::wchar_buffer wdest(std::move(wcb1)); 279 | EXPECT_EQ(0, T_strcmp(wdest.data(), L"Test")); 280 | EXPECT_EQ(4U, wdest.size()); 281 | 282 | ST::char_buffer cb2("operator=", 9); 283 | ST::wchar_buffer wcb2(L"operator=", 9); 284 | 285 | dest = std::move(cb2); 286 | EXPECT_EQ(0, T_strcmp(dest.data(), "operator=")); 287 | EXPECT_EQ(9U, dest.size()); 288 | wdest = std::move(wcb2); 289 | EXPECT_EQ(0, T_strcmp(wdest.data(), L"operator=")); 290 | EXPECT_EQ(9U, wdest.size()); 291 | 292 | ST::char_buffer cb3("0123456789abcdefghij", 20); 293 | ST::wchar_buffer wcb3(L"0123456789abcdefghij", 20); 294 | 295 | ST::char_buffer dest2(std::move(cb3)); 296 | EXPECT_EQ(0, T_strcmp(dest2.data(), "0123456789abcdefghij")); 297 | EXPECT_EQ(20U, dest2.size()); 298 | ST::wchar_buffer wdest2(std::move(wcb3)); 299 | EXPECT_EQ(0, T_strcmp(wdest2.data(), L"0123456789abcdefghij")); 300 | EXPECT_EQ(20U, wdest2.size()); 301 | 302 | ST::char_buffer cb4("9876543210zyxwvutsrqponm", 24); 303 | ST::wchar_buffer wcb4(L"9876543210zyxwvutsrqponm", 24); 304 | 305 | dest2 = std::move(cb4); 306 | EXPECT_EQ(0, T_strcmp(dest2.data(), "9876543210zyxwvutsrqponm")); 307 | EXPECT_EQ(24U, dest2.size()); 308 | wdest2 = std::move(wcb4); 309 | EXPECT_EQ(0, T_strcmp(wdest2.data(), L"9876543210zyxwvutsrqponm")); 310 | EXPECT_EQ(24U, wdest2.size()); 311 | } 312 | 313 | #if (defined(__clang__) && ((__clang_major__ > 3) || (__clang_major__ == 3 && __clang_minor__ > 5))) \ 314 | || (defined(__GNUC__) && (__GNUC__ >= 13)) 315 | # pragma GCC diagnostic push 316 | # pragma GCC diagnostic ignored "-Wself-move" 317 | # if defined(__clang__) && (__clang_major__ > 6) 318 | # pragma GCC diagnostic ignored "-Wself-assign-overloaded" 319 | # endif 320 | #endif 321 | 322 | TEST(char_buffer, self_assign) 323 | { 324 | // If this changes, this test may need to be updated to match 325 | ASSERT_EQ(16, ST_MAX_SSO_LENGTH); 326 | 327 | ST::char_buffer sbuf; 328 | sbuf = sbuf; 329 | EXPECT_EQ(0, T_strcmp(sbuf.data(), "")); 330 | 331 | ST::char_buffer shortbuf("0123456789", 10); 332 | sbuf = shortbuf; 333 | EXPECT_EQ(0, T_strcmp(sbuf.data(), "0123456789")); 334 | sbuf = sbuf; 335 | EXPECT_EQ(0, T_strcmp(sbuf.data(), "0123456789")); 336 | sbuf = std::move(sbuf); 337 | // Content not guaranteed after self-move 338 | 339 | ST::char_buffer longbuf("0123456789abcdefghij", 20); 340 | sbuf = longbuf; 341 | EXPECT_EQ(0, T_strcmp(sbuf.data(), "0123456789abcdefghij")); 342 | sbuf = sbuf; 343 | EXPECT_EQ(0, T_strcmp(sbuf.data(), "0123456789abcdefghij")); 344 | sbuf = std::move(sbuf); 345 | // Content not guaranteed after self-move 346 | } 347 | 348 | #if defined(__clang__) && ((__clang_major__ > 3) || (__clang_major__ == 3 && __clang_minor__ > 5)) 349 | # pragma GCC diagnostic pop 350 | #endif 351 | 352 | TEST(char_buffer, compare) 353 | { 354 | // Same length, chars 355 | EXPECT_EQ(0, ST_CHAR_LITERAL("abc").compare("abc")); 356 | EXPECT_GT(0, ST_CHAR_LITERAL("abc").compare("abd")); 357 | EXPECT_LT(0, ST_CHAR_LITERAL("abc").compare("abb")); 358 | EXPECT_GT(0, ST_CHAR_LITERAL("abC").compare("abc")); 359 | EXPECT_GT(0, ST_CHAR_LITERAL("Abc").compare("abc")); 360 | EXPECT_EQ(0, ST::char_buffer().compare("")); 361 | EXPECT_EQ(0, ST::char_buffer().compare(nullptr)); 362 | 363 | // Same length, wchars 364 | EXPECT_EQ(0, ST_WCHAR_LITERAL(L"abc").compare(L"abc")); 365 | EXPECT_GT(0, ST_WCHAR_LITERAL(L"abc").compare(L"abd")); 366 | EXPECT_LT(0, ST_WCHAR_LITERAL(L"abc").compare(L"abb")); 367 | EXPECT_GT(0, ST_WCHAR_LITERAL(L"abC").compare(L"abc")); 368 | EXPECT_GT(0, ST_WCHAR_LITERAL(L"Abc").compare(L"abc")); 369 | EXPECT_EQ(0, ST::wchar_buffer().compare(L"")); 370 | EXPECT_EQ(0, ST::wchar_buffer().compare(nullptr)); 371 | 372 | // Mismatched length, chars 373 | EXPECT_LT(0, ST_CHAR_LITERAL("abc").compare("ab")); 374 | EXPECT_GT(0, ST_CHAR_LITERAL("abc").compare("abcd")); 375 | EXPECT_LT(0, ST_CHAR_LITERAL("abc").compare("")); 376 | EXPECT_LT(0, ST_CHAR_LITERAL("abc").compare(nullptr)); 377 | EXPECT_GT(0, ST::char_buffer().compare("abc")); 378 | 379 | // Mismatched length, wchars 380 | EXPECT_LT(0, ST_WCHAR_LITERAL(L"abc").compare(L"ab")); 381 | EXPECT_GT(0, ST_WCHAR_LITERAL(L"abc").compare(L"abcd")); 382 | EXPECT_LT(0, ST_WCHAR_LITERAL(L"abc").compare(L"")); 383 | EXPECT_LT(0, ST_WCHAR_LITERAL(L"abc").compare(nullptr)); 384 | EXPECT_GT(0, ST::wchar_buffer().compare(L"abc")); 385 | } 386 | 387 | TEST(char_buffer, compare_n) 388 | { 389 | // Same length, chars 390 | EXPECT_EQ(0, ST_CHAR_LITERAL("abcXX").compare_n("abcYY", 3)); 391 | EXPECT_GT(0, ST_CHAR_LITERAL("abcXX").compare_n("abdYY", 3)); 392 | EXPECT_LT(0, ST_CHAR_LITERAL("abcXX").compare_n("abbYY", 3)); 393 | EXPECT_GT(0, ST_CHAR_LITERAL("abCXX").compare_n("abcYY", 3)); 394 | EXPECT_GT(0, ST_CHAR_LITERAL("AbcXX").compare_n("abcYY", 3)); 395 | 396 | // Same length, wchars 397 | EXPECT_EQ(0, ST_WCHAR_LITERAL(L"abcXX").compare_n(L"abcYY", 3)); 398 | EXPECT_GT(0, ST_WCHAR_LITERAL(L"abcXX").compare_n(L"abdYY", 3)); 399 | EXPECT_LT(0, ST_WCHAR_LITERAL(L"abcXX").compare_n(L"abbYY", 3)); 400 | EXPECT_GT(0, ST_WCHAR_LITERAL(L"abCXX").compare_n(L"abcYY", 3)); 401 | EXPECT_GT(0, ST_WCHAR_LITERAL(L"AbcXX").compare_n(L"abcYY", 3)); 402 | 403 | // Mismatched length, chars 404 | EXPECT_LT(0, ST_CHAR_LITERAL("abc").compare_n("ab", 3)); 405 | EXPECT_GT(0, ST_CHAR_LITERAL("abc").compare_n("abcd", 4)); 406 | EXPECT_LT(0, ST_CHAR_LITERAL("abc").compare_n("", 3)); 407 | EXPECT_LT(0, ST_CHAR_LITERAL("abc").compare_n(nullptr, 3)); 408 | EXPECT_GT(0, ST::char_buffer().compare_n("abc", 3)); 409 | 410 | // Mismatched length, wchars 411 | EXPECT_LT(0, ST_WCHAR_LITERAL(L"abc").compare_n(L"ab", 3)); 412 | EXPECT_GT(0, ST_WCHAR_LITERAL(L"abc").compare_n(L"abcd", 4)); 413 | EXPECT_LT(0, ST_WCHAR_LITERAL(L"abc").compare_n(L"", 3)); 414 | EXPECT_LT(0, ST_WCHAR_LITERAL(L"abc").compare_n(nullptr, 3)); 415 | EXPECT_GT(0, ST::wchar_buffer().compare_n(L"abc", 3)); 416 | } 417 | 418 | TEST(char_buffer, compare_op) 419 | { 420 | // Same length, chars 421 | EXPECT_EQ(ST::char_buffer("abc", 3), ST::char_buffer("abc", 3)); 422 | EXPECT_NE(ST::char_buffer("abc", 3), ST::char_buffer("abd", 3)); 423 | EXPECT_NE(ST::char_buffer("abc", 3), ST::char_buffer("abb", 3)); 424 | EXPECT_NE(ST::char_buffer("abC", 3), ST::char_buffer("abc", 3)); 425 | EXPECT_NE(ST::char_buffer("Abc", 3), ST::char_buffer("abc", 3)); 426 | EXPECT_EQ(ST::char_buffer("", 0), ST::char_buffer()); 427 | 428 | // Same length, wchars 429 | EXPECT_EQ(ST::wchar_buffer(L"abc", 3), ST::wchar_buffer(L"abc", 3)); 430 | EXPECT_NE(ST::wchar_buffer(L"abc", 3), ST::wchar_buffer(L"abd", 3)); 431 | EXPECT_NE(ST::wchar_buffer(L"abc", 3), ST::wchar_buffer(L"abb", 3)); 432 | EXPECT_NE(ST::wchar_buffer(L"abC", 3), ST::wchar_buffer(L"abc", 3)); 433 | EXPECT_NE(ST::wchar_buffer(L"Abc", 3), ST::wchar_buffer(L"abc", 3)); 434 | EXPECT_EQ(ST::wchar_buffer(L"", 0), ST::wchar_buffer()); 435 | 436 | // Mismatched length, chars 437 | EXPECT_NE(ST::char_buffer("abc", 3), ST::char_buffer("ab", 2)); 438 | EXPECT_NE(ST::char_buffer("abc", 3), ST::char_buffer("abcd", 4)); 439 | EXPECT_NE(ST::char_buffer("abc", 3), ST::char_buffer("", 0)); 440 | EXPECT_NE(ST::char_buffer(), ST::char_buffer("abc", 3)); 441 | 442 | // Mismatched length, wchars 443 | EXPECT_NE(ST::wchar_buffer(L"abc", 3), ST::wchar_buffer(L"ab", 2)); 444 | EXPECT_NE(ST::wchar_buffer(L"abc", 3), ST::wchar_buffer(L"abcd", 4)); 445 | EXPECT_NE(ST::wchar_buffer(L"abc", 3), ST::wchar_buffer(L"", 0)); 446 | EXPECT_NE(ST::wchar_buffer(), ST::wchar_buffer(L"abc", 3)); 447 | } 448 | 449 | TEST(char_buffer, udls) 450 | { 451 | using namespace ST::literals; 452 | 453 | // Only need to test the UDL usage -- the rest is covered above 454 | EXPECT_EQ(ST_CHAR_LITERAL(""), ""_stbuf); 455 | EXPECT_EQ(ST_CHAR_LITERAL("Test"), "Test"_stbuf); 456 | EXPECT_EQ(ST_WCHAR_LITERAL("Test"), L"Test"_stbuf); 457 | EXPECT_EQ(ST_UTF16_LITERAL("Test"), u"Test"_stbuf); 458 | EXPECT_EQ(ST_UTF32_LITERAL("Test"), U"Test"_stbuf); 459 | #ifdef ST_HAVE_CXX20_CHAR8_TYPES 460 | EXPECT_EQ(ST_CHAR_LITERAL("Test"), u8"Test"_stbuf); 461 | #endif 462 | } 463 | -------------------------------------------------------------------------------- /test/test_codecs.cpp: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2016 Michael Hansen 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a 4 | copy of this software and associated documentation files (the "Software"), 5 | to deal in the Software without restriction, including without limitation 6 | the rights to use, copy, modify, merge, publish, distribute, sublicense, 7 | and/or sell copies of the Software, and to permit persons to whom the 8 | Software is furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 18 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 19 | DEALINGS IN THE SOFTWARE. */ 20 | 21 | #include "st_codecs.h" 22 | #include "st_assert.h" 23 | 24 | #include 25 | #include 26 | 27 | namespace ST 28 | { 29 | // Teach GTest how to print an ST::string 30 | static void PrintTo(const ST::string &str, std::ostream *os) 31 | { 32 | *os << "ST::string{\"" << str.c_str() << "\"}"; 33 | } 34 | 35 | // Teach GTest how to print an ST::char_buffer 36 | static void PrintTo(const ST::char_buffer &buffer, std::ostream *os) 37 | { 38 | *os << "ST::char_buffer{ "; 39 | char hex[8]; 40 | for (size_t i = 0; i < buffer.size(); ++i) { 41 | unsigned int ch = static_cast(buffer.data()[i]); 42 | snprintf(hex, sizeof(hex), "%02X ", ch); 43 | *os << hex; 44 | } 45 | *os << "}"; 46 | } 47 | } 48 | 49 | static const unsigned char data_empty[] = { 0 }; 50 | static const unsigned char data_hex_ranges[] = { 51 | 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 52 | 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 53 | 0x10, 0xF0, 0xFF }; 54 | static const unsigned char data_base64_ranges[] = { 55 | /* Evaluates to the base64 numerals in order */ 56 | 0x00, 0x10, 0x83, 0x10, 0x51, 0x87, 0x20, 0x92, 57 | 0x8b, 0x30, 0xd3, 0x8f, 0x41, 0x14, 0x93, 0x51, 58 | 0x55, 0x97, 0x61, 0x96, 0x9b, 0x71, 0xd7, 0x9f, 59 | 0x82, 0x18, 0xa3, 0x92, 0x59, 0xa7, 0xa2, 0x9a, 60 | 0xab, 0xb2, 0xdb, 0xaf, 0xc3, 0x1c, 0xb3, 0xd3, 61 | 0x5d, 0xb7, 0xe3, 0x9e, 0xbb, 0xf3, 0xdf, 0xbf 62 | }; 63 | static const unsigned char data_1[] = { 0x01 }; 64 | static const unsigned char data_2[] = { 0x01, 0x02 }; 65 | static const unsigned char data_3[] = { 0x01, 0x02, 0x03 }; 66 | static const unsigned char data_4[] = { 0x01, 0x02, 0x03, 0x04 }; 67 | static const unsigned char data_15[] = { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 68 | 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F }; 69 | static const unsigned char data_16[] = { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 70 | 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10 }; 71 | static const unsigned char data_17[] = { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 72 | 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10, 73 | 0x11 }; 74 | 75 | #define cbuf(data) \ 76 | ST::char_buffer(reinterpret_cast(data), sizeof((data))) 77 | 78 | #define empty_buf \ 79 | ST::char_buffer(reinterpret_cast(data_empty), 0) 80 | 81 | TEST(codecs, hex_encode) 82 | { 83 | EXPECT_EQ(ST::string(), ST::hex_encode(data_empty, 0)); 84 | EXPECT_EQ(ST::string(), ST::hex_encode(nullptr, 0)); 85 | EXPECT_EQ(ST_LITERAL("000102030405060708090a0b0c0d0e0f10f0ff"), 86 | ST::hex_encode(data_hex_ranges, sizeof(data_hex_ranges))); 87 | 88 | EXPECT_EQ(ST_LITERAL("01"), ST::hex_encode(data_1, sizeof(data_1))); 89 | EXPECT_EQ(ST_LITERAL("0102"), ST::hex_encode(data_2, sizeof(data_2))); 90 | EXPECT_EQ(ST_LITERAL("010203"), ST::hex_encode(data_3, sizeof(data_3))); 91 | EXPECT_EQ(ST_LITERAL("0102030405060708090a0b0c0d0e0f"), 92 | ST::hex_encode(data_15, sizeof(data_15))); 93 | EXPECT_EQ(ST_LITERAL("0102030405060708090a0b0c0d0e0f10"), 94 | ST::hex_encode(data_16, sizeof(data_16))); 95 | EXPECT_EQ(ST_LITERAL("0102030405060708090a0b0c0d0e0f1011"), 96 | ST::hex_encode(data_17, sizeof(data_17))); 97 | } 98 | 99 | TEST(codecs, hex_decode) 100 | { 101 | EXPECT_EQ(empty_buf, ST::hex_decode(ST::string())); 102 | EXPECT_EQ(cbuf(data_hex_ranges), 103 | ST::hex_decode(ST_LITERAL("000102030405060708090A0B0C0D0E0F10F0FF"))); 104 | EXPECT_EQ(cbuf(data_hex_ranges), 105 | ST::hex_decode(ST_LITERAL("000102030405060708090a0b0c0d0e0f10f0ff"))); 106 | 107 | EXPECT_EQ(cbuf(data_1), ST::hex_decode(ST_LITERAL("01"))); 108 | EXPECT_EQ(cbuf(data_2), ST::hex_decode(ST_LITERAL("0102"))); 109 | EXPECT_EQ(cbuf(data_3), ST::hex_decode(ST_LITERAL("010203"))); 110 | EXPECT_EQ(cbuf(data_15), 111 | ST::hex_decode(ST_LITERAL("0102030405060708090A0B0C0D0E0F"))); 112 | EXPECT_EQ(cbuf(data_16), 113 | ST::hex_decode(ST_LITERAL("0102030405060708090A0B0C0D0E0F10"))); 114 | EXPECT_EQ(cbuf(data_17), 115 | ST::hex_decode(ST_LITERAL("0102030405060708090A0B0C0D0E0F1011"))); 116 | } 117 | 118 | TEST(codecs, hex_decode_buffer) 119 | { 120 | char buffer[64]; 121 | 122 | EXPECT_EQ(0, ST::hex_decode(ST::string(), nullptr, 0)); 123 | EXPECT_EQ(0, ST::hex_decode(ST::string(), buffer, sizeof(buffer))); 124 | EXPECT_EQ(empty_buf, ST::char_buffer(buffer, 0)); 125 | 126 | EXPECT_EQ(static_cast(sizeof(data_hex_ranges)), 127 | ST::hex_decode(ST_LITERAL("000102030405060708090A0B0C0D0E0F10F0FF"), nullptr, 0)); 128 | EXPECT_EQ(static_cast(sizeof(data_hex_ranges)), 129 | ST::hex_decode(ST_LITERAL("000102030405060708090A0B0C0D0E0F10F0FF"), buffer, sizeof(buffer))); 130 | EXPECT_EQ(cbuf(data_hex_ranges), ST::char_buffer(buffer, sizeof(data_hex_ranges))); 131 | EXPECT_EQ(static_cast(sizeof(data_hex_ranges)), 132 | ST::hex_decode(ST_LITERAL("000102030405060708090a0b0c0d0e0f10f0ff"), nullptr, 0)); 133 | EXPECT_EQ(static_cast(sizeof(data_hex_ranges)), 134 | ST::hex_decode(ST_LITERAL("000102030405060708090a0b0c0d0e0f10f0ff"), buffer, sizeof(buffer))); 135 | EXPECT_EQ(cbuf(data_hex_ranges), ST::char_buffer(buffer, sizeof(data_hex_ranges))); 136 | 137 | char tight_buffer[4]; 138 | EXPECT_EQ(static_cast(sizeof(tight_buffer)), 139 | ST::hex_decode(ST_LITERAL("01020304"), nullptr, 0)); 140 | EXPECT_EQ(static_cast(sizeof(tight_buffer)), 141 | ST::hex_decode(ST_LITERAL("01020304"), tight_buffer, sizeof(tight_buffer))); 142 | EXPECT_EQ(cbuf(data_4), ST::char_buffer(tight_buffer, sizeof(tight_buffer))); 143 | 144 | EXPECT_EQ(static_cast(sizeof(data_1)), 145 | ST::hex_decode(ST_LITERAL("01"), nullptr, 0)); 146 | EXPECT_EQ(static_cast(sizeof(data_1)), 147 | ST::hex_decode(ST_LITERAL("01"), buffer, sizeof(buffer))); 148 | EXPECT_EQ(cbuf(data_1), ST::char_buffer(buffer, sizeof(data_1))); 149 | EXPECT_EQ(static_cast(sizeof(data_2)), 150 | ST::hex_decode(ST_LITERAL("0102"), nullptr, 0)); 151 | EXPECT_EQ(static_cast(sizeof(data_2)), 152 | ST::hex_decode(ST_LITERAL("0102"), buffer, sizeof(buffer))); 153 | EXPECT_EQ(cbuf(data_2), ST::char_buffer(buffer, sizeof(data_2))); 154 | EXPECT_EQ(static_cast(sizeof(data_3)), 155 | ST::hex_decode(ST_LITERAL("010203"), nullptr, 0)); 156 | EXPECT_EQ(static_cast(sizeof(data_3)), 157 | ST::hex_decode(ST_LITERAL("010203"), buffer, sizeof(buffer))); 158 | EXPECT_EQ(cbuf(data_3), ST::char_buffer(buffer, sizeof(data_3))); 159 | 160 | EXPECT_EQ(static_cast(sizeof(data_15)), 161 | ST::hex_decode(ST_LITERAL("0102030405060708090A0B0C0D0E0F"), nullptr, 0)); 162 | EXPECT_EQ(static_cast(sizeof(data_15)), 163 | ST::hex_decode(ST_LITERAL("0102030405060708090A0B0C0D0E0F"), buffer, sizeof(buffer))); 164 | EXPECT_EQ(cbuf(data_15), ST::char_buffer(buffer, sizeof(data_15))); 165 | EXPECT_EQ(static_cast(sizeof(data_16)), 166 | ST::hex_decode(ST_LITERAL("0102030405060708090A0B0C0D0E0F10"), nullptr, 0)); 167 | EXPECT_EQ(static_cast(sizeof(data_16)), 168 | ST::hex_decode(ST_LITERAL("0102030405060708090A0B0C0D0E0F10"), buffer, sizeof(buffer))); 169 | EXPECT_EQ(cbuf(data_16), ST::char_buffer(buffer, sizeof(data_16))); 170 | EXPECT_EQ(static_cast(sizeof(data_17)), 171 | ST::hex_decode(ST_LITERAL("0102030405060708090A0B0C0D0E0F1011"), nullptr, 0)); 172 | EXPECT_EQ(static_cast(sizeof(data_17)), 173 | ST::hex_decode(ST_LITERAL("0102030405060708090A0B0C0D0E0F1011"), buffer, sizeof(buffer))); 174 | EXPECT_EQ(cbuf(data_17), ST::char_buffer(buffer, sizeof(data_17))); 175 | } 176 | 177 | TEST(codecs, hex_codec_errors) 178 | { 179 | EXPECT_THROW(ST::hex_encode(nullptr, 1), std::invalid_argument); 180 | 181 | EXPECT_THROW(ST::hex_decode(ST_LITERAL("1")), ST::codec_error); 182 | EXPECT_THROW(ST::hex_decode(ST_LITERAL("xF")), ST::codec_error); 183 | EXPECT_THROW(ST::hex_decode(ST_LITERAL("Fx")), ST::codec_error); 184 | EXPECT_THROW(ST::hex_decode(ST_LITERAL("\000F")), ST::codec_error); 185 | EXPECT_THROW(ST::hex_decode(ST_LITERAL("F\000")), ST::codec_error); 186 | 187 | char buffer[64]; 188 | EXPECT_EQ(-1, ST::hex_decode(ST_LITERAL("1"), buffer, sizeof(buffer))); 189 | EXPECT_EQ(-1, ST::hex_decode(ST_LITERAL("xF"), buffer, sizeof(buffer))); 190 | EXPECT_EQ(-1, ST::hex_decode(ST_LITERAL("Fx"), buffer, sizeof(buffer))); 191 | EXPECT_EQ(-1, ST::hex_decode(ST_LITERAL("\000F"), buffer, sizeof(buffer))); 192 | EXPECT_EQ(-1, ST::hex_decode(ST_LITERAL("F\000"), buffer, sizeof(buffer))); 193 | 194 | char tight_buffer[3]; 195 | EXPECT_EQ(-1, ST::hex_decode(ST_LITERAL("01020304"), tight_buffer, sizeof(tight_buffer))); 196 | } 197 | 198 | TEST(codecs, base64_encode) 199 | { 200 | EXPECT_EQ(ST::string(), ST::base64_encode(data_empty, 0)); 201 | EXPECT_EQ(ST::string(), ST::base64_encode(nullptr, 0)); 202 | EXPECT_EQ(ST_LITERAL("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"), 203 | ST::base64_encode(data_base64_ranges, sizeof(data_base64_ranges))); 204 | 205 | EXPECT_EQ(ST_LITERAL("AQ=="), ST::base64_encode(data_1, sizeof(data_1))); 206 | EXPECT_EQ(ST_LITERAL("AQI="), ST::base64_encode(data_2, sizeof(data_2))); 207 | EXPECT_EQ(ST_LITERAL("AQID"), ST::base64_encode(data_3, sizeof(data_3))); 208 | EXPECT_EQ(ST_LITERAL("AQIDBAUGBwgJCgsMDQ4P"), 209 | ST::base64_encode(data_15, sizeof(data_15))); 210 | EXPECT_EQ(ST_LITERAL("AQIDBAUGBwgJCgsMDQ4PEA=="), 211 | ST::base64_encode(data_16, sizeof(data_16))); 212 | EXPECT_EQ(ST_LITERAL("AQIDBAUGBwgJCgsMDQ4PEBE="), 213 | ST::base64_encode(data_17, sizeof(data_17))); 214 | } 215 | 216 | TEST(codecs, base64_decode) 217 | { 218 | EXPECT_EQ(empty_buf, ST::base64_decode(ST::string())); 219 | EXPECT_EQ(cbuf(data_base64_ranges), ST::base64_decode( 220 | ST_LITERAL("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"))); 221 | 222 | EXPECT_EQ(cbuf(data_1), ST::base64_decode(ST_LITERAL("AQ=="))); 223 | EXPECT_EQ(cbuf(data_2), ST::base64_decode(ST_LITERAL("AQI="))); 224 | EXPECT_EQ(cbuf(data_3), ST::base64_decode(ST_LITERAL("AQID"))); 225 | EXPECT_EQ(cbuf(data_15), 226 | ST::base64_decode(ST_LITERAL("AQIDBAUGBwgJCgsMDQ4P"))); 227 | EXPECT_EQ(cbuf(data_16), 228 | ST::base64_decode(ST_LITERAL("AQIDBAUGBwgJCgsMDQ4PEA=="))); 229 | EXPECT_EQ(cbuf(data_17), 230 | ST::base64_decode(ST_LITERAL("AQIDBAUGBwgJCgsMDQ4PEBE="))); 231 | } 232 | 233 | TEST(codecs, base64_decode_buffer) 234 | { 235 | char buffer[64]; 236 | 237 | EXPECT_EQ(0, ST::base64_decode(ST::string(), nullptr, 0)); 238 | EXPECT_EQ(0, ST::base64_decode(ST::string(), buffer, sizeof(buffer))); 239 | EXPECT_EQ(empty_buf, ST::char_buffer(buffer, 0)); 240 | 241 | EXPECT_EQ(static_cast(sizeof(data_base64_ranges)), 242 | ST::base64_decode(ST_LITERAL("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"), 243 | nullptr, 0)); 244 | EXPECT_EQ(static_cast(sizeof(data_base64_ranges)), 245 | ST::base64_decode(ST_LITERAL("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"), 246 | buffer, sizeof(buffer))); 247 | EXPECT_EQ(cbuf(data_base64_ranges), ST::char_buffer(buffer, sizeof(data_base64_ranges))); 248 | 249 | char tight_buffer[4]; 250 | EXPECT_EQ(static_cast(sizeof(tight_buffer)), 251 | ST::base64_decode(ST_LITERAL("AQIDBA=="), nullptr, 0)); 252 | EXPECT_EQ(static_cast(sizeof(tight_buffer)), 253 | ST::base64_decode(ST_LITERAL("AQIDBA=="), tight_buffer, sizeof(tight_buffer))); 254 | EXPECT_EQ(cbuf(data_4), ST::char_buffer(tight_buffer, sizeof(tight_buffer))); 255 | 256 | EXPECT_EQ(static_cast(sizeof(data_1)), 257 | ST::base64_decode(ST_LITERAL("AQ=="), nullptr, 0)); 258 | EXPECT_EQ(static_cast(sizeof(data_1)), 259 | ST::base64_decode(ST_LITERAL("AQ=="), buffer, sizeof(buffer))); 260 | EXPECT_EQ(cbuf(data_1), ST::char_buffer(buffer, sizeof(data_1))); 261 | EXPECT_EQ(static_cast(sizeof(data_2)), 262 | ST::base64_decode(ST_LITERAL("AQI="), nullptr, 0)); 263 | EXPECT_EQ(static_cast(sizeof(data_2)), 264 | ST::base64_decode(ST_LITERAL("AQI="), buffer, sizeof(buffer))); 265 | EXPECT_EQ(cbuf(data_2), ST::char_buffer(buffer, sizeof(data_2))); 266 | EXPECT_EQ(static_cast(sizeof(data_3)), 267 | ST::base64_decode(ST_LITERAL("AQID"), nullptr, 0)); 268 | EXPECT_EQ(static_cast(sizeof(data_3)), 269 | ST::base64_decode(ST_LITERAL("AQID"), buffer, sizeof(buffer))); 270 | EXPECT_EQ(cbuf(data_3), ST::char_buffer(buffer, sizeof(data_3))); 271 | 272 | EXPECT_EQ(static_cast(sizeof(data_15)), 273 | ST::base64_decode(ST_LITERAL("AQIDBAUGBwgJCgsMDQ4P"), nullptr, 0)); 274 | EXPECT_EQ(static_cast(sizeof(data_15)), 275 | ST::base64_decode(ST_LITERAL("AQIDBAUGBwgJCgsMDQ4P"), buffer, sizeof(buffer))); 276 | EXPECT_EQ(cbuf(data_15), ST::char_buffer(buffer, sizeof(data_15))); 277 | EXPECT_EQ(static_cast(sizeof(data_16)), 278 | ST::base64_decode(ST_LITERAL("AQIDBAUGBwgJCgsMDQ4PEA=="), nullptr, 0)); 279 | EXPECT_EQ(static_cast(sizeof(data_16)), 280 | ST::base64_decode(ST_LITERAL("AQIDBAUGBwgJCgsMDQ4PEA=="), buffer, sizeof(buffer))); 281 | EXPECT_EQ(cbuf(data_16), ST::char_buffer(buffer, sizeof(data_16))); 282 | EXPECT_EQ(static_cast(sizeof(data_17)), 283 | ST::base64_decode(ST_LITERAL("AQIDBAUGBwgJCgsMDQ4PEBE="), nullptr, 0)); 284 | EXPECT_EQ(static_cast(sizeof(data_17)), 285 | ST::base64_decode(ST_LITERAL("AQIDBAUGBwgJCgsMDQ4PEBE="), buffer, sizeof(buffer))); 286 | EXPECT_EQ(cbuf(data_17), ST::char_buffer(buffer, sizeof(data_17))); 287 | } 288 | 289 | TEST(codecs, base64_codec_errors) 290 | { 291 | EXPECT_THROW(ST::base64_encode(nullptr, 1), std::invalid_argument); 292 | 293 | EXPECT_THROW(ST::base64_decode(ST_LITERAL("A")), ST::codec_error); 294 | EXPECT_THROW(ST::base64_decode(ST_LITERAL("AB")), ST::codec_error); 295 | EXPECT_THROW(ST::base64_decode(ST_LITERAL("ABC")), ST::codec_error); 296 | EXPECT_THROW(ST::base64_decode(ST_LITERAL("!ABC")), ST::codec_error); 297 | EXPECT_THROW(ST::base64_decode(ST_LITERAL("A!BC")), ST::codec_error); 298 | EXPECT_THROW(ST::base64_decode(ST_LITERAL("AB!C")), ST::codec_error); 299 | EXPECT_THROW(ST::base64_decode(ST_LITERAL("ABC!")), ST::codec_error); 300 | EXPECT_THROW(ST::base64_decode(ST_LITERAL("\000ABC")), ST::codec_error); 301 | EXPECT_THROW(ST::base64_decode(ST_LITERAL("A\000BC")), ST::codec_error); 302 | EXPECT_THROW(ST::base64_decode(ST_LITERAL("AB\000C")), ST::codec_error); 303 | EXPECT_THROW(ST::base64_decode(ST_LITERAL("ABC\000")), ST::codec_error); 304 | EXPECT_THROW(ST::base64_decode(ST_LITERAL("=ABC")), ST::codec_error); 305 | EXPECT_THROW(ST::base64_decode(ST_LITERAL("A=BC")), ST::codec_error); 306 | EXPECT_THROW(ST::base64_decode(ST_LITERAL("AB=C")), ST::codec_error); 307 | EXPECT_THROW(ST::base64_decode(ST_LITERAL("A===")), ST::codec_error); 308 | 309 | EXPECT_THROW(ST::base64_decode(ST_LITERAL("++++++++A")), ST::codec_error); 310 | EXPECT_THROW(ST::base64_decode(ST_LITERAL("++++++++AB")), ST::codec_error); 311 | EXPECT_THROW(ST::base64_decode(ST_LITERAL("++++++++ABC")), ST::codec_error); 312 | EXPECT_THROW(ST::base64_decode(ST_LITERAL("!ABC++++++++")), ST::codec_error); 313 | EXPECT_THROW(ST::base64_decode(ST_LITERAL("A!BC++++++++")), ST::codec_error); 314 | EXPECT_THROW(ST::base64_decode(ST_LITERAL("AB!C++++++++")), ST::codec_error); 315 | EXPECT_THROW(ST::base64_decode(ST_LITERAL("ABC!++++++++")), ST::codec_error); 316 | EXPECT_THROW(ST::base64_decode(ST_LITERAL("\000ABC++++++++")), ST::codec_error); 317 | EXPECT_THROW(ST::base64_decode(ST_LITERAL("A\000BC++++++++")), ST::codec_error); 318 | EXPECT_THROW(ST::base64_decode(ST_LITERAL("AB\000C++++++++")), ST::codec_error); 319 | EXPECT_THROW(ST::base64_decode(ST_LITERAL("ABC\000++++++++")), ST::codec_error); 320 | EXPECT_THROW(ST::base64_decode(ST_LITERAL("=ABC++++++++")), ST::codec_error); 321 | EXPECT_THROW(ST::base64_decode(ST_LITERAL("A=BC++++++++")), ST::codec_error); 322 | EXPECT_THROW(ST::base64_decode(ST_LITERAL("AB=C++++++++")), ST::codec_error); 323 | EXPECT_THROW(ST::base64_decode(ST_LITERAL("ABC=++++++++")), ST::codec_error); 324 | EXPECT_THROW(ST::base64_decode(ST_LITERAL("++++=ABC++++")), ST::codec_error); 325 | EXPECT_THROW(ST::base64_decode(ST_LITERAL("++++A=BC++++")), ST::codec_error); 326 | EXPECT_THROW(ST::base64_decode(ST_LITERAL("++++AB=C++++")), ST::codec_error); 327 | EXPECT_THROW(ST::base64_decode(ST_LITERAL("++++ABC=++++")), ST::codec_error); 328 | 329 | char buffer[64]; 330 | EXPECT_EQ(-1, ST::base64_decode(ST_LITERAL("A"), buffer, sizeof(buffer))); 331 | EXPECT_EQ(-1, ST::base64_decode(ST_LITERAL("AB"), buffer, sizeof(buffer))); 332 | EXPECT_EQ(-1, ST::base64_decode(ST_LITERAL("ABC"), buffer, sizeof(buffer))); 333 | EXPECT_EQ(-1, ST::base64_decode(ST_LITERAL("!ABC"), buffer, sizeof(buffer))); 334 | EXPECT_EQ(-1, ST::base64_decode(ST_LITERAL("A!BC"), buffer, sizeof(buffer))); 335 | EXPECT_EQ(-1, ST::base64_decode(ST_LITERAL("AB!C"), buffer, sizeof(buffer))); 336 | EXPECT_EQ(-1, ST::base64_decode(ST_LITERAL("ABC!"), buffer, sizeof(buffer))); 337 | EXPECT_EQ(-1, ST::base64_decode(ST_LITERAL("\000ABC"), buffer, sizeof(buffer))); 338 | EXPECT_EQ(-1, ST::base64_decode(ST_LITERAL("A\000BC"), buffer, sizeof(buffer))); 339 | EXPECT_EQ(-1, ST::base64_decode(ST_LITERAL("AB\000C"), buffer, sizeof(buffer))); 340 | EXPECT_EQ(-1, ST::base64_decode(ST_LITERAL("ABC\000"), buffer, sizeof(buffer))); 341 | EXPECT_EQ(-1, ST::base64_decode(ST_LITERAL("=ABC"), buffer, sizeof(buffer))); 342 | EXPECT_EQ(-1, ST::base64_decode(ST_LITERAL("A=BC"), buffer, sizeof(buffer))); 343 | EXPECT_EQ(-1, ST::base64_decode(ST_LITERAL("AB=C"), buffer, sizeof(buffer))); 344 | EXPECT_EQ(-1, ST::base64_decode(ST_LITERAL("A==="), buffer, sizeof(buffer))); 345 | 346 | EXPECT_EQ(-1, ST::base64_decode(ST_LITERAL("++++++++A"), buffer, sizeof(buffer))); 347 | EXPECT_EQ(-1, ST::base64_decode(ST_LITERAL("++++++++AB"), buffer, sizeof(buffer))); 348 | EXPECT_EQ(-1, ST::base64_decode(ST_LITERAL("++++++++ABC"), buffer, sizeof(buffer))); 349 | EXPECT_EQ(-1, ST::base64_decode(ST_LITERAL("!ABC++++++++"), buffer, sizeof(buffer))); 350 | EXPECT_EQ(-1, ST::base64_decode(ST_LITERAL("A!BC++++++++"), buffer, sizeof(buffer))); 351 | EXPECT_EQ(-1, ST::base64_decode(ST_LITERAL("AB!C++++++++"), buffer, sizeof(buffer))); 352 | EXPECT_EQ(-1, ST::base64_decode(ST_LITERAL("ABC!++++++++"), buffer, sizeof(buffer))); 353 | EXPECT_EQ(-1, ST::base64_decode(ST_LITERAL("\000ABC++++++++"), buffer, sizeof(buffer))); 354 | EXPECT_EQ(-1, ST::base64_decode(ST_LITERAL("A\000BC++++++++"), buffer, sizeof(buffer))); 355 | EXPECT_EQ(-1, ST::base64_decode(ST_LITERAL("AB\000C++++++++"), buffer, sizeof(buffer))); 356 | EXPECT_EQ(-1, ST::base64_decode(ST_LITERAL("ABC\000++++++++"), buffer, sizeof(buffer))); 357 | EXPECT_EQ(-1, ST::base64_decode(ST_LITERAL("=ABC++++++++"), buffer, sizeof(buffer))); 358 | EXPECT_EQ(-1, ST::base64_decode(ST_LITERAL("A=BC++++++++"), buffer, sizeof(buffer))); 359 | EXPECT_EQ(-1, ST::base64_decode(ST_LITERAL("AB=C++++++++"), buffer, sizeof(buffer))); 360 | EXPECT_EQ(-1, ST::base64_decode(ST_LITERAL("ABC=++++++++"), buffer, sizeof(buffer))); 361 | EXPECT_EQ(-1, ST::base64_decode(ST_LITERAL("++++=ABC++++"), buffer, sizeof(buffer))); 362 | EXPECT_EQ(-1, ST::base64_decode(ST_LITERAL("++++A=BC++++"), buffer, sizeof(buffer))); 363 | EXPECT_EQ(-1, ST::base64_decode(ST_LITERAL("++++AB=C++++"), buffer, sizeof(buffer))); 364 | EXPECT_EQ(-1, ST::base64_decode(ST_LITERAL("++++ABC=++++"), buffer, sizeof(buffer))); 365 | 366 | char tight_buffer[3]; 367 | EXPECT_EQ(-1, ST::base64_decode(ST_LITERAL("AQIDBA=="), tight_buffer, sizeof(tight_buffer))); 368 | } 369 | -------------------------------------------------------------------------------- /test/test_iostream.cpp: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2018 Michael Hansen 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a 4 | copy of this software and associated documentation files (the "Software"), 5 | to deal in the Software without restriction, including without limitation 6 | the rights to use, copy, modify, merge, publish, distribute, sublicense, 7 | and/or sell copies of the Software, and to permit persons to whom the 8 | Software is furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 18 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 19 | DEALINGS IN THE SOFTWARE. */ 20 | 21 | #include "st_iostream.h" 22 | 23 | #include 24 | #include 25 | 26 | /* This file does not extensively test formatting, as that is already tested 27 | by test_format.cpp. Instead, this just tests the interfaces provided 28 | in . */ 29 | 30 | #define BUFFER_LEN (256) 31 | 32 | TEST(stdio, writef) 33 | { 34 | std::stringstream sstream; 35 | ST::writef(sstream, "xxxxx"); 36 | ST::writef(sstream, "Testing {>12_#} output", "formatted"); 37 | 38 | EXPECT_EQ(sstream.str(), "xxxxxTesting ###formatted output"); 39 | } 40 | 41 | TEST(stdio, writef_wide) 42 | { 43 | std::wstringstream sstream; 44 | ST::writef(sstream, "xxxxx"); 45 | ST::writef(sstream, "Testing {>12_#} output", "formatted"); 46 | 47 | EXPECT_EQ(sstream.str(), L"xxxxxTesting ###formatted output"); 48 | } 49 | 50 | TEST(stdio, stream_ops) 51 | { 52 | std::stringstream sstream; 53 | sstream << ST_LITERAL("xxxxx") << " " << ST_LITERAL("yyyyy"); 54 | 55 | sstream.seekg(0); 56 | ST::string x, y; 57 | sstream >> x; 58 | sstream >> y; 59 | 60 | EXPECT_EQ(ST_LITERAL("xxxxx"), x); 61 | EXPECT_EQ(ST_LITERAL("yyyyy"), y); 62 | } 63 | 64 | TEST(stdio, stream_ops_wide) 65 | { 66 | std::wstringstream sstream; 67 | sstream << ST_LITERAL("xxxxx") << L" " << ST_LITERAL("yyyyy"); 68 | 69 | sstream.seekg(0); 70 | ST::string x, y; 71 | sstream >> x; 72 | sstream >> y; 73 | 74 | EXPECT_EQ(ST_LITERAL("xxxxx"), x); 75 | EXPECT_EQ(ST_LITERAL("yyyyy"), y); 76 | } 77 | -------------------------------------------------------------------------------- /test/test_regress.cpp: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2016 Michael Hansen 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a 4 | copy of this software and associated documentation files (the "Software"), 5 | to deal in the Software without restriction, including without limitation 6 | the rights to use, copy, modify, merge, publish, distribute, sublicense, 7 | and/or sell copies of the Software, and to permit persons to whom the 8 | Software is furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 18 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 19 | DEALINGS IN THE SOFTWARE. */ 20 | 21 | #include "st_stringstream.h" 22 | #include "st_assert.h" 23 | 24 | #include 25 | 26 | TEST(regress, github_5) 27 | { 28 | static const char latin1_data[] = "F\xfcr Elise"; 29 | static const char utf8_data[] = "F\xc3\xbcr Elise"; 30 | ST::string s; 31 | ST::char_buffer init(latin1_data, strlen(latin1_data)); 32 | try { 33 | // This should throw 34 | s = std::move(init); 35 | ASSERT_FALSE(true); 36 | } catch (ST::unicode_error &) { 37 | s = ST::string::from_latin_1(init); 38 | } 39 | 40 | EXPECT_STREQ(utf8_data, s.c_str()); 41 | } 42 | 43 | TEST(regress, string_stream_move) 44 | { 45 | ST::string_stream ss; 46 | ss << "Hello"; 47 | EXPECT_EQ(5U, ss.size()); 48 | EXPECT_EQ(ST_LITERAL("Hello"), ss.to_string()); 49 | 50 | ST::string_stream ss2 = std::move(ss); 51 | EXPECT_EQ(5U, ss2.size()); 52 | EXPECT_EQ(ST_LITERAL("Hello"), ss2.to_string()); 53 | } 54 | 55 | TEST(regress, surrogate_replacement) 56 | { 57 | // string_theory 1.x would eat the trailing character after an incomplete 58 | // or invalid UTF-16 surrogate pair 59 | const char replacement_text[] = "A\xef\xbf\xbdz"; 60 | const char16_t incomplete_surrogate_padded[] = { 0x41, 0xd800, 0x7a, 0 }; 61 | ST::string incomplete_surr = ST::string::from_utf16(incomplete_surrogate_padded, 62 | ST_AUTO_SIZE, 63 | ST::substitute_invalid); 64 | EXPECT_STREQ(replacement_text, incomplete_surr.c_str()); 65 | 66 | // Ensure that a valid surrogate after an incomplete one still gets converted 67 | const char replacement_text2[] = "A\xef\xbf\xbd\xf4\x8f\xbf\xbfz"; 68 | const char16_t surrogate_dupe[] = { 0x41, 0xdbff, 0xdbff, 0xdfff, 0x7a, 0}; 69 | ST::string surr_dupe = ST::string::from_utf16(surrogate_dupe, ST_AUTO_SIZE, 70 | ST::substitute_invalid); 71 | EXPECT_STREQ(replacement_text2, surr_dupe.c_str()); 72 | 73 | // string_theory 1.x would use a 4-byte representation for \ufffd, even 74 | // though canonically it should use 3 bytes. 75 | const char replacement_text3[] = "A\xef\xbf\xbd\xef\xbf\xbdz"; 76 | const char16_t bad_surrogate_padded[] = { 0x41, 0xd800, 0xd801, 0x7a, 0 }; 77 | ST::string bad_surr = ST::string::from_utf16(bad_surrogate_padded, ST_AUTO_SIZE, 78 | ST::substitute_invalid); 79 | EXPECT_STREQ(replacement_text3, bad_surr.c_str()); 80 | } 81 | 82 | TEST(regress, null_init) 83 | { 84 | ST::string dont_crash = (const char *)nullptr; 85 | EXPECT_TRUE(dont_crash.empty()); 86 | 87 | dont_crash = (const char *)nullptr; 88 | dont_crash = ST::string::from_wchar((const wchar_t *)nullptr); 89 | dont_crash = ST::string::from_utf16((const char16_t *)nullptr); 90 | dont_crash = ST::string::from_utf32((const char32_t *)nullptr); 91 | 92 | ST::char_buffer cbuf; 93 | ST::utf16_buffer u16buf; 94 | ST::utf32_buffer u32buf; 95 | ST::wchar_buffer wbuf; 96 | u16buf = ST::utf8_to_utf16((const char *)nullptr, 0, ST::check_validity); 97 | u32buf = ST::utf8_to_utf32((const char *)nullptr, 0, ST::check_validity); 98 | wbuf = ST::utf8_to_wchar((const char *)nullptr, 0, ST::check_validity); 99 | cbuf = ST::utf8_to_latin_1((const char *)nullptr, 0, ST::check_validity); 100 | cbuf = ST::utf16_to_utf8((const char16_t *)nullptr, 0, ST::check_validity); 101 | u32buf = ST::utf16_to_utf32((const char16_t *)nullptr, 0, ST::check_validity); 102 | wbuf = ST::utf16_to_wchar((const char16_t *)nullptr, 0, ST::check_validity); 103 | cbuf = ST::utf16_to_latin_1((const char16_t *)nullptr, 0, ST::check_validity); 104 | cbuf = ST::utf32_to_utf8((const char32_t *)nullptr, 0, ST::check_validity); 105 | u16buf = ST::utf32_to_utf16((const char32_t *)nullptr, 0, ST::check_validity); 106 | wbuf = ST::utf32_to_wchar((const char32_t *)nullptr, 0, ST::check_validity); 107 | cbuf = ST::utf32_to_latin_1((const char32_t *)nullptr, 0, ST::check_validity); 108 | cbuf = ST::wchar_to_utf8((const wchar_t *)nullptr, 0, ST::check_validity); 109 | u16buf = ST::wchar_to_utf16((const wchar_t *)nullptr, 0, ST::check_validity); 110 | u32buf = ST::wchar_to_utf32((const wchar_t *)nullptr, 0, ST::check_validity); 111 | cbuf = ST::wchar_to_latin_1((const wchar_t *)nullptr, 0, ST::check_validity); 112 | cbuf = ST::latin_1_to_utf8((const char *)nullptr, 0); 113 | u16buf = ST::latin_1_to_utf16((const char *)nullptr, 0); 114 | u32buf = ST::latin_1_to_utf32((const char *)nullptr, 0); 115 | wbuf = ST::latin_1_to_wchar((const char *)nullptr, 0); 116 | } 117 | 118 | TEST(regress, allocate_fill) 119 | { 120 | ST::char_buffer cb; 121 | cb.allocate(20, 'x'); 122 | EXPECT_EQ(ST::char_buffer("xxxxxxxxxxxxxxxxxxxx", 20), cb); 123 | 124 | ST::wchar_buffer wcb; 125 | wcb.allocate(20, L'x'); 126 | EXPECT_EQ(ST::wchar_buffer(L"xxxxxxxxxxxxxxxxxxxx", 20), wcb); 127 | } 128 | -------------------------------------------------------------------------------- /test/test_sstream.cpp: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2018 Michael Hansen 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a 4 | copy of this software and associated documentation files (the "Software"), 5 | to deal in the Software without restriction, including without limitation 6 | the rights to use, copy, modify, merge, publish, distribute, sublicense, 7 | and/or sell copies of the Software, and to permit persons to whom the 8 | Software is furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 18 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 19 | DEALINGS IN THE SOFTWARE. */ 20 | 21 | #include "st_stringstream.h" 22 | 23 | #include 24 | 25 | namespace ST 26 | { 27 | // Teach GTest how to print an ST::string 28 | static void PrintTo(const ST::string &str, std::ostream *os) 29 | { 30 | *os << "ST::string{\"" << str.c_str() << "\"}"; 31 | } 32 | } 33 | 34 | TEST(string_stream, empty) 35 | { 36 | ST::string_stream ss; 37 | EXPECT_EQ(0U, ss.size()); 38 | EXPECT_EQ(ST_LITERAL(""), ss.to_string()); 39 | 40 | ss << ""; 41 | EXPECT_EQ(0U, ss.size()); 42 | EXPECT_EQ(ST_LITERAL(""), ss.to_string()); 43 | 44 | ss.append("", 0); 45 | EXPECT_EQ(0U, ss.size()); 46 | EXPECT_EQ(ST_LITERAL(""), ss.to_string()); 47 | 48 | ss.append(nullptr, 0); 49 | EXPECT_EQ(0U, ss.size()); 50 | EXPECT_EQ(ST_LITERAL(""), ss.to_string()); 51 | } 52 | 53 | TEST(string_stream, append) 54 | { 55 | // Explicit length 56 | ST::string_stream ss; 57 | ss.append("aaaaaaaaaa", 5); 58 | EXPECT_EQ(5U, ss.size()); 59 | EXPECT_EQ(ST_LITERAL("aaaaa"), ss.to_string()); 60 | 61 | ss.append("", 0); 62 | EXPECT_EQ(5U, ss.size()); 63 | EXPECT_EQ(ST_LITERAL("aaaaa"), ss.to_string()); 64 | 65 | ss.append("bbbbbbbbbb", 10); 66 | EXPECT_EQ(15U, ss.size()); 67 | EXPECT_EQ(ST_LITERAL("aaaaabbbbbbbbbb"), ss.to_string()); 68 | 69 | // Automatic length 70 | ST::string_stream ss2; 71 | ss2.append("aaaaa"); 72 | EXPECT_EQ(5U, ss2.size()); 73 | EXPECT_EQ(ST_LITERAL("aaaaa"), ss2.to_string()); 74 | 75 | ss2.append(""); 76 | EXPECT_EQ(5U, ss2.size()); 77 | EXPECT_EQ(ST_LITERAL("aaaaa"), ss2.to_string()); 78 | 79 | ss2.append("bbbbbbbbbb"); 80 | EXPECT_EQ(15U, ss2.size()); 81 | EXPECT_EQ(ST_LITERAL("aaaaabbbbbbbbbb"), ss2.to_string()); 82 | 83 | // Cause a heap allocation 84 | ST::string s1 = ST::string::fill(ST_STACK_STRING_SIZE / 2, 'x'); 85 | ST::string s2 = ST::string::fill(ST_STACK_STRING_SIZE / 2, 'y'); 86 | ST::string s3 = ST_LITERAL("z"); 87 | ST::string_stream ss3; 88 | ss3.append(s1.c_str(), s1.size()); 89 | ss3.append(s2.c_str(), s2.size()); 90 | ss3.append(s3.c_str(), s3.size()); 91 | EXPECT_EQ(s1.size() + s2.size() + s3.size(), ss3.size()); 92 | EXPECT_EQ(s1 + s2 + s3, ss3.to_string()); 93 | 94 | ST::string s4 = ST::string::fill(ST_STACK_STRING_SIZE * 4, 'x'); 95 | ST::string_stream ss4; 96 | ss4.append(s3.c_str(), s3.size()); 97 | ss4.append(s4.c_str(), s4.size()); 98 | EXPECT_EQ(s3.size() + s4.size(), ss4.size()); 99 | EXPECT_EQ(s3 + s4, ss4.to_string()); 100 | } 101 | 102 | TEST(string_stream, append_char) 103 | { 104 | ST::string_stream ss; 105 | ss.append_char('x'); 106 | EXPECT_EQ(1U, ss.size()); 107 | EXPECT_EQ(ST_LITERAL("x"), ss.to_string()); 108 | 109 | ss.append_char('y', 10); 110 | EXPECT_EQ(11U, ss.size()); 111 | EXPECT_EQ(ST_LITERAL("xyyyyyyyyyy"), ss.to_string()); 112 | 113 | // Cause a heap allocation 114 | ST::string_stream ss2; 115 | ss2.append_char('x', ST_STACK_STRING_SIZE / 2); 116 | ss2.append_char('y', ST_STACK_STRING_SIZE / 2); 117 | ss2.append_char('z'); 118 | ST::string s1 = ST::string::fill(ST_STACK_STRING_SIZE / 2, 'x'); 119 | ST::string s2 = ST::string::fill(ST_STACK_STRING_SIZE / 2, 'y'); 120 | ST::string s3 = ST_LITERAL("z"); 121 | EXPECT_EQ(s1.size() + s2.size() + s3.size(), ss2.size()); 122 | EXPECT_EQ(s1 + s2 + s3, ss2.to_string()); 123 | 124 | ST::string_stream ss3; 125 | ss3.append_char('z'); 126 | ss3.append_char('x', ST_STACK_STRING_SIZE * 4); 127 | ST::string s4 = ST::string::fill(ST_STACK_STRING_SIZE * 4, 'x'); 128 | EXPECT_EQ(s3.size() + s4.size(), ss3.size()); 129 | EXPECT_EQ(s3 + s4, ss3.to_string()); 130 | } 131 | 132 | TEST(string_stream, to_string) 133 | { 134 | const char latin1[] = "\x20\x7e\xa0\xff"; 135 | static const char utf8_test_data[] = 136 | "\x20" "\x7f" 137 | "\xc3\xbf" "\xc4\x80" 138 | "\xe7\xbf\xbf" 139 | "\xef\xbf\xbf" "\xf0\x90\x80\x80" 140 | "\xf0\x90\x80\xa0" "\xf1\x80\x80\x80" 141 | "\xf4\x8f\xbf\xbf"; 142 | 143 | ST::string_stream ss; 144 | ss.append(latin1); 145 | EXPECT_EQ(ST::string::from_latin_1(latin1), ss.to_string(false)); 146 | 147 | ST::string_stream ss2; 148 | ss2.append(utf8_test_data); 149 | EXPECT_EQ(ST::string::from_utf8(utf8_test_data), ss2.to_string(true)); 150 | } 151 | 152 | TEST(string_stream, cat_strings) 153 | { 154 | // C strings 155 | ST::string_stream ss; 156 | ss << "aaaaa"; 157 | EXPECT_EQ(5U, ss.size()); 158 | EXPECT_EQ(ST_LITERAL("aaaaa"), ss.to_string()); 159 | 160 | ss << ""; 161 | EXPECT_EQ(5U, ss.size()); 162 | EXPECT_EQ(ST_LITERAL("aaaaa"), ss.to_string()); 163 | 164 | ss << "bbbbbbbbbb"; 165 | EXPECT_EQ(15U, ss.size()); 166 | EXPECT_EQ(ST_LITERAL("aaaaabbbbbbbbbb"), ss.to_string()); 167 | 168 | // ST::string 169 | ST::string_stream ss2; 170 | ss2 << ST_LITERAL("aaaaa"); 171 | EXPECT_EQ(5U, ss2.size()); 172 | EXPECT_EQ(ST_LITERAL("aaaaa"), ss2.to_string()); 173 | 174 | ss2 << ST::string(); 175 | EXPECT_EQ(5U, ss2.size()); 176 | EXPECT_EQ(ST_LITERAL("aaaaa"), ss2.to_string()); 177 | 178 | ss2 << ST_LITERAL("bbbbbbbbbb"); 179 | EXPECT_EQ(15U, ss2.size()); 180 | EXPECT_EQ(ST_LITERAL("aaaaabbbbbbbbbb"), ss2.to_string()); 181 | 182 | // Cause a heap allocation 183 | ST::string s1 = ST::string::fill(ST_STACK_STRING_SIZE / 2, 'x'); 184 | ST::string s2 = ST::string::fill(ST_STACK_STRING_SIZE / 2, 'y'); 185 | ST::string s3 = ST_LITERAL("z"); 186 | ST::string_stream ss3; 187 | ss3 << s1 << s2 << s3; 188 | EXPECT_EQ(s1.size() + s2.size() + s3.size(), ss3.size()); 189 | EXPECT_EQ(s1 + s2 + s3, ss3.to_string()); 190 | 191 | ST::string s4 = ST::string::fill(ST_STACK_STRING_SIZE * 4, 'x'); 192 | ST::string_stream ss4; 193 | ss4 << s3 << s4; 194 | EXPECT_EQ(s3.size() + s4.size(), ss4.size()); 195 | EXPECT_EQ(s3 + s4, ss4.to_string()); 196 | 197 | // Append char 198 | ST::string_stream ss5; 199 | ss5 << s1 << s2 << 'z'; 200 | EXPECT_EQ(s1.size() + s2.size() + 1, ss5.size()); 201 | EXPECT_EQ(s1 + s2 + 'z', ss5.to_string()); 202 | 203 | ST::string_stream ss6; 204 | ss6 << 'z' << s4; 205 | EXPECT_EQ(1 + s4.size(), ss6.size()); 206 | EXPECT_EQ('z' + s4, ss6.to_string()); 207 | } 208 | 209 | TEST(string_stream, cat_ints) 210 | { 211 | EXPECT_EQ(ST_LITERAL("0"), (ST::string_stream() << 0).to_string()); 212 | EXPECT_EQ(ST_LITERAL("-80000"),(ST::string_stream() << -80000).to_string()); 213 | EXPECT_EQ(ST_LITERAL("80000"),(ST::string_stream() << 80000).to_string()); 214 | 215 | EXPECT_EQ(ST_LITERAL("0"), (ST::string_stream() << 0L).to_string()); 216 | EXPECT_EQ(ST_LITERAL("-1000000"), (ST::string_stream() << -1000000L).to_string()); 217 | EXPECT_EQ(ST_LITERAL("1000000"), (ST::string_stream() << 1000000L).to_string()); 218 | 219 | EXPECT_EQ(ST_LITERAL("0"), (ST::string_stream() << 0LL).to_string()); 220 | EXPECT_EQ(ST_LITERAL("-1000000000000"), (ST::string_stream() << -1000000000000LL).to_string()); 221 | EXPECT_EQ(ST_LITERAL("1000000000000"), (ST::string_stream() << 1000000000000LL).to_string()); 222 | 223 | static const int int32_min = std::numeric_limits::min(); 224 | static const int int32_max = std::numeric_limits::max(); 225 | EXPECT_EQ(ST_LITERAL("-2147483648"), (ST::string_stream() << int32_min).to_string()); 226 | EXPECT_EQ(ST_LITERAL("2147483647"), (ST::string_stream() << int32_max).to_string()); 227 | 228 | static const long long int64_min = std::numeric_limits::min(); 229 | static const long long int64_max = std::numeric_limits::max(); 230 | EXPECT_EQ(ST_LITERAL("-9223372036854775808"), (ST::string_stream() << int64_min).to_string()); 231 | EXPECT_EQ(ST_LITERAL("9223372036854775807"), (ST::string_stream() << int64_max).to_string()); 232 | } 233 | 234 | TEST(string_stream, cat_uints) 235 | { 236 | EXPECT_EQ(ST_LITERAL("0"), (ST::string_stream() << 0U).to_string()); 237 | EXPECT_EQ(ST_LITERAL("80000"),(ST::string_stream() << 80000U).to_string()); 238 | 239 | EXPECT_EQ(ST_LITERAL("0"), (ST::string_stream() << 0UL).to_string()); 240 | EXPECT_EQ(ST_LITERAL("1000000"), (ST::string_stream() << 1000000UL).to_string()); 241 | 242 | EXPECT_EQ(ST_LITERAL("0"), (ST::string_stream() << 0ULL).to_string()); 243 | EXPECT_EQ(ST_LITERAL("1000000000000"), (ST::string_stream() << 1000000000000ULL).to_string()); 244 | 245 | static const unsigned int uint32_max = std::numeric_limits::max(); 246 | EXPECT_EQ(ST_LITERAL("4294967295"), (ST::string_stream() << uint32_max).to_string()); 247 | 248 | static const unsigned long long uint64_max = std::numeric_limits::max(); 249 | EXPECT_EQ(ST_LITERAL("18446744073709551615"), (ST::string_stream() << uint64_max).to_string()); 250 | } 251 | 252 | TEST(string_stream, cat_floats) 253 | { 254 | EXPECT_EQ(ST_LITERAL("0"), (ST::string_stream() << 0.0f).to_string()); 255 | EXPECT_EQ(ST_LITERAL("0"), (ST::string_stream() << 0.0).to_string()); 256 | 257 | EXPECT_EQ(ST_LITERAL("-16"), (ST::string_stream() << -16.0f).to_string()); 258 | EXPECT_EQ(ST_LITERAL("16"), (ST::string_stream() << 16.0f).to_string()); 259 | EXPECT_EQ(ST_LITERAL("1.6"), (ST::string_stream() << 1.6f).to_string()); 260 | EXPECT_EQ(ST_LITERAL("16384.5"), (ST::string_stream() << 16384.5f).to_string()); 261 | EXPECT_EQ(ST_LITERAL("0.0078"), (ST::string_stream() << 0.0078f).to_string()); 262 | 263 | EXPECT_EQ(ST_LITERAL("-16"), (ST::string_stream() << -16.0).to_string()); 264 | EXPECT_EQ(ST_LITERAL("16"), (ST::string_stream() << 16.0).to_string()); 265 | EXPECT_EQ(ST_LITERAL("1.6"), (ST::string_stream() << 1.6).to_string()); 266 | EXPECT_EQ(ST_LITERAL("16384.5"), (ST::string_stream() << 16384.5).to_string()); 267 | EXPECT_EQ(ST_LITERAL("0.0078"), (ST::string_stream() << 0.0078).to_string()); 268 | 269 | // Special values (Different CRTs have very different ways of representing 270 | // infinity and NaN textually :( ) 271 | EXPECT_TRUE((ST::string_stream() << std::numeric_limits::infinity()) 272 | .to_string().find("inf", ST::case_insensitive) >= 0); 273 | EXPECT_TRUE((ST::string_stream() << std::numeric_limits::infinity()) 274 | .to_string().find("inf", ST::case_insensitive) >= 0); 275 | EXPECT_TRUE((ST::string_stream() << std::numeric_limits::quiet_NaN()) 276 | .to_string().find("nan", ST::case_insensitive) >= 0); 277 | EXPECT_TRUE((ST::string_stream() << std::numeric_limits::quiet_NaN()) 278 | .to_string().find("nan", ST::case_insensitive) >= 0); 279 | } 280 | 281 | TEST(string_stream, truncate) 282 | { 283 | ST::string_stream ss; 284 | ss << ST_LITERAL("aaaaa"); 285 | ss << ST_LITERAL("bbbbbbbbbb"); 286 | ss.truncate(10); 287 | EXPECT_EQ(10U, ss.size()); 288 | EXPECT_EQ(ST_LITERAL("aaaaabbbbb"), ss.to_string()); 289 | 290 | ss.truncate(); 291 | EXPECT_EQ(0U, ss.size()); 292 | EXPECT_EQ(ST::string(), ss.to_string()); 293 | 294 | ss << ST_LITERAL("ccccc"); 295 | ss << ST_LITERAL("dddddddddd"); 296 | EXPECT_EQ(15U, ss.size()); 297 | EXPECT_EQ(ST_LITERAL("cccccdddddddddd"), ss.to_string()); 298 | 299 | ss.truncate(30); 300 | EXPECT_EQ(15U, ss.size()); 301 | EXPECT_EQ(ST_LITERAL("cccccdddddddddd"), ss.to_string()); 302 | } 303 | 304 | TEST(string_stream, erase) 305 | { 306 | ST::string_stream ss; 307 | ss << ST_LITERAL("aaaaa"); 308 | ss << ST_LITERAL("bbbbbbbbbb"); 309 | ss.erase(5); 310 | EXPECT_EQ(10U, ss.size()); 311 | EXPECT_EQ(ST_LITERAL("aaaaabbbbb"), ss.to_string()); 312 | 313 | ss.erase(10); 314 | EXPECT_EQ(0U, ss.size()); 315 | EXPECT_EQ(ST::string(), ss.to_string()); 316 | 317 | ss << ST_LITERAL("ccccc"); 318 | ss << ST_LITERAL("dddddddddd"); 319 | EXPECT_EQ(15U, ss.size()); 320 | EXPECT_EQ(ST_LITERAL("cccccdddddddddd"), ss.to_string()); 321 | 322 | ss.erase(30); 323 | EXPECT_EQ(0U, ss.size()); 324 | EXPECT_EQ(ST::string(), ss.to_string()); 325 | } 326 | -------------------------------------------------------------------------------- /test/test_stdio.cpp: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2018 Michael Hansen 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a 4 | copy of this software and associated documentation files (the "Software"), 5 | to deal in the Software without restriction, including without limitation 6 | the rights to use, copy, modify, merge, publish, distribute, sublicense, 7 | and/or sell copies of the Software, and to permit persons to whom the 8 | Software is furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 18 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 19 | DEALINGS IN THE SOFTWARE. */ 20 | 21 | #include "st_stdio.h" 22 | 23 | #include 24 | 25 | /* This file does not extensively test formatting, as that is already tested 26 | by test_format.cpp. Instead, this just tests the stdio interfaces provided 27 | in . */ 28 | 29 | #define BUFFER_LEN (256) 30 | 31 | #if defined(_MSC_VER) 32 | # pragma warning(disable: 4996) 33 | #endif 34 | 35 | TEST(stdio, printf) 36 | { 37 | FILE *test_f = fopen("st_test.out", "w"); 38 | ST_ASSERT(test_f, "Could not open output file for test"); 39 | 40 | ST::printf(test_f, "xxxxx"); 41 | ST::printf(test_f, "Testing {>12_#} output", "formatted"); 42 | fclose(test_f); 43 | 44 | test_f = fopen("st_test.out", "r"); 45 | ST_ASSERT(test_f, "Could not open output file for test"); 46 | char buffer[BUFFER_LEN]; 47 | fgets(buffer, BUFFER_LEN, test_f); 48 | buffer[BUFFER_LEN - 1] = 0; 49 | fclose(test_f); 50 | 51 | EXPECT_STREQ(buffer, "xxxxxTesting ###formatted output"); 52 | } 53 | -------------------------------------------------------------------------------- /test/test_winheaders.cpp: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2019 Michael Hansen 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a 4 | copy of this software and associated documentation files (the "Software"), 5 | to deal in the Software without restriction, including without limitation 6 | the rights to use, copy, modify, merge, publish, distribute, sublicense, 7 | and/or sell copies of the Software, and to permit persons to whom the 8 | Software is furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 18 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 19 | DEALINGS IN THE SOFTWARE. */ 20 | 21 | #include 22 | #include "st_string.h" 23 | #include "st_format.h" 24 | #include "st_codecs.h" 25 | #include "st_iostream.h" 26 | #include "st_stdio.h" 27 | 28 | #include 29 | 30 | /* NOTE: This is not a real unit test. It specifically tests the evil 31 | macros that are pulled in by and its subsequent includes 32 | for compatibility with the string_theory installed headers. */ 33 | 34 | TEST(winheaders, dummy) 35 | { 36 | ASSERT_TRUE(true); 37 | } 38 | --------------------------------------------------------------------------------