From 323d5a62dc9c148be436d2ecb6153306fd4024f5 Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Sat, 6 Jun 2026 22:22:29 -0400 Subject: [PATCH] git subrepo clone git@github.com:Rconybea/xo-stringtable2.git xo-stringtable2 subrepo: subdir: "xo-stringtable2" merged: "356a03a0" upstream: origin: "git@github.com:Rconybea/xo-stringtable2.git" branch: "main" commit: "356a03a0" git-subrepo: version: "0.4.9" origin: "???" commit: "???" --- xo-stringtable2/.gitrepo | 12 + xo-stringtable2/CMakeLists.txt | 67 +++ xo-stringtable2/README.md | 1 + .../cmake/xo-bootstrap-macros.cmake | 41 ++ .../cmake/xo_stringtable2Config.cmake.in | 16 + xo-stringtable2/idl/IGCObject_DString.json5 | 18 + .../idl/IGCObject_DUniqueString.json5 | 18 + xo-stringtable2/idl/IPrintable_DString.json5 | 16 + .../idl/IPrintable_DUniqueString.json5 | 16 + .../include/xo/stringtable2/.gitkeep | 0 .../include/xo/stringtable2/DString.hpp | 346 ++++++++++++++ .../include/xo/stringtable2/DUniqueString.hpp | 142 ++++++ .../xo/stringtable2/SetupStringtable2.hpp | 25 + .../include/xo/stringtable2/String.hpp | 14 + .../include/xo/stringtable2/StringOps.hpp | 80 ++++ .../include/xo/stringtable2/StringTable.hpp | 69 +++ .../include/xo/stringtable2/UniqueString.hpp | 12 + .../xo/stringtable2/init_stringtable2.hpp | 21 + .../stringtable2/string/IGCObject_DString.hpp | 69 +++ .../string/IPrintable_DString.hpp | 62 +++ .../uniquestring/IGCObject_DUniqueString.hpp | 69 +++ .../uniquestring/IPrintable_DUniqueString.hpp | 62 +++ .../src/stringtable2/CMakeLists.txt | 34 ++ xo-stringtable2/src/stringtable2/DString.cpp | 191 ++++++++ .../src/stringtable2/DUniqueString.cpp | 119 +++++ .../src/stringtable2/IGCObject_DString.cpp | 32 ++ .../stringtable2/IGCObject_DUniqueString.cpp | 32 ++ .../src/stringtable2/IPrintable_DString.cpp | 28 ++ .../stringtable2/IPrintable_DUniqueString.cpp | 28 ++ .../src/stringtable2/SetupStringtable2.cpp | 55 +++ .../src/stringtable2/StringTable.cpp | 173 +++++++ .../src/stringtable2/init_stringtable2.cpp | 40 ++ xo-stringtable2/utest/CMakeLists.txt | 14 + xo-stringtable2/utest/DString.test.cpp | 430 ++++++++++++++++++ xo-stringtable2/utest/StringOps.test.cpp | 103 +++++ xo-stringtable2/utest/StringTable.test.cpp | 161 +++++++ .../utest/stringtable2_utest_main.cpp | 24 + 37 files changed, 2640 insertions(+) create mode 100644 xo-stringtable2/.gitrepo create mode 100644 xo-stringtable2/CMakeLists.txt create mode 100644 xo-stringtable2/README.md create mode 100644 xo-stringtable2/cmake/xo-bootstrap-macros.cmake create mode 100644 xo-stringtable2/cmake/xo_stringtable2Config.cmake.in create mode 100644 xo-stringtable2/idl/IGCObject_DString.json5 create mode 100644 xo-stringtable2/idl/IGCObject_DUniqueString.json5 create mode 100644 xo-stringtable2/idl/IPrintable_DString.json5 create mode 100644 xo-stringtable2/idl/IPrintable_DUniqueString.json5 create mode 100644 xo-stringtable2/include/xo/stringtable2/.gitkeep create mode 100644 xo-stringtable2/include/xo/stringtable2/DString.hpp create mode 100644 xo-stringtable2/include/xo/stringtable2/DUniqueString.hpp create mode 100644 xo-stringtable2/include/xo/stringtable2/SetupStringtable2.hpp create mode 100644 xo-stringtable2/include/xo/stringtable2/String.hpp create mode 100644 xo-stringtable2/include/xo/stringtable2/StringOps.hpp create mode 100644 xo-stringtable2/include/xo/stringtable2/StringTable.hpp create mode 100644 xo-stringtable2/include/xo/stringtable2/UniqueString.hpp create mode 100644 xo-stringtable2/include/xo/stringtable2/init_stringtable2.hpp create mode 100644 xo-stringtable2/include/xo/stringtable2/string/IGCObject_DString.hpp create mode 100644 xo-stringtable2/include/xo/stringtable2/string/IPrintable_DString.hpp create mode 100644 xo-stringtable2/include/xo/stringtable2/uniquestring/IGCObject_DUniqueString.hpp create mode 100644 xo-stringtable2/include/xo/stringtable2/uniquestring/IPrintable_DUniqueString.hpp create mode 100644 xo-stringtable2/src/stringtable2/CMakeLists.txt create mode 100644 xo-stringtable2/src/stringtable2/DString.cpp create mode 100644 xo-stringtable2/src/stringtable2/DUniqueString.cpp create mode 100644 xo-stringtable2/src/stringtable2/IGCObject_DString.cpp create mode 100644 xo-stringtable2/src/stringtable2/IGCObject_DUniqueString.cpp create mode 100644 xo-stringtable2/src/stringtable2/IPrintable_DString.cpp create mode 100644 xo-stringtable2/src/stringtable2/IPrintable_DUniqueString.cpp create mode 100644 xo-stringtable2/src/stringtable2/SetupStringtable2.cpp create mode 100644 xo-stringtable2/src/stringtable2/StringTable.cpp create mode 100644 xo-stringtable2/src/stringtable2/init_stringtable2.cpp create mode 100644 xo-stringtable2/utest/CMakeLists.txt create mode 100644 xo-stringtable2/utest/DString.test.cpp create mode 100644 xo-stringtable2/utest/StringOps.test.cpp create mode 100644 xo-stringtable2/utest/StringTable.test.cpp create mode 100644 xo-stringtable2/utest/stringtable2_utest_main.cpp diff --git a/xo-stringtable2/.gitrepo b/xo-stringtable2/.gitrepo new file mode 100644 index 00000000..550000d0 --- /dev/null +++ b/xo-stringtable2/.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-stringtable2.git + branch = main + commit = 356a03a0d0eb674d5832178b5d1658bda174e7e5 + parent = 05acb038a7b7ed23eae136cdf20c670eb29d6598 + method = merge + cmdver = 0.4.9 diff --git a/xo-stringtable2/CMakeLists.txt b/xo-stringtable2/CMakeLists.txt new file mode 100644 index 00000000..3a8d217b --- /dev/null +++ b/xo-stringtable2/CMakeLists.txt @@ -0,0 +1,67 @@ +# xo-stringtable2/CMakeLists.txt + +cmake_minimum_required(VERSION 3.10) + +project(xo_stringtable2 VERSION 1.0) +enable_language(CXX) + +include(GNUInstallDirs) +include(cmake/xo-bootstrap-macros.cmake) + +xo_cxx_toplevel_options3() + +# ---------------------------------------------------------------- +# c++ settings + +# one-time project-specific c++ flags. usually empty +set(PROJECT_CXX_FLAGS "") +add_definitions(${PROJECT_CXX_FLAGS}) + +# ---------------------------------------------------------------- + +# note: manual target; generated code committed to git +xo_add_genfacetimpl( + TARGET xo-stringtable2-facetimpl-gcobject-string + FACET_PKG xo_alloc2 + INPUT idl/IGCObject_DString.json5 +) + +# note: manual target; generated code committed to git +xo_add_genfacetimpl( + TARGET xo-stringtable2-facetimpl-printable-string + FACET_PKG xo_printable2 + INPUT idl/IPrintable_DString.json5 +) + +# ---------------------------------------------------------------- + +# note: manual target; generated code committed to git +xo_add_genfacetimpl( + TARGET xo-stringtable2-facetimpl-gcobject-uniquestring + FACET_PKG xo_alloc2 + INPUT idl/IGCObject_DUniqueString.json5 +) + +# note: manual target; generated code committed to git +xo_add_genfacetimpl( + TARGET xo-stringtable2-facetimpl-printable-uniquestring + FACET_PKG xo_printable2 + INPUT idl/IPrintable_DUniqueString.json5 +) + +# ---------------------------------------------------------------- + +xo_add_genfacet_all(xo-stringtable2-genfacet-all) + +# ---------------------------------------------------------------- +# output targets + +add_subdirectory(src/stringtable2) +add_subdirectory(utest) + +# ---------------------------------------------------------------- +# cmake export + +xo_export_cmake_config(${PROJECT_NAME} ${PROJECT_VERSION} ${PROJECT_NAME}Targets) + +# end CMakeLists.txt diff --git a/xo-stringtable2/README.md b/xo-stringtable2/README.md new file mode 100644 index 00000000..61ff9f06 --- /dev/null +++ b/xo-stringtable2/README.md @@ -0,0 +1 @@ +# xo-stringtable2 diff --git a/xo-stringtable2/cmake/xo-bootstrap-macros.cmake b/xo-stringtable2/cmake/xo-bootstrap-macros.cmake new file mode 100644 index 00000000..592272c0 --- /dev/null +++ b/xo-stringtable2/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-stringtable2/cmake/xo_stringtable2Config.cmake.in b/xo-stringtable2/cmake/xo_stringtable2Config.cmake.in new file mode 100644 index 00000000..0a084ae9 --- /dev/null +++ b/xo-stringtable2/cmake/xo_stringtable2Config.cmake.in @@ -0,0 +1,16 @@ +@PACKAGE_INIT@ + +include(CMakeFindDependencyMacro) + +# note: changes to find_dependency() calls here +# must coordinate with xo_dependency() calls +# in CMakeLists.txt +# +find_dependency(xo_alloc2) +find_dependency(xo_printable2) +find_dependency(subsys) +find_dependency(indentlog) + +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-stringtable2/idl/IGCObject_DString.json5 b/xo-stringtable2/idl/IGCObject_DString.json5 new file mode 100644 index 00000000..6d986ccb --- /dev/null +++ b/xo-stringtable2/idl/IGCObject_DString.json5 @@ -0,0 +1,18 @@ +{ + mode: "implementation", + output_cpp_dir: "src/stringtable2", + output_hpp_dir: "include/xo/stringtable2", + output_impl_subdir: "string", + includes: [ + "", + "" + ], + local_types: [ ], + namespace1: "xo", + namespace2: "scm", + facet_idl: "idl/GCObject.json5", + brief: "provide AGCObject interface for DString", + using_doxygen: true, + repr: "DString", + doc: [ "implement AGCObject for DString" ], +} diff --git a/xo-stringtable2/idl/IGCObject_DUniqueString.json5 b/xo-stringtable2/idl/IGCObject_DUniqueString.json5 new file mode 100644 index 00000000..a5eb0c6a --- /dev/null +++ b/xo-stringtable2/idl/IGCObject_DUniqueString.json5 @@ -0,0 +1,18 @@ +{ + mode: "implementation", + output_cpp_dir: "src/stringtable2", + output_hpp_dir: "include/xo/stringtable2", + output_impl_subdir: "uniquestring", + includes: [ + "", + "" + ], + local_types: [ ], + namespace1: "xo", + namespace2: "scm", + facet_idl: "idl/GCObject.json5", + brief: "provide AGCObject interface for DUniqueString", + using_doxygen: true, + repr: "DUniqueString", + doc: [ "implement AGCObject for DUniqueString" ], +} diff --git a/xo-stringtable2/idl/IPrintable_DString.json5 b/xo-stringtable2/idl/IPrintable_DString.json5 new file mode 100644 index 00000000..bbbd3bc7 --- /dev/null +++ b/xo-stringtable2/idl/IPrintable_DString.json5 @@ -0,0 +1,16 @@ +{ + mode: "implementation", + output_cpp_dir: "src/stringtable2", + output_hpp_dir: "include/xo/stringtable2", + output_impl_subdir: "string", + includes: [ "", + "" ], + local_types: [ ], + namespace1: "xo", + namespace2: "scm", + facet_idl: "idl/Printable.json5", + brief: "provide APrintable interface for DString", + using_doxygen: true, + repr: "DString", + doc: [ "implement APrintable for DString" ], +} diff --git a/xo-stringtable2/idl/IPrintable_DUniqueString.json5 b/xo-stringtable2/idl/IPrintable_DUniqueString.json5 new file mode 100644 index 00000000..b6af4eb3 --- /dev/null +++ b/xo-stringtable2/idl/IPrintable_DUniqueString.json5 @@ -0,0 +1,16 @@ +{ + mode: "implementation", + output_cpp_dir: "src/stringtable2", + output_hpp_dir: "include/xo/stringtable2", + output_impl_subdir: "uniquestring", + includes: [ "", + "" ], + local_types: [ ], + namespace1: "xo", + namespace2: "scm", + facet_idl: "idl/Printable.json5", + brief: "provide APrintable interface for DUniqueString", + using_doxygen: true, + repr: "DUniqueString", + doc: [ "implement APrintable for DUniqueString" ], +} diff --git a/xo-stringtable2/include/xo/stringtable2/.gitkeep b/xo-stringtable2/include/xo/stringtable2/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/xo-stringtable2/include/xo/stringtable2/DString.hpp b/xo-stringtable2/include/xo/stringtable2/DString.hpp new file mode 100644 index 00000000..2d05bcf1 --- /dev/null +++ b/xo-stringtable2/include/xo/stringtable2/DString.hpp @@ -0,0 +1,346 @@ +/** @file DString.hpp +* + * @author Roland Conybeare, Jan 2026 + **/ + +#pragma once + +#include +#include +#include +#include +#include +#include +#include +//#include + +namespace xo { + namespace scm { + /** @class DString + * @brief String implementation with gc hooks + * + * String implementation for Schematika. + * Size-prefixed and null-terminated. + * Note however that string length != size for utf-8. + * + * Uses flexible array for chars, + * with string contents in memory immediately + * following the DString itself + **/ + struct DString { + public: + /** @defgroup dstring-types type traits **/ + ///@{ + /** character traits for this DString **/ + using traits_type = std::char_traits; + /** type of each character in this DString **/ + using value_type = char; + /** type for string index / size **/ + using size_type = std::uint32_t; + /** representation for a read/write iterator **/ + using iterator = char *; + /** representation for a readonly iterator **/ + using const_iterator = const char *; + /** xo allocator **/ + using AAllocator = xo::mm::AAllocator; + /** object visitor (garbage collector proxy) **/ + using AGCObjectVisitor = xo::mm::AGCObjectVisitor; + /** visitor hint **/ + using VisitReason = xo::mm::VisitReason; + /** ppindentinfo for APrintable **/ + using ppindentinfo = xo::print::ppindentinfo; + ///@} + /** @defgroup dstring-ctors constructors **/ + ///@{ + + /** default ctor **/ + DString() = default; + + /** not simply copyable, because of flexible array. + * Need allocator + **/ + DString(const DString &) = delete; + + /** create empty string with space for @p cap chars + * (including null terminator). + * Use memory from allocator @p mm + **/ + static DString * empty(obj mm, + size_type cap); + + /** create string containing a copy of null-terminated @p cstr. + * Use memory from allocator @p mm + **/ + static DString * from_cstr(obj mm, + const char * cstr); + + /** create string containing a copy of @p sv. + * Use memory from allocator @p mm. + **/ + static DString * from_view(obj mm, + std::string_view sv); + + /** create string containing a copy @p str. + * Use memory from allocator @p mm. + **/ + static DString * from_str(obj mm, + const std::string & str); + + /** create string containing a copy of @p sv. + * Use memory from allocator @p mm via sub_alloc. + * (load-bearing for StringTable) + **/ + static DString * from_view_suballoc(obj mm, + std::string_view sv); + + /** clone existing string **/ + static DString * clone(obj mm, + const DString * src); + +#ifdef NOT_YET + /** **/ + static DString * concat(obj mm, + DString * s1, + DString * s2); +#endif + + /** create string using printf-style formatting. + * Use memory from allocator @p mm with capacity @p cap. + * Truncates if result exceeds capacity. + * @return pointer to newly created DString + **/ + template + static DString * printf(obj mm, + size_type cap, + const char * fmt, + Args&&... args); + + ///@} + /** @defgroup dstring-access access methods **/ + ///@{ + + /** get writeable access to string representation. + * Caller responsible for calling fixup() if string length modified + **/ + char * data() noexcept { return chars_; } + + /** return char at position @p pos in this string, counting from zero. + * Does not check bounds. Undefined behavior if @p pos = @ref capacity_ + **/ + char & operator[](size_type pos) noexcept { return chars_[pos]; } + const char & operator[](size_type pos) const noexcept { return chars_[pos]; } + + size_type capacity() const noexcept { return capacity_; } + size_type size() const noexcept { return size_; } + const char * chars() const noexcept { return chars_; } + + ///@} + /** @defgroup dstring-iterators iterators **/ + ///@{ + iterator begin() noexcept { return &chars_[0]; } + iterator end() noexcept { return &chars_[size_]; } + + const_iterator cbegin() const noexcept { return &chars_[0]; } + const_iterator cend() const noexcept { return &chars_[size_]; } + const_iterator begin() const noexcept { return cbegin(); } + const_iterator end() const noexcept { return cend(); } + + ///@} + /** @defgroup dstring-assign assignment **/ + ///@{ + + /** put string into empty state **/ + void clear() noexcept { size_ = 0; chars_[0] = '\0'; } + + /** replace contents with @p other, or prefix of up to @p capacity - 1 chars **/ + DString & assign(const DString & other); + ///@} + /** @defgroup dstring-general general methods **/ + ///@{ + + /** format string into this DString using printf-style formatting. + * Truncates if result exceeds capacity. + * @return number of characters written (excluding null terminator) + **/ + template + size_type sprintf(const char * fmt, Args&&... args) { + int n; + if constexpr (sizeof...(Args) == 0) { + n = std::snprintf(chars_, capacity_, "%s", fmt); + } else { + n = std::snprintf(chars_, capacity_, fmt, std::forward(args)...); + } + if (n < 0) { + size_ = 0; + chars_[0] = '\0'; + } else { + size_ = (n < static_cast(capacity_)) ? n : capacity_ - 1; + } + return size_; + } + + /** lexicographically compare two strings. + * @return <0 if lhs < rhs, 0 if equal, >0 if lhs > rhs + **/ + static int compare(const DString & lhs, const DString & rhs) noexcept; + + /** compute hash of string contents **/ + std::size_t hash() const noexcept { + return std::hash{}(std::string_view(chars_, size_)); + } + + // TODO - behave like std::string, to the extent feasible + // 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 + // starts_with + // end_with + // contains + // substr + + /** recalculate string size if string contents modified without + * through side effects + **/ + size_type fixup_size() noexcept; + + ///@} + /** @defgroup dstring-conversion-operators conversion operators **/ + ///@{ + + operator std::string_view() const noexcept { return std::string_view(chars_); } + + /** @brief conversion oeprator to C-style string. + * + * Example + * @code + * DString s = ...; + * ::strcmp(s, "obey..."); + * @endcode + **/ + operator const char * () const noexcept { return &(chars_[0]); } + + ///@} + /** @defgroup dstring-printable-methods printable facet methods **/ + ///@{ + + bool pretty(const ppindentinfo & ppii) const; + + ///@} + /** @defgroup dstring-gcobject-methods gcobject facet methods **/ + ///@{ + + /** clone string, using memory from allocator @p mm **/ + DString * gco_shallow_move(obj gc) noexcept; + + /** fixup child pointers (trivial for DString, no children) + * note: cref so we can use forward decl + **/ + void visit_gco_children(VisitReason reason, obj gc) noexcept; + + ///@} + + private: + /** @defgroup dstring-impl-methods implementation methods **/ + ///@{ + + /** create instance from view @p sv, using memory from @p mm. + * @p suballoc_flag chooses whether to use alloc() or suballoc(). + * Load-bearing for StringTable + **/ + static DString * _from_view_aux(obj mm, + std::string_view sv, + bool suballoc_flag); + + ///@} + + private: + /** @defgroup dstring-instance-variables instance variables **/ + ///@{ + + /** extent of @ref chars_ array **/ + size_type capacity_ = 0; + /** null terminator at @c chars_[size_] **/ + size_type size_ = 0; + /** string contents **/ + char chars_[]; + + ///@} + }; + + /** create string using printf-style formatting. + * Use memory from allocator @p mm with capacity @p cap. + * Truncates if result exceeds capacity. + * @return pointer to newly created DString + **/ + template + DString * DString::printf(obj mm, + size_type cap, + const char * fmt, + Args&&... args) + { + DString * result = DString::empty(mm, cap); + if (result) { + result->sprintf(fmt, std::forward(args)...); + } + return result; + } + + inline std::ostream & operator<<(std::ostream & os, const DString * x) { + if (x) { + os << std::string_view(*x); + } else { + os << "nullptr"; + } + return os; + } + + inline bool operator==(const DString & lhs, const DString & rhs) { + return DString::compare(lhs, rhs) == 0; + } + + inline bool operator!=(const DString & lhs, const DString & rhs) { + return DString::compare(lhs, rhs) != 0; + } + + inline bool operator<(const DString & lhs, const DString & rhs) { + return DString::compare(lhs, rhs) < 0; + } + + inline bool operator<=(const DString & lhs, const DString & rhs) { + return DString::compare(lhs, rhs) <= 0; + } + + inline bool operator>(const DString & lhs, const DString & rhs) { + return DString::compare(lhs, rhs) > 0; + } + + inline bool operator>=(const DString & lhs, const DString & rhs) { + return DString::compare(lhs, rhs) >= 0; + } + + } /*namespace scm*/ +} /*namespace xo*/ + +namespace std { + template <> + struct hash { + std::size_t operator()(const xo::scm::DString & x) const noexcept { + return x.hash(); + } + }; +} /*namespace std*/ + +/* end DString.hpp */ diff --git a/xo-stringtable2/include/xo/stringtable2/DUniqueString.hpp b/xo-stringtable2/include/xo/stringtable2/DUniqueString.hpp new file mode 100644 index 00000000..fedf6691 --- /dev/null +++ b/xo-stringtable2/include/xo/stringtable2/DUniqueString.hpp @@ -0,0 +1,142 @@ +/** @file DUniqueString.hpp + * + * @author Roland Conybeare, Jan 2026 + **/ + +#pragma once + +#include + +namespace xo { + namespace scm { + /** @class DUniqueString + * @brief unique immutable string + * + * A DUniqueString is an immutable string stored in a shared StringTable. + * Follows that DUniqueStrings at different memory locations + * have different contents. + * + * DUniqueString instances will be created by StringTable (see also). + * Application code will not allocate them directly. + * + * Needs to be gc-aware so that collector knows what to do when it encounters + * a obj with a DUnqiueString data pointer; such instances + * will not be allocated from GC memory + **/ + class DUniqueString { + public: + using AAllocator = xo::mm::AAllocator; + using ACollector = xo::mm::ACollector; + using AGCObjectVisitor = xo::mm::AGCObjectVisitor; + using VisitReason = xo::mm::VisitReason; + using size_type = DString::size_type; + using ppindentinfo = xo::print::ppindentinfo; + + /* Memory model for a DUniqueString allocated via xo allocator + * + * 0 8 16 20 24 24+z + * v v v v v v + * +---------------+-+-------------+-------+-------+-----------+ + * | header |u| padding | cap | size | text... \0| + * +---------------+-+-------------+-------+-------+-----------+ + * + * Legend + * header 8 byte allocation header + * u 1 byte DUniqueString placeholder (c++ insists) + * padding 7 bytes allocator-imposed padding to 8-byte alignment + * cap 4 bytes DString.capacity + * size 4 bytes DString.size + * text z bytes DString.size bytes of text (including null) + * In practice followed by padding to 8 byte + * alignment + */ + + /** @defgroup duniquestring-ctors constructors **/ + ///@{ + + /** not copyable **/ + DUniqueString(const DUniqueString &) = delete; + + static constexpr bool is_gc_eligible() { return false; } + + ///@} + /** @defgroup duniquestring-methods methods **/ + ///@{ + + /** Available storage for this instance. + * For completeness' sake since uniquestring not modifiable + **/ + size_type capacity() const noexcept { return _text()->capacity(); } + size_type size() const noexcept { return _text()->size(); } + const char * chars() const noexcept { return _text()->chars(); } + + /** compare unique strings: return n with {n<0, n=0, n>0} + * when @p lhs lexicographically {before, at, after} @p rhs + **/ + static int compare(const DUniqueString & lhs, const DUniqueString & rhs); + + std::size_t hash() const noexcept { return _text()->hash(); } + operator std::string_view() const noexcept { return std::string_view(*_text()); } + /** not assignable **/ + DUniqueString & operator=(const DUniqueString &) = delete; + + ///@} + /** @defgroup duniquestring-printable-methods printable facet methods **/ + ///@{ + + bool pretty(const ppindentinfo & ppii) const; + + ///@} + /** @defgroup duniquestring-gcobject-methods gcobject facet methods **/ + ///@{ + + /** clone unique string, using memory from allocator @p mm. **/ + DUniqueString * gco_shallow_move(obj gc) noexcept; + + /** fixup child pointers (trivial for DUniqueString, no gc-owned children **/ + void visit_gco_children(VisitReason reason, obj gc) noexcept; + + ///@} + + private: + /** @defgroup duniquestring-impl-methods implementation methods **/ + ///@{ + + /** default ctor **/ + DUniqueString() = default; + + /** DString containing actual string content immediately follows DUniqueString + * in memory; part of same alloc + **/ + DString * _text() const noexcept; + + //explicit DUniqueString(const DString * text) : text_{text} {} + + /** create instance using memory from @p mm, + * with string contents copied from @p sv + **/ + static DUniqueString * from_view(obj mm, + std::string_view sv); + + ///@} + + friend class StringTable; + }; + + /* since unique: just compare addresses */ + inline bool operator==(const DUniqueString & lhs, const DUniqueString & rhs) { + return (&lhs == &rhs); + } + + /* since unique: just compare addresses **/ + inline bool operator!=(const DUniqueString & lhs, const DUniqueString & rhs) { + return (&lhs != &rhs); + } + + inline bool operator<=(const DUniqueString & lhs, const DUniqueString & rhs) { + return (DUniqueString::compare(lhs, rhs) <= 0); + } + } /*namespace scm*/ +} /*namespace xo*/ + +/* end UniqueString.hpp */ diff --git a/xo-stringtable2/include/xo/stringtable2/SetupStringtable2.hpp b/xo-stringtable2/include/xo/stringtable2/SetupStringtable2.hpp new file mode 100644 index 00000000..b310bb86 --- /dev/null +++ b/xo-stringtable2/include/xo/stringtable2/SetupStringtable2.hpp @@ -0,0 +1,25 @@ +/** @file SetupStringtable2.hpp + * + * @author Roland Conybeare, Mar 2026 + **/ + +#pragma once + +#include + +namespace xo { + namespace scm { + struct SetupStringtable2 { + public: + using ACollector = xo::mm::ACollector; + + public: + /** Register object2 (facet,impl) combinations with FacetRegistry **/ + static bool register_facets(); + /** Register types with garbage collector **/ + static bool register_types(obj gc); + }; + } +} + +/* end SetupStringtable2.hpp */ diff --git a/xo-stringtable2/include/xo/stringtable2/String.hpp b/xo-stringtable2/include/xo/stringtable2/String.hpp new file mode 100644 index 00000000..f3d0527b --- /dev/null +++ b/xo-stringtable2/include/xo/stringtable2/String.hpp @@ -0,0 +1,14 @@ +/** @file String.hpp + * + * @author Roland Conybeare, Feb 2026 + **/ + +#pragma once + +#include "DString.hpp" +#include "string/IGCObject_DString.hpp" +#include "string/IPrintable_DString.hpp" + +#include + +/* end String.hpp */ diff --git a/xo-stringtable2/include/xo/stringtable2/StringOps.hpp b/xo-stringtable2/include/xo/stringtable2/StringOps.hpp new file mode 100644 index 00000000..70441b4b --- /dev/null +++ b/xo-stringtable2/include/xo/stringtable2/StringOps.hpp @@ -0,0 +1,80 @@ +/** @file StringOps.hpp +* + * @author Roland Conybeare, Jan 2026 + **/ + +#pragma once + +#include "string/IGCObject_DString.hpp" +#include "DString.hpp" + +namespace xo { + namespace scm { + /** @brief string functions + * + * note: separate from DString + **/ + struct StringOps { + using AGCObject = xo::mm::AGCObject; + using AAllocator = xo::mm::AAllocator; + using size_type = DString::size_type; + + /** wrapper for DString.empty() **/ + template + static obj empty(obj mm, + size_type cap); + + /** wrapper for DString.from_cstr() **/ + template + static obj from_cstr(obj mm, + const char * cstr); + + /** wrapper for DString.clone() **/ + template + static obj clone(obj mm, + obj src); + + /** wrapper for DString.printf() **/ + template + static obj printf(obj mm, + size_type cap, + const char * fmt, + Args&&... args); + }; + + template + obj + StringOps::empty(obj mm, size_type cap) + { + return obj(DString::empty(mm, cap)); + } + + template + obj + StringOps::from_cstr(obj mm, const char * cstr) + { + return obj(DString::from_cstr(mm, cstr)); + } + + template + obj + StringOps::clone(obj mm, obj src) + { + return obj(DString::clone(mm, src.data())); + } + + template + obj + StringOps::printf(obj mm, + size_type cap, + const char * fmt, + Args&&... args) + { + return obj(DString::printf(mm, cap, fmt, + std::forward(args)...)); + } + } +} + +/* end StringOps.hpp */ diff --git a/xo-stringtable2/include/xo/stringtable2/StringTable.hpp b/xo-stringtable2/include/xo/stringtable2/StringTable.hpp new file mode 100644 index 00000000..f7e0fc1c --- /dev/null +++ b/xo-stringtable2/include/xo/stringtable2/StringTable.hpp @@ -0,0 +1,69 @@ +/** @file StringTable.hpp + * + * @author Roland Conybeare, Jan 2026 + **/ + +#pragma once + +#include "DUniqueString.hpp" +#include +#include +#include + +namespace xo { + namespace scm { + + /** @class StringTable + * @brief table containing a set of interned strings + * + * A table of strings referenced in schematika expressions + **/ + class StringTable { + public: + using DArena = xo::mm::DArena; + using MemorySizeVisitor = xo::mm::MemorySizeVisitor; + using StringMap = xo::map::DArenaHashMap; + using size_type = StringMap::size_type; + + public: + /** hint_max_capacity in bytes = capacity for strings **/ + StringTable(size_type hint_max_capacity, + bool debug_flag = false); + + /** false -> not eligible for GC (maps own memory + not moveable) **/ + static constexpr bool is_gc_eligible() { return false; } + + /** lookup interned string; nullptr if not present **/ + const DUniqueString * lookup(std::string_view key) const; + + /** return unique string with contents @p key. Idempotent! **/ + const DUniqueString * intern(std::string_view key); + + /** generate unique symbol -- guaranteed not to collide + * with existing symbol in this table. + **/ + const DUniqueString * gensym(std::string_view prefix); + + /** verify StringTable invariants. + * Act on failure according to policy @p p + **/ + bool verify_ok(verify_policy p = verify_policy::throw_only()) const; + + /** visit string-table memory pools, call visitor(info) for each **/ + void visit_pools(const MemorySizeVisitor & visitor) const; + + private: + /** allocate string storage in this arena; use DString to represent each string. + * Can't use DArenaVector b/c DString has variable size + **/ + DArena strings_; + /** map_[s] points to arena strings, i.e. members of @ref strings_ **/ + StringMap map_; + }; + + + } /*namespace scm*/ +} /*namespace xo*/ + +/* end StringTable.hpp */ diff --git a/xo-stringtable2/include/xo/stringtable2/UniqueString.hpp b/xo-stringtable2/include/xo/stringtable2/UniqueString.hpp new file mode 100644 index 00000000..6dfaadfd --- /dev/null +++ b/xo-stringtable2/include/xo/stringtable2/UniqueString.hpp @@ -0,0 +1,12 @@ +/** @file UniqueString.hpp + * + * @author Roland Conybeare, Feb 2026 + **/ + +#pragma once + +#include "DUniqueString.hpp" +#include "uniquestring/IGCObject_DUniqueString.hpp" +#include "uniquestring/IPrintable_DUniqueString.hpp" + +/* end UniqueString.hpp */ diff --git a/xo-stringtable2/include/xo/stringtable2/init_stringtable2.hpp b/xo-stringtable2/include/xo/stringtable2/init_stringtable2.hpp new file mode 100644 index 00000000..4a48ceb1 --- /dev/null +++ b/xo-stringtable2/include/xo/stringtable2/init_stringtable2.hpp @@ -0,0 +1,21 @@ +/** @file init_stringtable2.hpp + * + * @author Roland Conybeare, Jan 2026 + **/ + +#pragma once + +#include + +namespace xo { + /* tag to represent the xo-expression2/ subsystem within ordered initialization */ + enum S_stringtable2_tag {}; + + template <> + struct InitSubsys { + static void init(); + static InitEvidence require(); + }; +} /*namespace xo*/ + +/* end init_stringtable2.hpp */ diff --git a/xo-stringtable2/include/xo/stringtable2/string/IGCObject_DString.hpp b/xo-stringtable2/include/xo/stringtable2/string/IGCObject_DString.hpp new file mode 100644 index 00000000..5d8a9063 --- /dev/null +++ b/xo-stringtable2/include/xo/stringtable2/string/IGCObject_DString.hpp @@ -0,0 +1,69 @@ +/** @file IGCObject_DString.hpp + * + * Generated automagically from ingredients: + * 1. code generator: + * [xo-facet/codegen/genfacet] + * arguments: + * --input [idl/IGCObject_DString.json5] + * 2. jinja2 template for abstract facet .hpp file: + * [iface_facet_repr.hpp.j2] + * 3. idl for facet methods + * [idl/IGCObject_DString.json5] + **/ + +#pragma once + +#include "GCObject.hpp" +#include +#include +#include "DString.hpp" + +namespace xo { namespace scm { class IGCObject_DString; } } + +namespace xo { + namespace facet { + template <> + struct FacetImplementation + { + using ImplType = xo::mm::IGCObject_Xfer + ; + }; + } +} + +namespace xo { + namespace scm { + /** @class IGCObject_DString + **/ + class IGCObject_DString { + public: + /** @defgroup scm-gcobject-dstring-type-traits **/ + ///@{ + using size_type = xo::mm::AGCObject::size_type; + using AAllocator = xo::mm::AGCObject::AAllocator; + using AGCObjectVisitor = xo::mm::AGCObject::AGCObjectVisitor; + using VisitReason = xo::mm::AGCObject::VisitReason; + using Copaque = xo::mm::AGCObject::Copaque; + using Opaque = xo::mm::AGCObject::Opaque; + ///@} + /** @defgroup scm-gcobject-dstring-methods **/ + ///@{ + // const methods + + // non-const methods + /** move instance using object visitor. +Arguably abusing the word 'visitor' here **/ + static Opaque gco_shallow_move(DString & self, obj gc) noexcept; + /** Invoke fn.visit_child(iface,data) for each child GCObject pointer. +Context: provides address of data pointer so it can be updated in place +when @p fn invokes garbage collector reentry point **/ + static void visit_gco_children(DString & self, VisitReason reason, obj fn) noexcept; + ///@} + }; + + } /*namespace scm*/ +} /*namespace xo*/ + +/* end */ \ No newline at end of file diff --git a/xo-stringtable2/include/xo/stringtable2/string/IPrintable_DString.hpp b/xo-stringtable2/include/xo/stringtable2/string/IPrintable_DString.hpp new file mode 100644 index 00000000..f898a17c --- /dev/null +++ b/xo-stringtable2/include/xo/stringtable2/string/IPrintable_DString.hpp @@ -0,0 +1,62 @@ +/** @file IPrintable_DString.hpp + * + * Generated automagically from ingredients: + * 1. code generator: + * [xo-facet/codegen/genfacet] + * arguments: + * --input [idl/IPrintable_DString.json5] + * 2. jinja2 template for abstract facet .hpp file: + * [iface_facet_repr.hpp.j2] + * 3. idl for facet methods + * [idl/IPrintable_DString.json5] + **/ + +#pragma once + +#include "Printable.hpp" +#include +#include +#include "DString.hpp" + +namespace xo { namespace scm { class IPrintable_DString; } } + +namespace xo { + namespace facet { + template <> + struct FacetImplementation + { + using ImplType = xo::print::IPrintable_Xfer + ; + }; + } +} + +namespace xo { + namespace scm { + /** @class IPrintable_DString + **/ + class IPrintable_DString { + public: + /** @defgroup scm-printable-dstring-type-traits **/ + ///@{ + using ppindentinfo = xo::print::APrintable::ppindentinfo; + using Copaque = xo::print::APrintable::Copaque; + using Opaque = xo::print::APrintable::Opaque; + ///@} + /** @defgroup scm-printable-dstring-methods **/ + ///@{ + // const methods + /** Pretty-printing support for this object. +See [xo-indentlog/xo/indentlog/pretty.hpp] **/ + static bool pretty(const DString & self, const ppindentinfo & ppii); + + // non-const methods + ///@} + }; + + } /*namespace scm*/ +} /*namespace xo*/ + +/* end */ \ No newline at end of file diff --git a/xo-stringtable2/include/xo/stringtable2/uniquestring/IGCObject_DUniqueString.hpp b/xo-stringtable2/include/xo/stringtable2/uniquestring/IGCObject_DUniqueString.hpp new file mode 100644 index 00000000..7fe96c5b --- /dev/null +++ b/xo-stringtable2/include/xo/stringtable2/uniquestring/IGCObject_DUniqueString.hpp @@ -0,0 +1,69 @@ +/** @file IGCObject_DUniqueString.hpp + * + * Generated automagically from ingredients: + * 1. code generator: + * [xo-facet/codegen/genfacet] + * arguments: + * --input [idl/IGCObject_DUniqueString.json5] + * 2. jinja2 template for abstract facet .hpp file: + * [iface_facet_repr.hpp.j2] + * 3. idl for facet methods + * [idl/IGCObject_DUniqueString.json5] + **/ + +#pragma once + +#include "GCObject.hpp" +#include +#include +#include "DUniqueString.hpp" + +namespace xo { namespace scm { class IGCObject_DUniqueString; } } + +namespace xo { + namespace facet { + template <> + struct FacetImplementation + { + using ImplType = xo::mm::IGCObject_Xfer + ; + }; + } +} + +namespace xo { + namespace scm { + /** @class IGCObject_DUniqueString + **/ + class IGCObject_DUniqueString { + public: + /** @defgroup scm-gcobject-duniquestring-type-traits **/ + ///@{ + using size_type = xo::mm::AGCObject::size_type; + using AAllocator = xo::mm::AGCObject::AAllocator; + using AGCObjectVisitor = xo::mm::AGCObject::AGCObjectVisitor; + using VisitReason = xo::mm::AGCObject::VisitReason; + using Copaque = xo::mm::AGCObject::Copaque; + using Opaque = xo::mm::AGCObject::Opaque; + ///@} + /** @defgroup scm-gcobject-duniquestring-methods **/ + ///@{ + // const methods + + // non-const methods + /** move instance using object visitor. +Arguably abusing the word 'visitor' here **/ + static Opaque gco_shallow_move(DUniqueString & self, obj gc) noexcept; + /** Invoke fn.visit_child(iface,data) for each child GCObject pointer. +Context: provides address of data pointer so it can be updated in place +when @p fn invokes garbage collector reentry point **/ + static void visit_gco_children(DUniqueString & self, VisitReason reason, obj fn) noexcept; + ///@} + }; + + } /*namespace scm*/ +} /*namespace xo*/ + +/* end */ \ No newline at end of file diff --git a/xo-stringtable2/include/xo/stringtable2/uniquestring/IPrintable_DUniqueString.hpp b/xo-stringtable2/include/xo/stringtable2/uniquestring/IPrintable_DUniqueString.hpp new file mode 100644 index 00000000..a57039a8 --- /dev/null +++ b/xo-stringtable2/include/xo/stringtable2/uniquestring/IPrintable_DUniqueString.hpp @@ -0,0 +1,62 @@ +/** @file IPrintable_DUniqueString.hpp + * + * Generated automagically from ingredients: + * 1. code generator: + * [xo-facet/codegen/genfacet] + * arguments: + * --input [idl/IPrintable_DUniqueString.json5] + * 2. jinja2 template for abstract facet .hpp file: + * [iface_facet_repr.hpp.j2] + * 3. idl for facet methods + * [idl/IPrintable_DUniqueString.json5] + **/ + +#pragma once + +#include "Printable.hpp" +#include +#include +#include "DUniqueString.hpp" + +namespace xo { namespace scm { class IPrintable_DUniqueString; } } + +namespace xo { + namespace facet { + template <> + struct FacetImplementation + { + using ImplType = xo::print::IPrintable_Xfer + ; + }; + } +} + +namespace xo { + namespace scm { + /** @class IPrintable_DUniqueString + **/ + class IPrintable_DUniqueString { + public: + /** @defgroup scm-printable-duniquestring-type-traits **/ + ///@{ + using ppindentinfo = xo::print::APrintable::ppindentinfo; + using Copaque = xo::print::APrintable::Copaque; + using Opaque = xo::print::APrintable::Opaque; + ///@} + /** @defgroup scm-printable-duniquestring-methods **/ + ///@{ + // const methods + /** Pretty-printing support for this object. +See [xo-indentlog/xo/indentlog/pretty.hpp] **/ + static bool pretty(const DUniqueString & self, const ppindentinfo & ppii); + + // non-const methods + ///@} + }; + + } /*namespace scm*/ +} /*namespace xo*/ + +/* end */ \ No newline at end of file diff --git a/xo-stringtable2/src/stringtable2/CMakeLists.txt b/xo-stringtable2/src/stringtable2/CMakeLists.txt new file mode 100644 index 00000000..b861c4c0 --- /dev/null +++ b/xo-stringtable2/src/stringtable2/CMakeLists.txt @@ -0,0 +1,34 @@ +# xo-stringtable2/src/stringtable2/CMakeLists.txt + +set(SELF_LIB xo_stringtable2) +set(SELF_SRCS + init_stringtable2.cpp + SetupStringtable2.cpp + + StringTable.cpp + + DString.cpp + IGCObject_DString.cpp + IPrintable_DString.cpp + + DUniqueString.cpp + IGCObject_DUniqueString.cpp + IPrintable_DUniqueString.cpp + +) + +xo_add_shared_library4(${SELF_LIB} ${PROJECT_NAME}Targets ${PROJECT_VERSION} 1 ${SELF_SRCS}) +xo_install_include_tree3(include/xo/stringtable2) + +# ---------------------------------------------------------------- +# input dependencies +# +# NOTE: dependency set here must be kept consistent with +# xo-stringtable2/cmake/xo_stringtable2Config.cmake.in + +xo_dependency(${SELF_LIB} xo_alloc2) +xo_dependency(${SELF_LIB} xo_printable2) +xo_dependency(${SELF_LIB} subsys) +xo_dependency(${SELF_LIB} indentlog) + +# end src/stringtable2/CMakeLists.txt diff --git a/xo-stringtable2/src/stringtable2/DString.cpp b/xo-stringtable2/src/stringtable2/DString.cpp new file mode 100644 index 00000000..17a94b70 --- /dev/null +++ b/xo-stringtable2/src/stringtable2/DString.cpp @@ -0,0 +1,191 @@ +/** @file DString.cpp + * + * @author Roland Conybeare, Jan 2026 + **/ + +#include "DString.hpp" +#include +#include +#include +#include + +namespace xo { + using xo::reflect::typeseq; + using xo::print::ppdetail_atomic; + + namespace scm { + DString * + DString::empty(obj mm, + size_type cap) + { + assert(cap > 0); + + DString * result = nullptr; + + if (cap > 0) { + void * mem = mm.alloc(typeseq::id(), + sizeof(DString) + cap); + + if (mem) { + result = new (mem) DString(); + + assert(result); + + result->capacity_ = cap; + result->size_ = 0; + if (cap > 0) { + result->chars_[0] = '\0'; + } + } + } + + return result; + } + + DString * + DString::from_cstr(obj mm, + const char * cstr) + { + size_type len = std::strlen(cstr); + size_type cap = len + 1; + + void * mem = mm.alloc(typeseq::id(), + sizeof(DString) + cap); + + DString * result = nullptr; + + if (mem) { + result = new (mem) DString(); + result->capacity_ = cap; + result->size_ = len; + + std::memcpy(result->chars_, cstr, cap); + } + + return result; + } + + DString * + DString::_from_view_aux(obj mm, + std::string_view sv, + bool suballoc_flag) + { + size_type len = sv.size(); + size_type cap = len + 1; + + auto tseq = typeseq::id(); + void * mem = nullptr; + size_type mem_z = sizeof(DString) + cap; + + if (suballoc_flag) + mem = mm.sub_alloc(mem_z, false /*!complete_flag*/); + else + mem = mm.alloc(tseq, mem_z); + + DString * result = new (mem) DString(); + + result->capacity_ = cap; + result->size_ = len; + std::memcpy(result->chars_, sv.data(), len); + result->chars_[len] = '\0'; + + return result; + } + + DString * + DString::from_view(obj mm, + std::string_view sv) + { + return _from_view_aux(mm, sv, false /*!suballoc_flag*/); + } + + DString * + DString::from_str(obj mm, + const std::string & str) + { + return _from_view_aux(mm, + std::string_view(str), + false /*!suballoc_flag*/); + } + + DString * + DString::from_view_suballoc(obj mm, + std::string_view sv) + { + return _from_view_aux(mm, sv, true /*suballoc_flag*/); + } + + DString * + DString::clone(obj mm, const DString * src) + { + size_type cap = src->capacity_; + + void * mem = mm.alloc(typeseq::id(), + sizeof(DString) + cap); + + DString * result = new (mem) DString(); + result->capacity_ = cap; + result->size_ = src->size_; + std::memcpy(result->chars_, src->chars_, cap); + + return result; + } + + DString & + DString::assign(const DString & other) + { + size_type n = std::min(other.size_, capacity_ - 1); + std::memcpy(chars_, other.chars_, n); + chars_[n] = '\0'; + size_ = n; + + return *this; + } + + int + DString::compare(const DString & lhs, const DString & rhs) noexcept + { + return ::strcmp(lhs.chars_, rhs.chars_); + } + + auto + DString::fixup_size() noexcept -> size_type + { + this->chars_[capacity_ - 1] = '\0'; + this->size_ = ::strlen(chars_); + return this->size_; + } + + DString * + DString::gco_shallow_move(obj gc) noexcept + { + // note: not using gc.std_move_for() here + // b/c DString flexible array means not move-constructible + + DString * copy = (DString *)gc.alloc_copy_for(this); + + if (copy) { + copy->capacity_ = capacity_; + copy->size_ = size_; + ::memcpy(copy->chars_, chars_, capacity_); + } + + return copy; + } + + void + DString::visit_gco_children(VisitReason, obj) noexcept + { + // no-op. no children! + } + + bool + DString::pretty(const ppindentinfo & ppii) const + { + return ppdetail_atomic::print_pretty(ppii, &(chars_[0])); + } + + } /*namespace scm*/ +} /*namespace xo*/ + +/* end DString.cpp */ diff --git a/xo-stringtable2/src/stringtable2/DUniqueString.cpp b/xo-stringtable2/src/stringtable2/DUniqueString.cpp new file mode 100644 index 00000000..bc63cf0d --- /dev/null +++ b/xo-stringtable2/src/stringtable2/DUniqueString.cpp @@ -0,0 +1,119 @@ +/** @file DUniqueString.cpp +* + * @author Roland Conybeare, Jan 2026 + **/ + +#include "DUniqueString.hpp" +#include "DString.hpp" +#include +#include +#include + +namespace xo { + using xo::mm::padding; + using xo::facet::typeseq; + + namespace scm { + int + DUniqueString::compare(const DUniqueString & lhs, const DUniqueString & rhs) + { + if (&lhs == &rhs) + return 0; + + return DString::compare(*(lhs._text()), *(rhs._text())); + } + + DString * + DUniqueString::_text() const noexcept + { + // location of paired DString is chosen + // by allocator (DArena, probably). + // + // In general allocator alignment more conservative + // than C++ alignment + // + // Remmebr also: although DUniqueString has zero members, + // C++ requires it to behave asif size at least 1 byte + // for iterator consistency + // (e.g. because c++ would support iterating over + // std::vector) + // + size_t offset = padding::with_padding(sizeof(*this)); + assert(offset > 0); + + return (DString *)(((std::byte *)this) + offset); + } + + bool + DUniqueString::pretty(const ppindentinfo & ppii) const + { + return _text()->pretty(ppii); + } + + DUniqueString * + DUniqueString::from_view(obj mm, + std::string_view sv) + { + scope log(XO_DEBUG(false)); + + /** fine point: choosing to allocate DUniqueString ahead of DString, + * so it comes first in bump allocator + **/ + + void * mem = mm.super_alloc(typeseq::id(), + sizeof(DUniqueString)); + DUniqueString * result = new (mem) DUniqueString(); + + /** allocated in memory immediate following @p result. + * This optimization saves us one pointer (8 bytes) in DUniqueString + * itself, plus one allocation header (8 bytes) for 16 bytes total + **/ + DString * text = DString::from_view_suballoc(mm, sv); + + log && log(xtag("result", result), xtag("result.text", result->_text()), xtag("text", text)); + + assert(text); + assert(text == result->_text()); + + /** must finish super-allocation before next alloc **/ + mm.sub_alloc(0, true); + + return result; + } + + DUniqueString * + DUniqueString::gco_shallow_move(obj gc) noexcept + { + // well-posed, but not expected to be used. + // + // Not using gc.std_move_for() here because compiler doesn't know + // actual alloc size of a DUniqueString instance + + assert(false); + + DUniqueString * copy = (DUniqueString *)gc.alloc_copy((std::byte *)this); + + if (copy) { + // Copy assignment not implemented in general + // *copy = *this; + // in this case *copy already has the same size as *this + + assert(size() <= capacity()); + + strncpy(copy->_text()->data(), + this->_text()->chars(), + this->size()); + } + + return copy; + } + + void + DUniqueString::visit_gco_children(VisitReason, obj) noexcept + { + // no-op -- childless! + } + } /*namespace scm*/ +} /*namespace xo*/ + +/* end DUniqueString.cpp */ diff --git a/xo-stringtable2/src/stringtable2/IGCObject_DString.cpp b/xo-stringtable2/src/stringtable2/IGCObject_DString.cpp new file mode 100644 index 00000000..a3a384bb --- /dev/null +++ b/xo-stringtable2/src/stringtable2/IGCObject_DString.cpp @@ -0,0 +1,32 @@ +/** @file IGCObject_DString.cpp + * + * Generated automagically from ingredients: + * 1. code generator: + * [xo-facet/codegen/genfacet] + * arguments: + * --input [idl/IGCObject_DString.json5] + * 2. jinja2 template for abstract facet .hpp file: + * [iface_facet_any.hpp.j2] + * 3. idl for facet methods + * [idl/IGCObject_DString.json5] +**/ + +#include "string/IGCObject_DString.hpp" + +namespace xo { + namespace scm { + auto + IGCObject_DString::gco_shallow_move(DString & self, obj gc) noexcept -> Opaque + { + return self.gco_shallow_move(gc); + } + auto + IGCObject_DString::visit_gco_children(DString & self, VisitReason reason, obj fn) noexcept -> void + { + self.visit_gco_children(reason, fn); + } + + } /*namespace scm*/ +} /*namespace xo*/ + +/* end IGCObject_DString.cpp */ diff --git a/xo-stringtable2/src/stringtable2/IGCObject_DUniqueString.cpp b/xo-stringtable2/src/stringtable2/IGCObject_DUniqueString.cpp new file mode 100644 index 00000000..88fbaaa4 --- /dev/null +++ b/xo-stringtable2/src/stringtable2/IGCObject_DUniqueString.cpp @@ -0,0 +1,32 @@ +/** @file IGCObject_DUniqueString.cpp + * + * Generated automagically from ingredients: + * 1. code generator: + * [xo-facet/codegen/genfacet] + * arguments: + * --input [idl/IGCObject_DUniqueString.json5] + * 2. jinja2 template for abstract facet .hpp file: + * [iface_facet_any.hpp.j2] + * 3. idl for facet methods + * [idl/IGCObject_DUniqueString.json5] +**/ + +#include "uniquestring/IGCObject_DUniqueString.hpp" + +namespace xo { + namespace scm { + auto + IGCObject_DUniqueString::gco_shallow_move(DUniqueString & self, obj gc) noexcept -> Opaque + { + return self.gco_shallow_move(gc); + } + auto + IGCObject_DUniqueString::visit_gco_children(DUniqueString & self, VisitReason reason, obj fn) noexcept -> void + { + self.visit_gco_children(reason, fn); + } + + } /*namespace scm*/ +} /*namespace xo*/ + +/* end IGCObject_DUniqueString.cpp */ diff --git a/xo-stringtable2/src/stringtable2/IPrintable_DString.cpp b/xo-stringtable2/src/stringtable2/IPrintable_DString.cpp new file mode 100644 index 00000000..f5ec4967 --- /dev/null +++ b/xo-stringtable2/src/stringtable2/IPrintable_DString.cpp @@ -0,0 +1,28 @@ +/** @file IPrintable_DString.cpp + * + * Generated automagically from ingredients: + * 1. code generator: + * [xo-facet/codegen/genfacet] + * arguments: + * --input [idl/IPrintable_DString.json5] + * 2. jinja2 template for abstract facet .hpp file: + * [iface_facet_any.hpp.j2] + * 3. idl for facet methods + * [idl/IPrintable_DString.json5] +**/ + +#include "string/IPrintable_DString.hpp" + +namespace xo { + namespace scm { + auto + IPrintable_DString::pretty(const DString & self, const ppindentinfo & ppii) -> bool + { + return self.pretty(ppii); + } + + + } /*namespace scm*/ +} /*namespace xo*/ + +/* end IPrintable_DString.cpp */ diff --git a/xo-stringtable2/src/stringtable2/IPrintable_DUniqueString.cpp b/xo-stringtable2/src/stringtable2/IPrintable_DUniqueString.cpp new file mode 100644 index 00000000..ef704dee --- /dev/null +++ b/xo-stringtable2/src/stringtable2/IPrintable_DUniqueString.cpp @@ -0,0 +1,28 @@ +/** @file IPrintable_DUniqueString.cpp + * + * Generated automagically from ingredients: + * 1. code generator: + * [xo-facet/codegen/genfacet] + * arguments: + * --input [idl/IPrintable_DUniqueString.json5] + * 2. jinja2 template for abstract facet .hpp file: + * [iface_facet_any.hpp.j2] + * 3. idl for facet methods + * [idl/IPrintable_DUniqueString.json5] +**/ + +#include "uniquestring/IPrintable_DUniqueString.hpp" + +namespace xo { + namespace scm { + auto + IPrintable_DUniqueString::pretty(const DUniqueString & self, const ppindentinfo & ppii) -> bool + { + return self.pretty(ppii); + } + + + } /*namespace scm*/ +} /*namespace xo*/ + +/* end IPrintable_DUniqueString.cpp */ diff --git a/xo-stringtable2/src/stringtable2/SetupStringtable2.cpp b/xo-stringtable2/src/stringtable2/SetupStringtable2.cpp new file mode 100644 index 00000000..2c43a16e --- /dev/null +++ b/xo-stringtable2/src/stringtable2/SetupStringtable2.cpp @@ -0,0 +1,55 @@ +/** @file SetupStringtable2.cpp + * + * @author Roland Conybeare, Mar 2026 + **/ + +#include "SetupStringtable2.hpp" + +#include +#include + +#include +#include + +namespace xo { + using xo::print::APrintable; + using xo::mm::ACollector; + using xo::mm::AGCObject; + using xo::scm::DString; + using xo::facet::FacetRegistry; + using xo::facet::typeseq; + using xo::facet::impl_for; + + namespace scm { + bool + SetupStringtable2::register_facets() + { + scope log(XO_DEBUG(true)); + + FacetRegistry::register_impl(); + FacetRegistry::register_impl(); + + FacetRegistry::register_impl(); + FacetRegistry::register_impl(); + + log && log(xtag("DString.tseq", typeseq::id())); + + return true; + } + + bool + SetupStringtable2::register_types(obj gc) + { + scope log(XO_DEBUG(true)); + + bool ok = true; + + ok &= gc.install_type(impl_for()); + ok &= gc.install_type(impl_for()); + + return ok; + } + } /*namespace scm*/ +} /*namespace xo*/ + +/* end SetupStringtable2.cpp */ diff --git a/xo-stringtable2/src/stringtable2/StringTable.cpp b/xo-stringtable2/src/stringtable2/StringTable.cpp new file mode 100644 index 00000000..2d23d03e --- /dev/null +++ b/xo-stringtable2/src/stringtable2/StringTable.cpp @@ -0,0 +1,173 @@ +/** @file StringTable.cpp +* + * @author Roland Conybeare, Jan 2026 + **/ + +#include "StringTable.hpp" +#include +#include + +namespace xo { + using xo::mm::ArenaConfig; + using xo::mm::AAllocator; + using xo::mm::MemorySizeInfo; + using xo::facet::with_facet; + using xo::facet::obj; + + namespace scm { + StringTable::StringTable(size_type hint_max_capacity, + bool debug_flag) + : strings_{DArena::map(ArenaConfig{.name_ = "strings", + .size_ = hint_max_capacity})}, + map_{"stringkeys", hint_max_capacity} + { + (void)debug_flag; + } + + const DUniqueString * + StringTable::lookup(std::string_view key) const + { + auto ix = map_.find(key); + + if (ix != map_.end()) + return ix->second; + + return nullptr; + } + + const DUniqueString * + StringTable::intern(std::string_view key) + { + // 1a. lookup key in map_. + // 1b. if present, return existing DString* + + auto ix = map_.find(key); + + if (ix != map_.end()) + return ix->second; + + // 2. otherwise need to add. + // + // 2d. return key2 address + + // 2a. allocate DUniqueString copy 'interned' of key in strings_ + auto mm = with_facet::mkobj(&strings_); + DUniqueString * interned = DUniqueString::from_view(mm, key); + + assert(interned); + if (interned) { + // 2b. make string_view from *interned + std::string_view interned_key = std::string_view(*interned); + + // interned_key has same lifetime as StringTable, + // we can use it in map_ + + // 2c. store address of 'interned' in map_ + auto & slot = this->map_[interned_key]; + + slot = interned; + + return slot; + } + + return nullptr; + } + + const DUniqueString * + StringTable::gensym(std::string_view prefix) + { + static std::size_t s_counter = 0; + + while (true) { + ++s_counter; + + char buf[80]; + assert(prefix.size() + 20 < sizeof(buf)); + + int n = snprintf(buf, sizeof(buf), + "%s:%lu", + prefix.data(), s_counter); + + if ((0 < n) && (std::size_t(n) < sizeof(buf))) + buf[n] = '\0'; + else + buf[sizeof(buf)-1] = '\0'; + + std::string_view sv(buf); + const DUniqueString * retval = this->lookup(sv); + if (!retval) { + /* not already in string view -> we have viable candidate */ + retval = this->intern(sv); + return retval; + } + } + } + + bool + StringTable::verify_ok(verify_policy policy) const + { + using xo::scope; + using xo::xtag; + + constexpr const char * c_self = "StringTable::verify_ok"; + scope log(XO_DEBUG(false)); + + /* ST1: underlying hash map passes its invariants */ + if (!map_.verify_ok(policy)) { + return policy.report_error(log, + c_self, ": map_.verify_ok failed"); + } + + /* ST2: for each entry, key points to value's string data */ + for (const auto & kv : map_) { + const std::string_view & key = kv.first; + const DUniqueString * value = kv.second; + + /* ST2.1: value is not null */ + if (value == nullptr) { + return policy.report_error(log, + c_self, ": null value in map", + xtag("key", key)); + } + + /* ST2.2: value lies within strings_ arena */ + if (!strings_.contains(value)) { + return policy.report_error(log, + c_self, ": value not in strings_ arena", + xtag("key", key), + xtag("value", (void*)value)); + } + + /* ST2.3: key.data() points to value's chars */ + if (key.data() != value->chars()) { + return policy.report_error(log, + c_self, ": key.data() != value->chars()", + xtag("key", key), + xtag("key.data()", (void*)key.data()), + xtag("value->chars()", (void*)value->chars())); + } + + /* ST2.4: key.size() == value->size() */ + if (key.size() != value->size()) { + return policy.report_error(log, + c_self, ": key.size() != value->size()", + xtag("key", key), + xtag("key.size()", key.size()), + xtag("value->size()", value->size())); + } + } + + return true; + } + + void + StringTable::visit_pools(const MemorySizeVisitor & visitor) const + { + strings_.visit_pools(visitor); + map_.visit_pools(visitor); + } + + } /*namespace scm*/ +} /*namespace xo*/ + +/* end StringTable.cpp */ diff --git a/xo-stringtable2/src/stringtable2/init_stringtable2.cpp b/xo-stringtable2/src/stringtable2/init_stringtable2.cpp new file mode 100644 index 00000000..2b09c587 --- /dev/null +++ b/xo-stringtable2/src/stringtable2/init_stringtable2.cpp @@ -0,0 +1,40 @@ +/** @file init_stringtable2.cpp + * + * @author Roland Conybeare, Jan 2026 + **/ + +#include "init_stringtable2.hpp" +#include "SetupStringtable2.hpp" +#include +#include +#include + +namespace xo { + using xo::scm::SetupStringtable2; + using xo::mm::CollectorTypeRegistry; + + void + InitSubsys::init() + { + SetupStringtable2::register_facets(); + + CollectorTypeRegistry::instance().register_types(&SetupStringtable2::register_types); + } + + InitEvidence + InitSubsys::require() + { + InitEvidence retval; + + /* direct subsystem deps for xo-stringtable2/ */ + retval ^= InitSubsys::require(); + //retval ^= InitSubsys::require(); + + /* xo-expression2/'s own initialization code */ + retval ^= Subsystem::provide("stringtable2", &init); + + return retval; + } +} /*namespace xo*/ + +/* end init_stringtable2.cpp */ diff --git a/xo-stringtable2/utest/CMakeLists.txt b/xo-stringtable2/utest/CMakeLists.txt new file mode 100644 index 00000000..46f4a4e7 --- /dev/null +++ b/xo-stringtable2/utest/CMakeLists.txt @@ -0,0 +1,14 @@ +# built unittest xo-object2/utest + +set(UTEST_EXE utest.stringtable2) +set(UTEST_SRCS + stringtable2_utest_main.cpp + StringTable.test.cpp + DString.test.cpp + StringOps.test.cpp +) + +xo_add_utest_executable(${UTEST_EXE} ${UTEST_SRCS}) +xo_self_dependency(${UTEST_EXE} xo_stringtable2) +#xo_dependency(${UTEST_EXE} randomgen) +xo_external_target_dependency(${UTEST_EXE} Catch2 Catch2::Catch2) diff --git a/xo-stringtable2/utest/DString.test.cpp b/xo-stringtable2/utest/DString.test.cpp new file mode 100644 index 00000000..1f095b0a --- /dev/null +++ b/xo-stringtable2/utest/DString.test.cpp @@ -0,0 +1,430 @@ +/** @file DString.test.cpp + * + * @author Roland Conybeare, Jan 2026 + **/ + +#include "init_stringtable2.hpp" +#include "StringOps.hpp" +#include +#include +//#include +#include +#include +#include +#include + +namespace xo { + using xo::scm::DString; + using xo::mm::AAllocator; + using xo::mm::DArena; + using xo::mm::ArenaConfig; + using xo::facet::with_facet; + using xo::facet::obj; + + namespace ut { + + static InitEvidence s_init = (InitSubsys::require()); + + TEST_CASE("DString-init", "[object2][DString]") + { + // real purpose: ensure s_init survives static linking + REQUIRE(s_init.evidence()); + } + + TEST_CASE("DString-empty", "[object2][DString]") + { + ArenaConfig cfg { .name_ = "testarena", + .size_ = 4*1024 }; + DArena arena = DArena::map(cfg); + auto alloc = with_facet::mkobj(&arena); + + DString * s = DString::empty(alloc, 16); + + REQUIRE(s != nullptr); + REQUIRE(s->capacity() == 16); + REQUIRE(s->size() == 0); + REQUIRE(s->chars()[0] == '\0'); + } + + TEST_CASE("DString-from_cstr", "[object2][DString]") + { + ArenaConfig cfg { .name_ = "testarena", + .size_ = 4*1024 }; + DArena arena = DArena::map(cfg); + auto alloc = with_facet::mkobj(&arena); + + const char * cstr = "hello world"; + DString * s = DString::from_cstr(alloc, cstr); + + REQUIRE(s != nullptr); + REQUIRE(s->capacity() == 12); + REQUIRE(s->size() == 11); + REQUIRE(std::strcmp(s->chars(), cstr) == 0); + } + + TEST_CASE("DString-from_view", "[object2][DString]") + { + ArenaConfig cfg { .name_ = "testarena", + .size_ = 4*1024 }; + DArena arena = DArena::map(cfg); + auto alloc = with_facet::mkobj(&arena); + + std::string_view sv = "hello world"; + DString * s = DString::from_view(alloc, sv); + + REQUIRE(s != nullptr); + REQUIRE(s->capacity() == 12); + REQUIRE(s->size() == 11); + REQUIRE(std::strcmp(s->chars(), "hello world") == 0); + + // test with substring (not null-terminated) + std::string_view sub = sv.substr(0, 5); + DString * s2 = DString::from_view(alloc, sub); + + REQUIRE(s2 != nullptr); + REQUIRE(s2->capacity() == 6); + REQUIRE(s2->size() == 5); + REQUIRE(std::strcmp(s2->chars(), "hello") == 0); + } + + TEST_CASE("DString-assign", "[object2][DString]") + { + ArenaConfig cfg { .name_ = "testarena", + .size_ = 4*1024 }; + DArena arena = DArena::map(cfg); + auto alloc = with_facet::mkobj(&arena); + + DString * src = DString::from_cstr(alloc, "hello"); + DString * dst = DString::empty(alloc, 16); + + dst->assign(*src); + + REQUIRE(dst->size() == 5); + REQUIRE(std::strcmp(dst->chars(), "hello") == 0); + } + + TEST_CASE("DString-assign-truncate", "[object2][DString]") + { + ArenaConfig cfg { .name_ = "testarena", + .size_ = 4*1024 }; + DArena arena = DArena::map(cfg); + auto alloc = with_facet::mkobj(&arena); + + DString * src = DString::from_cstr(alloc, "hello world"); + DString * dst = DString::empty(alloc, 6); + + dst->assign(*src); + + REQUIRE(dst->size() == 5); + REQUIRE(std::strcmp(dst->chars(), "hello") == 0); + } + + TEST_CASE("DString-data", "[object2][DString]") + { + ArenaConfig cfg { .name_ = "testarena", + .size_ = 4*1024 }; + DArena arena = DArena::map(cfg); + auto alloc = with_facet::mkobj(&arena); + + DString * s = DString::empty(alloc, 16); + char * p = s->data(); + std::strcpy(p, "test"); + s->fixup_size(); + + REQUIRE(s->size() == 4); + REQUIRE(std::strcmp(s->chars(), "test") == 0); + } + + TEST_CASE("DString-operator-bracket", "[object2][DString]") + { + ArenaConfig cfg { .name_ = "testarena", + .size_ = 4*1024 }; + DArena arena = DArena::map(cfg); + auto alloc = with_facet::mkobj(&arena); + + DString * s = DString::from_cstr(alloc, "hello"); + + REQUIRE((*s)[0] == 'h'); + REQUIRE((*s)[4] == 'o'); + + (*s)[0] = 'H'; + REQUIRE(std::strcmp(s->chars(), "Hello") == 0); + } + + TEST_CASE("DString-clear", "[object2][DString]") + { + ArenaConfig cfg { .name_ = "testarena", + .size_ = 4*1024 }; + DArena arena = DArena::map(cfg); + auto alloc = with_facet::mkobj(&arena); + + DString * s = DString::from_cstr(alloc, "hello"); + REQUIRE(s->size() == 5); + + s->clear(); + + REQUIRE(s->size() == 0); + REQUIRE(s->chars()[0] == '\0'); + REQUIRE(s->capacity() == 6); + } + + TEST_CASE("DString-fixup_size", "[object2][DString]") + { + ArenaConfig cfg { .name_ = "testarena", + .size_ = 4*1024 }; + DArena arena = DArena::map(cfg); + auto alloc = with_facet::mkobj(&arena); + + DString * s = DString::empty(alloc, 16); + char * p = s->data(); + p[0] = 'a'; + p[1] = 'b'; + p[2] = 'c'; + p[3] = '\0'; + + REQUIRE(s->size() == 0); + + auto new_size = s->fixup_size(); + + REQUIRE(new_size == 3); + REQUIRE(s->size() == 3); + } + + TEST_CASE("DString-string_view", "[object2][DString]") + { + ArenaConfig cfg { .name_ = "testarena", + .size_ = 4*1024 }; + DArena arena = DArena::map(cfg); + auto alloc = with_facet::mkobj(&arena); + + DString * s = DString::from_cstr(alloc, "hello"); + std::string_view sv = *s; + + REQUIRE(sv == "hello"); + REQUIRE(sv.size() == 5); + } + + TEST_CASE("DString-cstr-conversion", "[object2][DString]") + { + ArenaConfig cfg { .name_ = "testarena", + .size_ = 4*1024 }; + DArena arena = DArena::map(cfg); + auto alloc = with_facet::mkobj(&arena); + + DString * s = DString::from_cstr(alloc, "hello"); + const char * cstr = *s; + + REQUIRE(std::strcmp(cstr, "hello") == 0); + } + + TEST_CASE("DString-begin-end", "[object2][DString]") + { + ArenaConfig cfg { .name_ = "testarena", + .size_ = 4*1024 }; + DArena arena = DArena::map(cfg); + auto alloc = with_facet::mkobj(&arena); + + DString * s = DString::from_cstr(alloc, "hello"); + + REQUIRE(s->begin() == s->chars()); + REQUIRE(s->end() == s->chars() + 5); + REQUIRE(s->end() - s->begin() == 5); + + *s->begin() = 'H'; + REQUIRE(std::strcmp(s->chars(), "Hello") == 0); + } + + TEST_CASE("DString-cbegin-cend", "[object2][DString]") + { + ArenaConfig cfg { .name_ = "testarena", + .size_ = 4*1024 }; + DArena arena = DArena::map(cfg); + auto alloc = with_facet::mkobj(&arena); + + DString * s = DString::from_cstr(alloc, "hello"); + + REQUIRE(s->cbegin() == s->chars()); + REQUIRE(s->cend() == s->chars() + 5); + REQUIRE(s->cend() - s->cbegin() == 5); + } + + TEST_CASE("DString-range-for", "[object2][DString]") + { + ArenaConfig cfg { .name_ = "testarena", + .size_ = 4*1024 }; + DArena arena = DArena::map(cfg); + auto alloc = with_facet::mkobj(&arena); + + DString * s = DString::from_cstr(alloc, "hello"); + + std::string result; + for (char c : *s) { + result += c; + } + + REQUIRE(result == "hello"); + } + + TEST_CASE("DString-iterator-modify", "[object2][DString]") + { + ArenaConfig cfg { .name_ = "testarena", + .size_ = 4*1024 }; + DArena arena = DArena::map(cfg); + auto alloc = with_facet::mkobj(&arena); + + DString * s = DString::from_cstr(alloc, "hello"); + + for (auto it = s->begin(); it != s->end(); ++it) { + *it = std::toupper(*it); + } + + REQUIRE(std::strcmp(s->chars(), "HELLO") == 0); + } + + TEST_CASE("DString-clone", "[object2][DString]") + { + ArenaConfig cfg { .name_ = "testarena", + .size_ = 4*1024 }; + DArena arena = DArena::map(cfg); + auto alloc = with_facet::mkobj(&arena); + + DString * src = DString::from_cstr(alloc, "hello world"); + DString * copy = DString::clone(alloc, src); + + REQUIRE(copy != nullptr); + REQUIRE(copy != src); + REQUIRE(copy->size() == src->size()); + REQUIRE(copy->capacity() == src->capacity()); + REQUIRE(std::strcmp(copy->chars(), src->chars()) == 0); + } + + TEST_CASE("DString-sprintf", "[object2][DString]") + { + ArenaConfig cfg { .name_ = "testarena", + .size_ = 4*1024 }; + DArena arena = DArena::map(cfg); + auto alloc = with_facet::mkobj(&arena); + + DString * s = DString::empty(alloc, 32); + + auto n = s->sprintf("hello %s %d", "world", 42); + + REQUIRE(n == 14); + REQUIRE(s->size() == 14); + REQUIRE(std::strcmp(s->chars(), "hello world 42") == 0); + } + + TEST_CASE("DString-sprintf-truncate", "[object2][DString]") + { + ArenaConfig cfg { .name_ = "testarena", + .size_ = 4*1024 }; + DArena arena = DArena::map(cfg); + auto alloc = with_facet::mkobj(&arena); + + DString * s = DString::empty(alloc, 8); + + auto n = s->sprintf("hello world"); + + REQUIRE(n == 7); + REQUIRE(s->size() == 7); + REQUIRE(std::strcmp(s->chars(), "hello w") == 0); + } + + TEST_CASE("DString-compare", "[object2][DString]") + { + ArenaConfig cfg { .name_ = "testarena", + .size_ = 4*1024 }; + DArena arena = DArena::map(cfg); + auto alloc = with_facet::mkobj(&arena); + + DString * s1 = DString::from_cstr(alloc, "apple"); + DString * s2 = DString::from_cstr(alloc, "apple"); + DString * s3 = DString::from_cstr(alloc, "banana"); + DString * s4 = DString::from_cstr(alloc, "aardvark"); + + REQUIRE(DString::compare(*s1, *s2) == 0); + REQUIRE(DString::compare(*s1, *s3) < 0); + REQUIRE(DString::compare(*s3, *s1) > 0); + REQUIRE(DString::compare(*s1, *s4) > 0); + REQUIRE(DString::compare(*s4, *s1) < 0); + } + + TEST_CASE("DString-comparison-operators", "[object2][DString]") + { + ArenaConfig cfg { .name_ = "testarena", + .size_ = 4*1024 }; + DArena arena = DArena::map(cfg); + auto alloc = with_facet::mkobj(&arena); + + DString * apple1 = DString::from_cstr(alloc, "apple"); + DString * apple2 = DString::from_cstr(alloc, "apple"); + DString * banana = DString::from_cstr(alloc, "banana"); + + // operator== + REQUIRE(*apple1 == *apple1); + REQUIRE(*apple2 == *apple2); + REQUIRE(*banana == *banana); + REQUIRE(*apple1 == *apple2); + + REQUIRE_FALSE(*apple1 == *banana); + + // operator!= + REQUIRE(*apple1 != *banana); + REQUIRE_FALSE(*apple1 != *apple1); + REQUIRE_FALSE(*apple2 != *apple2); + REQUIRE_FALSE(*apple1 != *apple2); + REQUIRE_FALSE(*banana != *banana); + + // operator< + REQUIRE(*apple1 < *banana); + REQUIRE_FALSE(*banana < *apple1); + REQUIRE_FALSE(*apple1 < *apple2); + + // operator<= + REQUIRE(*apple1 <= *banana); + REQUIRE(*apple1 <= *apple2); + REQUIRE_FALSE(*banana <= *apple1); + + // operator> + REQUIRE(*banana > *apple1); + REQUIRE_FALSE(*apple1 > *banana); + REQUIRE_FALSE(*apple1 > *apple2); + + // operator>= + REQUIRE(*banana >= *apple1); + REQUIRE(*apple1 >= *apple2); + REQUIRE_FALSE(*apple1 >= *banana); + } + + TEST_CASE("DString-hash", "[object2][DString]") + { + ArenaConfig cfg { .name_ = "testarena", + .size_ = 4*1024 }; + DArena arena = DArena::map(cfg); + auto alloc = with_facet::mkobj(&arena); + + DString * s1 = DString::from_cstr(alloc, "hello"); + DString * s2 = DString::from_cstr(alloc, "hello"); + DString * s3 = DString::from_cstr(alloc, "world"); + DString * empty1 = DString::empty(alloc, 16); + DString * empty2 = DString::empty(alloc, 32); + + // same content produces same hash + REQUIRE(s1->hash() == s2->hash()); + + // empty strings have same hash + REQUIRE(empty1->hash() == empty2->hash()); + + // different content produces different hash (not guaranteed, but highly likely) + REQUIRE(s1->hash() != s3->hash()); + + // std::hash specialization works + std::hash hasher; + REQUIRE(hasher(*s1) == s1->hash()); + REQUIRE(hasher(*s2) == s2->hash()); + REQUIRE(hasher(*s1) == hasher(*s2)); + } + } /*namespace ut*/ +} /*namespace xo*/ + +/* end DString.test.cpp */ diff --git a/xo-stringtable2/utest/StringOps.test.cpp b/xo-stringtable2/utest/StringOps.test.cpp new file mode 100644 index 00000000..ac9cee81 --- /dev/null +++ b/xo-stringtable2/utest/StringOps.test.cpp @@ -0,0 +1,103 @@ +/** @file StringOps.test.cpp + * + * @author Roland Conybeare, Jan 2026 + **/ + +#include +#include +#include +#include + +namespace xo { + using xo::scm::StringOps; + using xo::scm::DString; + using xo::mm::AAllocator; + using xo::mm::AGCObject; + using xo::mm::DArena; + using xo::mm::ArenaConfig; + using xo::facet::with_facet; + using xo::facet::obj; + + namespace ut { + + TEST_CASE("StringOps-empty", "[object2][StringOps]") + { + ArenaConfig cfg { .name_ = "testarena", + .size_ = 4*1024 }; + DArena arena = DArena::map(cfg); + auto alloc = with_facet::mkobj(&arena); + + auto s = StringOps::empty(alloc, 16); + + REQUIRE(s.data() != nullptr); + REQUIRE(s.data()->capacity() == 16); + REQUIRE(s.data()->size() == 0); + REQUIRE(s.data()->chars()[0] == '\0'); + } + + TEST_CASE("StringOps-empty-with-content", "[object2][StringOps]") + { + ArenaConfig cfg { .name_ = "testarena", + .size_ = 4*1024 }; + DArena arena = DArena::map(cfg); + auto alloc = with_facet::mkobj(&arena); + + auto s = StringOps::empty(alloc, 32); + + s.data()->sprintf("hello %s %d", "world", 42); + + REQUIRE(s.data()->size() == 14); + REQUIRE(std::strcmp(s.data()->chars(), "hello world 42") == 0); + } + + TEST_CASE("StringOps-from_cstr", "[object2][StringOps]") + { + ArenaConfig cfg { .name_ = "testarena", + .size_ = 4*1024 }; + DArena arena = DArena::map(cfg); + auto alloc = with_facet::mkobj(&arena); + + const char * cstr = "hello world"; + auto s = StringOps::from_cstr(alloc, cstr); + + REQUIRE(s.data() != nullptr); + REQUIRE(s.data()->capacity() == 12); + REQUIRE(s.data()->size() == 11); + REQUIRE(std::strcmp(s.data()->chars(), cstr) == 0); + } + + TEST_CASE("StringOps-clone", "[object2][StringOps]") + { + ArenaConfig cfg { .name_ = "testarena", + .size_ = 4*1024 }; + DArena arena = DArena::map(cfg); + auto alloc = with_facet::mkobj(&arena); + + auto src = StringOps::from_cstr(alloc, "hello world"); + auto copy = StringOps::clone(alloc, src); + + REQUIRE(copy.data() != nullptr); + REQUIRE(copy.data() != src.data()); + REQUIRE(copy.data()->size() == src.data()->size()); + REQUIRE(copy.data()->capacity() == src.data()->capacity()); + REQUIRE(std::strcmp(copy.data()->chars(), src.data()->chars()) == 0); + } + + TEST_CASE("StringOps-printf", "[object2][StringOps]") + { + ArenaConfig cfg { .name_ = "testarena", + .size_ = 4*1024 }; + DArena arena = DArena::map(cfg); + auto alloc = with_facet::mkobj(&arena); + + auto s = StringOps::printf(alloc, 32, "hello %s %d", "world", 42); + + REQUIRE(s.data() != nullptr); + REQUIRE(s.data()->capacity() == 32); + REQUIRE(s.data()->size() == 14); + REQUIRE(std::strcmp(s.data()->chars(), "hello world 42") == 0); + } + } /*namespace ut*/ +} /*namespace xo*/ + +/* end StringOps.test.cpp */ diff --git a/xo-stringtable2/utest/StringTable.test.cpp b/xo-stringtable2/utest/StringTable.test.cpp new file mode 100644 index 00000000..c880df66 --- /dev/null +++ b/xo-stringtable2/utest/StringTable.test.cpp @@ -0,0 +1,161 @@ +/** @file StringTable.test.cpp + * + * @author Roland Conybeare, Jan 2026 + **/ + +#include +#include +#include + +namespace xo { + using xo::scm::StringTable; + using xo::scm::DUniqueString; + //using xo::scm::DString; + + namespace ut { + TEST_CASE("StringTable-lookup-empty", "[expression2][StringTable]") + { + StringTable table(1024); + + // lookup on empty table returns nullptr + REQUIRE(table.lookup("foo") == nullptr); + REQUIRE(table.lookup("") == nullptr); + } + + TEST_CASE("StringTable-intern", "[expression2][StringTable]") + { + StringTable table(1024); + + const DUniqueString * s1 = table.intern("hello"); + + REQUIRE(s1 != nullptr); + REQUIRE(std::strcmp(s1->chars(), "hello") == 0); + REQUIRE(s1->size() == 5); + } + + TEST_CASE("StringTable-intern-idempotent", "[expression2][StringTable]") + { + StringTable table(1024); + + const DUniqueString * s1 = table.intern("hello"); + const DUniqueString * s2 = table.intern("hello"); + + // same key returns same pointer + REQUIRE(s1 != nullptr); + REQUIRE(s2 != nullptr); + REQUIRE(s1 == s2); + } + + TEST_CASE("StringTable-lookup-after-intern", "[expression2][StringTable]") + { + StringTable table(1024); + + REQUIRE(table.lookup("hello") == nullptr); + + const DUniqueString * s1 = table.intern("hello"); + + const DUniqueString * s2 = table.lookup("hello"); + + REQUIRE(s2 != nullptr); + REQUIRE(s1 == s2); + } + + TEST_CASE("StringTable-multiple-strings", "[expression2][StringTable]") + { + StringTable table(1024); + + const DUniqueString * s1 = table.intern("apple"); + const DUniqueString * s2 = table.intern("banana"); + const DUniqueString * s3 = table.intern("cherry"); + + // all different pointers + REQUIRE(s1 != s2); + REQUIRE(s2 != s3); + REQUIRE(s1 != s3); + + // correct contents + REQUIRE(std::strcmp(s1->chars(), "apple") == 0); + REQUIRE(std::strcmp(s2->chars(), "banana") == 0); + REQUIRE(std::strcmp(s3->chars(), "cherry") == 0); + + // lookup still works + REQUIRE(table.lookup("apple") == s1); + REQUIRE(table.lookup("banana") == s2); + REQUIRE(table.lookup("cherry") == s3); + REQUIRE(table.lookup("date") == nullptr); + } + + TEST_CASE("StringTable-intern-empty-string", "[expression2][StringTable]") + { + StringTable table(1024); + + const DUniqueString * s1 = table.intern(""); + + REQUIRE(s1 != nullptr); + REQUIRE(s1->size() == 0); + REQUIRE(s1->chars()[0] == '\0'); + + // idempotent for empty string too + const DUniqueString * s2 = table.intern(""); + REQUIRE(s1 == s2); + } + + TEST_CASE("StringTable-verify_ok", "[expression2][StringTable]") + { + StringTable table(4096); + + { + INFO("1. empty table"); + + // empty table passes verify_ok + REQUIRE(table.verify_ok()); + } + + // after interning strings, still passes + { + INFO("2. intern(hello)"); + + table.intern("hello"); + REQUIRE(table.verify_ok()); + } + + { + INFO("3. intern(world)"); + + table.intern("world"); + REQUIRE(table.verify_ok()); + } + + { + INFO("4. intern(foo)"); + + table.intern("foo"); + REQUIRE(table.verify_ok()); + } + + { + INFO("5. intern(bar)"); + + table.intern("bar"); + REQUIRE(table.verify_ok()); + } + + // idempotent intern doesn't break invariants + { + INFO("6. intern(hello)"); + + table.intern("hello"); + REQUIRE(table.verify_ok()); + } + + { + INFO("7. intern(world)"); + + table.intern("world"); + REQUIRE(table.verify_ok()); + } + } + } /*namespace ut*/ +} /*namespace xo*/ + +/* end StringTable.test.cpp */ diff --git a/xo-stringtable2/utest/stringtable2_utest_main.cpp b/xo-stringtable2/utest/stringtable2_utest_main.cpp new file mode 100644 index 00000000..206c1067 --- /dev/null +++ b/xo-stringtable2/utest/stringtable2_utest_main.cpp @@ -0,0 +1,24 @@ +/* file stringtable2_utest_main.cpp */ + +#include + +#define CATCH_CONFIG_RUNNER +#include "catch2/catch.hpp" + +int +main(int argc, char* argv[]) +{ + using xo::Subsystem; + + // Your custom initialization code here + Subsystem::initialize_all(); + + // Run Catch2's test session + int result = Catch::Session().run(argc, argv); + + // cleanup here, if any + + return result; +} + +/* end stringtable2_utest_main.cpp */