/** @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_{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; } std::size_t StringTable::_n_store() const noexcept { return 1 + map_._n_store(); } MemorySizeInfo StringTable::_store_info(std::size_t i) const noexcept { if (i == 0) return strings_._store_info(); if (i+1 < map_._n_store()) return map_._store_info(i-1); return MemorySizeInfo::sentinel(); } } /*namespace scm*/ } /*namespace xo*/ /* end StringTable.cpp */