From 5d281a804bef535d74b8b6776f1b7773d2615589 Mon Sep 17 00:00:00 2001 From: konard Date: Mon, 2 Feb 2026 19:49:08 +0100 Subject: [PATCH 1/6] Initial commit with task details Adding CLAUDE.md with task information for AI processing. This file will be removed when the task is complete. Issue: https://github.com/netkeep80/avm/issues/3 --- CLAUDE.md | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 CLAUDE.md diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..db5fc49 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,7 @@ +Issue to solve: https://github.com/netkeep80/avm/issues/3 +Your prepared branch: issue-3-d3af30f6e279 +Your prepared working directory: /tmp/gh-issue-solver-1770058142069 +Your forked repository: konard/netkeep80-avm +Original repository (upstream): netkeep80/avm + +Proceed. From 02591072414ed9f4fa725030ec707ffba69fb988 Mon Sep 17 00:00:00 2001 From: konard Date: Mon, 2 Feb 2026 20:07:08 +0100 Subject: [PATCH 2/6] Fix use-after-free in base_voc destructor The base_voc destructor was calling db->clear() which destroyed unique_ptrs, triggering ~rel_t() destructors that tried to erase from maps of already-destroyed objects. Now we release() all unique_ptrs first to prevent destructors from running during static destruction order. Co-Authored-By: Claude Opus 4.5 --- include/avm.h | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/include/avm.h b/include/avm.h index f1bbcc5..2f5a723 100644 --- a/include/avm.h +++ b/include/avm.h @@ -354,10 +354,12 @@ struct rel_t : obj_aspect, } ~base_voc() { + // Освобождаем память без вызова деструкторов rel_t, + // чтобы избежать use-after-free при обращении к картам + // уже уничтоженных объектов во время завершения программы. + for (auto& ptr : *db) + ptr.release(); db->clear(); - // std::cout << "rel_t::count() = " << rel_t::count() << std::endl; - // std::cout << "rel_t::created() = " << rel_t::created() << std::endl; - // std::cout << "rel_t::deleted() = " << rel_t::deleted() << std::endl; } } voc; From 4c8cdd2343bdb3b07e6411dece59eaf468e92673 Mon Sep 17 00:00:00 2001 From: konard Date: Mon, 2 Feb 2026 20:07:16 +0100 Subject: [PATCH 3/6] Extract import_json/export_json into shared json_io.h header Move JSON import/export functions from main.cpp into include/json_io.h so they can be reused by test code and other modules. Main.cpp now includes the shared header instead of defining these functions inline. Co-Authored-By: Claude Opus 4.5 --- include/json_io.h | 199 +++++++++++++++++++++++++++ src/main.cpp | 334 ++-------------------------------------------- 2 files changed, 208 insertions(+), 325 deletions(-) create mode 100644 include/json_io.h diff --git a/include/json_io.h b/include/json_io.h new file mode 100644 index 0000000..09d24a6 --- /dev/null +++ b/include/json_io.h @@ -0,0 +1,199 @@ +#pragma once +#include "avm.h" +#include +#include +#include +#include + +using namespace std; + +inline void get_json(json &ent, const string &PathName) +{ + std::ifstream in(PathName.c_str()); + if (!in.good()) + throw runtime_error(__func__ + ": Can't load json from the "s + PathName + " file!"); + in >> ent; +} + +inline void add_json(const json &ent, const string &PathName) +{ + std::ofstream out(PathName.c_str()); + if (!out.good()) + throw runtime_error(__func__ + ": Can't open "s + PathName + " file!"s); + out << ent; +} + +inline rel_t *import_json(const json &j) +{ + switch (j.type()) + { + case json::value_t::null: + return rel_t::rel(); + + case json::value_t::boolean: + if (j.get()) + return rel_t::True; + else + return rel_t::False; + + case json::value_t::array: + { + auto array = rel_t::R; + auto lambda_func = [&array](const json& j) { + array = rel_t::rel(import_json(j), array); + }; + std::for_each(j.rbegin(), j.rend(), lambda_func); + return array; + } + + case json::value_t::string: + { + auto str = j.get(); + auto array = rel_t::E; + + for (auto &it : str) + { + uint8_t val = *reinterpret_cast(&it); + for (uint8_t i = 1; i; i <<= 1) + array = rel_t::rel(rel_t::rel(array, (val & i) ? rel_t::True : rel_t::False), rel_t::R); + } + return array = rel_t::rel(array, rel_t::E); + } + + case json::value_t::number_unsigned: + { + json::number_unsigned_t val = j.get(); + auto array = rel_t::E; + for (json::number_unsigned_t i = 1; i; i <<= 1) + array = rel_t::rel(rel_t::rel(array, (val & i) ? rel_t::True : rel_t::False), rel_t::R); + return array = rel_t::rel(array, rel_t::Unsigned); + } + + case json::value_t::number_integer: + { + json::number_integer_t ival = j.get(); + json::number_unsigned_t val = *reinterpret_cast(&ival); + auto array = rel_t::E; + for (json::number_unsigned_t i = 1; i; i <<= 1) + array = rel_t::rel(rel_t::rel(array, (val & i) ? rel_t::True : rel_t::False), rel_t::R); + return array = rel_t::rel(array, rel_t::Integer); + } + + case json::value_t::number_float: + { + json::number_float_t fval = j.get(); + json::number_unsigned_t val = *reinterpret_cast(&fval); + auto array = rel_t::E; + for (json::number_unsigned_t i = 1; i; i <<= 1) + array = rel_t::rel(rel_t::rel(array, (val & i) ? rel_t::True : rel_t::False), rel_t::R); + return array = rel_t::rel(array, rel_t::Float); + } + + case json::value_t::object: + return rel_t::E; + + default: + return rel_t::E; + } +} + +inline void export_json(const rel_t *ent, json &j) +{ + if (ent == rel_t::E) + j = json(); + else if (ent == rel_t::R) + { + j = json::array(); + j.push_back(json()); + } + else if (ent == rel_t::True) + j = json(true); + else if (ent == rel_t::False) + j = json(false); + else if (ent->obj == rel_t::R) + { + j = json::array(); + auto cur = ent; + do + { + json last; + export_json(cur->sub->obj, last); + j.push_back(last); + } while ((cur = cur->sub->sub) != rel_t::E); + + std::reverse(j.begin(), j.end()); + } + else if (ent->obj == rel_t::E) + { + export_json(ent->sub, j); + if (j.is_array()) + { + json::string_t str{}; + uint8_t val{}, i{1}; + for (auto &it : j) + { + if (it.is_boolean()) + if (it.get()) + val |= i; + + if ((i <<= 1) == 0x00) + { + str += *reinterpret_cast(&val); + i = 1; + val = 0; + } + } + j = json(str); + } + } + else if (ent->obj == rel_t::Unsigned) + { + export_json(ent->sub, j); + if (j.is_array()) + { + json::number_unsigned_t val{}, i{1}; + for (auto &it : j) + { + if (it.is_boolean()) + if (it.get()) + val |= i; + i <<= 1; + } + j = json(val); + } + } + else if (ent->obj == rel_t::Integer) + { + export_json(ent->sub, j); + if (j.is_array()) + { + json::number_unsigned_t val{}, i{1}; + for (auto &it : j) + { + if (it.is_boolean()) + if (it.get()) + val |= i; + i <<= 1; + } + j = json(*reinterpret_cast(&val)); + } + } + else if (ent->obj == rel_t::Float) + { + export_json(ent->sub, j); + if (j.is_array()) + { + json::number_unsigned_t val{}, i{1}; + for (auto &it : j) + { + if (it.is_boolean()) + if (it.get()) + val |= i; + i <<= 1; + } + j = json(*reinterpret_cast(&val)); + } + } + else + j = json("is string"); +} diff --git a/src/main.cpp b/src/main.cpp index 636597a..ccb1e26 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,11 +1,11 @@ -#include "avm.h" +#include "json_io.h" +#include "anumber.h" +#include "str_switch/str_switch.h" #include #include #include -#include #include -#include "str_switch/str_switch.h" using namespace std; using namespace Platform::Data::Doublets::Memory::United::Generic; @@ -22,6 +22,7 @@ using namespace Platform::Data::Doublets::Memory::United::Generic; 4. сериализация/десериализация number..............V 5. сериализация/десериализация string..............V 6. сериализация/десериализация object.............. +7. сериализация/десериализация ачисел (МТС)........V Есть вариант для простых типов данных смоделировать память с линейной дресацией через бинарное дерево. Можно использовать 16-тиричную систему адресов. @@ -59,327 +60,6 @@ array это компактная сериализация дерева map, к */ -void get_json(json &ent, const string &PathName) -{ - std::ifstream in(PathName.c_str()); - if (!in.good()) - throw runtime_error(__func__ + ": Can't load json from the "s + PathName + " file!"); - in >> ent; -} - -void add_json(const json &ent, const string &PathName) -{ - std::ofstream out(PathName.c_str()); - if (!out.good()) - throw runtime_error(__func__ + ": Can't open "s + PathName + " file!"s); - out << ent; -} - -rel_t *import_json(const json &j) -{ - switch (j.type()) - { - case json::value_t::null: // начало массива и конец строки - return rel_t::rel(); - - case json::value_t::boolean: - if (j.get()) - return rel_t::True; - else - return rel_t::False; - - case json::value_t::array: // лямбда вектор, который управляет последовательным изменением отношения сущности - { - auto array = rel_t::R; - - // лямбда функция с захватом переменной array - auto lambda_func = [&array](const json& j) { - array = rel_t::rel(import_json(j), array); - }; - - // применяем лямбда функцию ко всем элементам массива j от конца к началу - std::for_each(j.rbegin(), j.rend(), lambda_func); - return array; - } - - case json::value_t::string: // битовая последовательность для описания строк - { - auto str = j.get(); - auto array = rel_t::E; // начало массива - - for (auto &it : str) - { - uint8_t val = *reinterpret_cast(&it); - for (uint8_t i = 1; i; i <<= 1) - array = rel_t::rel(rel_t::rel(array, (val & i) ? rel_t::True : rel_t::False), rel_t::R); - } - return array = rel_t::rel(array, rel_t::E); // конец массива - } - - case json::value_t::number_unsigned: - { - json::number_unsigned_t val = j.get(); - auto array = rel_t::E; - for (json::number_unsigned_t i = 1; i; i <<= 1) - array = rel_t::rel(rel_t::rel(array, (val & i) ? rel_t::True : rel_t::False), rel_t::R); - return array = rel_t::rel(array, rel_t::Unsigned); - } - - case json::value_t::number_integer: - { - json::number_integer_t ival = j.get(); - json::number_unsigned_t val = *reinterpret_cast(&ival); - auto array = rel_t::E; - for (json::number_unsigned_t i = 1; i; i <<= 1) - array = rel_t::rel(rel_t::rel(array, (val & i) ? rel_t::True : rel_t::False), rel_t::R); - return array = rel_t::rel(array, rel_t::Integer); - } - - case json::value_t::number_float: - { - json::number_float_t fval = j.get(); - json::number_unsigned_t val = *reinterpret_cast(&fval); - auto array = rel_t::E; - for (json::number_unsigned_t i = 1; i; i <<= 1) - array = rel_t::rel(rel_t::rel(array, (val & i) ? rel_t::True : rel_t::False), rel_t::R); - return array = rel_t::rel(array, rel_t::Float); - } - - case json::value_t::object: - { - /*auto end = j.end(); - - if (auto ref = j.find("$ref"); ref != end) - { // это ссылка на json значение - if (ref->is_string()) - { - // try { exec_ent($, string_ref_to($, ref->get_ref())); } - // catch (json& j) { throw json({ {ref->get_ref(), j} }); } - } - else - throw json({{"$ref", *ref}}); - } - else if (auto rel = j.find("$rel"); rel != end) - { // это сущность, которую надо исполнить в новом контексте? - auto obj = j.find("$obj"); - auto sub = j.find("$sub"); - } - else - { // контроллер это лямбда структура, которая управляет параллельным проецированием сущностей - auto it = j.begin(); - - // for (auto &it : vct) - // if (!it.exc.is_null()) - // throw json({ {it.key, it.exc} }); - }*/ - return rel_t::E; - } - - default: - return rel_t::E; - } -} - -void export_json(const rel_t *ent, json &j) -{ - if (ent == rel_t::E) // R[E] - j = json(); // null - else if (ent == rel_t::R) // E[E] - { - j = json::array(); // [] - j.push_back(json()); - } - else if (ent == rel_t::True) // R[R] - j = json(true); // true - else if (ent == rel_t::False) // E[R] - j = json(false); // false - else if (ent->obj == rel_t::R) // array - { - j = json::array(); - auto cur = ent; - do - { - json last; - export_json(cur->sub->obj, last); - j.push_back(last); - } while ((cur = cur->sub->sub) != rel_t::E); - - std::reverse(j.begin(), j.end()); - } - else if (ent->obj == rel_t::E) // битовая последовательность для описания строк - { - export_json(ent->sub, j); - if (j.is_array()) - { - json::string_t str{}; - uint8_t val{}, i{1}; - for (auto &it : j) - { - if (it.is_boolean()) - if (it.get()) - val |= i; - - if ((i <<= 1) == 0x00) - { - str += *reinterpret_cast(&val); - i = 1; - val = 0; - } - } - j = json(str); - } - } - else if (ent->obj == rel_t::Unsigned) // sub[Unsigned] - { - export_json(ent->sub, j); - if (j.is_array()) - { - json::number_unsigned_t val{}, i{1}; - for (auto &it : j) - { - if (it.is_boolean()) - if (it.get()) - val |= i; - i <<= 1; - } - j = json(val); - } - } - else if (ent->obj == rel_t::Integer) // sub[Integer] - { - export_json(ent->sub, j); - if (j.is_array()) - { - json::number_unsigned_t val{}, i{1}; - for (auto &it : j) - { - if (it.is_boolean()) - if (it.get()) - val |= i; - i <<= 1; - } - j = json(*reinterpret_cast(&val)); - } - } - else if (ent->obj == rel_t::Float) // sub[Float] - { - export_json(ent->sub, j); - if (j.is_array()) - { - json::number_unsigned_t val{}, i{1}; - for (auto &it : j) - { - if (it.is_boolean()) - if (it.get()) - val |= i; - i <<= 1; - } - j = json(*reinterpret_cast(&val)); - } - } - else - j = json("is string"); -} -/* -string export_string(const rel_t *ent) -{ - if (ent == rel_t::E) // R[E] - return "E"s; - else if (ent == rel_t::R) // E[E] - return "R"s; - else if (ent == rel_t::True) // E[R] - return "O"s; - else if (ent == rel_t::False) // R[R] - return "S"s; - else if (ent->obj == rel_t::R) // array - { - string last; - auto cur = ent; - do - { - if (last.empty()) - last = export_string(cur->sub->obj) + "]"s; - else - last = export_string(cur->sub->obj) + "," + last; - } while ((cur = cur->sub->sub) != rel_t::E); - return "["s + last; - } - else if (ent->obj == rel_t::E) // битовая последовательность для описания строк - { - json j; - export_json(ent->sub, j); - if (j.is_array()) - { - json::string_t str{}; - uint8_t val{}, i{1}; - for (auto &it : j) - { - if (it.is_boolean()) - if (it.get()) - val |= i; - - if ((i <<= 1) == 0x00) - { - str += *reinterpret_cast(&val); - i = 1; - val = 0; - } - } - j = json(str); - } - } - else if (ent->obj == rel_t::Unsigned) // sub[Unsigned] - { - export_json(ent->sub, j); - if (j.is_array()) - { - json::number_unsigned_t val{}, i{1}; - for (auto &it : j) - { - if (it.is_boolean()) - if (it.get()) - val |= i; - i <<= 1; - } - j = json(val); - } - } - else if (ent->obj == rel_t::Integer) // sub[Integer] - { - export_json(ent->sub, j); - if (j.is_array()) - { - json::number_unsigned_t val{}, i{1}; - for (auto &it : j) - { - if (it.is_boolean()) - if (it.get()) - val |= i; - i <<= 1; - } - j = json(*reinterpret_cast(&val)); - } - } - else if (ent->obj == rel_t::Float) // sub[Float] - { - export_json(ent->sub, j); - if (j.is_array()) - { - json::number_unsigned_t val{}, i{1}; - for (auto &it : j) - { - if (it.is_boolean()) - if (it.get()) - val |= i; - i <<= 1; - } - j = json(*reinterpret_cast(&val)); - } - } - else - j = json("is string"); -} -*/ size_t link_name(vector &sub, const string &str, size_t start_pos, size_t end_pos) { if (end_pos > start_pos) @@ -521,7 +201,11 @@ Copyright (c) 2022 Vertushkin Roman Pavlovich export_json(root_ent, res); // cout << res.dump() << endl; add_json(res, "res.json"s); - //add_json(json(export_string(root_ent)), "res.txt"s); + + // сериализация в ачисло (МТС) + string anumber = format_anumber(root_ent); + cout << "anumber: " << anumber << endl; + std::cout << "rel_t::created() = " << rel_t::created() << std::endl; return 0; // ok } From b1a398d4145683486561132aec89b8f03ecdc22f Mon Sep 17 00:00:00 2001 From: konard Date: Mon, 2 Feb 2026 20:07:25 +0100 Subject: [PATCH 4/6] Implement MTS-based anumber serialization/deserialization MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add anumber.h with complete implementation of associative number (ачисло) serialization and deserialization based on the Meta-Theory of Links (МТС/MTS) draft specification. Features: - EBNF grammar: anumber = "∞" { abit }; abit = "1" | "-1" | "(" abit_seq ")" - 4 abits: 1 (link presence), -1 (link absence), ( (group open), ) (group close) - Serialization: rel_t graph → anumber string - Deserialization: anumber string → rel_t graph - Quaternary notation: -1→0, 1→1, (→2, )→3 - Tokenizer with whitespace tolerance - 20 test cases (65 assertions) covering roundtrips, edge cases, errors Updated CMakeLists.txt: - Cross-platform build (Windows DLL dependency gated by if(WIN32)) - Added test_anumber target with CTest integration - Version bump 0.0.0 → 0.0.1 Fixes #3 Co-Authored-By: Claude Opus 4.5 --- CMakeLists.txt | 32 ++- include/anumber.h | 519 ++++++++++++++++++++++++++++++++++++++ test/src/test_anumber.cpp | 402 +++++++++++++++++++++++++++++ 3 files changed, 947 insertions(+), 6 deletions(-) create mode 100644 include/anumber.h create mode 100644 test/src/test_anumber.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index df047c3..a865bac 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,7 +2,7 @@ cmake_minimum_required(VERSION 3.20 FATAL_ERROR) message( "Configuring: ${CMAKE_CURRENT_SOURCE_DIR}") -set(CMAKE_PROJECT_VERSION 0.0.0 ) +set(CMAKE_PROJECT_VERSION 0.0.1 ) # Project name project( avm @@ -23,11 +23,14 @@ set( CMAKE_CXX_EXTENSIONS OFF ) # Name of executable add_executable( ${TARGET_NAME} ) -link_directories( - "${CMAKE_CURRENT_SOURCE_DIR}/3p" ) +# LinksPlatform dependency (Windows only) +if( WIN32 ) + link_directories( + "${CMAKE_CURRENT_SOURCE_DIR}/3p" ) -target_link_libraries(${PROJECT_NAME} PRIVATE - "${CMAKE_CURRENT_SOURCE_DIR}/3p/doublets_ffi.dll.lib" ) + target_link_libraries(${PROJECT_NAME} PRIVATE + "${CMAKE_CURRENT_SOURCE_DIR}/3p/doublets_ffi.dll.lib" ) +endif() # Adding build-requirements target_include_directories( ${TARGET_NAME} PRIVATE @@ -36,9 +39,26 @@ target_include_directories( ${TARGET_NAME} PRIVATE target_sources( ${TARGET_NAME} PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/include/avm.h" + "${CMAKE_CURRENT_SOURCE_DIR}/include/json_io.h" + "${CMAKE_CURRENT_SOURCE_DIR}/include/anumber.h" "${CMAKE_CURRENT_SOURCE_DIR}/3p/UnitedMemoryLinks.h" - "${CMAKE_CURRENT_SOURCE_DIR}/src/main.cpp" ) + "${CMAKE_CURRENT_SOURCE_DIR}/src/main.cpp" ) if( WIN32 ) add_definitions(-DUNICODE -D_UNICODE) endif() + +# Test target for anumber serialization/deserialization +add_executable( test_anumber + "${CMAKE_CURRENT_SOURCE_DIR}/test/src/test_anumber.cpp" ) + +target_include_directories( test_anumber PRIVATE + "${CMAKE_CURRENT_SOURCE_DIR}/include" + "${CMAKE_CURRENT_SOURCE_DIR}/3p" ) + +if( WIN32 ) + target_link_libraries( test_anumber PRIVATE + "${CMAKE_CURRENT_SOURCE_DIR}/3p/doublets_ffi.dll.lib" ) +endif() + +add_test( NAME test_anumber COMMAND test_anumber ) diff --git a/include/anumber.h b/include/anumber.h new file mode 100644 index 0000000..1c38b3d --- /dev/null +++ b/include/anumber.h @@ -0,0 +1,519 @@ +#pragma once +#include "avm.h" +#include +#include +#include +#include +#include + +/* + Сериализация/десериализация ачисел (ассоциативных чисел) на основе МТС + (Метатеории связей). + + Ачисло — имя связи в виде последовательности абит, описывающих полную + структуру связи в сериализованном виде. Все ачисла неявно начинаются с ∞. + + РБНФ (расширенная форма Бэкуса-Наура) нотации ачисел: + + anumber = "∞" { abit } ; + abit = "1" | "-1" | "(" abit_seq ")" ; + abit_seq = { abit } ; + + Четверичная нотация абит: + 0 ↔ -1 (∞♀ → ♂∞, отсутствие связи, несвязь ↛) + 1 ↔ 1 (♂∞ → ∞♀, наличие связи →) + 2 ↔ ( (♂∞, начало вложенной связи, самозамыкание по ссылке) + 3 ↔ ) (∞♀, конец вложенной связи, самозамыкание по значению) + + Определения символических знаков МТС: + ∞ — ассоциативный корень, полностью самозамкнутая связь (∞ ≡ ∞ → ∞) + ♂∞ — связь с самозамкнутым началом (♂∞ ≡ ♂∞ → ∞), роль ссылки, "(" + ∞♀ — связь с самозамкнутым концом (∞♀ ≡ ∞ → ∞♀), роль значения, ")" + 1 — наличие связи (♂∞ → ∞♀), роль связи "→" + -1 — отсутствие связи (∞♀ → ♂∞), роль несвязи "↛" + + Четыре вида формы связей: + 1. ∞ → ∞ — полное самозамыкание (∞) + 2. ♂∞ → ∞ — самозамыкание начала (♂) + 3. ∞ → ∞♀ — самозамыкание конца (♀) + 4. ♂∞ → ∞♀ — связь без самозамыканий (→) + + Внутренняя структура связей в rel_t: + rel(obj, sub) — связь от obj (ссылка/источник) к sub (значение/приёмник) + + Кодирование типов данных в rel_t (из import_json): + - null: rel() → самоссылающийся узел (obj=self, sub=self) + - true: rel_t::True (singleton, obj=R, sub=R) + - false: rel_t::False (singleton, obj=E, sub=R) + - array: связный список rel(element, next), конец: R + [a,b,c] → rel(a, rel(b, rel(c, R))) + - string: rel(bit_chain, E), где bit_chain — цепочка бит + цепочка: rel(rel(...rel(E, bit0)..., bitN-1), R) + каждый уровень чередует sub=R (обёртка) и sub=True/False (бит) + - unsigned: rel(bit_chain, Unsigned) — аналогично string + - integer: rel(bit_chain, Integer) + - float: rel(bit_chain, Float) + + Алгоритм сериализации связи в ачисло: + + Связь рекурсивно обходится и кодируется абитами: + - E (null/пустая сущность) → пустое ачисло (только ∞) + - R (корень массивов) → пустое ачисло + - True → "1" + - False → "-1" + - Битовая последовательность (string/number): + биты извлекаются из цепочки и записываются как 1/-1 + - Массив: элементы записываются в скобках + [a, b, c] → "(сер.a)(сер.b)(сер.c)" + - Произвольная связь rel(obj, sub): + "(сер.obj)(сер.sub)" + + Алгоритм десериализации ачисла в связь: + + Используется стэк начал связей (stack of link starts). + - "1" : добавить бит true к текущей битовой цепочке + - "-1" : добавить бит false к текущей битовой цепочке + - "(" : сохранить текущий контекст в стэк, начать новый + - ")" : завершить текущий контекст, встроить в родительский + + Пример ачисел: + ∞ — null (E) + ∞1 — true + ∞-1 — false + ∞(1)(-1)(1) — массив [true, false, true] + ∞1-1-1-1-11 1-1 — строка "a" (биты: 1,0,0,0,0,1,1,0) +*/ + +using namespace std; + +// Перечисление абит (ассоциативных бит) +enum class abit_t { + LINK = 1, // 1 — наличие связи (♂∞ → ∞♀) + NOLINK = 0, // -1 — отсутствие связи (∞♀ → ♂∞) + OPEN = 2, // ( — начало вложенной связи (♂∞) + CLOSE = 3 // ) — конец вложенной связи (∞♀) +}; + +/* + Сериализация rel_t в строку абит (МТС нотация). + + Структура данных в rel_t: + - Битовые последовательности (строки, числа) хранятся как вложенные пары: + Каждый уровень цепочки чередует обёртку (sub=R) и бит (sub=True/False), + начиная с E как базы цепочки. + - Массивы хранятся как правосвязные списки rel(elem, next) с R на конце. +*/ + +// Вспомогательная функция: извлечение битов из цепочки бит +// Обход цепочки от внешнего уровня к внутреннему (через obj), +// чередуя sub=R (обёртка) и sub=True/False (бит). +// Биты записываются в обратном порядке (от внутреннего к внешнему). +inline void extract_bits(const rel_t* chain, vector& bits) +{ + const rel_t* cur = chain; + while (cur != rel_t::E) + { + // Уровень обёртки: sub=R, obj=пара_с_битом + if (cur->sub == rel_t::R) + { + const rel_t* bit_pair = cur->obj; + if (bit_pair) + { + // Уровень бита: sub=True/False, obj=предыдущий_элемент_цепочки + bool bit_val = (bit_pair->sub == rel_t::True); + bits.push_back(bit_val); + cur = bit_pair->obj; // идём глубже по цепочке + } + else + break; + } + else + break; + } +} + +// Сериализация rel_t в строку абит (МТС нотация) +inline string serialize_anumber(const rel_t* ent) +{ + if (!ent) + return ""; + + // ∞ — пустое ачисло (корень, null) + if (ent == rel_t::E) + return ""; + + // R — корень массивов (пустой массив) + if (ent == rel_t::R) + return ""; + + // Базовые абиты + if (ent == rel_t::True) + return "1"; + + if (ent == rel_t::False) + return "-1"; + + // Типы-маркеры (не должны сериализоваться напрямую) + if (ent == rel_t::Unsigned || ent == rel_t::Integer || ent == rel_t::Float) + return ""; + + // Самоссылающийся узел (null из import_json) + if (ent->obj == ent && ent->sub == ent) + return ""; + + // Битовые последовательности: строки и числа + // Определяем по типу-терминатору в sub + if (ent->sub == rel_t::E || ent->sub == rel_t::Unsigned || + ent->sub == rel_t::Integer || ent->sub == rel_t::Float) + { + // ent = rel(bit_chain, type_marker) + // Извлекаем биты из цепочки в obj + vector bits; + extract_bits(ent->obj, bits); + + if (bits.empty()) + return ""; + + // Биты в обратном порядке (от внутреннего к внешнему = LSB first) + string result; + for (auto it = bits.rbegin(); it != bits.rend(); ++it) + result += (*it) ? "1" : "-1"; + + return result; + } + + // Массивы: правосвязный список rel(element, next), конец: R + // Определяем массив по наличию R в цепочке sub + { + // Проверяем, является ли это списком (массивом) + bool is_list = false; + const rel_t* tail = ent; + int depth = 0; + while (tail != rel_t::R && tail != rel_t::E && depth < 1000) + { + if (tail->sub == rel_t::R) + { + is_list = true; + break; + } + tail = tail->sub; + depth++; + if (tail == rel_t::R) + { + is_list = true; + break; + } + } + + if (is_list) + { + string result; + const rel_t* cur = ent; + while (cur != rel_t::R && cur != rel_t::E) + { + string elem = serialize_anumber(cur->obj); + result += "(" + elem + ")"; + cur = cur->sub; + } + return result; + } + } + + // Общий случай: произвольная связь rel(obj, sub) + string obj_str = serialize_anumber(ent->obj); + string sub_str = serialize_anumber(ent->sub); + + if (obj_str.empty() && sub_str.empty()) + return ""; + + return "(" + obj_str + ")(" + sub_str + ")"; +} + +// Форматирование ачисла с префиксом ∞ +inline string format_anumber(const rel_t* ent) +{ + string body = serialize_anumber(ent); + return "\xe2\x88\x9e" + body; // ∞ в UTF-8 +} + +/* + Десериализация ачисла (строки абит) в связь (rel_t). + + Парсер читает последовательность абит и строит дерево связей, + используя стэк для обработки вложенных контекстов. + + Токены: + "1" → abit_t::LINK (наличие связи) + "-1" → abit_t::NOLINK (отсутствие связи) + "(" → abit_t::OPEN (начало вложенного контекста) + ")" → abit_t::CLOSE (конец вложенного контекста) +*/ + +// Токенизация строки ачисла в последовательность абит +inline vector tokenize_anumber(const string& str) +{ + vector tokens; + size_t pos = 0; + + // Пропускаем опциональный префикс ∞ (UTF-8: E2 88 9E) + if (pos + 3 <= str.size() && + (unsigned char)str[pos] == 0xE2 && + (unsigned char)str[pos+1] == 0x88 && + (unsigned char)str[pos+2] == 0x9E) + { + pos += 3; + } + + while (pos < str.size()) + { + char c = str[pos]; + + if (c == '(') + { + tokens.push_back(abit_t::OPEN); + pos++; + } + else if (c == ')') + { + tokens.push_back(abit_t::CLOSE); + pos++; + } + else if (c == '1') + { + tokens.push_back(abit_t::LINK); + pos++; + } + else if (c == '-' && pos + 1 < str.size() && str[pos+1] == '1') + { + tokens.push_back(abit_t::NOLINK); + pos += 2; + } + else if (c == ' ' || c == '\t' || c == '\n' || c == '\r') + { + pos++; // пропуск пробелов + } + else + { + throw runtime_error( + "anumber parse error: unexpected character '"s + + c + "' at position " + to_string(pos)); + } + } + + return tokens; +} + +/* + Десериализация последовательности абит в связь. + + Контекстная модель: + - Каждый контекст собирает биты (1/-1) в битовую цепочку + - Скобки "(" ... ")" создают вложенный контекст и встраивают + результат как элемент массива в родительский контекст. + - Если контекст содержит только биты → битовая последовательность + (строка с терминатором E) + - Если контекст содержит вложенные элементы → массив (список) +*/ +inline rel_t* deserialize_anumber(const string& str) +{ + vector tokens = tokenize_anumber(str); + + if (tokens.empty()) + return rel_t::E; // пустое ачисло = ∞ ≡ null/E + + // Контекст десериализации + struct context_t { + rel_t* bit_chain; // текущая цепочка бит (начинается с E) + bool has_bits; // есть ли биты в текущем контексте? + vector elements; // элементы массива (из вложенных контекстов) + }; + + stack ctx_stack; + ctx_stack.push({rel_t::E, false, {}}); + + for (size_t i = 0; i < tokens.size(); i++) + { + abit_t tok = tokens[i]; + + switch (tok) + { + case abit_t::LINK: + { + auto& ctx = ctx_stack.top(); + // Добавить бит true к текущей цепочке бит + // Структура: rel(rel(prev_chain, True), R) + ctx.bit_chain = rel_t::rel( + rel_t::rel(ctx.bit_chain, rel_t::True), + rel_t::R + ); + ctx.has_bits = true; + break; + } + + case abit_t::NOLINK: + { + auto& ctx = ctx_stack.top(); + // Добавить бит false к текущей цепочке бит + ctx.bit_chain = rel_t::rel( + rel_t::rel(ctx.bit_chain, rel_t::False), + rel_t::R + ); + ctx.has_bits = true; + break; + } + + case abit_t::OPEN: + { + // Открыть новый контекст + ctx_stack.push({rel_t::E, false, {}}); + break; + } + + case abit_t::CLOSE: + { + if (ctx_stack.size() <= 1) + throw runtime_error( + "anumber parse error: unexpected ')' — no matching '('"); + + // Завершить текущий контекст + context_t inner = ctx_stack.top(); + ctx_stack.pop(); + + // Определить результат внутреннего контекста + rel_t* inner_result; + + if (!inner.elements.empty()) + { + // Массив: строим правосвязный список + inner_result = rel_t::R; + for (auto it = inner.elements.rbegin(); it != inner.elements.rend(); ++it) + inner_result = rel_t::rel(*it, inner_result); + } + else if (inner.has_bits) + { + // Проверяем: если это одиночный бит, возвращаем True/False + // Одиночный бит: rel(rel(E, True/False), R) + if (inner.bit_chain->sub == rel_t::R && + inner.bit_chain->obj != nullptr && + inner.bit_chain->obj->obj == rel_t::E) + { + if (inner.bit_chain->obj->sub == rel_t::True) + inner_result = rel_t::True; + else if (inner.bit_chain->obj->sub == rel_t::False) + inner_result = rel_t::False; + else + inner_result = rel_t::rel(inner.bit_chain, rel_t::E); + } + else + { + // Битовая последовательность → строка (терминатор E) + inner_result = rel_t::rel(inner.bit_chain, rel_t::E); + } + } + else + { + // Пустой контекст → null + inner_result = rel_t::E; + } + + // Встроить результат в родительский контекст как элемент + auto& parent = ctx_stack.top(); + parent.elements.push_back(inner_result); + break; + } + } + } + + if (ctx_stack.size() != 1) + throw runtime_error( + "anumber parse error: unclosed '(' — missing ')'"); + + auto& final_ctx = ctx_stack.top(); + + // Определить результат корневого контекста + if (!final_ctx.elements.empty()) + { + // Массив: строим правосвязный список + rel_t* result = rel_t::R; + for (auto it = final_ctx.elements.rbegin(); it != final_ctx.elements.rend(); ++it) + result = rel_t::rel(*it, result); + return result; + } + + if (final_ctx.has_bits) + { + // Проверяем: если это одиночный бит, возвращаем True/False + if (final_ctx.bit_chain->sub == rel_t::R && + final_ctx.bit_chain->obj != nullptr && + final_ctx.bit_chain->obj->obj == rel_t::E) + { + if (final_ctx.bit_chain->obj->sub == rel_t::True) + return rel_t::True; + else if (final_ctx.bit_chain->obj->sub == rel_t::False) + return rel_t::False; + } + // Битовая последовательность → строка (терминатор E) + return rel_t::rel(final_ctx.bit_chain, rel_t::E); + } + + return rel_t::E; +} + +/* + Вспомогательная функция: преобразование ачисла в четверичную нотацию. + + Четверичная система: + -1 → 0 + 1 → 1 + ( → 2 + ) → 3 +*/ +inline string anumber_to_quaternary(const string& anumber_str) +{ + vector tokens = tokenize_anumber(anumber_str); + string result; + + for (auto tok : tokens) + { + switch (tok) + { + case abit_t::NOLINK: result += '0'; break; + case abit_t::LINK: result += '1'; break; + case abit_t::OPEN: result += '2'; break; + case abit_t::CLOSE: result += '3'; break; + } + } + + return result; +} + +/* + Вспомогательная функция: преобразование четверичной нотации в ачисло. + + Четверичная система: + 0 → -1 + 1 → 1 + 2 → ( + 3 → ) +*/ +inline string quaternary_to_anumber(const string& quat_str) +{ + string result = "\xe2\x88\x9e"; // ∞ prefix + + for (char c : quat_str) + { + switch (c) + { + case '0': result += "-1"; break; + case '1': result += "1"; break; + case '2': result += "("; break; + case '3': result += ")"; break; + case ' ': case '\t': case '\n': break; // пропуск пробелов + default: + throw runtime_error( + "quaternary parse error: unexpected character '"s + c + "'"); + } + } + + return result; +} diff --git a/test/src/test_anumber.cpp b/test/src/test_anumber.cpp new file mode 100644 index 0000000..a37b94b --- /dev/null +++ b/test/src/test_anumber.cpp @@ -0,0 +1,402 @@ +#include "json_io.h" +#include "anumber.h" +#include +#include + +using namespace std; + +// Вспомогательная функция для проверки утверждений с сообщениями +#define TEST_ASSERT(cond, msg) \ + do { \ + if (!(cond)) { \ + cerr << "FAIL: " << msg << endl; \ + cerr << " at " << __FILE__ << ":" << __LINE__ << endl; \ + failed++; \ + } else { \ + passed++; \ + } \ + } while (0) + +// Вспомогательная функция: извлечь биты из битовой цепочки строки/числа +vector get_bits(const rel_t* ent) +{ + vector bits; + if (!ent || ent == rel_t::E || ent == rel_t::R) + return bits; + + // ent = rel(bit_chain, terminator) + const rel_t* cur = ent->obj; + while (cur != rel_t::E) + { + if (cur->sub == rel_t::R) + { + const rel_t* bit_pair = cur->obj; + if (bit_pair) + { + bits.push_back(bit_pair->sub == rel_t::True); + cur = bit_pair->obj; + } + else break; + } + else break; + } + // Reverse: bits are stored from outermost (last) to innermost (first) + std::reverse(bits.begin(), bits.end()); + return bits; +} + +int main() +{ + int passed = 0, failed = 0; + + cout << "=== Тесты сериализации/десериализации ачисел (МТС) ===" << endl; + + // --- Тест 1: Токенизация --- + cout << endl << "--- Тест 1: Токенизация ---" << endl; + { + auto tokens = tokenize_anumber("\xe2\x88\x9e" "1-1(1-1)1"); + TEST_ASSERT(tokens.size() == 7, "tokenize '∞1-1(1-1)1' should produce 7 tokens, got " + to_string(tokens.size())); + TEST_ASSERT(tokens[0] == abit_t::LINK, "token[0] should be LINK"); + TEST_ASSERT(tokens[1] == abit_t::NOLINK, "token[1] should be NOLINK"); + TEST_ASSERT(tokens[2] == abit_t::OPEN, "token[2] should be OPEN"); + TEST_ASSERT(tokens[3] == abit_t::LINK, "token[3] should be LINK"); + TEST_ASSERT(tokens[4] == abit_t::NOLINK, "token[4] should be NOLINK"); + TEST_ASSERT(tokens[5] == abit_t::CLOSE, "token[5] should be CLOSE"); + TEST_ASSERT(tokens[6] == abit_t::LINK, "token[6] should be LINK"); + cout << " tokenize '∞1-1(1-1)1': " << tokens.size() << " tokens OK" << endl; + } + + // --- Тест 2: Токенизация без префикса ∞ --- + cout << endl << "--- Тест 2: Токенизация без префикса ∞ ---" << endl; + { + auto tokens = tokenize_anumber("1-11"); + TEST_ASSERT(tokens.size() == 3, "tokenize '1-11' should produce 3 tokens"); + TEST_ASSERT(tokens[0] == abit_t::LINK, "token[0] should be LINK"); + TEST_ASSERT(tokens[1] == abit_t::NOLINK, "token[1] should be NOLINK"); + TEST_ASSERT(tokens[2] == abit_t::LINK, "token[2] should be LINK"); + cout << " tokenize '1-11': " << tokens.size() << " tokens OK" << endl; + } + + // --- Тест 3: Четверичная нотация --- + cout << endl << "--- Тест 3: Четверичная нотация ---" << endl; + { + string quat = anumber_to_quaternary("\xe2\x88\x9e" "1-1(1-1)1"); + TEST_ASSERT(quat == "1021031", "quaternary of '∞1-1(1-1)1' should be '1021031', got '" + quat + "'"); + cout << " anumber_to_quaternary('∞1-1(1-1)1') = '" << quat << "'" << endl; + + string anumber = quaternary_to_anumber("1021031"); + auto tokens = tokenize_anumber(anumber); + TEST_ASSERT(tokens.size() == 7, "roundtrip quaternary should produce 7 tokens"); + cout << " quaternary_to_anumber('1021031') roundtrip OK" << endl; + } + + // --- Тест 4: Сериализация базовых связей --- + cout << endl << "--- Тест 4: Сериализация базовых связей ---" << endl; + { + string e_str = serialize_anumber(rel_t::E); + TEST_ASSERT(e_str.empty(), "serialize E should be empty, got '" + e_str + "'"); + cout << " serialize(E) = '" << format_anumber(rel_t::E) << "'" << endl; + + string true_str = serialize_anumber(rel_t::True); + TEST_ASSERT(true_str == "1", "serialize True should be '1', got '" + true_str + "'"); + cout << " serialize(True) = '" << format_anumber(rel_t::True) << "'" << endl; + + string false_str = serialize_anumber(rel_t::False); + TEST_ASSERT(false_str == "-1", "serialize False should be '-1', got '" + false_str + "'"); + cout << " serialize(False) = '" << format_anumber(rel_t::False) << "'" << endl; + + string r_str = serialize_anumber(rel_t::R); + TEST_ASSERT(r_str.empty(), "serialize R should be empty, got '" + r_str + "'"); + cout << " serialize(R) = '" << format_anumber(rel_t::R) << "'" << endl; + } + + // --- Тест 5: Сериализация строки "a" --- + cout << endl << "--- Тест 5: Сериализация строки 'a' ---" << endl; + { + // 'a' = 0x61, binary: 01100001, LSB first: 1,0,0,0,0,1,1,0 + json j = json("a"); + rel_t* str_rel = import_json(j); + string str_anumber = serialize_anumber(str_rel); + cout << " serialize('a') = '" << format_anumber(str_rel) << "'" << endl; + + auto tokens = tokenize_anumber(str_anumber); + TEST_ASSERT(tokens.size() == 8, "serialize 'a' should produce 8 abit tokens, got " + to_string(tokens.size())); + + if (tokens.size() == 8) { + // LSB first: 1,0,0,0,0,1,1,0 + TEST_ASSERT(tokens[0] == abit_t::LINK, "'a' bit 0 should be 1 (LINK)"); + TEST_ASSERT(tokens[1] == abit_t::NOLINK, "'a' bit 1 should be 0 (NOLINK)"); + TEST_ASSERT(tokens[2] == abit_t::NOLINK, "'a' bit 2 should be 0 (NOLINK)"); + TEST_ASSERT(tokens[3] == abit_t::NOLINK, "'a' bit 3 should be 0 (NOLINK)"); + TEST_ASSERT(tokens[4] == abit_t::NOLINK, "'a' bit 4 should be 0 (NOLINK)"); + TEST_ASSERT(tokens[5] == abit_t::LINK, "'a' bit 5 should be 1 (LINK)"); + TEST_ASSERT(tokens[6] == abit_t::LINK, "'a' bit 6 should be 1 (LINK)"); + TEST_ASSERT(tokens[7] == abit_t::NOLINK, "'a' bit 7 should be 0 (NOLINK)"); + } + } + + // --- Тест 6: Roundtrip десериализация строки "a" --- + cout << endl << "--- Тест 6: Roundtrip десериализация строки 'a' ---" << endl; + { + json j_orig = json("a"); + rel_t* orig = import_json(j_orig); + string anumber = serialize_anumber(orig); + + rel_t* restored = deserialize_anumber(anumber); + + TEST_ASSERT(restored->sub == rel_t::E, "restored sub should be E (string terminator)"); + + auto orig_bits = get_bits(orig); + auto rest_bits = get_bits(restored); + TEST_ASSERT(orig_bits.size() == rest_bits.size(), + "bit count should match: orig=" + to_string(orig_bits.size()) + + " restored=" + to_string(rest_bits.size())); + if (orig_bits.size() == rest_bits.size()) + { + bool bits_match = true; + for (size_t i = 0; i < orig_bits.size(); i++) + if (orig_bits[i] != rest_bits[i]) { bits_match = false; break; } + TEST_ASSERT(bits_match, "all bits should match after roundtrip"); + } + cout << " roundtrip string 'a' OK" << endl; + } + + // --- Тест 7: Десериализация пустого ачисла --- + cout << endl << "--- Тест 7: Десериализация пустого ачисла ---" << endl; + { + rel_t* result = deserialize_anumber("\xe2\x88\x9e"); + TEST_ASSERT(result == rel_t::E, "deserialize '∞' should be E (null)"); + cout << " deserialize('∞') = E (null) OK" << endl; + } + + // --- Тест 8: Сериализация числа 42 --- + cout << endl << "--- Тест 8: Сериализация числа 42 ---" << endl; + { + json j_orig = json(42u); + rel_t* orig = import_json(j_orig); + string anumber = serialize_anumber(orig); + cout << " serialize(42u) = '∞" << anumber << "'" << endl; + + auto tokens = tokenize_anumber(anumber); + TEST_ASSERT(tokens.size() == 64, "serialize 42u should produce 64 abit tokens (64-bit), got " + to_string(tokens.size())); + + if (tokens.size() >= 8) { + // 42 = 0x2A = 00101010, LSB first: 0,1,0,1,0,1,0,0 + TEST_ASSERT(tokens[0] == abit_t::NOLINK, "42u bit 0 should be 0"); + TEST_ASSERT(tokens[1] == abit_t::LINK, "42u bit 1 should be 1"); + TEST_ASSERT(tokens[2] == abit_t::NOLINK, "42u bit 2 should be 0"); + TEST_ASSERT(tokens[3] == abit_t::LINK, "42u bit 3 should be 1"); + TEST_ASSERT(tokens[4] == abit_t::NOLINK, "42u bit 4 should be 0"); + TEST_ASSERT(tokens[5] == abit_t::LINK, "42u bit 5 should be 1"); + } + + string quat = anumber_to_quaternary(anumber); + cout << " quaternary(42u) = '" << quat << "'" << endl; + } + + // --- Тест 9: Сериализация массива [true, false, true] --- + cout << endl << "--- Тест 9: Сериализация массива [true, false, true] ---" << endl; + { + json j_orig = json::array({true, false, true}); + rel_t* orig = import_json(j_orig); + string anumber = serialize_anumber(orig); + cout << " serialize([true, false, true]) = '∞" << anumber << "'" << endl; + + TEST_ASSERT(anumber == "(1)(-1)(1)", + "serialize [true, false, true] should be '(1)(-1)(1)', got '" + anumber + "'"); + } + + // --- Тест 10: Roundtrip массива --- + cout << endl << "--- Тест 10: Roundtrip массива ---" << endl; + { + json j_orig = json::array({true, false, true}); + rel_t* orig = import_json(j_orig); + string anumber = serialize_anumber(orig); + + rel_t* restored = deserialize_anumber(anumber); + + // [true, false, true] → rel(True, rel(False, rel(True, R))) + TEST_ASSERT(restored->obj == rel_t::True, "first element should be True"); + TEST_ASSERT(restored->sub != rel_t::R, "should have more elements"); + if (restored->sub != rel_t::R) + { + TEST_ASSERT(restored->sub->obj == rel_t::False, "second element should be False"); + if (restored->sub->sub != rel_t::R) + { + TEST_ASSERT(restored->sub->sub->obj == rel_t::True, "third element should be True"); + TEST_ASSERT(restored->sub->sub->sub == rel_t::R, "list should end with R"); + } + } + cout << " roundtrip [true, false, true] OK" << endl; + } + + // --- Тест 11: Токенизация с пробелами --- + cout << endl << "--- Тест 11: Токенизация с пробелами ---" << endl; + { + auto tokens = tokenize_anumber("\xe2\x88\x9e" " 1 -1 ( 1 ) "); + TEST_ASSERT(tokens.size() == 5, "tokenize with spaces should produce 5 tokens, got " + to_string(tokens.size())); + cout << " tokenize '∞ 1 -1 ( 1 )': " << tokens.size() << " tokens OK" << endl; + } + + // --- Тест 12: Ошибка парсинга — лишняя ) --- + cout << endl << "--- Тест 12: Ошибка парсинга — лишняя ) ---" << endl; + { + bool caught = false; + try { + deserialize_anumber("1)"); + } catch (const runtime_error& e) { + caught = true; + cout << " caught expected error: " << e.what() << endl; + } + TEST_ASSERT(caught, "deserialize '1)' should throw runtime_error"); + } + + // --- Тест 13: Ошибка парсинга — незакрытая ( --- + cout << endl << "--- Тест 13: Ошибка парсинга — незакрытая ( ---" << endl; + { + bool caught = false; + try { + deserialize_anumber("(1"); + } catch (const runtime_error& e) { + caught = true; + cout << " caught expected error: " << e.what() << endl; + } + TEST_ASSERT(caught, "deserialize '(1' should throw runtime_error"); + } + + // --- Тест 14: Format anumber --- + cout << endl << "--- Тест 14: Format anumber ---" << endl; + { + string formatted = format_anumber(rel_t::True); + TEST_ASSERT(formatted == "\xe2\x88\x9e" "1", + "format_anumber(True) should be '∞1'"); + cout << " format_anumber(True) = '" << formatted << "'" << endl; + + formatted = format_anumber(rel_t::E); + TEST_ASSERT(formatted == "\xe2\x88\x9e", + "format_anumber(E) should be '∞'"); + cout << " format_anumber(E) = '" << formatted << "'" << endl; + } + + // --- Тест 15: Сериализация строки "ab" --- + cout << endl << "--- Тест 15: Сериализация строки 'ab' ---" << endl; + { + json j = json("ab"); + rel_t* str_rel = import_json(j); + string str_anumber = serialize_anumber(str_rel); + cout << " serialize('ab') = '∞" << str_anumber << "'" << endl; + + auto tokens = tokenize_anumber(str_anumber); + TEST_ASSERT(tokens.size() == 16, "serialize 'ab' should produce 16 abit tokens, got " + to_string(tokens.size())); + } + + // --- Тест 16: Roundtrip строки "ab" --- + cout << endl << "--- Тест 16: Roundtrip строки 'ab' ---" << endl; + { + json j_orig = json("ab"); + rel_t* orig = import_json(j_orig); + string anumber = serialize_anumber(orig); + + rel_t* restored = deserialize_anumber(anumber); + + auto orig_bits = get_bits(orig); + auto rest_bits = get_bits(restored); + TEST_ASSERT(orig_bits.size() == rest_bits.size(), + "bit count should match: orig=" + to_string(orig_bits.size()) + + " restored=" + to_string(rest_bits.size())); + if (orig_bits.size() == rest_bits.size()) + { + bool bits_match = true; + for (size_t i = 0; i < orig_bits.size(); i++) + if (orig_bits[i] != rest_bits[i]) { bits_match = false; break; } + TEST_ASSERT(bits_match, "all bits should match after roundtrip for 'ab'"); + } + cout << " roundtrip string 'ab' OK" << endl; + } + + // --- Тест 17: Сериализация вложенного массива --- + cout << endl << "--- Тест 17: Сериализация вложенного массива ---" << endl; + { + // [[true], [false]] + json j_orig = json::array({json::array({true}), json::array({false})}); + rel_t* orig = import_json(j_orig); + string anumber = serialize_anumber(orig); + cout << " serialize([[true], [false]]) = '∞" << anumber << "'" << endl; + + TEST_ASSERT(anumber == "((1))((-1))", + "serialize [[true],[false]] should be '((1))((-1))', got '" + anumber + "'"); + } + + // --- Тест 18: Roundtrip вложенного массива --- + cout << endl << "--- Тест 18: Roundtrip вложенного массива ---" << endl; + { + string anumber = "((1))((-1))"; + rel_t* restored = deserialize_anumber(anumber); + + // [[true], [false]] → rel([true], rel([false], R)) + // [true] → rel(True, R), [false] → rel(False, R) + TEST_ASSERT(restored->obj != nullptr, "first element should exist"); + if (restored->obj) { + TEST_ASSERT(restored->obj->obj == rel_t::True, "first inner array should contain True"); + TEST_ASSERT(restored->obj->sub == rel_t::R, "first inner array should end with R"); + } + TEST_ASSERT(restored->sub != rel_t::R, "should have second element"); + if (restored->sub != rel_t::R) { + TEST_ASSERT(restored->sub->obj != nullptr, "second element should exist"); + if (restored->sub->obj) { + TEST_ASSERT(restored->sub->obj->obj == rel_t::False, "second inner array should contain False"); + } + TEST_ASSERT(restored->sub->sub == rel_t::R, "outer array should end with R"); + } + cout << " roundtrip [[true],[false]] OK" << endl; + } + + // --- Тест 19: Десериализация одиночных абит --- + cout << endl << "--- Тест 19: Десериализация одиночных абит ---" << endl; + { + // "1" десериализуется как True (одиночный абит = наличие связи) + rel_t* r1 = deserialize_anumber("1"); + TEST_ASSERT(r1 == rel_t::True, "deserialize '1' should be True"); + cout << " deserialize('1') = True OK" << endl; + + // "-1" десериализуется как False (одиночный абит = отсутствие связи) + rel_t* r0 = deserialize_anumber("-1"); + TEST_ASSERT(r0 == rel_t::False, "deserialize '-1' should be False"); + cout << " deserialize('-1') = False OK" << endl; + + // Roundtrip: serialize(True) = "1", deserialize("1") = True + TEST_ASSERT(serialize_anumber(r1) == "1", "serialize(True) should be '1'"); + TEST_ASSERT(serialize_anumber(r0) == "-1", "serialize(False) should be '-1'"); + cout << " roundtrip True/False OK" << endl; + } + + // --- Тест 20: Стабильность двойного roundtrip --- + cout << endl << "--- Тест 20: Стабильность двойного roundtrip ---" << endl; + { + string example = "1-1(1-1)1"; + rel_t* result = deserialize_anumber(example); + string reserialized = serialize_anumber(result); + cout << " '∞1-1(1-1)1' -> deserialize -> serialize -> '∞" << reserialized << "'" << endl; + + rel_t* result2 = deserialize_anumber(reserialized); + string reserialized2 = serialize_anumber(result2); + TEST_ASSERT(reserialized == reserialized2, "double roundtrip should be stable"); + cout << " double roundtrip stable OK" << endl; + } + + // --- Итоги --- + cout << endl << "========================================" << endl; + cout << "=== Итоги ===" << endl; + cout << " Passed: " << passed << endl; + cout << " Failed: " << failed << endl; + cout << "========================================" << endl; + + if (failed > 0) + { + cerr << endl << "SOME TESTS FAILED!" << endl; + return 1; + } + + cout << endl << "ALL TESTS PASSED!" << endl; + return 0; +} From 247b97ae00b0edb6c468ece0ab8de30b3cd15a0a Mon Sep 17 00:00:00 2001 From: konard Date: Mon, 2 Feb 2026 20:07:30 +0100 Subject: [PATCH 5/6] Add debug/experiment scripts for rel_t structure analysis These scripts were used during development to understand the internal rel_t encoding of strings, numbers, and arrays. Kept for future reference and debugging. Co-Authored-By: Claude Opus 4.5 --- experiments/debug_string.cpp | 57 +++++++++++++++++ experiments/debug_structure.cpp | 108 ++++++++++++++++++++++++++++++++ 2 files changed, 165 insertions(+) create mode 100644 experiments/debug_string.cpp create mode 100644 experiments/debug_structure.cpp diff --git a/experiments/debug_string.cpp b/experiments/debug_string.cpp new file mode 100644 index 0000000..1273438 --- /dev/null +++ b/experiments/debug_string.cpp @@ -0,0 +1,57 @@ +#include "json_io.h" +#include +using namespace std; + +void debug_rel(const rel_t* ent, string indent = "") { + if (ent == rel_t::E) { cout << indent << "E" << endl; return; } + if (ent == rel_t::R) { cout << indent << "R" << endl; return; } + if (ent == rel_t::True) { cout << indent << "True" << endl; return; } + if (ent == rel_t::False) { cout << indent << "False" << endl; return; } + if (ent == rel_t::Unsigned) { cout << indent << "Unsigned" << endl; return; } + if (ent == rel_t::Integer) { cout << indent << "Integer" << endl; return; } + if (ent == rel_t::Float) { cout << indent << "Float" << endl; return; } + + cout << indent << "rel(" << (void*)ent << "):" << endl; + cout << indent << " obj: "; + if (ent->obj == rel_t::E) cout << "E" << endl; + else if (ent->obj == rel_t::R) cout << "R" << endl; + else if (ent->obj == rel_t::True) cout << "True" << endl; + else if (ent->obj == rel_t::False) cout << "False" << endl; + else cout << "rel(" << (void*)ent->obj << ")" << endl; + + cout << indent << " sub: "; + if (ent->sub == rel_t::E) cout << "E" << endl; + else if (ent->sub == rel_t::R) cout << "R" << endl; + else if (ent->sub == rel_t::True) cout << "True" << endl; + else if (ent->sub == rel_t::False) cout << "False" << endl; + else cout << "rel(" << (void*)ent->sub << ")" << endl; +} + +void trace_string_encoding(const rel_t* ent, int depth = 0) { + if (depth > 20) { cout << " ... (max depth)" << endl; return; } + debug_rel(ent, string(depth*2, ' ')); + if (ent != rel_t::E && ent != rel_t::R && ent != rel_t::True && + ent != rel_t::False && ent != rel_t::Unsigned && + ent != rel_t::Integer && ent != rel_t::Float) { + cout << string(depth*2, ' ') << " -> obj detail:" << endl; + trace_string_encoding(ent->obj, depth+1); + cout << string(depth*2, ' ') << " -> sub detail:" << endl; + trace_string_encoding(ent->sub, depth+1); + } +} + +int main() { + // Import string "a" + json j = json("a"); + rel_t* str_rel = import_json(j); + + cout << "=== String 'a' structure ===" << endl; + trace_string_encoding(str_rel); + + cout << endl << "=== Export back ===" << endl; + json result; + export_json(str_rel, result); + cout << "Result: " << result.dump() << endl; + + return 0; +} diff --git a/experiments/debug_structure.cpp b/experiments/debug_structure.cpp new file mode 100644 index 0000000..63c19b2 --- /dev/null +++ b/experiments/debug_structure.cpp @@ -0,0 +1,108 @@ +#include "json_io.h" +#include +using namespace std; + +void print_rel(const rel_t* ent, string name = "", int depth = 0) { + string indent(depth*2, ' '); + if (!name.empty()) cout << indent << name << ": "; + else cout << indent; + + if (ent == rel_t::E) { cout << "E (null)" << endl; return; } + if (ent == rel_t::R) { cout << "R (array)" << endl; return; } + if (ent == rel_t::True) { cout << "True" << endl; return; } + if (ent == rel_t::False) { cout << "False" << endl; return; } + if (ent == rel_t::Unsigned) { cout << "Unsigned" << endl; return; } + if (ent == rel_t::Integer) { cout << "Integer" << endl; return; } + if (ent == rel_t::Float) { cout << "Float" << endl; return; } + + cout << "rel@" << (void*)ent << endl; + if (depth < 10) { + print_rel(ent->obj, "obj", depth+1); + print_rel(ent->sub, "sub", depth+1); + } +} + +int main() { + cout << "=== Base vocabulary ===" << endl; + cout << "E: obj="; print_rel(rel_t::E->obj, "", 0); + cout << "E: sub="; print_rel(rel_t::E->sub, "", 0); + cout << "R: obj="; print_rel(rel_t::R->obj, "", 0); + cout << "R: sub="; print_rel(rel_t::R->sub, "", 0); + cout << "True: obj="; print_rel(rel_t::True->obj, "", 0); + cout << "True: sub="; print_rel(rel_t::True->sub, "", 0); + cout << "False: obj="; print_rel(rel_t::False->obj, "", 0); + cout << "False: sub="; print_rel(rel_t::False->sub, "", 0); + + cout << endl << "=== null ===" << endl; + json j_null = json(nullptr); + rel_t* null_r = import_json(j_null); + print_rel(null_r, "null"); + + cout << endl << "=== [true] ===" << endl; + json j_arr = json::array({true}); + rel_t* arr_r = import_json(j_arr); + print_rel(arr_r, "[true]"); + + cout << endl << "=== [true, false] ===" << endl; + json j_arr2 = json::array({true, false}); + rel_t* arr2_r = import_json(j_arr2); + print_rel(arr2_r, "[true, false]"); + + cout << endl << "=== \"a\" ===" << endl; + json j_str = json("a"); + rel_t* str_r = import_json(j_str); + // Only show top 4 levels + cout << "str: rel@" << (void*)str_r << endl; + cout << " obj: "; + if (str_r->obj == rel_t::E) cout << "E" << endl; + else if (str_r->obj == rel_t::R) cout << "R" << endl; + else cout << "rel@" << (void*)str_r->obj << endl; + cout << " sub: "; + if (str_r->sub == rel_t::E) cout << "E" << endl; + else if (str_r->sub == rel_t::R) cout << "R" << endl; + else cout << "rel@" << (void*)str_r->sub << endl; + + // Traverse the bit chain + cout << endl << "=== Bit chain for \"a\" ===" << endl; + const rel_t* cur = str_r; + int level = 0; + while (cur != rel_t::E && cur != rel_t::R && level < 20) { + string obj_name = "?", sub_name = "?"; + if (cur->obj == rel_t::E) obj_name = "E"; + else if (cur->obj == rel_t::R) obj_name = "R"; + else if (cur->obj == rel_t::True) obj_name = "True"; + else if (cur->obj == rel_t::False) obj_name = "False"; + else obj_name = "rel@" + to_string((uintptr_t)cur->obj); + + if (cur->sub == rel_t::E) sub_name = "E"; + else if (cur->sub == rel_t::R) sub_name = "R"; + else if (cur->sub == rel_t::True) sub_name = "True"; + else if (cur->sub == rel_t::False) sub_name = "False"; + else sub_name = "rel@" + to_string((uintptr_t)cur->sub); + + cout << "Level " << level << ": obj=" << obj_name << " sub=" << sub_name << endl; + + // Go deeper through obj (the chain direction) + cur = cur->obj; + level++; + } + if (cur == rel_t::E) cout << "End: E" << endl; + if (cur == rel_t::R) cout << "End: R" << endl; + + cout << endl << "=== 42u ===" << endl; + json j_num = json(42u); + rel_t* num_r = import_json(j_num); + cout << "num: rel@" << (void*)num_r << endl; + cout << " obj: "; + if (num_r->obj == rel_t::E) cout << "E" << endl; + else if (num_r->obj == rel_t::R) cout << "R" << endl; + else if (num_r->obj == rel_t::Unsigned) cout << "Unsigned" << endl; + else cout << "rel@" << (void*)num_r->obj << endl; + cout << " sub: "; + if (num_r->sub == rel_t::E) cout << "E" << endl; + else if (num_r->sub == rel_t::R) cout << "R" << endl; + else if (num_r->sub == rel_t::Unsigned) cout << "Unsigned" << endl; + else cout << "rel@" << (void*)num_r->sub << endl; + + return 0; +} From 301c99089c730c85a3a7e30a24865f5caf4d98ea Mon Sep 17 00:00:00 2001 From: konard Date: Mon, 2 Feb 2026 20:08:32 +0100 Subject: [PATCH 6/6] Revert "Initial commit with task details" This reverts commit 5d281a804bef535d74b8b6776f1b7773d2615589. --- CLAUDE.md | 7 ------- 1 file changed, 7 deletions(-) delete mode 100644 CLAUDE.md diff --git a/CLAUDE.md b/CLAUDE.md deleted file mode 100644 index db5fc49..0000000 --- a/CLAUDE.md +++ /dev/null @@ -1,7 +0,0 @@ -Issue to solve: https://github.com/netkeep80/avm/issues/3 -Your prepared branch: issue-3-d3af30f6e279 -Your prepared working directory: /tmp/gh-issue-solver-1770058142069 -Your forked repository: konard/netkeep80-avm -Original repository (upstream): netkeep80/avm - -Proceed.