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/CMakeLists.txt b/xo-flatstring/CMakeLists.txt new file mode 100644 index 00000000..357c5113 --- /dev/null +++ b/xo-flatstring/CMakeLists.txt @@ -0,0 +1,52 @@ +# 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) + +# ---------------------------------------------------------------- +# 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..aba31169 --- /dev/null +++ b/xo-flatstring/cmake/xo-bootstrap-macros.cmake @@ -0,0 +1,35 @@ +# ---------------------------------------------------------------- +# 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 (NOT XO_SUBMODULE_BUILD) + 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..e5ee1778 --- /dev/null +++ b/xo-flatstring/cmake/xo_flatstringConfig.cmake.in @@ -0,0 +1,17 @@ +@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") +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 00000000..15da2145 Binary files /dev/null and b/xo-flatstring/docs/_static/img/favicon.ico differ diff --git a/xo-flatstring/docs/_static/img/icon.svg b/xo-flatstring/docs/_static/img/icon.svg new file mode 100644 index 00000000..e37851a2 --- /dev/null +++ b/xo-flatstring/docs/_static/img/icon.svg @@ -0,0 +1,77 @@ + + + + + + + + + + + + + + + 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..faa72baf --- /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 + +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 + +Supported compilers +------------------- + +* developed with gcc 13.2.0; github CI using gcc 11.4.0 (asof April 2024) + +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` 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..c026a159 --- /dev/null +++ b/xo-flatstring/example/ex1/CMakeLists.txt @@ -0,0 +1,15 @@ +# xo-flatstring/example/ex1/CMakeLists.txt + +set(SELF_EXE xo_flatstring_ex1) +set(SELF_SRCS ex1.cpp) + +add_executable(${SELF_EXE} ${SELF_SRCS}) +xo_include_options2(${SELF_EXE}) + +# ---------------------------------------------------------------- +# dependencies.. + +xo_self_headeronly_dependency(${SELF_EXE} xo_flatstring) +#xo_dependency(${SELF_EXE} reflect) + +# 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..a332950d --- /dev/null +++ b/xo-flatstring/include/xo/flatstring/flatstring.hpp @@ -0,0 +1,641 @@ +/** @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 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 **/ + ///@{ + /** @brief put string into empty state. fills entire char array with nulls **/ + constexpr void clear() noexcept { std::fill_n(value_, N, '\0'); } + + /** @brief 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/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 **/