From fa57de89fefe07b0d3ca012bde7695ad49ab5c62 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sat, 6 Jun 2026 21:34:32 -0400 Subject: [PATCH] git subrepo clone (merge) git@github.com:Rconybea/xo-flatstring xo-flatstring subrepo: subdir: "xo-flatstring" merged: "996c220e" upstream: origin: "git@github.com:Rconybea/xo-flatstring" branch: "main" commit: "996c220e" git-subrepo: version: "0.4.9" origin: "???" commit: "???" --- .../.github/workflows/ubuntu-main.yml | 151 ++++ .../.github/workflows/xo-cpp-main.yml | 145 ++++ xo-flatstring/.gitignore | 6 + xo-flatstring/.gitrepo | 12 + xo-flatstring/CMakeLists.txt | 58 ++ xo-flatstring/LICENSE | 29 + xo-flatstring/README.md | 86 +++ xo-flatstring/cmake/xo-bootstrap-macros.cmake | 41 ++ .../cmake/xo_flatstringConfig.cmake.in | 18 + xo-flatstring/docs/CMakeLists.txt | 5 + xo-flatstring/docs/_static/README | 1 + xo-flatstring/docs/_static/img/favicon.ico | Bin 0 -> 309803 bytes xo-flatstring/docs/_static/img/icon.svg | 77 ++ xo-flatstring/docs/_static/img/xo-icon.svg | 77 ++ xo-flatstring/docs/conf.py | 36 + xo-flatstring/docs/flatstring-class.rst | 65 ++ xo-flatstring/docs/flatstring-functions.rst | 16 + xo-flatstring/docs/flatstring-reference.rst | 11 + xo-flatstring/docs/index.rst | 38 + xo-flatstring/docs/install.rst | 86 +++ xo-flatstring/docs/lessons.rst | 98 +++ xo-flatstring/example/CMakeLists.txt | 1 + xo-flatstring/example/ex1/CMakeLists.txt | 12 + xo-flatstring/example/ex1/ex1.cpp | 171 +++++ .../include/xo/flatstring/flatstring.hpp | 656 ++++++++++++++++++ .../xo/flatstring/flatstring_iostream.hpp | 37 + .../xo/flatstring/flatstring_pretty.hpp | 28 + .../include/xo/flatstring/int128_iostream.hpp | 31 + .../xo/flatstring/string_view_concat.hpp | 30 + xo-flatstring/utest/CMakeLists.txt | 56 ++ xo-flatstring/utest/flatstring.test.cpp | 445 ++++++++++++ xo-flatstring/utest/flatstring_utest_main.cpp | 6 + 32 files changed, 2529 insertions(+) create mode 100644 xo-flatstring/.github/workflows/ubuntu-main.yml create mode 100644 xo-flatstring/.github/workflows/xo-cpp-main.yml create mode 100644 xo-flatstring/.gitignore create mode 100644 xo-flatstring/.gitrepo create mode 100644 xo-flatstring/CMakeLists.txt create mode 100644 xo-flatstring/LICENSE create mode 100644 xo-flatstring/README.md create mode 100644 xo-flatstring/cmake/xo-bootstrap-macros.cmake create mode 100644 xo-flatstring/cmake/xo_flatstringConfig.cmake.in create mode 100644 xo-flatstring/docs/CMakeLists.txt create mode 100644 xo-flatstring/docs/_static/README create mode 100644 xo-flatstring/docs/_static/img/favicon.ico create mode 100644 xo-flatstring/docs/_static/img/icon.svg create mode 100644 xo-flatstring/docs/_static/img/xo-icon.svg create mode 100644 xo-flatstring/docs/conf.py create mode 100644 xo-flatstring/docs/flatstring-class.rst create mode 100644 xo-flatstring/docs/flatstring-functions.rst create mode 100644 xo-flatstring/docs/flatstring-reference.rst create mode 100644 xo-flatstring/docs/index.rst create mode 100644 xo-flatstring/docs/install.rst create mode 100644 xo-flatstring/docs/lessons.rst create mode 100644 xo-flatstring/example/CMakeLists.txt create mode 100644 xo-flatstring/example/ex1/CMakeLists.txt create mode 100644 xo-flatstring/example/ex1/ex1.cpp create mode 100644 xo-flatstring/include/xo/flatstring/flatstring.hpp create mode 100644 xo-flatstring/include/xo/flatstring/flatstring_iostream.hpp create mode 100644 xo-flatstring/include/xo/flatstring/flatstring_pretty.hpp create mode 100644 xo-flatstring/include/xo/flatstring/int128_iostream.hpp create mode 100644 xo-flatstring/include/xo/flatstring/string_view_concat.hpp create mode 100644 xo-flatstring/utest/CMakeLists.txt create mode 100644 xo-flatstring/utest/flatstring.test.cpp create mode 100644 xo-flatstring/utest/flatstring_utest_main.cpp diff --git a/xo-flatstring/.github/workflows/ubuntu-main.yml b/xo-flatstring/.github/workflows/ubuntu-main.yml new file mode 100644 index 00000000..dc647f52 --- /dev/null +++ b/xo-flatstring/.github/workflows/ubuntu-main.yml @@ -0,0 +1,151 @@ +name: build xo-flatstring + dependencies + +on: + push: + branches: [ "main" ] + pull_request: + branches: [ "main" ] + +env: + # Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.) + BUILD_TYPE: Release + +jobs: + build: + # The CMake configure and build commands are platform agnostic and should work equally well on Windows or Mac. + # You can convert this to a matrix build if you need cross-platform coverage. + # See: https://docs.github.com/en/free-pro-team@latest/actions/learn-github-actions/managing-complex-workflows#using-a-build-matrix + runs-on: ubuntu-latest + + steps: + - name: checkout source + uses: actions/checkout@v3 + + - name: Install dependencies + run: | + # install catch2, doxygen. see + # [[https://stackoverflow.com/questions/57982945/how-to-apt-get-install-in-a-github-actions-workflow]] + + echo "::group::install catch2" + sudo apt-get install -y catch2 + echo "::endgroup" + + echo "::group::install doxygen" + sudo apt-get install -y doxygen + echo "::endgroup" + + echo "::group::install sphinx" + sudo apt-get install -y python3-sphinx + echo "::endgroup" + + echo "::group::install sphinx readthedocs theme" + sudo apt-get install -y python3-sphinx-rtd-theme + echo "::endgroup" + + #echo "::group::install pybind11" + #sudo apt-get install -y pybind11-dev + #echo "::endgroup" + + # ---------------------------------------------------------------- + + - name: clone xo-cmake + uses: actions/checkout@v3 + with: + repository: Rconybea/xo-cmake + path: repo/xo-cmake + + - name: build xo-cmake + run: | + XONAME=xo-cmake + XOSRC=repo/${XONAME} + BUILDDIR=${{github.workspace}}/build_${XONAME} + PREFIX=${{github.workspace}}/local + + echo "::group::configure ${XONAME}" + cmake -B ${BUILDDIR} -DCMAKE_INSTALL_PREFIX=${PREFIX} ${XOSRC} + echo "::endgroup" + + echo "::group::compile ${XONAME}" + cmake --build ${BUILDDIR} --config ${{env.BUILD_TYPE}} + echo "::endgroup" + + echo "::group::local install ${XONAME}" + cmake --install ${BUILDDIR} + echo "::endgroup" + + echo "::group::local dir tree" + tree ${PREFIX} + echo "::endgroup" + + # ---------------------------------------------------------------- + + - name: clone xo-indentlog + uses: actions/checkout@v3 + with: + repository: Rconybea/indentlog + path: repo/xo-indentlog + + - name: build xo-indentlog + run: | + XONAME=xo-indentlog + XOSRC=repo/${XONAME} + BUILDDIR=${{github.workspace}}/build_${XONAME} + PREFIX=${{github.workspace}}/local + + echo "::group::repo dir tree" + tree -L 2 repo + echo "::endgroup" + + echo "::group::configure ${XONAME}" + cmake -B ${BUILDDIR} -DCMAKE_INSTALL_PREFIX=${PREFIX} ${XOSRC} + echo "::endgroup" + + echo "::group::compile ${XONAME}" + cmake --build ${BUILDDIR} --config ${{env.BUILD_TYPE}} -j + echo "::endgroup" + + echo "::group::local install ${XONAME}" + cmake --install ${BUILDDIR} + echo "::endgroup" + + echo "::group::local dir tree" + tree -L 3 ${PREFIX} + echo "::endgroup" + + # ---------------------------------------------------------------- + + - name: build self (xo-flatstring) + # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make. + # See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type + run: | + XONAME=xo-flatstring + BUILDDIR=${{github.workspace}}/build_${XONAME} + PREFIX=${{github.workspace}}/local + + echo "::group::repo dir tree" + tree -L 2 repo + echo "::endgroup" + + echo "::group::configure ${XONAME}" + cmake -B ${BUILDDIR} -DCMAKE_MODULE_PATH=${PREFIX}/share/cmake -DCMAKE_PREFIX_PATH=${PREFIX} -DCMAKE_INSTALL_PREFIX=${PREFIX} -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} + echo "::endgroup" + + echo "::group::compile ${XONAME}" + cmake --build ${BUILDDIR} --config ${{env.BUILD_TYPE}} + echo "::endgroup" + + echo "::group::run unit tests ${XONAME}" + cmake --build ${BUILDDIR} -- test + echo "::endgroup" + + echo "::group::local install ${XONAME}" + cmake --install ${BUILDDIR} + echo "::endgroup" + + echo "::group::local dir tree" + tree -L 3 ${PREFIX} + echo "::endgroup" + + # Execute tests defined by the CMake configuration. + # See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail + (cd ${BUILDDIR} && ctest -C ${{env.BUILD_TYPE}}) diff --git a/xo-flatstring/.github/workflows/xo-cpp-main.yml b/xo-flatstring/.github/workflows/xo-cpp-main.yml new file mode 100644 index 00000000..91aa5523 --- /dev/null +++ b/xo-flatstring/.github/workflows/xo-cpp-main.yml @@ -0,0 +1,145 @@ +name: XO flatstring builder + +on: + push: + branches: [ "main" ] + pull_request: + branches: [ "main" ] + +env: + # Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.) + BUILD_TYPE: Release + +jobs: + build: + # The CMake configure and build commands are platform agnostic and should work equally well on Windows or Mac. + # You can convert this to a matrix build if you need cross-platform coverage. + # See: https://docs.github.com/en/free-pro-team@latest/actions/learn-github-actions/managing-complex-workflows#using-a-build-matrix + runs-on: ubuntu-latest + + container: + # custom docker image. see github.com:rconybea/docker-xo-builder for definition + image: ghcr.io/rconybea/docker-xo-builder:v1 + + steps: + - name: xo-cmake + run: | + # treat github.com as known host to prevent shtoopid SSL errors + mkdir -p ~/.ssh + ssh-keyscan -t rsa github.com >> ~/.ssh/known_hosts + cat ~/.ssh/known_hosts + + git config --global http.sslVerify false + + XO_NAME=xo-cmake + XO_SRC=repo/${XO_NAME} + XO_BUILDDIR=${{github.workspace}}/build_${XO_NAME} + PREFIX=${{github.workspace}}/local + + XO_REPO=https://github.com/rconybea/xo-cmake.git + + mkdir -p ${XO_SRC} + mkdir -p ${XO_BUILDDIR} + + echo "::group::clone ${XO_NAME}" + export GIT_SSH_COMMAND='ssh -o StrictHostKeyChecking=no' + export GIT_SSL_NOVERIFY=true + git clone ${XO_REPO} ${XO_SRC} + echo "::endgroup" + + echo "::group::configure ${XO_NAME}" + cmake -B ${XO_BUILDDIR} -DCMAKE_INSTALL_PREFIX=${PREFIX} -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} ${XO_SRC} + echo "::endgroup" + + echo "::group::compile ${XO_NAME}" + cmake --build ${XO_BUILDDIR} -j + echo "::endgroup" + + echo "::group::local install ${XO_NAME}" + cmake --install ${XO_BUILDDIR} + echo "::endgroup" + + echo "::group::local dir tree" + tree ${PREFIX} + echo "::endgroup" + + # ---------------------------------------------------------------- + + - name: xo-indentlog + run: | + XO_NAME=xo-indentlog + XO_SRC=repo/${XO_NAME} + XO_BUILDDIR=${{github.workspace}}/build_${XO_NAME} + PREFIX=${{github.workspace}}/local + + XO_REPO=https://github.com/rconybea/indentlog.git + + mkdir -p ${XO_SRC} + mkdir -p ${XO_BUILDDIR} + + echo "::group::clone ${XO_NAME}" + export GIT_SSH_COMMAND='ssh -o StrictHostKeyChecking=no' + export GIT_SSL_NOVERIFY=true + git clone ${XO_REPO} ${XO_SRC} + echo "::endgroup" + + echo "::group::configure ${XO_NAME}" + cmake -B ${XO_BUILDDIR} -DCMAKE_INSTALL_PREFIX=${PREFIX} -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} ${XO_SRC} + echo "::endgroup" + + echo "::group::compile ${XO_NAME}" + cmake --build ${XO_BUILDDIR} -j + echo "::endgroup" + + echo "::group::local install ${XO_NAME}" + cmake --install ${XO_BUILDDIR} + echo "::endgroup" + + echo "::group::local dir tree" + tree ${PREFIX} + echo "::endgroup" + + # ---------------------------------------------------------------- + + - name: xo-flatstring + run: | + XO_NAME=xo-flatstring + XO_SRC=repo/${XO_NAME} + XO_BUILDDIR=${{github.workspace}}/build_${XO_NAME} + PREFIX=${{github.workspace}}/local + + XO_REPO=https://github.com/rconybea/${XO_NAME}.git + + mkdir -p ${XO_SRC} + mkdir -p ${XO_BUILDDIR} + + echo "::group::clone ${XO_NAME}" + export GIT_SSH_COMMAND='ssh -o StrictHostKeyChecking=no' + export GIT_SSL_NOVERIFY=true + git clone ${XO_REPO} ${XO_SRC} + echo "::endgroup" + + echo "::group::configure ${XO_NAME}" + cmake -B ${XO_BUILDDIR} -DCMAKE_INSTALL_PREFIX=${PREFIX} -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} ${XO_SRC} + echo "::endgroup" + + echo "::group::compile ${XO_NAME}" + cmake --build ${XO_BUILDDIR} -j + echo "::endgroup" + + echo "::group::test ${XO_NAME}" + cmake --build ${XO_BUILDDIR} -- test + echo "::endgroup" + + echo "::group::build ${XO_NAME} docs" + cmake --build ${XO_BUILDDIR} -- doxygen + # note: sphinx+breathe not working (cause unknown) from docker-xo-builder, asof 25apr2024. + echo "::endgroup" + + echo "::group::local install ${XO_NAME}" + cmake --install ${XO_BUILDDIR} + echo "::endgroup" + + echo "::group::local dir tree" + tree ${PREFIX} + echo "::endgroup" diff --git a/xo-flatstring/.gitignore b/xo-flatstring/.gitignore new file mode 100644 index 00000000..13c0afb7 --- /dev/null +++ b/xo-flatstring/.gitignore @@ -0,0 +1,6 @@ +# clangd working space (see emacs+lsp) +.cache +# typical cmake build directory (source-tree-nephew) +.build* +# symlink to builddir/compile_commands.json; should be set manually in dev sandbox +compile_commands.json diff --git a/xo-flatstring/.gitrepo b/xo-flatstring/.gitrepo new file mode 100644 index 00000000..8982541a --- /dev/null +++ b/xo-flatstring/.gitrepo @@ -0,0 +1,12 @@ +; DO NOT EDIT (unless you know what you are doing) +; +; This subdirectory is a git "subrepo", and this file is maintained by the +; git-subrepo command. See https://github.com/ingydotnet/git-subrepo#readme +; +[subrepo] + remote = git@github.com:Rconybea/xo-flatstring + branch = main + commit = 996c220eee7f6341e2a5432edb25c352a3bb6b2a + parent = b2490dedd9c084aee187ae87574fa96679d91b38 + method = merge + cmdver = 0.4.9 diff --git a/xo-flatstring/CMakeLists.txt b/xo-flatstring/CMakeLists.txt new file mode 100644 index 00000000..cd561e87 --- /dev/null +++ b/xo-flatstring/CMakeLists.txt @@ -0,0 +1,58 @@ +# xo-flatstring/CMakeLists.txt + +cmake_minimum_required(VERSION 3.25) + +project(xo_flatstring VERSION 1.0) + +include(GNUInstallDirs) +include(cmake/xo-bootstrap-macros.cmake) + +xo_cxx_toplevel_options2() + +# ---------------------------------------------------------------- +# cmake -DCMAKE_BUILD_TYPE=coverage +xo_toplevel_coverage_config2() + +# ---------------------------------------------------------------- +# cmake -DCMAKE_BUILD_TYPE=debug +xo_toplevel_debug_config2() + +# ---------------------------------------------------------------- +# c++ settings + +# one-time project-specific c++ flags. usually empty +set(PROJECT_CXX_FLAGS "") +#set(PROJECT_CXX_FLAGS "-fconcepts-diagnostics-depth=2") +add_definitions(${PROJECT_CXX_FLAGS}) + +# ---------------------------------------------------------------- + +add_subdirectory(example) +add_subdirectory(utest) + +# ---------------------------------------------------------------- + +set(SELF_LIB xo_flatstring) +xo_add_headeronly_library(${SELF_LIB}) +xo_install_library4(${SELF_LIB} ${PROJECT_NAME}Targets) +# provide find_package() support for projects using this library +xo_export_cmake_config(${PROJECT_NAME} ${PROJECT_VERSION} ${PROJECT_NAME}Targets) + +# ---------------------------------------------------------------- + +if (XO_ENABLE_EXAMPLES) + install(TARGETS flatstring_ex1) +endif() + +# ---------------------------------------------------------------- +# docs targets depend on all the other library/utest targets +# +add_subdirectory(docs) + +# ---------------------------------------------------------------- +# dependencies + +#xo_headeronly_dependency(${SELF_LIB} randomgen) +# etc.. + +# end CMakeLists.txt diff --git a/xo-flatstring/LICENSE b/xo-flatstring/LICENSE new file mode 100644 index 00000000..cae3cb5d --- /dev/null +++ b/xo-flatstring/LICENSE @@ -0,0 +1,29 @@ +Copyright (c) 2024 Roland Conybeare , All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +Please also refer to the file .github/CONTRIBUTING.md, which clarifies licensing of +external contributions to this project including patches, pull requests, etc. diff --git a/xo-flatstring/README.md b/xo-flatstring/README.md new file mode 100644 index 00000000..20bc5165 --- /dev/null +++ b/xo-flatstring/README.md @@ -0,0 +1,86 @@ +# flatstring library + +Fixed-length no-allocation string implementation. + +Features: +- char array representation with maximum size set at compile time. +- compile time construction from char array and string concatenation +- pointer-free implementation, instances can be used as template arguments +- To the extent practical, provides the same api as `std::string`: includes iterators, + access methods, assignment, conversion operators. + +Limitations: +- requires c++20 +- not resizable. +- does not support wide characters. +- (asof April 2024) missing features: `insert`, `erase`, `push_back`, `append`, `replace`, + `find`, `compare`, `starts_with`, `ends_with`, `contains`, `substr`. + +## Documentation + +- xo-flatstring documentation here: [documentation](https://rconybea.github.io/web/xo-flatstring/html/index.html) +- unit test coverage here: [coverage](https://rconybea.github.io/web/xo-flatstring/ccov/html/index.html) + +## Getting started + +### Install dependencies + +- [github/Rconybea/xo-cmake](https://github.com/Rconybea/xo-cmake) cmake macros +- [github/Rconybea/xo-indentlog](https://github.com/Rconybea/indentlog) logging (used by unit tests) + +### Clone xo-flatstring + +``` +$ cd ~/proj # for example +$ git clone https://github.com/rconybea/xo-flatstring +``` + +### build + install +``` +$ cd xo-flatstring +$ mkdir .build +$ PREFIX=/usr/local # for example +$ cmake -DCMAKE_INSTALL_PREFIX=${PREFIX} -B .build +$ cmake --build .build +$ cmake --install .build +``` + +### build documentation +``` +$ cd xo-flatstring +$ cmake --build .build -- docs +``` + +Must invoke `docs` target explicitly to prepare documentation +When complete, point local browser to `xo-flatstring/.build/docs/sphinx/index.html` + +### build with test coverage +``` +$ cd xo-flatstring +$ mkdir .build-ccov +$ cmake -DCMAKE_INSTALL_PREFIX=$PREFIX -DCMAKE_BUILD_TYPE=coverage -B .build-ccov +$ cmake --build .build-ccov +``` + +run coverage-enabled unit tests +``` +$ cmake --build .build-ccov -- test +``` + +generate html+text coverage report +``` +$ cmake --build .build-ccov -- ccov +``` + +For html test coverage browse to `.build-ccov/ccov/html/index.html` + +install documentation only, along with test-coverage report +``` +$ cmake --install .build-ccov --component Documentation +``` + +### LSP support +``` +$ cd xo-flatstring +$ ln -s .build/compile_commands.json +``` diff --git a/xo-flatstring/cmake/xo-bootstrap-macros.cmake b/xo-flatstring/cmake/xo-bootstrap-macros.cmake new file mode 100644 index 00000000..592272c0 --- /dev/null +++ b/xo-flatstring/cmake/xo-bootstrap-macros.cmake @@ -0,0 +1,41 @@ +# ---------------------------------------------------------------- +# for example: +# $ PREFIX=/usr/local # for example +# $ cmake -DCMAKE_MODULE_PATH=prefix -DCMAKE_INSTALL_PREFIX=$PREFIX -B .build +# +# will get +# CMAKE_MODULE_PATH +# from xo-cmake-config --cmake-module-path +# +# and expect .cmake macros in +# CMAKE_MODULE_PATH/xo_macros/xo_cxx.cmake +# ---------------------------------------------------------------- + +find_program(XO_CMAKE_CONFIG_EXECUTABLE NAMES xo-cmake-config REQUIRED) + +if ("${XO_CMAKE_CONFIG_EXECUTABLE}" STREQUAL "XO_CMAKE_CONFIG_EXECUTABLE-NOT_FOUND") + message(FATAL "could not find xo-cmake-config executable") +endif() + +message(STATUS "XO_CMAKE_CONFIG_EXECUTABLE=${XO_CMAKE_CONFIG_EXECUTABLE}") + +if (XO_SUBMODULE_BUILD) + if (("${CMAKE_MODULE_PATH}" STREQUAL "") OR ("${CMAKE_MODULE_PATH}" STREQUAL prefix)) + # local version of xo-cmake macros + set(CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/xo-cmake/cmake") + message(STATUS "CMAKE_MODULE_PATH=${CMAKE_MODULE_PATH}") + endif() +else() + if (("${CMAKE_MODULE_PATH}" STREQUAL "") OR ("${CMAKE_MODULE_PATH}" STREQUAL prefix)) + # default to typical install location for xo-project-macros + execute_process(COMMAND ${XO_CMAKE_CONFIG_EXECUTABLE} --cmake-module-path OUTPUT_VARIABLE CMAKE_MODULE_PATH) + message(STATUS "CMAKE_MODULE_PATH=${CMAKE_MODULE_PATH}") + endif() +endif() + +# needs to have been installed somewhere on CMAKE_MODULE_PATH, +# (e.g. from xo-cmake with the same value for CMAKE_INSTALL_PREFIX) +# +include(xo_macros/xo_cxx) + +xo_cxx_bootstrap_message() diff --git a/xo-flatstring/cmake/xo_flatstringConfig.cmake.in b/xo-flatstring/cmake/xo_flatstringConfig.cmake.in new file mode 100644 index 00000000..c6b988e1 --- /dev/null +++ b/xo-flatstring/cmake/xo_flatstringConfig.cmake.in @@ -0,0 +1,18 @@ +@PACKAGE_INIT@ + +include(CMakeFindDependencyMacro) + +# note: changes to find_dependency() calls here +# must coordinate with xo_dependency() calls +# in xo-reactor/src/reactor/CMakeLists.txt +# +#find_dependency(reflect) +#find_dependency(subsys) +#find_dependency(Eigen3) +#find_dependency(webutil) +#find_dependency(printjson) +#find_dependency(callback) + +include("${CMAKE_CURRENT_LIST_DIR}/@PROJECT_NAME@Targets.cmake") +include("${CMAKE_CURRENT_LIST_DIR}/@PROJECT_NAME@Share.cmake") +check_required_components("@PROJECT_NAME@") diff --git a/xo-flatstring/docs/CMakeLists.txt b/xo-flatstring/docs/CMakeLists.txt new file mode 100644 index 00000000..936d662d --- /dev/null +++ b/xo-flatstring/docs/CMakeLists.txt @@ -0,0 +1,5 @@ +# xo-flatstring/docs/CMakeLists.txt + +xo_doxygen_collect_deps() +xo_docdir_doxygen_config() +xo_docdir_sphinx_config(index.rst install.rst lessons.rst flatstring-reference.rst flatstring-class.rst) diff --git a/xo-flatstring/docs/_static/README b/xo-flatstring/docs/_static/README new file mode 100644 index 00000000..8230095c --- /dev/null +++ b/xo-flatstring/docs/_static/README @@ -0,0 +1 @@ +add any static {.html, .js, ..} files for sphinx to pickup here \ No newline at end of file diff --git a/xo-flatstring/docs/_static/img/favicon.ico b/xo-flatstring/docs/_static/img/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..15da2145f93eb26e6995c7868ab7883fb2361288 GIT binary patch literal 309803 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 + + + + + + + + + + + + + + diff --git a/xo-flatstring/docs/_static/img/xo-icon.svg b/xo-flatstring/docs/_static/img/xo-icon.svg new file mode 100644 index 00000000..aec7e2e1 --- /dev/null +++ b/xo-flatstring/docs/_static/img/xo-icon.svg @@ -0,0 +1,77 @@ + + + + + + + + + + + + + + + diff --git a/xo-flatstring/docs/conf.py b/xo-flatstring/docs/conf.py new file mode 100644 index 00000000..0860b3d7 --- /dev/null +++ b/xo-flatstring/docs/conf.py @@ -0,0 +1,36 @@ +# Configuration file for the Sphinx documentation builder. +# +# For the full list of built-in configuration values, see the documentation: +# https://www.sphinx-doc.org/en/master/usage/configuration.html + +# -- Project information ----------------------------------------------------- +# https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information + +project = 'xo flatstring documentation' +copyright = '2024, Roland Conybeare' +author = 'Roland Conybeare' + +# -- General configuration --------------------------------------------------- +# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration + +#extensions = [] +extensions = [ "breathe", + "sphinx.ext.autodoc" # generate info from docstrings + ] + +# note: breathe requires doxygen xml output -> must have GENERATE_XML = YES in Doxyfile.in +# match project name in Doxyfile.in +breathe_default_project = "xodoxxml" + +templates_path = ['_templates'] +exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] + +pygments_style = 'sphinx' + +# -- Options for HTML output ------------------------------------------------- +# https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output + +#html_theme = 'alabaster' +html_theme = 'sphinx_rtd_theme' +html_static_path = ['_static'] +html_favicon = '_static/img/favicon.ico' diff --git a/xo-flatstring/docs/flatstring-class.rst b/xo-flatstring/docs/flatstring-class.rst new file mode 100644 index 00000000..3e34cd4c --- /dev/null +++ b/xo-flatstring/docs/flatstring-class.rst @@ -0,0 +1,65 @@ +.. _flatstring-class: + +Flatstring +========== + +.. code-block:: cpp + + #include + +A ``flatstring`` is a thin wrapper around a character array. + +.. doxygenclass:: xo::flatstring + +Instance Variables +------------------ + +.. doxygengroup:: flatstring-instance-variables + :content-only: + +Types +----- + +.. doxygengroup:: flatstring-types + +Constants +--------- + +.. doxygengroup:: flatstring-constants + :content-only: + +Constructors +------------ + +.. doxygengroup:: flatstring-ctor + :content-only: + +Properties +---------- + +.. doxygengroup:: flatstring-properties + :content-only: + +Access Methods +-------------- + +.. doxygengroup:: flatstring-access + :content-only: + +Iterators +--------- + +.. doxygengroup:: flatstring-iterators + :content-only: + +Assignment +---------- + +.. doxygengroup:: flatstring-assign + :content-only: + +Conversion +---------- + +.. doxygengroup:: flatstring-conversion-operators + :content-only: diff --git a/xo-flatstring/docs/flatstring-functions.rst b/xo-flatstring/docs/flatstring-functions.rst new file mode 100644 index 00000000..9106a2b5 --- /dev/null +++ b/xo-flatstring/docs/flatstring-functions.rst @@ -0,0 +1,16 @@ +.. _flatstring_functions: + +.. toctree:: + :maxdepth: 2 + +Flatstring Functions +==================== + +.. code-block:: cpp + + #include + +.. doxygenfunction:: xo::flatstring_concat + +.. doxygengroup:: flatstring-3way-compare + :content-only: diff --git a/xo-flatstring/docs/flatstring-reference.rst b/xo-flatstring/docs/flatstring-reference.rst new file mode 100644 index 00000000..baf8f75e --- /dev/null +++ b/xo-flatstring/docs/flatstring-reference.rst @@ -0,0 +1,11 @@ +.. _flatstring-reference: + +Flatstring Reference +==================== + +.. toctree:: + :maxdepth: 2 + :caption: Flatstring Reference + + flatstring-class + flatstring-functions diff --git a/xo-flatstring/docs/index.rst b/xo-flatstring/docs/index.rst new file mode 100644 index 00000000..430233d8 --- /dev/null +++ b/xo-flatstring/docs/index.rst @@ -0,0 +1,38 @@ +xo-flatstring documentation master file + +xo-flatstring documentation +=========================== + +xo-flatstring is a lightweight header-only library that provides a constexpr +fixed-size no-allocation string implementation. + +Why ``flatstring``? + +1. ``flatstring`` instances can be used as template arguments. [1]_ + +2. ``flatstring`` operations (construction, concatenation, ...) are ``constexpr``, so can be done at compile time. [2]_ + +3. a ``flatstring`` expression can occupy both compile-time and runtime roles. [3]_ + +.. [1] A fixed-size char array *can* be used as a template + argument, but char* pointers cannot. Automatic conversion of char arrays to pointers in various contexts + makes them difficult to work with in c++ templates. + +.. [2] Although allocation is permitted in constexpr code, it's subject to several restrictions. + it's not yet possible (as of c++23) to use ``std::string`` at compile time. + +.. [3] contrast with a solution relying on template arguments, which must then be compile-time-only. + +.. toctree:: + :maxdepth: 2 + :caption: xo-flatstring contents: + + install + lessons + flatstring-reference + +Indices and Tables +------------------ + +* :ref:`genindex` +* :ref:`search` diff --git a/xo-flatstring/docs/install.rst b/xo-flatstring/docs/install.rst new file mode 100644 index 00000000..be0fd537 --- /dev/null +++ b/xo-flatstring/docs/install.rst @@ -0,0 +1,86 @@ +.. _install: + +.. toctree + :maxdepth: 2 + +Source +====== + +Source code lives on github `here`_ + +.. _here: https://github.com/rconybea/xo-flatstring + +To clone from git: + +.. code-block:: bash + + git clone https://github.com/rconybea/xo-flatstring + +Implementation relies on c++20 features (expanded use of constexpr; class-instances as template arguments). +Tested with gcc 13.2 + +Install +======= + +Since xo-flatstring is header-only, can incorporate into another project just by copying the include directories +to somewhere convenient. + +Copy includes +------------- + +.. code-block:: bash + + # For example.. + cd myproject + mkdir -p ext/xo-flatstring + rsync -a -v path/to/xo-flatstring/include/ ext/xo-flatstring/ + +Include as git submodule +------------------------ + +.. code-block:: bash + + cd myproject + git submodule add -b main https://github.com/rconybea/xo-flatstring ext/xo-flatstring + git submodule update --init ext/xo-flatstring + +This assumes you organize directly-incorporated dependencies under directory ``myproject/ext``. +You would then add ``myproject/ext/xo-flatstring/include`` to your compiler's include path, +and from c++ do something like + +.. code-block:: c++ + + #include + +in c++ source files that rely on xo-flatstring + +Building from source +-------------------- + +Although the xo-flatstring library is header-only, unit tests have some dependencies. +Example instructions (github CI) for build starting from stock ubuntu are in `ubuntu-main.yml`_ + +.. _ubuntu-main.yml: https://github.com/Rconybea/xo-flatstring/blob/main/.github/workflows/ubuntu-main.yml + +Unit test dependencies: + +* `catch2`_ header-only unit-test framework +* `xo-cmake`_ cmake macros +* `xo-indentlog`_ logging with call-structure indenting + +.. _catch2: https://github.com/catchorg/Catch2 +.. _xo-cmake: https://github.com/rconybea/xo-cmake +.. _xo-indentlog: https://github.com/rconybea/indentlog + +To build documentation, will also need: + +* `doxygen` +* `graphviz` +* `sphinx` +* `breathe` +* `sphinx_rtd_theme` + +Supported compilers +------------------- + +* developed with gcc 13.2.0; github CI using gcc 11.4.0 (asof April 2024) diff --git a/xo-flatstring/docs/lessons.rst b/xo-flatstring/docs/lessons.rst new file mode 100644 index 00000000..a5459c6b --- /dev/null +++ b/xo-flatstring/docs/lessons.rst @@ -0,0 +1,98 @@ +.. _lessons: + +.. toctree + :maxdepth: 2 + +Lessons +======= + +This is a rogue's gallery of experiments, typically unsuccessful. +One hurdle we've created for ourselves, is we need both gcc and clang to agree +that an expression can be computed at compile-time; +otherwise will get false alarms in our IDE (raised by LSP running in the background, which relies on clang). + +Must fully initialize memory +---------------------------- + +Struggled for a while with the implementation of :ref:xo::flatstring_concat + +.. code-block:: cpp + + template + flatstring::flatstring() { + if (N > 0) + value_[0] = '\0'; + } + + +This implementation satisfies gcc, but not clang: in the following snippet, clang doesn't recognize ``tmp`` as constexpr: + +.. code-block:: cpp + + constexpr n = ...; + flatstring tmp; + + static_assert(tmp.size() == ...); // tmp not constexpr! + +Correction is to prove to clang that every memory address owned by an empty ``flatstring`` is initialized: + +.. code-block:: cpp + + template + flatstring::flatstring() { + std::fill_n(value_, N, '\0'); + } + +Still need equality comparison alongside spaceship operator +----------------------------------------------------------- + +Had the impression that spaceship operator for :ref:xo::flatstring would be sufficient +to get all six comparison operators: + +.. code-block:: cpp + + template + constexpr auto + operator<=>(const flatstring & s1, + const flatstring & s2) noexcept + { + return (std::string_view(s1) <=> std::string_view(s2)); + } + +We observe this is not the case, at least with gcc 13.1; need to separately define :ref:xo::operator== + +.. code-block:: cpp + + template + constexpr bool + operator==(const flatstring & s1, + const flatstring & s2) noexcept + { + return ((s1 <=> s2) == std::strong_ordering::equal); + } + +Constexpr strict about pointer arithmetic +----------------------------------------- + +Initially attempted to implement :ref:xo::flatstring reverse iterators using char pointers. + +Notice there's an assymetry between reverse iterators and forward iterators. +We can (and do) implement forward iterators using char pointers. +The natural value of ``flatstring::end()`` is a char pointer referring to just past the end of +the string, i.e. to its null terminator. From the compiler's perspective, this is an ordinary +char pointer, just like other iterator values. + +For reverse iterators this isn't the case. The natural value for ``flatstring::rend()`` might +seem to be a char pointer referring to just before the first character in the string. +However this is no longer a valid pointer address -- dereferencing would be undefined behavior. + +In particular, with this implementation, gcc demotes ``flatstring::rend()`` to non-constexpr + +Workaround is to implement a shim iterator class, where representation is pointer to the +character just after the one the iterator position; iterator's ``operator*`` adjusts pointer before +dereferencing. + +This works because gcc can observe that we never dereference a reverse iterator with pointer value +at the beginning of a flatstring. diff --git a/xo-flatstring/example/CMakeLists.txt b/xo-flatstring/example/CMakeLists.txt new file mode 100644 index 00000000..4151ec21 --- /dev/null +++ b/xo-flatstring/example/CMakeLists.txt @@ -0,0 +1 @@ +add_subdirectory(ex1) diff --git a/xo-flatstring/example/ex1/CMakeLists.txt b/xo-flatstring/example/ex1/CMakeLists.txt new file mode 100644 index 00000000..a16109d1 --- /dev/null +++ b/xo-flatstring/example/ex1/CMakeLists.txt @@ -0,0 +1,12 @@ +# xo-flatstring/example/ex1/CMakeLists.txt + +set(SELF_EXE flatstring_ex1) +set(SELF_SRCS ex1.cpp) + +if (XO_ENABLE_EXAMPLES) + add_executable(${SELF_EXE} ${SELF_SRCS}) + xo_include_options2(${SELF_EXE}) + xo_self_headeronly_dependency(${SELF_EXE} xo_flatstring) +endif() + +# end CMakeLists.txt diff --git a/xo-flatstring/example/ex1/ex1.cpp b/xo-flatstring/example/ex1/ex1.cpp new file mode 100644 index 00000000..7f46f432 --- /dev/null +++ b/xo-flatstring/example/ex1/ex1.cpp @@ -0,0 +1,171 @@ +/* @file ex1.cpp */ + +#include "xo/flatstring/flatstring.hpp" +//#include "xo/stringliteral/stringliteral_iostream.hpp" +//#include "xo/flatstring/experiment.hpp" +//#include "xo/stringliteral/string_view_concat.hpp" +#include + +int +main() { + using namespace std; + using xo::flatstring; +#ifdef WAITAMO + using xo::stringliteral_compare; +#endif + +#ifdef NOT_USING + static_assert(foo1().x_ == 1); + static_assert(foo1().y_ == 2); + + constexpr foo1 s1; + + static_assert(s1.x_ == 1); + static_assert(s1.y_ == 2); + + constexpr foo2 s2; + + static_assert(s2.v_[0] == 'a'); + static_assert(s2.v_[1] == 'b'); + + constexpr foo3<2> s3; + + static_assert(s3.v_[0] == 'a'); + static_assert(s3.v_[1] == 'b'); + + constexpr foo4<6> s4("hello"); + + constexpr foo5 s5("hello"); + + static_assert(s5.v_[0] == 'h'); + static_assert(s5.v_[5] == '\0'); + + constexpr foo6 s6("hello", ", world!"); + + static_assert(s6.v_[0] == 'h'); + static_assert(s6.size() == 13); + + cerr << "s6=" << s6.c_str() << endl; + + /* z gives allocation size. string size is z-1 */ + constexpr std::size_t z = concat_size("hello", ", world!", " What's up?"); + + static_assert(z == 25); + + constexpr foo7<10> s7("Hello", ", world!", " What's up?"); + + constexpr stringlit<10> s8("0123", "45678"); + + static_assert(s8.size() == 9); + constexpr std::size_t z8 = stringlit_capacity(s8); + + + static_assert(sizeof("0123") == 5); + static_assert(sizeof("45") == 3); + static_assert(sizeof("78") == 3); + + static_assert(literal_strlen("0123") == 4); + + + static_assert(z8 == 10); +#endif + +#ifdef NOT_USING + static_assert(count_size("0123") == 5); + + static_assert(count_size("0123", "45") == 7); + static_assert(count_size("0123", "45", "67", "8") == 10); + + constexpr auto z9 = count_size("0123", "45", "78"); + + static_assert(z9 == 9); + + constexpr auto z10 = foofn("0123"); + + static_assert(z10 == 5); +#endif + +#ifdef NOT_USING + //constexpr auto z11 = foofn2("0123"); + + //static_assert(z9 > 22); + + constexpr auto s9 = stringlit_make("0123", "456", "78"); + //constexpr auto s9 = stringlit_makepalooza("0123", "45678"); + + static_assert(s9.size() == 9); + + constexpr auto s10 = stringlit_make("0", "123", "456", "78"); + + static_assert(s10.size() == 9); + + cerr << s10.c_str() << endl; +#endif + +#ifdef NOT_SUCCESSFUL + constexpr auto s11 = stringlit_make("0", "1", "23", "456", "78"); +#endif + +#ifdef NOT_USING + constexpr std::size_t z9 = stringlit_capacity(s9, s10); + + static_assert(z9 == 19); + + constexpr auto s12 = stringlit_cat(s9, s10); + + static_assert(s12.size() == 18); + + cerr << s12.c_str() << endl; + + constexpr auto s13 = stringlit_cat(s9, s10, s12); + + static_assert(s13.size() == 36); + + cerr << s13.c_str() << endl; +#endif + +#ifdef NOT_USING + static_assert(stringliteral_compare(s1, s1) == 0); + + cerr << s1 << endl; +#endif + + constexpr flatstring s14 = flatstring_concat(flatstring("foo"), flatstring("bar")); + + static_assert(s14.fixed_capacity == 7); + static_assert(sizeof(s14) == 7); + + constexpr flatstring s15 = flatstring_concat(flatstring("hello"), + flatstring(", "), + flatstring("world")); + static_assert(s15.fixed_capacity == 13); + static_assert(sizeof(s15) == 13); + + constexpr auto s16 = xo::flatstring_concat(flatstring("foo"), flatstring("bar")); + + static_assert(s16.fixed_capacity == 7); + + constexpr auto cmp = flatstring_compare(s14, s14); + + static_assert(cmp == 0); + +#ifdef WAITAMO + constexpr stringliteral s2 = stringliteral_stringlit_make(stringliteral("hello"), + stringliteral(", world")); +#endif + +#ifdef NOT_USING + static constexpr string_view hello("hello"); + static constexpr string_view world(" world"); + + static constexpr auto s3 = stringlit_make_v; + + static constexpr string_view hello_world("hello world"); + + static_assert(s3 == hello_world); + + cerr << hello_world << endl; +#endif +} + +/* end ex1.cpp */ diff --git a/xo-flatstring/include/xo/flatstring/flatstring.hpp b/xo-flatstring/include/xo/flatstring/flatstring.hpp new file mode 100644 index 00000000..c1594b04 --- /dev/null +++ b/xo-flatstring/include/xo/flatstring/flatstring.hpp @@ -0,0 +1,656 @@ +/** @file flatstring.hpp + * + * Author: Roland Conybeare + **/ + +#pragma once + +#include +#include +#include +#include + +namespace xo { + /** @class flatstring + * @brief class to represent a string with a fixed amount of storage space. + * + * - Flatstring memory layout is a fixed-size, null-terminated char array. + * - With a few exceptions, flatstring methods are noexcept. + * @c flatstring::at() may throw, for consistency with @c std::string::at() behavior + * - Construction and concatenation of flatstrings are constexpr, + * and can be done at compile time. + * We rely on this in related projects (e.g. https://github.com:rconybea/xo-unit) + * - Preserves as much of the c++23 @c std::string api as practicable + * + * @c N includes mandatory null terminator, so we require @c N > 0. + * + * @invariant all flatstring instances are null-terminated. + * @invariant sizeof(flatstring) == N + **/ + template + struct flatstring { + /** @defgroup flatstring-types template types + * @brief Template types exposed by @c flatstring + **/ + ///@{ + /** @brief character traits for this flatstring **/ + using traits_type = std::char_traits; + /** @brief type of each character in this flatstring **/ + using value_type = char; + using allocator_type = std::allocator; + using size_type = std::allocator_traits::size_type; + using difference_type = std::allocator_traits::difference_type; + /** @brief type of a character reference **/ + using reference = value_type &; + /** @brief type of a readonly character reference **/ + using const_reference = const value_type &; + using pointer = std::allocator_traits::pointer; + using const_pointer = std::allocator_traits::const_pointer; + /** @brief representation for a read/write iterator **/ + using iterator = char *; + /** @brief representation for a readonly iterator **/ + using const_iterator = const char *; + + /** @brief representation for a read/write reverse iterator + * + * constexpr implementation is tricky here, since we can't + * form the address 'just before the beginning of the string' for @p rend() + * without losing constexprness (at least with gcc 13.1) + * + * Instead iterator always refers to the address immediately after its + * real target. This works since @c rbegin() refers to the char just before + * trailing null + **/ + struct reverse_iterator { + public: + constexpr reverse_iterator(char * p) : p_{p} {} + + constexpr bool _has_pointer() const { return p_ != nullptr; } + + constexpr bool operator==(const reverse_iterator & rhs) const noexcept { + return p_ == rhs.p_; + } + + constexpr char & operator* () const { return *(p_ - 1); } + + constexpr reverse_iterator & operator++ () { + --p_; + return *this; + } + + constexpr reverse_iterator operator++ (int) { + reverse_iterator copy = *this; + --p_; + return copy; + } + + private: + char * p_; + }; + + /** @brief representation for a readonly reverse iterator + * + * constexpr implementation is tricky here, since we can't + * form the address 'just before the beginning of the string' for @p rend() + * without losing constexprness (at least with gcc 13.1) + * + * Instead iterator always refers to the address immediately after its + * real target. This works since @c rbegin() refers to the char just before + * trailing null + **/ + struct const_reverse_iterator { + public: + constexpr const_reverse_iterator(const char * p) : p_{p} {} + + constexpr bool _has_pointer() const { return p_ != nullptr; } + + constexpr bool operator==(const const_reverse_iterator & rhs) const noexcept { + return p_ == rhs.p_; + } + + constexpr const char & operator* () const { return *(p_ - 1); } + + constexpr const_reverse_iterator & operator++ () { + --p_; + return *this; + } + + constexpr const_reverse_iterator operator++ (int) { + const_reverse_iterator copy = *this; + --p_; + return copy; + } + + private: + const char * p_; + }; + ///@} + + /** @defgroup flatstring-constants constants **/ + ///@{ + static constexpr const size_type npos = size_type(-1); + + /** @brief capacity of this flatstring, including final null terminator. + * + * @note not present in @c std::string api + **/ + static constexpr const std::size_t fixed_capacity = N; + ///@} + + public: + /** @defgroup flatstring-ctor constructors **/ + ///@{ + /** @brief create empty string literal. Will contain N null characters + * + * Example + * @code + * constexpr flatstring<5> s1; + * static_assert(s1.empty()); + * @endcode + **/ + constexpr flatstring() noexcept { + /* note: clang verifies that we fully initialize memory; otherwise will not recognize + * instance as constexpr + */ + std::fill_n(value_, N, '\0'); + } + + /** @brief create string literal from a correctly-sized char array + * + * Example + * @code + * constexpr flatstring s1("hello"); + * static_assert(s1.size() > 0); + * @endcode + **/ + constexpr flatstring(const char (&str)[N]) noexcept { + std::copy_n(str, N, value_); + } + ///@} + + /** @brief construct from another flatstring **/ + template + static constexpr flatstring from_flatstring(const flatstring & str) noexcept { + flatstring retval; + + retval.assign(str); + + return retval; + } + + /** @brief construct from char array **/ + template + static constexpr flatstring from_chars(const char (&str)[N2]) noexcept { + flatstring retval; + + retval.assign(str); + + return retval; + } + + /** @brief construct from integer **/ + template + requires std::is_integral_v + static constexpr flatstring from_int(Int x) { + /* 32-bit int ~ 4e9 + * 64-bit int ~ 16e18 + * 128-bit int - 256e36 ~ 2.6e38 + */ + constexpr size_t buf_z = 64; + + bool negative_flag = (x < 0); + std::size_t i = buf_z; + char buf[buf_z]; + std::fill_n(buf, N, '\0'); + + if (negative_flag) + x = -x; + + buf[--i] = '\0'; + + if (x == 0) + buf[--i] = '0'; + + while ((i > 0) && (x != 0)) { + buf[--i] = ('0' + x % 10); + x = x / 10; + } + + if ((i > 0) && negative_flag) + buf[--i] = '-'; + + char retv[N]; + std::fill_n(retv, N, '\0'); + std::copy_n(buf + i, buf_z - i, retv); + + return retv; + } + + /** @defgroup flatstring-properties property-methods **/ + ///@{ + /** @brief true if (and only if) string is empty **/ + constexpr bool empty() const noexcept { return value_[0] == '\0'; } + /** @brief returns current size of this string **/ + constexpr size_type size() const noexcept { + return this->cend() - this->cbegin(); + } + /** @brief synonym for @c size() **/ + constexpr size_type length() const noexcept { return size(); } + + constexpr size_type capacity() const noexcept { return fixed_capacity - 1; } + constexpr size_type max_size() const noexcept { return fixed_capacity - 1; } + + /** @brief contents as plain old C-style string. **/ + constexpr const char * c_str() const noexcept { return value_; } + ///@} + + /** @defgroup flatstring-access access methods **/ + ///@{ + /** @brief get writeable access to string representation + * Caller responsible for ensuring trailing null char + **/ + constexpr char * data() noexcept { return value_; } + /** @brief return char at position @p pos in this string (counting from zero). + * + * Throws @c std::out_of_range exception if @p pos >= @c N + **/ + constexpr value_type & at(size_type pos) throw() { return this->at_aux(pos); } + constexpr const value_type & at(size_type pos) const throw() { return const_cast(this)->at_aux(pos); } + + /** @brief return char at position @p pos in this string (counting from zero). + * + * Does not check bounds: undefined behavior if @p pos >= @c N + * + * @pre 0<=pos<=N-1 + **/ + constexpr value_type & operator[](size_type pos) noexcept { return value_[pos]; } + constexpr const value_type & operator[](size_type pos) const noexcept { return value_[pos]; } + ///@} + + /** @defgroup flatstring-iterators iterators **/ + ///@{ + constexpr iterator begin() { return &value_[0]; } + constexpr iterator end() { return this->last(); } + + constexpr const_iterator cbegin() const { return &value_[0]; } + constexpr const_iterator cend() const { return const_cast(this)->last(); } + constexpr const_iterator begin() const { return cbegin(); } + constexpr const_iterator end() const { return cend(); } + + constexpr reverse_iterator rbegin() { return reverse_iterator(this->last()); } + constexpr reverse_iterator rend() { return reverse_iterator(&value_[0]); } + constexpr const_reverse_iterator crbegin() const { return const_cast(this)->last(); } + constexpr const_reverse_iterator crend() const { return &value_[0]; } + constexpr const_reverse_iterator rbegin() const { return crbegin(); } + constexpr const_reverse_iterator rend() const { return crend(); } + ///@} + + /** @defgroup flatstring-assign assignment **/ + ///@{ + /** put string into empty state. fills entire char array with nulls **/ + constexpr void clear() noexcept { std::fill_n(value_, N, '\0'); } + /** ensure null-terminated representation. + * + * Example: + * @code + * flatstring x; + * snprintf(x.data(), x.capacity(), ...); + * return x.ensure_final_null(); + * @endcode + **/ + constexpr flatstring & ensure_final_null() noexcept { value_[N-1] = '\0'; return *this; } + + /** replace contents with min(count,N-1) copies of character ch **/ + constexpr flatstring & assign(size_type count, value_type ch) { + std::size_t pos = 0; + for (; pos < std::min(count, N-1); ++pos) + value_[pos] = ch; + for (; pos < N; ++pos) + value_[pos] = '\0'; + + return *this; + } + /** @brief replace contents with first N-1 characters of @p x **/ + constexpr flatstring & assign(const flatstring & x) { + for (std::size_t pos = 0; pos < N-1; ++pos) + value_[pos] = x.value_[pos]; + value_[N-1] = '\0'; + return *this; + } + /** @brief replace contents with substring [pos,pos+count] of str **/ + template + constexpr flatstring & assign(const flatstring & x, + size_type pos, size_type count = npos) { + std::size_t i = 0; + for (; + i < std::min(std::min(count, + (x.fixed_capacity-1 > pos) + ? x.fixed_capacity-1 - pos + : 0ul), + N-1); + ++i) + value_[i] = x.value_[pos+i]; + for (; i < N; ++i) + value_[i] = '\0'; + + return *this; + } + /** @brief replace contents with range [cstr, cstr + count) **/ + constexpr flatstring & assign(const value_type * cstr, size_type count) { + std::size_t i = 0; + for (; i < std::min(N-1, count); ++i) + value_[i] = cstr[i]; + for (; i < N; ++i) + value_[i] = '\0'; + + return *this; + } + /** @brief replace contents with C-style string cstr **/ + constexpr flatstring & assign(const value_type * cstr) { + std::size_t i = 0; + const value_type * p = cstr; + while ((i < N-1) && (*p != '\0')) { + value_[i] = *p; + ++i; + ++p; + } + for (; i < N; ++i) + value_[i] = '\0'; + + return *this; + } + /** @brief replace contents with iterator range [first, last) **/ + template + constexpr flatstring & assign(InputIter first, InputIter last) { + InputIter ix = first; + std::size_t i = 0; + for (; (i < N-1) && (ix != last); ++i) { + value_[i] = *ix; + } + for (; i < N; ++i) + value_[i] = '\0'; + return *this; + } + ///@} + + /** @defgroup flatstring-append append **/ + ///@{ + /** @brief append contents of null-terminated string cstr **/ + constexpr flatstring & append(const value_type * cstr) { + std::size_t z = this->size(); + std::size_t i = 0; + for (; (z+i < N-1) && (cstr[i] != '\0'); ++i) + value_[z+i] = cstr[i]; + for (; z+i < N; ++i) + value_[z+i] = '\0'; + + return *this; + } + + /** @brief append the first count members of cstr[] **/ + constexpr flatstring & append(const value_type * cstr, size_type count) { + std::size_t z = this->size(); + std::size_t i = 0; + for (; z+i < std::min(N-1, count); ++i) + value_[z+i] = cstr[i]; + for (; z+i < N; ++i) + value_[z+i] = '\0'; + + return *this; + } + + /** @brief append substring [pos .. pos + count) of x **/ + template + constexpr flatstring & append(const flatstring & x, + size_type pos, size_type count = npos) + { + std::size_t i_src = 0; + std::size_t i_dest = size(); + for (; + i_src < std::min(std::min(count, + (x.fixed_capacity-1 > pos) + ? x.fixed_capacity-1 - pos + : 0ul), + N-1); + ++i_src, ++i_dest) + value_[i_dest] = x.value_[pos+i_src]; + for (; i_dest < N; ++i_dest) + value_[i_dest] = '\0'; + + return *this; + } + ///@} + + // insert + // insert_range + // erase + // push_back + // append + // append_range + // operator+= + // replace + // replace_with_range + // copy + // find + // rfind + // find_first_of + // find_first_not_of + // find_last_of + // find_last_not_of + // compare + // starts_with + // end_with + // contains + // substr + + /** @defgroup flatstring-conversion-operators conversion operators **/ + ///@{ + /** @brief conversion to @c std::string + * + * Example + * @code + * constexpr flatstring s("bazinga!"); + * std::string s_str{s.str()}; + * @endcode + **/ + std::string str() const { return std::string(value_); } + + /** @brief conversion operator to string_view **/ + constexpr operator std::string_view() const noexcept { return std::string_view(value_); } + + /** @brief conversion operator to C-style string. + * + * Example + * @code + * constexpr flatstring s("obey gravity.."); + * strcmp(s, "obey..."); + * @endcode + **/ + constexpr operator const char * () const noexcept { return value_; } + ///@} + + private: + constexpr value_type & at_aux(size_type pos) { + if (pos >= N) { +#ifdef NOT_USING + /* note: can't build stringstream at compile time */ + std::stringstream ss; + ss << "flatstring<" << N << ">::at: expected pos=[" << pos << "] in interval [0," << N << ")" << std::endl; +#endif + + throw std::out_of_range("at_aux: range error"); + } + + return (*this)[pos]; + } + + template + constexpr Iterator last() noexcept { + Iterator p = &value_[N-1]; + + /* search backward for first padding '\0' */ + while ((p > &value_[0]) && (*(p-1) == '\0')) + --p; + + return p; + } + + public: + /** @defgroup flatstring-instance-variables instance variables **/ + ///@{ + + /** @brief characters comprising this literal string **/ + char value_[N]; + + ///@} + }; + + /** @brief sentinel type, for forbidden flatstring with no space for a null terminator **/ + template <> + struct flatstring<0> { flatstring() = delete; }; + + // non-member functions + // erase + // erase_if + // operator<< + // operator>> + // getline + // stoi + // stol + // stoll + // stoul + // stoull + // stof + // stod + // stold + +#ifdef NOT_USING + /** @brief all_same_v is true iff types T1 = .. = Tn + **/ + template < typename First, typename... Rest > + constexpr auto + all_same_v = std::conjunction_v< std::is_same... >; +#endif + + /** @brief Concatenate flatstrings, possibly mixed with C-style char arrays + * + * Example: + * @code + * constexpr auto s = flatstring_concat(flatstring("hello"), + * ", ", + * flatstring("world")); + * static_assert(s.capacity == 13); + * @endcode + * + **/ + template < typename... Ts> + constexpr auto + flatstring_concat(Ts && ... args) noexcept + { +#ifdef NOT_USING + static_assert(all_same_v...>, + "string must share the same char type"); + + using char_type = std::remove_const_t< std::remove_pointer_t < std::common_type_t < Ts... > > >; +#endif + using value_type = char; + + /** n1: total number of bytes used by arguments **/ + constexpr std::size_t n1 = (sizeof(Ts) + ...); + /** z1: each string arg has a null terminator included in its size, + * z1 is the number of arguments in parameter pack Ts, + * which equals the number of null terminators used + **/ + constexpr std::size_t z1 = sizeof...(Ts); + + /** n: number of chars in concatenated string. +1 for final null **/ + constexpr std::size_t n + = (n1 / sizeof(value_type)) - z1 + 1; + + flatstring result; + + std::size_t pos = 0; + + auto detail_concat = [ &pos, &result ](auto && arg) { + /* tradeoff here: + * 1. flatstring::size() is constexpr, so we can concat strings with size() < capacity(). + * (note flatstring::from_int() likely creates such strings) + * 2. ..but no size() method on char arrays. + * 3. std::size() not suitable: size of char array includes null terminator, + * while flatstring.size() excludes it, and flatstring behavior is consistent with + * std::string.size() + * Consequence of using arg.size() here; have to wrap char arrays with + * flatstring() to use them with flatstring_concat() + */ + auto count = arg.size(); + //constexpr auto count = (sizeof(arg) - sizeof(value_type)) / sizeof(value_type); + + std::copy_n(/*arg.c_str()*/ static_cast(arg), count, result.value_ + pos); + pos += count; + }; + + (detail_concat(args), ...); + + return result; + } + + /** @brief compare two flatstrings lexicographically. + * + * Example: + * @code + * constexpr auto cmp = flatstring_compare(flatstring("foo"), flatstring("bar")); + * static_assert(cmp > 0); + * @endcode + **/ + template + constexpr auto + flatstring_compare(const flatstring & s1, + const flatstring & s2) noexcept + { + return (std::string_view(s1.value_) <=> std::string_view(s2.value_)); + } + + /** @defgroup flatstring-3way-compare 3way-compare **/ + ///@{ + /** @brief 3-way compare for two flatstrings + * + * Example + * @code + * constexpr auto cmp = (flatstring("foo") <=> flatstring("bar")); + * static_assert(cmp != 0); + * @endcode + **/ + template + constexpr auto + operator<=>(const flatstring & s1, + const flatstring & s2) noexcept + { + return (std::string_view(s1) <=> std::string_view(s2)); + } + + /** @brief equality comparison for two flatstrings. + * + * Example + * @code + * constexpr bool cmp = (flatstring("foo") == flatstring("foo")); + * static_assert(cmp == true); + * @endcode + * + * @note spaceship operator alone isn't sufficient to get this defined, + * at least with gcc 13.1 + **/ + template + constexpr bool + operator==(const flatstring & s1, + const flatstring & s2) noexcept + { + return ((s1 <=> s2) == std::strong_ordering::equal); + } + ///@} + +} /*namespace xo*/ + +/** end flatstring.hpp **/ diff --git a/xo-flatstring/include/xo/flatstring/flatstring_iostream.hpp b/xo-flatstring/include/xo/flatstring/flatstring_iostream.hpp new file mode 100644 index 00000000..c2d7e738 --- /dev/null +++ b/xo-flatstring/include/xo/flatstring/flatstring_iostream.hpp @@ -0,0 +1,37 @@ +/** @file flatstring_iostream.hpp + * + * Author: Roland Conybeare + **/ + +#pragma once + +#include "flatstring.hpp" +#include +//#include + +namespace xo { + /** @brief print flatstring on stream os. + * + **/ + template + void + print_flatstring (std::ostream & os, const flatstring & x) { + os << x.c_str(); + } + + /** @brief print flatstring x on stream os. + * + * Example + * @code + * cout << flatstring("foo"); // outputs "foo" + * @endcode + **/ + template + inline std::ostream & + operator<< (std::ostream & os, const flatstring & x) { + print_flatstring(os, x); + return os; + } +} /*namespace xo*/ + +/** end flatstring_iostream.hpp **/ diff --git a/xo-flatstring/include/xo/flatstring/flatstring_pretty.hpp b/xo-flatstring/include/xo/flatstring/flatstring_pretty.hpp new file mode 100644 index 00000000..2474a3a4 --- /dev/null +++ b/xo-flatstring/include/xo/flatstring/flatstring_pretty.hpp @@ -0,0 +1,28 @@ +/** @file flatstring_pretty.hpp + * + * Author: Roland Conybeare, Jul 2025 + **/ + +#pragma once + +#include "flatstring.hpp" +#include "flatstring_iostream.hpp" +#include "xo/indentlog/print/ppdetail_atomic.hpp" + +namespace xo { +#ifndef ppdetail_atomic + namespace print { + struct ppindentinfo; + + template + struct ppdetail> { + static bool print_pretty(const ppindentinfo & ppii, + const flatstring & x) { + return ppdetail_atomic>::print_pretty(ppii, x); + }; + }; + } +#endif +} + +/** end flatstring_pretty.hpp **/ diff --git a/xo-flatstring/include/xo/flatstring/int128_iostream.hpp b/xo-flatstring/include/xo/flatstring/int128_iostream.hpp new file mode 100644 index 00000000..cc546a18 --- /dev/null +++ b/xo-flatstring/include/xo/flatstring/int128_iostream.hpp @@ -0,0 +1,31 @@ +/** @file int128_iostream.hpp + * + * Author: Roland Conybeare + **/ + +#pragma once + +#include "flatstring.hpp" +#include + +namespace std { + /* print a 128-bit integer */ + inline std::ostream & + operator<< (std::ostream & os, __int128 x) { + os << xo::flatstring<48>::from_int(x); + return os; + } +} + +#ifdef NOT_USING +namespace xo { + /* print a 128-bit integer */ + inline std::ostream & + operator<< (std::ostream & os, __int128 x) { + os << xo::flatstring<48>::from_int(x); + return os; + } +} +#endif + +/** end int128_iostream.hpp **/ diff --git a/xo-flatstring/include/xo/flatstring/string_view_concat.hpp b/xo-flatstring/include/xo/flatstring/string_view_concat.hpp new file mode 100644 index 00000000..4105420d --- /dev/null +++ b/xo-flatstring/include/xo/flatstring/string_view_concat.hpp @@ -0,0 +1,30 @@ +#include +#include + +template +struct sv_concat +{ + static constexpr auto impl() noexcept { + constexpr std::size_t n = (Strings.size() + ... + 0); + + std::array arr{}; + + auto append = [i=0, &arr](const auto & s) mutable { + for (auto c : s) + arr[i++] = c; + }; + (append(Strings), ...); + arr[n] = '\0'; + + return arr; + } + + static constexpr auto arr = impl(); + static constexpr std::string_view value { + arr.data(), + arr.size() - 1 + }; +}; + +template +static constexpr auto concat_v = sv_concat::value; diff --git a/xo-flatstring/utest/CMakeLists.txt b/xo-flatstring/utest/CMakeLists.txt new file mode 100644 index 00000000..88dff3fc --- /dev/null +++ b/xo-flatstring/utest/CMakeLists.txt @@ -0,0 +1,56 @@ +# xo-flatstring/utest/CMakeLists.txt + +set(SELF_EXE utest.flatstring) +set(SELF_SRCS + flatstring_utest_main.cpp + flatstring.test.cpp) + +xo_add_utest_executable(${SELF_EXE} ${SELF_SRCS}) + +# ---------------------------------------------------------------- +# in coverage build, target to build+install coverage report + +if (XO_SUBMODULE_BUILD) + # in submodule build, generate aggregate coverage report + # for all xo libraries +else() + set(CCOV_OUTPUT_DIR ${PROJECT_BINARY_DIR}/ccov/html) + set(CCOV_INDEX_FILE ${CCOV_OUTPUT_DIR}/index.html) + set(CCOV_REPORT_EXE ${PROJECT_BINARY_DIR}/gen-ccov) + # CMAKE_INSTALL_DOCDIR + # =default=> DATAROOTDIR/doc/PROJECT_NAME + # =default=> CMAKE_INSTALL_PREFIX/share/doc/xo_flatstring + set(CCOV_INSTALL_DOCDIR ${CMAKE_INSTALL_DOCDIR}/ccov) + + # 'test' target should always be out-of-date + # + # DEPENDS: reminder - can't put 'test' here, requires 'all' target + # + add_custom_command( + OUTPUT ${CCOV_INDEX_FILE} + DEPENDS ${SELF_EXE} + COMMAND ${CCOV_REPORT_EXE} + WORKING_DIRECTORY ${PROJECT_BINARY_DIR} + COMMENT "Generating coverage report -> [${CCOV_OUTPUT_DIR}]") + + add_custom_target( + ccov + DEPENDS ${CCOV_INDEX_FILE} ${SELF_EXE}) + + # OPTIONAL: quietly skip this step if ccov report not generated + install( + DIRECTORY ${CCOV_OUTPUT_DIR} + FILE_PERMISSIONS OWNER_READ GROUP_READ WORLD_READ + DESTINATION ${CCOV_INSTALL_DOCDIR} + COMPONENT Documentation + OPTIONAL) +endif() + +# ---------------------------------------------------------------- +# deps: logutils, ... + +xo_self_headeronly_dependency(${SELF_EXE} xo_flatstring) +xo_dependency(${SELF_EXE} indentlog) +xo_external_target_dependency(${SELF_EXE} Catch2 Catch2::Catch2) + +# end CMakeLists.txt diff --git a/xo-flatstring/utest/flatstring.test.cpp b/xo-flatstring/utest/flatstring.test.cpp new file mode 100644 index 00000000..69b24c53 --- /dev/null +++ b/xo-flatstring/utest/flatstring.test.cpp @@ -0,0 +1,445 @@ +/** @file flatstring.utest.cpp **/ + +#include "xo/flatstring/flatstring.hpp" +#include "xo/flatstring/int128_iostream.hpp" +#include "xo/indentlog/scope.hpp" +#include "xo/indentlog/print/tag.hpp" +#include "xo/indentlog/print/hex.hpp" +#include +#include +//#include + +namespace xo { + using namespace std; + + namespace ut { + template + void + flatstring_iter_tests(const String & str, const char * text) { + size_t n = ::strlen(text); + + REQUIRE(str.size() == n); + + /* verify range iteration visits contents in order */ + { + size_t i = 0; + for (char ch : str) { + INFO(XTAG(i)); + + CHECK(ch == text[i]); + + ++i; + } + + REQUIRE(i == n); + } + + String str_copy; + + REQUIRE(str_copy.capacity() == str.capacity()); + REQUIRE(str_copy.empty()); + + /* verify const iteration visits string elements in order */ + { + str_copy = str; + REQUIRE(str_copy == str); + + size_t i = 0; + + for (auto ix = str_copy.cbegin(), end_ix = str_copy.cend(); ix != end_ix; ++ix) { + INFO(XTAG(i)); + + char ch = *ix; + + CHECK(ch == text[i]); + + ++i; + } + + REQUIRE(i == n); + } + + /* verify string overwrite through iterator */ + { + size_t i = 0; + + for (auto ix = str_copy.begin(), end_ix = str_copy.end(); ix != end_ix; ++ix) { + INFO(XTAG(i)); + + *ix = ('a' + i); + + ++i; + } + + REQUIRE(i == n); + + for (i = 0; i < n; ++i) { + CHECK(str_copy[i] == ('a' + i)); + } + } + + /* verify reverse iteration visits string elements in reverse order */ + { + str_copy = str; + REQUIRE(str_copy == str); + + size_t i = 0; + + for (auto ix = str_copy.rbegin(), end_ix = str_copy.rend(); ix != end_ix; ++ix) { + INFO(XTAG(i)); + + char ch = *ix; + + CHECK(ch == text[n-1-i]); + + ++i; + } + + REQUIRE(i == n); + } + + /* verify string overwrite through reverse iterator */ + { + str_copy = str; + REQUIRE(str_copy == str); + + size_t i = 0; + + for (auto ix = str_copy.rbegin(), end_ix = str_copy.rend(); ix != end_ix; ++ix) { + INFO(XTAG(i)); + + *ix = ('a' + i); + + ++i; + } + + REQUIRE(i == n); + + for (i = 0; i< n; ++i) { + CHECK(str_copy[n-1-i] == ('a' + i)); + } + } + + /* verify const reverse iteration visits string elements in reverse order */ + { + str_copy = str; + REQUIRE(str_copy == str); + + size_t i = 0; + + for (auto ix = str_copy.crbegin(), end_ix = str_copy.crend(); ix != end_ix; ++ix) { + INFO(XTAG(i)); + + char ch = *ix; + + CHECK(ch == text[n-1-i]); + + ++i; + } + + REQUIRE(i == n); + } + } + + template + void + flatstring_assign_tests(const String1 & str, const char * text, + const String2 & str2, const char * text2) { + INFO(tostr(XTAG(str), XTAG(text), XTAG(text2))); + + String1 str_copy; + + str_copy.assign(str.c_str()); + REQUIRE(str_copy == str); + + /* verify assignment from C-style string **/ + { + str_copy.assign(text2); + + INFO(tostr(XTAG(str_copy), XTAG(text2))); + + REQUIRE(::strncmp(str_copy.c_str(), text2, + std::min(::strlen(text2)+1, str_copy.capacity())) == 0); + } + + /* verify assignment from prefix of C-style string */ + for (size_t prefix = 0, n_prefix = ::strlen(text2); prefix < n_prefix; ++prefix) + { + str_copy.assign(str); + + REQUIRE(str_copy == str); + + str_copy.assign(text2, prefix); + + INFO(tostr(XTAG(prefix), XTAG(str_copy), XTAG(text2))); + + if (prefix == 0) { + REQUIRE(str_copy.empty()); + } else { + REQUIRE(str_copy.size() == std::min(prefix, str_copy.capacity())); + REQUIRE(::strncmp(str_copy.c_str(), text2, + std::min(prefix, str_copy.capacity())) == 0); + } + } + + /* verify assignment from substring */ + String2 text2_copy; + text2_copy.assign(text2); + + INFO(tostr(XTAG(text2_copy))); + + for (size_t i = 0, n = text2_copy.size(); i < n; ++i) { + /* deliberately letting j extend beyond the end of text2_copy */ + for (size_t j = i; j < n+10; ++j) { + INFO(tostr(XTAG(n), XTAG(i), XTAG(j))); + + str_copy.assign(str); + + REQUIRE(str_copy == str); + + str_copy.assign(text2_copy, i, j-i); + + INFO(tostr(XTAG(str_copy.fixed_capacity), XTAG(str_copy))); + + REQUIRE(str_copy.size() == std::min(j-i, + std::min(text2_copy.size()-i, + str_copy.capacity()))); + REQUIRE(::strncmp(str_copy.c_str(), text2_copy.c_str() + i, + std::min(j-i, str_copy.capacity())) == 0); + } + } + } + + template + void + flatstring_concat_tests(const String1 & str, const char * text, + const String2 & str2, const char * text2) + { + flatstring concat; + + REQUIRE(concat.empty()); + + /* forcing concat to occur at runtime */ + { + concat = flatstring_concat(str, str2); + auto req_str = string(text) + string(text2); + + REQUIRE(::strcmp(concat.c_str(), req_str.c_str()) == 0); + } + { + concat = flatstring_concat(str2, str); + auto req_str = string(text2) + string(text); + + REQUIRE(::strcmp(concat.c_str(), req_str.c_str()) == 0); + } + +#ifdef NOT_USING + { + auto concat4 = flatstring_concat(str, + flatstring(text2), + str, + flatstring(text2)); + auto req_str = string(text) + string(text2) + string(text) + string(text2); + + REQUIRE(::strcmp(concat4.c_str(), req_str.c_str()) == 0); + } +#endif + + { + auto concat4 = flatstring_concat(str, str2, str, str2); + auto req_str = string(text) + string(text2) + string(text) + string(text2); + + REQUIRE(::strcmp(concat4.c_str(), req_str.c_str()) == 0); + } + } + + template + void + flatstring_runtime_tests(const String & str, const char * text) { + INFO(tostr(XTAG(str), XTAG(text))); + + REQUIRE(str.fixed_capacity == strlen(text)+1); + REQUIRE(str.capacity() == strlen(text)); + REQUIRE(str.size() == strlen(text)); + REQUIRE(str.length() == strlen(text)); + REQUIRE(strcmp(str.c_str(), text) == 0); + REQUIRE(strcmp(str, text) == 0); + + String str2 = str; + + { + string str3{str.str()}; + + REQUIRE(::strcmp(str3.c_str(), str.c_str()) == 0); + } + + REQUIRE(string_view(str2) == string_view(str)); + + { + auto cmp = (str2 <=> str); + REQUIRE(cmp == strong_ordering::equal); + } + + { + bool cmp = (str2 == str); + INFO(xtag("cmp", cmp)); + REQUIRE(str2 == str); + + bool cmp2 = (str2 != str); + REQUIRE(cmp2 != cmp); + } + + str2.clear(); + REQUIRE(str2.empty()); + + str2.assign(100, ' '); + REQUIRE(str2.size() == str2.capacity()); + + /* verify entirely ' ' */ + { + size_t i = 0; + for (char ch : str2) { + INFO(XTAG(i)); + + CHECK(ch == ' '); + + ++i; + } + + REQUIRE(i == str2.size()); + } + } + + /* using macro here because template argument depends on size of literal C string, + * and we can't use such a string as a template argument. + * + * static_asserts: using these to verify that constexpr methods are being computed + * at compile time. + * + * REQUIRE() calls to do verification that relies on non-constexpr calls such as + * strlen(), strcmp() + */ +# define LITERAL_TEST_BODY(name, name2, text, text2) \ + constexpr flatstring name{text}; \ + constexpr flatstring name2{text2}; \ + static_assert(name[0]==text[0]); \ + static_assert(name.at(0)==text[0]); \ + static_assert(name.empty() == true || name.empty() == false); \ + static_assert(name.capacity() >= 0); \ + static_assert(name.begin() != nullptr); \ + static_assert(name.end() != nullptr); \ + static_assert(name.cbegin() != nullptr); \ + static_assert(name.cend() != nullptr); \ + static_assert(name.crbegin()._has_pointer()); \ + static_assert(name.crend()._has_pointer()); \ + /*static_assert(name.rbegin() != nullptr);*/ \ + /*static_assert(!name.rend());*/ \ + static_assert(name.size() >= 0); \ + static_assert(name.c_str() != nullptr); \ + static_assert((name <=> name) == 0); \ + static_assert(name == name); \ + static_assert(name >= name); \ + static_assert(name <= name); \ + static_assert(!(name != name)); \ + static_assert(!(name > name)); \ + static_assert(!(name < name)); \ + flatstring_runtime_tests(name, text); \ + flatstring_iter_tests(name, text); \ + flatstring_assign_tests(name, text, name2, text2); \ + flatstring_concat_tests(name, text, name2, text2); \ + static_assert(string_view(name) == string_view(name)); \ + /* end LITERAL_TEST_BODY */ + + + TEST_CASE("flatstring", "[flatstring][compile-time]") { + constexpr bool c_debug_flag = false; + + // can get bits from /dev/random by uncommenting the 2nd line below + //uint64_t seed = xxx; + //rng::Seed seed; + + //auto rng = xo::rng::xoshiro256ss(seed); + + scope log(XO_DEBUG2(c_debug_flag, "TEST_CASE.flatstring")); + //log && log("(A)", xtag("foo", foo)); + + /* mostly compile-time tests here */ + + LITERAL_TEST_BODY(s1, t1, "h", "abracadabra!"); + LITERAL_TEST_BODY(s2, t2, "he", "bracadabra!"); + LITERAL_TEST_BODY(s3, t3, "hel", "racadabra!"); + LITERAL_TEST_BODY(s4, t4, "hell", "acadabra!"); + LITERAL_TEST_BODY(s5, t5, "hello", "cadabra!"); + LITERAL_TEST_BODY(s6, t6, "hello,", "adabra!"); + LITERAL_TEST_BODY(s7, t7, "hello, ", "dabra!"); + LITERAL_TEST_BODY(s8, t8, "hello, w", "abra!"); + LITERAL_TEST_BODY(s9, t9, "hello, wo", "bra!"); + LITERAL_TEST_BODY(s10, t10, "hello, wor", "ra!"); + LITERAL_TEST_BODY(s11, t11, "hello, worl", "a!"); + LITERAL_TEST_BODY(s12, t12, "hello, world", "!"); + LITERAL_TEST_BODY(s13, t13, "hello, world!", ""); + + static_assert(s1 == s1); + + static_assert(s1 != s2); + static_assert(s2 != s3); + static_assert(s3 != s4); + static_assert(s4 != s5); + static_assert(s12 != s13); + + static_assert(s1 < s2); + static_assert(s2 < s3); + static_assert(s3 < s4); + static_assert(s4 < s5); + static_assert(s12 < s13); + + static_assert(s2 > s1); + static_assert(s3 > s2); + static_assert(s4 > s3); + static_assert(s5 > s4); + static_assert(s13 > s12); + + /* concat */ + static_assert(flatstring_concat(s1,t1) == flatstring("habracadabra!")); + + /* clear */ + auto s13_copy = s13; + s13_copy.clear(); + + REQUIRE(s13_copy.empty()); + + constexpr auto s13_copy2 = s13; + + static_assert(s13_copy2.size() == s13.size()); + + //cerr << "s13=[" << s13 << "] s13_copy2=[" << s13_copy2 << "]" << endl; + //cerr << xtag("s13", hex_view(s13.c_str(), s13.c_str() + s13.capacity(), true)) << endl; + //cerr << xtag("s13_copy2", hex_view(s13_copy2.c_str(), s13_copy2.c_str() + s13_copy2.capacity(), true)) << endl; + + REQUIRE(s13_copy2 == s13); + + } /*TEST_CASE(flatstring)*/ + + TEST_CASE("flatstring_int128", "[flatstring]") { + //constexpr bool c_debug_flag = false; + + // can get bits from /dev/random by uncommenting the 2nd line below + //uint64_t seed = xxx; + //rng::Seed seed; + + //auto rng = xo::rng::xoshiro256ss(seed); + + //scope log(XO_DEBUG2(c_debug_flag, "TEST_CASE.flatstring_int128")); + //log && log("(A)", xtag("foo", foo)); + + __int128_t x = 65536UL*65536UL*65536UL*65536UL*65536UL; + + stringstream ss; + ss << x; + } /*TEST_CASE(flatstring_int128)*/ + + + } /*namespace ut*/ +} /*namespace xo*/ + +/** end flatstring.utest.cpp **/ diff --git a/xo-flatstring/utest/flatstring_utest_main.cpp b/xo-flatstring/utest/flatstring_utest_main.cpp new file mode 100644 index 00000000..e2721d06 --- /dev/null +++ b/xo-flatstring/utest/flatstring_utest_main.cpp @@ -0,0 +1,6 @@ +/** @file flatstring_utest_main.cpp **/ + +#define CATCH_CONFIG_MAIN +#include "catch2/catch.hpp" + +/** end flatstring_utest_main.cpp **/