Add RandomAccessMemory class for in-memory binary operations

This commit is contained in:
ImBenji
2025-11-23 05:29:08 +00:00
parent 9216cd1638
commit 4295d119d7
26 changed files with 2124 additions and 949 deletions

View File

@@ -4,43 +4,21 @@ project(BinaryTable)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
# Core Binary Table Library
add_library(binary_table
# Main executable with integrated binary table implementation
add_executable(main
binary_table.h
binary_table.cpp
)
# Main Application
add_executable(main main.cpp)
target_link_libraries(main binary_table)
# Debug Test Executables
add_executable(debug_multi_key debug/debug_multi_key.cpp)
target_link_libraries(debug_multi_key binary_table)
add_executable(debug_alloc debug/debug_alloc.cpp)
target_link_libraries(debug_alloc binary_table)
add_executable(debug_address_table debug/debug_address_table.cpp)
target_link_libraries(debug_address_table binary_table)
add_executable(debug_step_by_step debug/debug_step_by_step.cpp)
target_link_libraries(debug_step_by_step binary_table)
add_executable(debug_simple debug/debug_simple.cpp)
target_link_libraries(debug_simple binary_table)
# Compiler Settings
if(MSVC)
target_compile_options(binary_table PRIVATE /W4)
target_compile_options(main PRIVATE /W4)
else()
target_compile_options(binary_table PRIVATE -Wall -Wextra -pedantic)
target_compile_options(main PRIVATE -Wall -Wextra -pedantic)
# Apply warnings to debug executables too
target_compile_options(debug_multi_key PRIVATE -Wall -Wextra -pedantic)
target_compile_options(debug_alloc PRIVATE -Wall -Wextra -pedantic)
target_compile_options(debug_address_table PRIVATE -Wall -Wextra -pedantic)
target_compile_options(debug_step_by_step PRIVATE -Wall -Wextra -pedantic)
target_compile_options(debug_simple PRIVATE -Wall -Wextra -pedantic)
endif()
# Link required libraries
if(UNIX AND NOT APPLE)
# Only link stdc++fs on Linux, not macOS
target_link_libraries(main PRIVATE stdc++fs)
endif()

View File

@@ -4,6 +4,9 @@
#include <functional>
#include <filesystem>
#include <iostream>
#include <fstream>
#include <sstream>
#include <iomanip>
namespace bt {
@@ -483,8 +486,25 @@ BinaryTable::~BinaryTable() {
void BinaryTable::initialize() {
fseek(file_, 0, SEEK_SET);
writeInt64(0, BT_Null.address()); // Address table pointer (8 bytes)
writeInt32(8, 0); // Free list entry count (4 bytes)
// Magic number "SWPS" (0/4 bytes)
const char magic[] = "SWPS";
fwrite(magic, 1, 4, file_);
// Version (1.0 float16) (4/2 bytes)
uint16_t version = 0x3C00; // 1.0 in float16 format
uint8_t versionBytes[2] = {
static_cast<uint8_t>(version & 0xFF),
static_cast<uint8_t>((version >> 8) & 0xFF)
};
fwrite(versionBytes, 1, 2, file_);
// Address table pointer (null) (6/8 bytes)
writeInt64(6, BT_Null.address());
// Free list count (0) (14/4 bytes)
writeInt32(14, 0);
fflush(file_);
}
@@ -600,7 +620,7 @@ void BinaryTable::setFilePosition(int64_t position) {
// Address table management
std::unordered_map<int64_t, BT_Pointer> BinaryTable::getAddressTable() {
int64_t tableAddress = readInt64(0);
int64_t tableAddress = readInt64(6);
DEBUG_PRINTLN("DEBUG: getAddressTable reading from address " << tableAddress);
if (tableAddress == -1) { // Null pointer
@@ -674,7 +694,7 @@ void BinaryTable::setAddressTable(const std::unordered_map<int64_t, BT_Pointer>&
}
// Read old table pointer FIRST to ensure we can clean it up later
int64_t oldTablePointerAddress = readInt64(0);
int64_t oldTablePointerAddress = readInt64(6);
BT_Pointer oldTablePtr(oldTablePointerAddress);
int32_t oldTableSize = 0;
@@ -729,7 +749,7 @@ void BinaryTable::setAddressTable(const std::unordered_map<int64_t, BT_Pointer>&
fflush(file_);
// Atomically update header to point to new table
writeInt64(0, newTableAddress.address());
writeInt64(6, newTableAddress.address());
fflush(file_);
// Only free old table after new one is successfully committed
@@ -748,19 +768,15 @@ std::vector<BT_FreeListEntry> BinaryTable::getFreeList() {
return freeListCache_;
}
int64_t fileLength = getFileLength();
if (fileLength < 4) {
return {};
}
int32_t entryCount = readInt32(fileLength - 4);
int32_t entryCount = readInt32(14);
if (entryCount == 0) {
return {};
}
int32_t entrySize = 8 + 4; // Pointer + Size
int32_t freeListSize = entryCount * entrySize;
int64_t freeListStart = fileLength - 4 - freeListSize;
int64_t fileLength = getFileLength();
int64_t freeListStart = fileLength - freeListSize;
std::vector<BT_FreeListEntry> freeList;
for (int32_t i = 0; i < entryCount; i++) {
@@ -781,65 +797,44 @@ void BinaryTable::setFreeList(const std::vector<BT_FreeListEntry>& list) {
return;
}
// Always remove old free list first (matching Dart behavior)
int64_t fileLength = getFileLength();
DEBUG_PRINTLN("DEBUG: setFreeList fileLength=" << fileLength);
// Calculate old free list size to remove
int32_t oldEntryCount = 0;
if (fileLength >= 4) {
oldEntryCount = readInt32(fileLength - 4);
}
DEBUG_PRINTLN("DEBUG: setFreeList oldEntryCount=" << oldEntryCount);
// Remove old free list (matching Dart: always truncate first)
// Read OLD count from header (position 14)
int32_t oldEntryCount = readInt32(14);
// Calculate old free list size (entries only, not count)
int32_t oldListSize = oldEntryCount * (8 + 4);
// Remove old free list entries from EOF
if (oldEntryCount > 0) {
int32_t oldListSize = (oldEntryCount * (8 + 4)) + 4; // Entries + Count
int64_t newFileLength = fileLength - oldListSize;
DEBUG_PRINTLN("DEBUG: setFreeList - removing old free list, oldListSize=" << oldListSize << ", truncating to: " << newFileLength);
truncateFile(newFileLength);
fileLength = newFileLength; // Update file length
int64_t currentLength = getFileLength();
truncateFile(currentLength - oldListSize);
}
// If the new free list is empty, we're done (old list already removed)
if (list.empty()) {
DEBUG_PRINTLN("DEBUG: setFreeList - empty list, old list removed, done");
return;
}
// Write new free list at end of file
int64_t newLogicalEnd = fileLength;
// Encode new free list
std::vector<uint8_t> buffer;
// Entries
for (const auto& entry : list) {
// Pointer (8 bytes, little endian)
int64_t addr = entry.pointer.address();
for (int i = 0; i < 8; i++) {
buffer.push_back(static_cast<uint8_t>((addr >> (i * 8)) & 0xFF));
}
// Size (4 bytes, little endian)
int32_t size = entry.size;
for (int i = 0; i < 4; i++) {
buffer.push_back(static_cast<uint8_t>((size >> (i * 8)) & 0xFF));
// Write NEW count to header (position 14)
writeInt32(14, static_cast<int32_t>(list.size()));
// Write NEW entries to EOF (if any)
if (!list.empty()) {
// Encode new free list entries
std::vector<uint8_t> buffer;
for (const auto& entry : list) {
// Pointer (8 bytes, little endian)
int64_t addr = entry.pointer.address();
for (int i = 0; i < 8; i++) {
buffer.push_back(static_cast<uint8_t>((addr >> (i * 8)) & 0xFF));
}
// Size (4 bytes, little endian)
int32_t size = entry.size;
for (int i = 0; i < 4; i++) {
buffer.push_back(static_cast<uint8_t>((size >> (i * 8)) & 0xFF));
}
}
fseek(file_, 0, SEEK_END);
fwrite(buffer.data(), 1, buffer.size(), file_);
}
// Entry count (4 bytes, little endian)
int32_t count = static_cast<int32_t>(list.size());
for (int i = 0; i < 4; i++) {
buffer.push_back(static_cast<uint8_t>((count >> (i * 8)) & 0xFF));
}
// Write at the logical end position
fseek(file_, newLogicalEnd, SEEK_SET);
fwrite(buffer.data(), 1, buffer.size(), file_);
fflush(file_);
// Update logical file length
// File will be extended automatically by write operations
}
void BinaryTable::truncateFile(int64_t newSize) {
@@ -865,29 +860,24 @@ void BinaryTable::liftFreeList() {
throw std::runtime_error("Free list is already lifted");
}
// Cache the free list
freeListCache_ = getFreeList();
// Remove free list from end of file
int64_t fileLength = getFileLength();
int32_t oldEntryCount = (fileLength >= 4) ? readInt32(fileLength - 4) : 0;
// Read count from header (position 14)
int32_t oldEntryCount = readInt32(14);
if (oldEntryCount > 0) {
int32_t oldEntrySize = 8 + 4;
int32_t oldFreeListSize = oldEntryCount * oldEntrySize + 4;
int64_t newFileLength = fileLength - oldFreeListSize;
int32_t oldFreeListSize = oldEntryCount * oldEntrySize; // Just entries, no count
// Store current file position to restore later if needed
long currentPos = ftell(file_);
// Properly truncate the file
truncateFile(newFileLength);
// Restore file position if it's still valid
if (currentPos >= 0 && currentPos < newFileLength) {
fseek(file_, currentPos, SEEK_SET);
}
// Remove free list entries from EOF
int64_t fileLength = getFileLength();
truncateFile(fileLength - oldFreeListSize);
}
// Clear count in header (position 14)
writeInt32(14, 0);
freeListLifted_ = true;
}
@@ -898,9 +888,7 @@ void BinaryTable::dropFreeList() {
}
freeListLifted_ = false;
DEBUG_PRINTLN("DEBUG: About to call setFreeList - this might corrupt the address table!");
setFreeList(freeListCache_);
DEBUG_PRINTLN("DEBUG: setFreeList completed");
setFreeList(freeListCache_); // This now writes count to header and entries to EOF
freeListCache_.clear();
}
@@ -1106,4 +1094,150 @@ void BinaryTable::debugAddressTable(const std::string& context) {
DEBUG_PRINTLN("=========================");
}
} // namespace bt
std::string binaryDump(const std::vector<uint8_t>& data) {
std::ostringstream buffer;
for (size_t i = 0; i < data.size(); i += 16) {
// Address
buffer << "0x" << std::hex << std::setfill('0') << std::setw(4) << std::uppercase << i
<< " (" << std::dec << std::setfill(' ') << std::setw(4) << i << ") | ";
// Hex bytes
for (int j = 0; j < 16; j++) {
if (i + j < data.size()) {
buffer << std::hex << std::setfill('0') << std::setw(2) << std::uppercase
<< static_cast<int>(data[i + j]) << " ";
} else {
buffer << " ";
}
}
buffer << " | ";
// Integer representation
for (int j = 0; j < 16; j++) {
if (i + j < data.size()) {
buffer << std::dec << std::setfill(' ') << std::setw(3)
<< static_cast<int>(data[i + j]) << " ";
} else {
buffer << " ";
}
}
buffer << " | ";
// ASCII representation
for (int j = 0; j < 16; j++) {
if (i + j < data.size()) {
uint8_t byte = data[i + j];
if (byte >= 32 && byte <= 126) {
buffer << static_cast<char>(byte);
} else {
buffer << '.';
}
}
}
buffer << " | ";
if (i + 16 < data.size()) {
buffer << "\n";
}
}
return buffer.str();
}
} // namespace bt
int main() {
// Delete existing file if it exists
if (std::filesystem::exists("example.bin")) {
std::filesystem::remove("example.bin");
}
bt::BinaryTable table("example.bin");
table.initialize();
// Read file for dump
std::ifstream file("example.bin", std::ios::binary);
std::vector<uint8_t> fileData((std::istreambuf_iterator<char>(file)),
std::istreambuf_iterator<char>());
file.close();
std::cout << "File dump:\n";
std::cout << bt::binaryDump(fileData) << "\n";
std::cout << "File size: " << fileData.size() << " bytes\n";
std::cout << " \n";
// Set values
table.set("int_array", std::vector<int32_t>{6, 3, 9, 2, 5});
table.set("float_array", std::vector<float>{1.5f, 2.5f, 3.5f});
table.set("empty", std::vector<int32_t>{});
// Get arrays and modify elements
auto intArray = table.getArray<int32_t>("int_array");
auto floatArray = table.getArray<float>("float_array");
intArray.set(0, 1);
floatArray.set(1, 4.5f);
std::cout << "int_array pointer: " << intArray.getPointer().address() << "\n";
std::cout << "float_array pointer: " << floatArray.getPointer().address() << "\n";
intArray.add(10);
floatArray.add(5.5f);
intArray.addAll({420, 69, 1337, 1738});
floatArray.addAll({6.5f, 7.5f, 8.5f});
// Test the fetchSublist method
auto intArraySublist = intArray.fetchSublist(0, 3);
auto floatArraySublist = floatArray.fetchSublist(0, 2);
std::cout << "Sublist int_array (0-3): ";
for (auto val : intArraySublist) {
std::cout << val << " ";
}
std::cout << "\n";
std::cout << "Sublist float_array (0-2): ";
for (auto val : floatArraySublist) {
std::cout << val << " ";
}
std::cout << "\n";
// Read back values
auto readback1 = table.get<std::vector<int32_t>>("int_array");
auto readback2 = table.get<std::vector<float>>("float_array");
auto readback3 = table.get<std::vector<int32_t>>("empty");
std::cout << "Readback1: ";
for (auto val : readback1) {
std::cout << val << " ";
}
std::cout << "\n";
std::cout << "Readback2: ";
for (auto val : readback2) {
std::cout << val << " ";
}
std::cout << "\n";
std::cout << "Readback3: ";
for (auto val : readback3) {
std::cout << val << " ";
}
std::cout << "\n";
std::cout << " \n";
// Final file dump
std::ifstream finalFile("example.bin", std::ios::binary);
std::vector<uint8_t> finalFileData((std::istreambuf_iterator<char>(finalFile)),
std::istreambuf_iterator<char>());
finalFile.close();
std::cout << "File dump:\n";
std::cout << bt::binaryDump(finalFileData) << "\n";
std::cout << "File size: " << finalFileData.size() << " bytes\n";
return 0;
}

View File

@@ -11,7 +11,7 @@
<EFBFBD> 2025-26 by Benjamin Watt of IMBENJI.NET LIMITED - All rights reserved.
Use of this source code is governed by a MIT license that can be found in the LICENSE file.
Use of this source code is governed by the Business Source License 1.1 that can be found in the LICENSE file.
This file is part of the SweepStore (formerly Binary Table) package for C++.
@@ -192,6 +192,9 @@ private:
int64_t hashString(const std::string& str) const;
void truncateFile(int64_t newSize);
void antiFreeListScope(std::function<void()> fn);
void free(BT_Pointer pointer, int32_t size);
BT_Pointer alloc(int32_t size);
// File I/O helpers
int32_t readInt32(int64_t position);
@@ -212,12 +215,9 @@ public:
void initialize();
// Memory management
// Memory management
void liftFreeList();
void dropFreeList();
void antiFreeListScope(std::function<void()> fn);
void free(BT_Pointer pointer, int32_t size);
BT_Pointer alloc(int32_t size);
// Data operations
template<typename T>

View File

@@ -1,50 +0,0 @@
#include <iostream>
#include <filesystem>
#include "../binary_table.h"
void printAddressTable(bt::BinaryTable& table) {
// We can't access getAddressTable directly, so let's use a different approach
// Try to retrieve all known keys and see what happens
std::vector<std::string> keys = {"key1", "key2", "key3"};
for (const std::string& key : keys) {
try {
auto ref = table.getReference(key);
std::cout << " " << key << " -> address " << ref.getPointer().address()
<< " (type " << static_cast<int>(ref.getType()) << ")" << std::endl;
} catch (const std::exception& e) {
std::cout << " " << key << " -> ERROR: " << e.what() << std::endl;
}
}
}
int main() {
using namespace bt;
const std::string filename = "debug_addr_table.bin";
if (std::filesystem::exists(filename)) {
std::filesystem::remove(filename);
}
BinaryTable table(filename);
table.initialize();
std::cout << "=== Testing Address Table Corruption ===\n" << std::endl;
std::cout << "Initial state (empty):" << std::endl;
printAddressTable(table);
std::cout << "\n1. After storing key1:" << std::endl;
table.set<int32_t>("key1", 100);
printAddressTable(table);
std::cout << "\n2. After storing key2:" << std::endl;
table.set<int32_t>("key2", 200);
printAddressTable(table);
std::cout << "\n3. After storing key3:" << std::endl;
table.set<int32_t>("key3", 300);
printAddressTable(table);
return 0;
}

View File

@@ -1,61 +0,0 @@
#include <iostream>
#include <filesystem>
#include "../binary_table.h"
int main() {
using namespace bt;
const std::string filename = "debug_alloc.bin";
if (std::filesystem::exists(filename)) {
std::filesystem::remove(filename);
}
BinaryTable table(filename);
table.initialize();
std::cout << "=== Testing Memory Allocation Issues ===\n" << std::endl;
// Store first key and see what address it gets
std::cout << "1. Storing first key..." << std::endl;
table.set<int32_t>("key1", 100);
// Get the address where key1's value was stored
auto addressTable1 = table.getReference("key1").getPointer();
std::cout << " key1 value stored at: " << addressTable1.address() << std::endl;
// Store second key and see what addresses are used
std::cout << "2. Storing second key..." << std::endl;
table.set<int32_t>("key2", 200);
auto addressTable2 = table.getReference("key2").getPointer();
std::cout << " key2 value stored at: " << addressTable2.address() << std::endl;
// Check if key1 is still accessible
std::cout << "3. Checking if key1 is still accessible..." << std::endl;
try {
int32_t val1 = table.get<int32_t>("key1");
std::cout << " ✅ key1 still works: " << val1 << std::endl;
} catch (const std::exception& e) {
std::cout << " ❌ key1 broken: " << e.what() << std::endl;
// Let's see what's actually stored at key1's address
try {
auto ref = table.getReference("key1");
std::cout << " key1 type is: " << static_cast<int>(ref.getType()) << std::endl;
} catch (const std::exception& e2) {
std::cout << " Can't even get type: " << e2.what() << std::endl;
}
}
std::cout << "\n=== Address Comparison ===\n" << std::endl;
std::cout << "key1 address: " << addressTable1.address() << std::endl;
std::cout << "key2 address: " << addressTable2.address() << std::endl;
if (addressTable1.address() == addressTable2.address()) {
std::cout << "💥 SAME ADDRESS! This proves the bug!" << std::endl;
} else {
std::cout << "Addresses are different, issue is elsewhere" << std::endl;
}
return 0;
}

View File

@@ -1,69 +0,0 @@
#include <iostream>
#include <filesystem>
#include "../binary_table.h"
int main() {
using namespace bt;
const std::string filename = "debug_multi.bin";
if (std::filesystem::exists(filename)) {
std::filesystem::remove(filename);
}
BinaryTable table(filename);
table.initialize();
std::cout << "=== Testing Multi-Key Storage ===" << std::endl;
// Store first key
std::cout << "1. Storing first key..." << std::endl;
table.set<int32_t>("key1", 100);
// Try to read it back
try {
int32_t val1 = table.get<int32_t>("key1");
std::cout << " ✅ First key retrieved: " << val1 << std::endl;
} catch (const std::exception& e) {
std::cout << " ❌ First key failed: " << e.what() << std::endl;
return 1;
}
// Store second key - this is where it likely breaks
std::cout << "2. Storing second key..." << std::endl;
table.set<int32_t>("key2", 200);
// Try to read second key
try {
int32_t val2 = table.get<int32_t>("key2");
std::cout << " ✅ Second key retrieved: " << val2 << std::endl;
} catch (const std::exception& e) {
std::cout << " ❌ Second key failed: " << e.what() << std::endl;
}
// Try to read first key again - this will likely fail
std::cout << "3. Re-reading first key..." << std::endl;
try {
int32_t val1_again = table.get<int32_t>("key1");
std::cout << " ✅ First key still accessible: " << val1_again << std::endl;
} catch (const std::exception& e) {
std::cout << " ❌ First key now broken: " << e.what() << std::endl;
std::cout << " 💥 CONFIRMED: Table breaks after storing 2+ keys!" << std::endl;
}
// Store third key to see if pattern continues
std::cout << "4. Storing third key..." << std::endl;
try {
table.set<int32_t>("key3", 300);
int32_t val3 = table.get<int32_t>("key3");
std::cout << " ✅ Third key works: " << val3 << std::endl;
} catch (const std::exception& e) {
std::cout << " ❌ Third key failed: " << e.what() << std::endl;
}
std::cout << "\n=== Conclusion ===" << std::endl;
std::cout << "The issue is definitely in the address table management" << std::endl;
std::cout << "when storing multiple keys. Single key = perfect," << std::endl;
std::cout << "multiple keys = corruption." << std::endl;
return 0;
}

View File

@@ -1,49 +0,0 @@
#include <iostream>
#include <filesystem>
#include "../binary_table.h"
int main() {
using namespace bt;
const std::string filename = "debug_simple.bin";
if (std::filesystem::exists(filename)) {
std::filesystem::remove(filename);
}
BinaryTable table(filename);
table.initialize();
std::cout << "1. Storing key1..." << std::endl;
table.set<int32_t>("key1", 100);
table.debugAddressTable("after key1");
std::cout << "2. Reading key1..." << std::endl;
try {
int32_t val = table.get<int32_t>("key1");
std::cout << " ✅ key1 = " << val << std::endl;
} catch (const std::exception& e) {
std::cout << " ❌ key1 failed: " << e.what() << std::endl;
}
std::cout << "3. Storing key2..." << std::endl;
table.set<int32_t>("key2", 200);
table.debugAddressTable("after key2");
std::cout << "4. Reading key2..." << std::endl;
try {
int32_t val = table.get<int32_t>("key2");
std::cout << " ✅ key2 = " << val << std::endl;
} catch (const std::exception& e) {
std::cout << " ❌ key2 failed: " << e.what() << std::endl;
}
std::cout << "5. Re-reading key1..." << std::endl;
try {
int32_t val = table.get<int32_t>("key1");
std::cout << " ✅ key1 = " << val << std::endl;
} catch (const std::exception& e) {
std::cout << " ❌ key1 failed: " << e.what() << std::endl;
}
return 0;
}

View File

@@ -1,105 +0,0 @@
#include <iostream>
#include <filesystem>
#include <fstream>
#include <iomanip>
#include "../binary_table.h"
void dumpFile(const std::string& filename) {
std::ifstream file(filename, std::ios::binary);
file.seekg(0, std::ios::end);
size_t size = file.tellg();
file.seekg(0, std::ios::beg);
std::vector<uint8_t> data(size);
file.read(reinterpret_cast<char*>(data.data()), size);
std::cout << "File size: " << size << " bytes" << std::endl;
for (size_t i = 0; i < std::min(size, size_t(80)); i++) {
if (i % 16 == 0) std::cout << std::hex << i << ": ";
std::cout << std::hex << std::setfill('0') << std::setw(2) << (int)data[i] << " ";
if (i % 16 == 15) std::cout << std::endl;
}
if (size % 16 != 0) std::cout << std::endl;
}
int main() {
using namespace bt;
const std::string filename = "debug_step.bin";
if (std::filesystem::exists(filename)) {
std::filesystem::remove(filename);
}
BinaryTable table(filename);
table.initialize();
std::cout << "=== Step-by-step Address Table Debug ===\n" << std::endl;
std::cout << "After initialize():" << std::endl;
dumpFile(filename);
std::cout << "\n1. Before storing key1:" << std::endl;
// Try reading the address table header
{
std::ifstream file(filename, std::ios::binary);
int64_t addr;
file.read(reinterpret_cast<char*>(&addr), 8);
std::cout << "Address table pointer: " << addr << std::endl;
}
std::cout << "\n2. Storing key1..." << std::endl;
table.set<int32_t>("key1", 100);
std::cout << "After storing key1:" << std::endl;
dumpFile(filename);
// Try reading the address table
{
std::ifstream file(filename, std::ios::binary);
int64_t addr;
file.read(reinterpret_cast<char*>(&addr), 8);
std::cout << "Address table pointer: " << addr << std::endl;
if (addr != -1) {
file.seekg(addr);
uint8_t type;
int32_t count;
file.read(reinterpret_cast<char*>(&type), 1);
file.read(reinterpret_cast<char*>(&count), 4);
std::cout << "Address table type: " << (int)type << ", count: " << count << std::endl;
}
}
std::cout << "\n3. Storing key2..." << std::endl;
table.set<int32_t>("key2", 200);
std::cout << "After storing key2:" << std::endl;
dumpFile(filename);
// Try reading the address table again
{
std::ifstream file(filename, std::ios::binary);
int64_t addr;
file.read(reinterpret_cast<char*>(&addr), 8);
std::cout << "Address table pointer: " << addr << std::endl;
if (addr != -1) {
file.seekg(addr);
uint8_t type;
int32_t count;
file.read(reinterpret_cast<char*>(&type), 1);
file.read(reinterpret_cast<char*>(&count), 4);
std::cout << "Address table type: " << (int)type << ", count: " << count << std::endl;
// Read the entries
for (int32_t i = 0; i < count && i < 5; i++) {
int64_t keyHash, valueAddr;
file.read(reinterpret_cast<char*>(&keyHash), 8);
file.read(reinterpret_cast<char*>(&valueAddr), 8);
std::cout << "Entry " << i << ": hash=" << keyHash << ", addr=" << valueAddr << std::endl;
}
}
}
return 0;
}

View File

@@ -1,213 +0,0 @@
#include <iostream>
#include <filesystem>
#include "binary_table.h"
void printBinaryDump(const std::vector<uint8_t>& data) {
for (size_t i = 0; i < data.size(); i += 16) {
// Address
printf("0x%04X (%4zu) | ", static_cast<unsigned int>(i), i);
// Hex bytes
for (int j = 0; j < 16; j++) {
if (i + j < data.size()) {
printf("%02X ", data[i + j]);
} else {
printf(" ");
}
}
printf(" | ");
// Integer representation
for (int j = 0; j < 16; j++) {
if (i + j < data.size()) {
printf("%3d ", data[i + j]);
} else {
printf(" ");
}
}
printf(" | ");
// ASCII representation
for (int j = 0; j < 16; j++) {
if (i + j < data.size()) {
uint8_t byte = data[i + j];
if (byte >= 32 && byte <= 126) {
printf("%c", static_cast<char>(byte));
} else {
printf(".");
}
}
}
printf(" |\n");
}
}
std::vector<uint8_t> readFile(const std::string& path) {
std::ifstream file(path, std::ios::binary);
file.seekg(0, std::ios::end);
size_t size = file.tellg();
file.seekg(0, std::ios::beg);
std::vector<uint8_t> data(size);
file.read(reinterpret_cast<char*>(data.data()), size);
return data;
}
int main() {
using namespace bt;
std::cout << "C++ Binary Table - Reading Dart Reference File" << std::endl;
std::cout << "===============================================" << std::endl;
// Read the file created by Dart
const std::string filename = "dart_reference.bin";
if (!std::filesystem::exists(filename)) {
std::cout << "❌ Reference file not found: " << filename << std::endl;
return 1;
}
std::cout << "📁 Reading reference file created by Dart..." << std::endl;
auto fileData = readFile(filename);
printBinaryDump(fileData);
std::cout << "File size: " << fileData.size() << " bytes\n" << std::endl;
// Try to read the file with C++ implementation
try {
BinaryTable table(filename);
std::cout << "🔍 Testing C++ reading of Dart-created file..." << std::endl;
// Try to read the arrays that Dart created
std::cout << "Attempting to read 'int_array'..." << std::endl;
try {
auto intArray = table.getArray<int32_t>("int_array");
std::cout << "✅ int_array found, length: " << intArray.length() << std::endl;
if (intArray.length() > 0) {
std::cout << "First few elements: ";
int count = std::min(5, static_cast<int>(intArray.length()));
for (int i = 0; i < count; i++) {
std::cout << intArray[i] << " ";
}
std::cout << std::endl;
}
} catch (const std::exception& e) {
std::cout << "❌ Failed to read int_array: " << e.what() << std::endl;
}
std::cout << "\nAttempting to read 'float_array'..." << std::endl;
try {
auto floatArray = table.getArray<float>("float_array");
std::cout << "✅ float_array found, length: " << floatArray.length() << std::endl;
if (floatArray.length() > 0) {
std::cout << "First few elements: ";
int count = std::min(5, static_cast<int>(floatArray.length()));
for (int i = 0; i < count; i++) {
std::cout << floatArray[i] << " ";
}
std::cout << std::endl;
}
} catch (const std::exception& e) {
std::cout << "❌ Failed to read float_array: " << e.what() << std::endl;
}
std::cout << "\nAttempting to read 'empty' array..." << std::endl;
try {
auto emptyArray = table.getArray<int32_t>("empty");
std::cout << "✅ empty array found, length: " << emptyArray.length() << std::endl;
} catch (const std::exception& e) {
std::cout << "❌ Failed to read empty array: " << e.what() << std::endl;
}
} catch (const std::exception& e) {
std::cout << "❌ Failed to read file: " << e.what() << std::endl;
}
std::cout << "\n" << std::string(50, '=') << std::endl;
std::cout << "Testing C++ Writing -> C++ Reading" << std::endl;
std::cout << std::string(50, '=') << std::endl;
// Test C++ writing by creating a simple file
const std::string testFilename = "cpp_test.bin";
if (std::filesystem::exists(testFilename)) {
std::filesystem::remove(testFilename);
}
try {
BinaryTable writeTable(testFilename);
writeTable.initialize();
std::cout << "📝 Writing simple data with C++..." << std::endl;
// Write very simple data first
writeTable.set<int32_t>("test_int", 42);
std::cout << "✅ Wrote integer" << std::endl;
// Read it back immediately
int32_t readInt = writeTable.get<int32_t>("test_int");
std::cout << "✅ Read back integer: " << readInt << std::endl;
// Write a simple array
writeTable.set<std::vector<int32_t>>("simple_array", {1, 2, 3});
std::cout << "✅ Wrote simple array" << std::endl;
auto readArray = writeTable.getArray<int32_t>("simple_array");
std::cout << "✅ Read back array, length: " << readArray.length() << std::endl;
if (readArray.length() > 0) {
std::cout << "Array elements: ";
for (int i = 0; i < readArray.length(); i++) {
std::cout << readArray[i] << " ";
}
std::cout << std::endl;
}
// Test array operations
std::cout << "\n📝 Testing array operations..." << std::endl;
readArray.set(0, 99); // Modify first element
readArray.add(4); // Add element
readArray.addAll({5, 6}); // Add multiple
std::cout << "After modifications, length: " << readArray.length() << std::endl;
std::cout << "Elements: ";
for (int i = 0; i < readArray.length(); i++) {
std::cout << readArray[i] << " ";
}
std::cout << std::endl;
// Test sublist
auto sublist = readArray.fetchSublist(0, 3);
std::cout << "Sublist (0-3): ";
for (auto val : sublist) {
std::cout << val << " ";
}
std::cout << std::endl;
std::cout << "\n🎉 C++ Implementation Status:" << std::endl;
std::cout << "✅ File reading (Dart compatibility)" << std::endl;
std::cout << "✅ File writing" << std::endl;
std::cout << "✅ Basic data types (int, float, string)" << std::endl;
std::cout << "✅ Array storage and retrieval" << std::endl;
std::cout << "✅ Array operations (set, add, addAll)" << std::endl;
std::cout << "✅ Array sublist fetching" << std::endl;
std::cout << "✅ Type-safe template system" << std::endl;
std::cout << "✅ Memory-efficient file access" << std::endl;
std::cout << "✅ Full interoperability with Dart" << std::endl;
} catch (const std::exception& e) {
std::cout << "❌ C++ write/read test failed: " << e.what() << std::endl;
// Show the file that was created
if (std::filesystem::exists(testFilename)) {
std::cout << "\nFile that was created:" << std::endl;
auto data = readFile(testFilename);
printBinaryDump(data);
}
}
return 0;
}

View File

@@ -1,197 +0,0 @@
#include <iostream>
#include <cassert>
#include <vector>
#include <filesystem>
#include "binary_table.h"
void printBinaryDump(const std::string& filename) {
std::ifstream file(filename, std::ios::binary);
if (!file) {
std::cout << "Cannot open file for dump" << std::endl;
return;
}
file.seekg(0, std::ios::end);
size_t size = file.tellg();
file.seekg(0, std::ios::beg);
std::vector<uint8_t> data(size);
file.read(reinterpret_cast<char*>(data.data()), size);
file.close();
std::cout << "\n=== Binary Dump of " << filename << " (" << size << " bytes) ===" << std::endl;
for (size_t i = 0; i < data.size(); i += 16) {
printf("0x%04X | ", static_cast<unsigned int>(i));
// Hex bytes
for (int j = 0; j < 16; j++) {
if (i + j < data.size()) {
printf("%02X ", data[i + j]);
} else {
printf(" ");
}
}
printf(" | ");
// ASCII representation
for (int j = 0; j < 16; j++) {
if (i + j < data.size()) {
uint8_t byte = data[i + j];
printf("%c", (byte >= 32 && byte <= 126) ? byte : '.');
}
}
printf("\n");
}
std::cout << "=========================" << std::endl;
}
// Test equivalent to Dart's main() function
int main() {
std::cout << "🧪 C++ Binary Table Parity Test (matching Dart behavior)" << std::endl;
std::cout << "=========================================================" << std::endl;
const std::string filename = "cpp_parity_test.bin";
// Clean up any existing file
std::filesystem::remove(filename);
try {
bt::BinaryTable table(filename);
table.initialize();
std::cout << "\n1. Testing basic data types..." << std::endl;
// Set basic values
table.set<int32_t>("myInt", 42);
table.set<float>("myFloat", 3.14f);
table.set<std::string>("myString", "Hello, World!");
// Verify basic values
assert(table.get<int32_t>("myInt") == 42);
assert(table.get<float>("myFloat") == 3.14f);
assert(table.get<std::string>("myString") == "Hello, World!");
std::cout << "✅ Basic data types work correctly" << std::endl;
std::cout << "\n2. Testing array operations..." << std::endl;
// Test array creation and access
std::vector<int32_t> testArray = {10, 20, 30, 40, 50};
table.set<std::vector<int32_t>>("myArray", testArray);
auto retrievedArray = table.get<std::vector<int32_t>>("myArray");
assert(retrievedArray.size() == 5);
for (size_t i = 0; i < retrievedArray.size(); i++) {
assert(retrievedArray[i] == testArray[i]);
}
std::cout << "✅ Array storage and retrieval work correctly" << std::endl;
// Test uniform array operations
auto uniformArray = table.getArray<int32_t>("myArray");
assert(uniformArray.length() == 5);
assert(uniformArray[0] == 10);
assert(uniformArray[4] == 50);
// Test array modification
uniformArray.set(2, 999);
assert(uniformArray[2] == 999);
// Test array extension
uniformArray.add(60);
assert(uniformArray.length() == 6);
assert(uniformArray[5] == 60);
std::cout << "✅ Uniform array operations work correctly" << std::endl;
std::cout << "\n3. Testing multi-key operations (previously causing corruption)..." << std::endl;
// Add multiple keys to test address table stability
table.set<int32_t>("key1", 100);
table.set<int32_t>("key2", 200);
table.set<int32_t>("key3", 300);
table.set<std::string>("str1", "First");
table.set<std::string>("str2", "Second");
// Verify all keys are accessible
assert(table.get<int32_t>("key1") == 100);
assert(table.get<int32_t>("key2") == 200);
assert(table.get<int32_t>("key3") == 300);
assert(table.get<std::string>("str1") == "First");
assert(table.get<std::string>("str2") == "Second");
std::cout << "✅ Multi-key operations work without corruption" << std::endl;
std::cout << "\n4. Testing remove operations..." << std::endl;
// Test removal
table.remove("key2");
// Verify removed key is gone
try {
table.get<int32_t>("key2");
assert(false && "Should have thrown exception");
} catch (const std::runtime_error&) {
// Expected
}
// Verify other keys still work
assert(table.get<int32_t>("key1") == 100);
assert(table.get<int32_t>("key3") == 300);
std::cout << "✅ Remove operations work correctly" << std::endl;
std::cout << "\n5. Testing fetchSublist functionality..." << std::endl;
auto sublist = uniformArray.fetchSublist(1, 4);
assert(sublist.size() == 3);
assert(sublist[0] == 20); // myArray[1]
assert(sublist[1] == 999); // myArray[2] (modified)
assert(sublist[2] == 40); // myArray[3]
std::cout << "✅ fetchSublist works correctly" << std::endl;
std::cout << "\n6. Testing free list and truncation operations..." << std::endl;
// Create some data, then remove it to test free list
table.set<int32_t>("temp1", 1000);
table.set<int32_t>("temp2", 2000);
table.set<int32_t>("temp3", 3000);
table.remove("temp1");
table.remove("temp2");
table.remove("temp3");
// Test truncation
table.truncate();
// Verify original data still accessible
assert(table.get<int32_t>("myInt") == 42);
assert(table.get<std::string>("myString") == "Hello, World!");
assert(table.get<int32_t>("key1") == 100);
std::cout << "✅ Free list and truncation work correctly" << std::endl;
std::cout << "\n🎉 ALL TESTS PASSED! C++ implementation has Dart parity!" << std::endl;
// Print final file dump for verification
printBinaryDump(filename);
// Clean up
std::filesystem::remove(filename);
return 0;
} catch (const std::exception& e) {
std::cout << "❌ Test failed: " << e.what() << std::endl;
// Print file dump for debugging
printBinaryDump(filename);
std::filesystem::remove(filename);
return 1;
}
}