diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..40f375b --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,31 @@ +# 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}) + +# ---------------------------------------------------------------- +# 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/cmake/xo-bootstrap-macros.cmake b/cmake/xo-bootstrap-macros.cmake new file mode 100644 index 0000000..592272c --- /dev/null +++ b/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/cmake/xo_stringtable2Config.cmake.in b/cmake/xo_stringtable2Config.cmake.in new file mode 100644 index 0000000..0a084ae --- /dev/null +++ b/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/idl/IGCObject_DString.json5 b/idl/IGCObject_DString.json5 new file mode 100644 index 0000000..caf33fa --- /dev/null +++ b/idl/IGCObject_DString.json5 @@ -0,0 +1,18 @@ +{ + mode: "implementation", + output_cpp_dir: "src/object2", + output_hpp_dir: "include/xo/object2", + 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/idl/IPrintable_DString.json5 b/idl/IPrintable_DString.json5 new file mode 100644 index 0000000..8510938 --- /dev/null +++ b/idl/IPrintable_DString.json5 @@ -0,0 +1,16 @@ +{ + mode: "implementation", + output_cpp_dir: "src/object2", + output_hpp_dir: "include/xo/object2", + 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/include/xo/stringtable2/.gitkeep b/include/xo/stringtable2/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/include/xo/stringtable2/DString.hpp b/include/xo/stringtable2/DString.hpp new file mode 100644 index 0000000..bb57e3d --- /dev/null +++ b/include/xo/stringtable2/DString.hpp @@ -0,0 +1,348 @@ +/** @file DString.hpp +* + * @author Roland Conybeare, Jan 2026 + **/ + +#pragma once + +#include +#include +#include +#include +#include +#include +#include +//#include + +namespace xo { + namespace mm { class ACollector; } + + 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; + /** garbage collector **/ + using ACollector = xo::mm::ACollector; + /** 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 **/ + ///@{ + + size_type shallow_size() const noexcept; + + /** clone string, using memory from allocator @p mm **/ + DString * shallow_copy(obj mm) const noexcept; + + size_type forward_children(obj gc) noexcept; + /** fixup child pointers (trivial for DString, no children) + * note: cref so we can use forward decl + **/ + + ///@} + + 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/include/xo/stringtable2/String.hpp b/include/xo/stringtable2/String.hpp new file mode 100644 index 0000000..f3d0527 --- /dev/null +++ b/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/include/xo/stringtable2/StringOps.hpp b/include/xo/stringtable2/StringOps.hpp new file mode 100644 index 0000000..70441b4 --- /dev/null +++ b/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/include/xo/stringtable2/init_stringtable2.hpp b/include/xo/stringtable2/init_stringtable2.hpp new file mode 100644 index 0000000..4a48ceb --- /dev/null +++ b/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/include/xo/stringtable2/string/IGCObject_DString.hpp b/include/xo/stringtable2/string/IGCObject_DString.hpp new file mode 100644 index 0000000..2a907f3 --- /dev/null +++ b/include/xo/stringtable2/string/IGCObject_DString.hpp @@ -0,0 +1,67 @@ +/** @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 ACollector = xo::mm::AGCObject::ACollector; + using Copaque = xo::mm::AGCObject::Copaque; + using Opaque = xo::mm::AGCObject::Opaque; + ///@} + /** @defgroup scm-gcobject-dstring-methods **/ + ///@{ + // const methods + /** memory consumption for this instance **/ + static size_type shallow_size(const DString & self) noexcept; + /** copy instance using allocator **/ + static Opaque shallow_copy(const DString & self, obj mm) noexcept; + + // non-const methods + /** during GC: forward immdiate children **/ + static size_type forward_children(DString & self, obj gc) noexcept; + ///@} + }; + + } /*namespace scm*/ +} /*namespace xo*/ + +/* end */ diff --git a/include/xo/stringtable2/string/IPrintable_DString.hpp b/include/xo/stringtable2/string/IPrintable_DString.hpp new file mode 100644 index 0000000..f898a17 --- /dev/null +++ b/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/include/xo/stringtable2/stringtable2_register_facets.hpp b/include/xo/stringtable2/stringtable2_register_facets.hpp new file mode 100644 index 0000000..36f6610 --- /dev/null +++ b/include/xo/stringtable2/stringtable2_register_facets.hpp @@ -0,0 +1,15 @@ +/** @file stringtable2_register_facets.hpp + * + * @author Roland Conybeare, Mar 2026 + **/ + +#pragma once + +namespace xo { + namespace scm { + /** Register object2 (facet,impl) combinations with FacetRegistry **/ + bool stringtable2_register_facets(); + } +} + +/* end stringtable2_register_facets.hpp */ diff --git a/include/xo/stringtable2/stringtable2_register_types.hpp b/include/xo/stringtable2/stringtable2_register_types.hpp new file mode 100644 index 0000000..724d247 --- /dev/null +++ b/include/xo/stringtable2/stringtable2_register_types.hpp @@ -0,0 +1,17 @@ +/** @file stringtable2_register_types.hpp + * + * @author Roland Conybeare, Dec 2025 + **/ + +#pragma once + +#include + +namespace xo { + namespace scm { + /** Register stringtable2 (facet,impl) combinations with FacetRegistry **/ + bool stringtable2_register_types(obj gc); + } +} + +/* end stringtable2_register_types.hpp */ diff --git a/src/stringtable2/CMakeLists.txt b/src/stringtable2/CMakeLists.txt new file mode 100644 index 0000000..5874217 --- /dev/null +++ b/src/stringtable2/CMakeLists.txt @@ -0,0 +1,28 @@ +# xo-stringtable2/src/stringtable2/CMakeLists.txt + +set(SELF_LIB xo_stringtable2) +set(SELF_SRCS + init_stringtable2.cpp + stringtable2_register_facets.cpp + stringtable2_register_types.cpp + + DString.cpp + IGCObject_DString.cpp + IPrintable_DString.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/src/stringtable2/DString.cpp b/src/stringtable2/DString.cpp new file mode 100644 index 0000000..230fa4a --- /dev/null +++ b/src/stringtable2/DString.cpp @@ -0,0 +1,187 @@ +/** @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); + + 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 = 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_; + } + + auto + DString::shallow_size() const noexcept -> size_type + { + return sizeof(DString) + capacity_; + } + + DString * + DString::shallow_copy(obj mm) const noexcept + { + DString * copy = (DString *)mm.alloc_copy((std::byte *)this); + + if (copy) { + copy->capacity_ = capacity_; + copy->size_ = size_; + ::memcpy(copy->chars_, chars_, capacity_); + } + + return copy; + } + + auto + DString::forward_children(obj) noexcept -> size_type + { + return shallow_size(); + } + + 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/src/stringtable2/IGCObject_DString.cpp b/src/stringtable2/IGCObject_DString.cpp new file mode 100644 index 0000000..d34f0ea --- /dev/null +++ b/src/stringtable2/IGCObject_DString.cpp @@ -0,0 +1,39 @@ +/** @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::shallow_size(const DString & self) noexcept -> size_type + { + return self.shallow_size(); + } + + auto + IGCObject_DString::shallow_copy(const DString & self, obj mm) noexcept -> Opaque + { + return self.shallow_copy(mm); + } + + auto + IGCObject_DString::forward_children(DString & self, obj gc) noexcept -> size_type + { + return self.forward_children(gc); + } + + } /*namespace scm*/ +} /*namespace xo*/ + +/* end IGCObject_DString.cpp */ diff --git a/src/stringtable2/IPrintable_DString.cpp b/src/stringtable2/IPrintable_DString.cpp new file mode 100644 index 0000000..f5ec496 --- /dev/null +++ b/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/src/stringtable2/init_stringtable2.cpp b/src/stringtable2/init_stringtable2.cpp new file mode 100644 index 0000000..26d84ab --- /dev/null +++ b/src/stringtable2/init_stringtable2.cpp @@ -0,0 +1,43 @@ +/** @file init_stringtable2.cpp + * + * @author Roland Conybeare, Jan 2026 + **/ + +#include "init_stringtable2.hpp" +#include "stringtable2_register_facets.hpp" +#include "stringtable2_register_types.hpp" +#include +//n#include +#include +#include + +namespace xo { + using xo::scm::stringtable2_register_facets; + using xo::scm::stringtable2_register_types; + using xo::mm::CollectorTypeRegistry; + + void + InitSubsys::init() + { + stringtable2_register_facets(); + + CollectorTypeRegistry::instance().register_types(&stringtable2_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/src/stringtable2/stringtable2_register_facets.cpp b/src/stringtable2/stringtable2_register_facets.cpp new file mode 100644 index 0000000..6a3aa41 --- /dev/null +++ b/src/stringtable2/stringtable2_register_facets.cpp @@ -0,0 +1,36 @@ +/** @file stringtable2_register_facets.cpp + * + * @author Roland Conybeare, Mar 2026 + **/ + +#include "stringtable2_register_facets.hpp" + +#include + +#include +#include + +namespace xo { + using xo::print::APrintable; + using xo::mm::AGCObject; + using xo::scm::DString; + using xo::facet::FacetRegistry; + using xo::facet::typeseq; + + namespace scm { + bool + stringtable2_register_facets() + { + scope log(XO_DEBUG(true)); + + FacetRegistry::register_impl(); + FacetRegistry::register_impl(); + + log && log(xtag("DString.tseq", typeseq::id())); + + return true; + } + } /*namespace scm*/ +} /*namespace xo*/ + +/* end stringtable2_register_facets.cpp */ diff --git a/src/stringtable2/stringtable2_register_types.cpp b/src/stringtable2/stringtable2_register_types.cpp new file mode 100644 index 0000000..fda9074 --- /dev/null +++ b/src/stringtable2/stringtable2_register_types.cpp @@ -0,0 +1,33 @@ +/** @file stringtable2_register_types.cpp + * + * @author Roland Conybeare, Mar 2026 + **/ + +#include "stringtable2_register_types.hpp" +#include "String.hpp" + +//#include +#include + +namespace xo { + using xo::mm::ACollector; + using xo::mm::AGCObject; + using xo::facet::impl_for; + using xo::scope; + + namespace scm { + bool + stringtable2_register_types(obj gc) + { + scope log(XO_DEBUG(true)); + + bool ok = true; + + ok &= gc.install_type(impl_for()); + + return ok; + } + } +} /*namespace xo*/ + +/* end stringtable2_register_types.cpp */ diff --git a/utest/CMakeLists.txt b/utest/CMakeLists.txt new file mode 100644 index 0000000..1b0b979 --- /dev/null +++ b/utest/CMakeLists.txt @@ -0,0 +1,13 @@ +# built unittest xo-object2/utest + +set(UTEST_EXE utest.stringtable2) +set(UTEST_SRCS + stringtable2_utest_main.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/utest/DString.test.cpp b/utest/DString.test.cpp new file mode 100644 index 0000000..9a7e116 --- /dev/null +++ b/utest/DString.test.cpp @@ -0,0 +1,429 @@ +/** @file DString.test.cpp + * + * @author Roland Conybeare, Jan 2026 + **/ + +#include "init_stringtable2.hpp" +#include "StringOps.hpp" +#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/utest/StringOps.test.cpp b/utest/StringOps.test.cpp new file mode 100644 index 0000000..ac9cee8 --- /dev/null +++ b/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/utest/stringtable2_utest_main.cpp b/utest/stringtable2_utest_main.cpp new file mode 100644 index 0000000..206c106 --- /dev/null +++ b/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 */