From 87077e2da174129b0b8c2ce59b5e8a6dd2771135 Mon Sep 17 00:00:00 2001 From: konard Date: Fri, 6 Feb 2026 18:12:52 +0100 Subject: [PATCH 1/3] 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/18 --- 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..63f2ac3 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,7 @@ +Issue to solve: https://github.com/netkeep80/avm/issues/18 +Your prepared branch: issue-18-6104c510297a +Your prepared working directory: /tmp/gh-issue-solver-1770397966511 +Your forked repository: konard/netkeep80-avm +Original repository (upstream): netkeep80/avm + +Proceed. From 6fcf48741fa8c873bf4c41b64bd3ae088ed25f47 Mon Sep 17 00:00:00 2001 From: konard Date: Fri, 6 Feb 2026 18:21:58 +0100 Subject: [PATCH 2/3] =?UTF-8?q?=D0=A0=D0=B5=D0=B0=D0=BB=D0=B8=D0=B7=D0=B0?= =?UTF-8?q?=D1=86=D0=B8=D1=8F=20=D1=80=D0=B5=D0=BA=D1=83=D1=80=D1=81=D0=B8?= =?UTF-8?q?=D0=B2=D0=BD=D1=8B=D1=85=20=D1=84=D1=83=D0=BD=D0=BA=D1=86=D0=B8?= =?UTF-8?q?=D0=B9=20Def/Call=20=D0=B8=20=D0=BE=D0=B1=D0=BD=D0=BE=D0=B2?= =?UTF-8?q?=D0=BB=D0=B5=D0=BD=D0=B8=D0=B5=20=D0=B4=D0=BE=D0=BA=D1=83=D0=BC?= =?UTF-8?q?=D0=B5=D0=BD=D1=82=D0=B0=D1=86=D0=B8=D0=B8=20=D0=B8=20=D0=B2?= =?UTF-8?q?=D0=B5=D1=80=D1=81=D0=B8=D0=B8=20=D0=B4=D0=BE=200.0.5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.6 --- CMakeLists.txt | 2 +- README.md | 40 +++-- analysis.md | 13 +- include/avm.h | 16 +- plan.md | 5 +- src/main.cpp | 138 +++++++++++++++- test/expr_def_call_identity.json | 1 + test/expr_def_call_not.json | 1 + test/expr_def_call_recursive.json | 1 + test/unit_test.cpp | 261 ++++++++++++++++++++++++++++++ 10 files changed, 449 insertions(+), 29 deletions(-) create mode 100644 test/expr_def_call_identity.json create mode 100644 test/expr_def_call_not.json create mode 100644 test/expr_def_call_recursive.json diff --git a/CMakeLists.txt b/CMakeLists.txt index 303be16..b6d0c34 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.4 ) +set(CMAKE_PROJECT_VERSION 0.0.5 ) # Project name project( avm diff --git a/README.md b/README.md index 38a28a6..f26f6ac 100644 --- a/README.md +++ b/README.md @@ -44,22 +44,25 @@ The Associative Relations Model (ARM) is a mathematical model for storing and pr - JSON serialization/deserialization (null, boolean, array, number, string, object) - Logical operations (NOT, AND, OR) defined as truth tables in entity maps - Conditional construct `If` with lazy evaluation of then/else branches +- Recursive functions via `Def`/`Call` with named parameters and recursion depth protection - Relative addressing operator `[]` for evaluating functions via `eval()` - Multi-dimensional relative addressing for passing arguments -- Expression interpreter `interpret()` for evaluating logical and conditional expressions from JSON +- Expression interpreter `interpret()` for evaluating logical, conditional and recursive expressions from JSON +- Sequential expression execution via JSON arrays for function definition and invocation ### Current Status -**Version: 0.0.4** (Alpha) +**Version: 0.0.5** (Alpha) Implemented: - JSON null, boolean, array, number (unsigned, integer, float), string, object serialization/deserialization -- Base vocabulary initialization (R, E, True, False, Unsigned, Integer, Float, String, Object, Not, And, Or, If) +- Base vocabulary initialization (R, E, True, False, Unsigned, Integer, Float, String, Object, Not, And, Or, If, Def, Call) - Logical operations NOT, AND, OR with truth tables via entity map - Conditional construct If with lazy evaluation (e.g. `{"If": [true, "yes", "no"]}` → `"yes"`) +- Recursive functions via Def/Call (e.g. `[{"Def": ["f", ["x"], body]}, {"Call": ["f", arg]}]`) - Relative addressing operator `[]` via `eval()` function for computing logical functions -- Expression interpreter for evaluating logical and conditional expressions from JSON -- 147 unit tests + 16 JSON roundtrip tests +- Expression interpreter for evaluating logical, conditional and recursive expressions from JSON +- 179 unit tests + 16 JSON roundtrip tests - CI/CD pipeline (GitHub Actions) for Linux, macOS, Windows In Progress: @@ -117,6 +120,13 @@ echo '{"If": [true, true, false]}' > cond.json cat res.json # true ``` +Recursive functions with Def/Call: +```bash +echo '[{"Def": ["myNot", ["x"], {"Not": ["x"]}]}, {"Call": ["myNot", true]}]' > rec.json +./avm rec.json +cat res.json # false +``` + ### Dependencies - C++20 compatible compiler @@ -164,22 +174,25 @@ AVM (Associative Virtual Machine) — проект, реализующий ви - Сериализация/десериализация JSON (null, boolean, array, number, string, object) - Логические операции (NOT, AND, OR), определённые как таблицы истинности в entity map - Условная конструкция `If` с ленивым вычислением веток then/else +- Рекурсивные функции через `Def`/`Call` с именованными параметрами и защитой от бесконечной рекурсии - Оператор относительной адресации `[]` для вычисления функций через `eval()` - Многомерная относительная адресация для передачи аргументов -- Интерпретатор выражений `interpret()` для вычисления логических и условных выражений из JSON +- Интерпретатор выражений `interpret()` для вычисления логических, условных и рекурсивных выражений из JSON +- Последовательное выполнение выражений через JSON-массивы для определения и вызова функций ### Текущее состояние -**Версия: 0.0.4** (Альфа) +**Версия: 0.0.5** (Альфа) Реализовано: - Сериализация/десериализация JSON null, boolean, array, number (unsigned, integer, float), string, object -- Инициализация базового словаря (R, E, True, False, Unsigned, Integer, Float, String, Object, Not, And, Or, If) +- Инициализация базового словаря (R, E, True, False, Unsigned, Integer, Float, String, Object, Not, And, Or, If, Def, Call) - Логические операции NOT, AND, OR с таблицами истинности через entity map - Условная конструкция If с ленивым вычислением (например `{"If": [true, "да", "нет"]}` → `"да"`) +- Рекурсивные функции через Def/Call (например `[{"Def": ["f", ["x"], тело]}, {"Call": ["f", арг]}]`) - Оператор относительной адресации `[]` через функцию `eval()` для вычисления логических функций -- Интерпретатор выражений для вычисления логических и условных выражений из JSON -- 147 модульных тестов + 16 JSON roundtrip тестов +- Интерпретатор выражений для вычисления логических, условных и рекурсивных выражений из JSON +- 179 модульных тестов + 16 JSON roundtrip тестов - CI/CD пайплайн (GitHub Actions) для Linux, macOS, Windows В разработке: @@ -237,6 +250,13 @@ echo '{"If": [true, true, false]}' > cond.json cat res.json # true ``` +Рекурсивные функции через Def/Call: +```bash +echo '[{"Def": ["myNot", ["x"], {"Not": ["x"]}]}, {"Call": ["myNot", true]}]' > rec.json +./avm rec.json +cat res.json # false +``` + ### Зависимости - Компилятор с поддержкой C++20 diff --git a/analysis.md b/analysis.md index b8e3396..0c4a809 100644 --- a/analysis.md +++ b/analysis.md @@ -132,8 +132,8 @@ AVM (Associative Virtual Machine) — это проект, реализующи | Заголовочный файл | ~473 строки (avm.h) | | Внешние зависимости | nlohmann/json, LinksPlatform, str_switch | | Лицензия | MIT | -| Unit-тесты | 147 модульных тестов | -| Тестовые файлы | 24 JSON файла | +| Unit-тесты | 179 модульных тестов | +| Тестовые файлы | 27 JSON файлов | --- @@ -142,9 +142,9 @@ AVM (Associative Virtual Machine) — это проект, реализующи Проект находится на стадии альфа-версии: - **Концепция**: Хорошо проработана теоретически -- **Реализация**: JSON roundtrip полностью работает, логические операции реализованы, интерпретатор выражений работает, условная конструкция If с ленивым вычислением +- **Реализация**: JSON roundtrip полностью работает, логические операции реализованы, интерпретатор выражений работает, условная конструкция If с ленивым вычислением, рекурсивные функции через Def/Call - **Документация**: Двуязычная документация, описание алгоритма сериализации -- **Тестирование**: 147 модульных тестов + 16 интеграционных, CI/CD на 3 платформах +- **Тестирование**: 179 модульных тестов + 16 интеграционных, CI/CD на 3 платформах - **Готовность к использованию**: Прототип с работающей базовой функциональностью --- @@ -158,10 +158,11 @@ AVM представляет собой интересный исследова 2. Создание публичного API для CRUD операций 3. Примеры использования и руководства 4. Пакеты для менеджеров зависимостей (vcpkg, Conan) -5. ~~Условные конструкции~~ ✅ If с ленивым вычислением (PR #28) и рекурсия в интерпретаторе +5. ~~Условные конструкции~~ ✅ If с ленивым вычислением (PR #28) +6. ~~Рекурсивные функции~~ ✅ Def/Call с параметрами и защитой от бесконечной рекурсии (PR #29) --- *Дата анализа: январь 2026* *Последнее обновление: февраль 2026* -*Версия проекта: 0.0.4* +*Версия проекта: 0.0.5* diff --git a/include/avm.h b/include/avm.h index 4ae78d1..bbecf1a 100644 --- a/include/avm.h +++ b/include/avm.h @@ -278,6 +278,8 @@ struct rel_t : obj_aspect, static inline rel_t *And; static inline rel_t *Or; static inline rel_t *If; + static inline rel_t *Def; + static inline rel_t *Call; protected: rel_t() @@ -365,11 +367,15 @@ struct rel_t : obj_aspect, add_rel(And); add_rel(Or); add_rel(If); - - Not->update(Not, E); // (NOT, Ent) — NOT есть сущность - And->update(And, E); // (AND, Ent) — AND есть сущность - Or->update(Or, E); // (OR, Ent) — OR есть сущность - If->update(If, E); // (IF, Ent) — IF есть сущность + add_rel(Def); + add_rel(Call); + + Not->update(Not, E); // (NOT, Ent) — NOT есть сущность + And->update(And, E); // (AND, Ent) — AND есть сущность + Or->update(Or, E); // (OR, Ent) — OR есть сущность + If->update(If, E); // (IF, Ent) — IF есть сущность + Def->update(Def, E); // (DEF, Ent) — DEF есть сущность + Call->update(Call, E); // (CALL, Ent) — CALL есть сущность // NOT: таблица истинности через entity map // NOT[True] = False, NOT[False] = True diff --git a/plan.md b/plan.md index 518fbb2..46f3e50 100644 --- a/plan.md +++ b/plan.md @@ -54,7 +54,7 @@ #### Средний приоритет 4. ~~**Реализовать условные конструкции**~~ ✅ — If с ленивым вычислением через entity map и `interpret()` (PR #28) -5. **Добавить поддержку рекурсии** — рекурсивные определения функций +5. ~~**Добавить поддержку рекурсии**~~ ✅ — рекурсивные определения функций через `Def`/`Call` с параметрами и защитой от бесконечной рекурсии (PR #29) 6. **Создать стандартную библиотеку** — базовые функции и операции #### Низкий приоритет @@ -135,7 +135,7 @@ ### Задачи по приоритетам #### Критический приоритет (сделать в первую очередь) -1. ~~**Добавить unit-тесты**~~ ✅ — 147 модульных тестов (PR #9, #19, #20, #28) +1. ~~**Добавить unit-тесты**~~ ✅ — 179 модульных тестов (PR #9, #19, #20, #28, #29) 2. ~~**Настроить CI/CD**~~ ✅ — GitHub Actions для Ubuntu, Windows, macOS (PR #6) 3. ~~**Обеспечить кроссплатформенность**~~ ✅ — сборка для Linux, macOS, Windows (PR #6, #14) 4. ~~**Улучшить README**~~ ✅ — двуязычная документация (PR #2) @@ -181,6 +181,7 @@ - ~~Логические операции и вычисления~~ ✅ NOT, AND, OR + оператор `eval()` (PR #19) - ~~Интерпретатор выражений~~ ✅ вычисление логических выражений из JSON через `interpret()` (PR #20) - ~~Условные конструкции~~ ✅ If с ленивым вычислением через entity map (PR #28) +- ~~Рекурсивные функции~~ ✅ Def/Call с параметрами и защитой от бесконечной рекурсии (PR #29) - Персистентное хранение - API для программного использования - Примеры и руководства diff --git a/src/main.cpp b/src/main.cpp index 072de74..f86d536 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -188,6 +188,9 @@ rel_t *import_json(const json &j) // {"Not": [expr]} — логическое НЕ // {"And": [expr1, expr2]} — логическое И // {"Or": [expr1, expr2]} — логическое ИЛИ +// {"If": [cond, then, else]} — условная конструкция +// {"Def": ["name", ["param1", ...], body]} — определение рекурсивной функции +// {"Call": ["name", arg1, ...]} — вызов определённой функции // Вложенные выражения: {"Not": [{"And": [true, false]}]} = NOT[AND[True][False]] = True // // Поиск оператора по имени в базовом словаре @@ -201,9 +204,38 @@ rel_t *resolve_operator(const string &name) return rel_t::Or; if (name == "If") return rel_t::If; + if (name == "Def") + return rel_t::Def; + if (name == "Call") + return rel_t::Call; return nullptr; } +// Окружение функций / Function environment +// Хранит определения именованных функций для поддержки рекурсии +struct func_def +{ + vector params; // имена параметров + json body; // тело функции (JSON-выражение) +}; + +static map func_env; // глобальный реестр функций + +// Контекст параметров / Parameter context +// Стек контекстов для привязки параметров при вызове функции +static vector> param_stack; + +// Максимальная глубина рекурсии / Maximum recursion depth +static const size_t MAX_RECURSION_DEPTH = 1000; + +// Очистка окружения функций / Clear function environment +// Используется для сброса состояния между тестами и вызовами +void clear_func_env() +{ + func_env.clear(); + param_stack.clear(); +} + // Рекурсивная интерпретация JSON-выражения // Возвращает результат вычисления как ARM-сущность rel_t *interpret(const json &expr) @@ -216,11 +248,28 @@ rel_t *interpret(const json &expr) case json::value_t::boolean: return expr.get() ? rel_t::True : rel_t::False; + case json::value_t::string: + { + // Строка: проверяем, является ли она ссылкой на параметр функции + const string &name = expr.get_ref(); + // Ищем параметр в стеке контекстов (от последнего к первому) + for (auto it = param_stack.rbegin(); it != param_stack.rend(); ++it) + { + auto param_it = it->find(name); + if (param_it != it->end()) + return param_it->second; + } + // Не найден параметр — импортируем как строковые данные + return import_json(expr); + } + case json::value_t::object: { // Объект с одним ключом — оператор с аргументами // {"Not": [true]} или {"And": [true, false]} // {"If": [condition, then_expr, else_expr]} — условная конструкция + // {"Def": ["name", ["param1", ...], body]} — определение функции + // {"Call": ["name", arg1, ...]} — вызов функции if (expr.size() != 1) return rel_t::E; // некорректное выражение @@ -235,6 +284,69 @@ rel_t *interpret(const json &expr) if (!args.is_array() || args.empty()) return rel_t::E; // аргументы должны быть непустым массивом + // Def: определение именованной функции + // {"Def": ["name", ["param1", "param2", ...], body_expr]} + if (op == rel_t::Def) + { + if (args.size() != 3) + return rel_t::E; // Def требует ровно 3 аргумента + + if (!args[0].is_string()) + return rel_t::E; // имя функции должно быть строкой + + if (!args[1].is_array()) + return rel_t::E; // параметры должны быть массивом + + const string &func_name = args[0].get_ref(); + func_def def; + for (auto &p : args[1]) + { + if (!p.is_string()) + return rel_t::E; // имена параметров должны быть строками + def.params.push_back(p.get()); + } + def.body = args[2]; + func_env[func_name] = std::move(def); + return rel_t::E; // Def не возвращает значение (side effect) + } + + // Call: вызов именованной функции с аргументами + // {"Call": ["name", arg1, arg2, ...]} + if (op == rel_t::Call) + { + if (args.size() < 1) + return rel_t::E; + + if (!args[0].is_string()) + return rel_t::E; // имя функции должно быть строкой + + const string &func_name = args[0].get_ref(); + auto func_it = func_env.find(func_name); + if (func_it == func_env.end()) + return rel_t::E; // функция не определена + + const func_def &def = func_it->second; + + // Проверяем соответствие количества аргументов и параметров + if (args.size() - 1 != def.params.size()) + return rel_t::E; // несоответствие числа аргументов + + // Проверяем глубину рекурсии + if (param_stack.size() >= MAX_RECURSION_DEPTH) + return rel_t::E; // превышена максимальная глубина рекурсии + + // Вычисляем аргументы и привязываем к параметрам + map context; + for (size_t i = 0; i < def.params.size(); ++i) + context[def.params[i]] = interpret(args[i + 1]); + + // Помещаем контекст в стек и вычисляем тело функции + param_stack.push_back(std::move(context)); + rel_t *result = interpret(def.body); + param_stack.pop_back(); + return result; + } + // If: ленивое вычисление — вычисляется только нужная ветка // {"If": [condition, then_expr, else_expr]} if (op == rel_t::If) @@ -267,8 +379,19 @@ rel_t *interpret(const json &expr) return result; } + case json::value_t::array: + { + // Массив: последовательное выполнение выражений, возвращает результат последнего + // Позволяет сначала определить функции через Def, затем вызвать через Call + // [{"Def": [...]}, {"Def": [...]}, {"Call": [...]}] + rel_t *result = rel_t::E; + for (auto &item : expr) + result = interpret(item); + return result; + } + default: - // Для остальных типов (числа, строки, массивы) — импортируем как данные + // Для остальных типов (числа) — импортируем как данные return import_json(expr); } } @@ -503,7 +626,7 @@ int main(int argc, char *argv[]) break; default: cout << R"(https://github.com/netkeep80/avm - Associative Virtual Machine [Version 0.0.4] + Associative Virtual Machine [Version 0.0.5] _____________ / \ / \ @@ -542,13 +665,18 @@ Copyright (c) 2022 Vertushkin Roman Pavlovich res = json::object(); // parse_json(root, res); // Проверяем, является ли вход выражением для интерпретации - // Выражение — JSON объект с одним ключом-оператором (Not, And, Or) - bool is_expression = root.is_object() && root.size() == 1 && - resolve_operator(root.begin().key()) != nullptr; + // Выражение — JSON объект с одним ключом-оператором (Not, And, Or, If, Def, Call) + // или JSON массив выражений (для Def + Call последовательностей) + bool is_expression = (root.is_object() && root.size() == 1 && + resolve_operator(root.begin().key()) != nullptr) || + (root.is_array() && !root.empty() && root[0].is_object() && + root[0].size() == 1 && resolve_operator(root[0].begin().key()) != nullptr); rel_t *root_ent; if (is_expression) { // Интерпретируем выражение + func_env.clear(); // очищаем окружение функций + param_stack.clear(); // очищаем стек параметров root_ent = interpret(root); } else diff --git a/test/expr_def_call_identity.json b/test/expr_def_call_identity.json new file mode 100644 index 0000000..4c2b190 --- /dev/null +++ b/test/expr_def_call_identity.json @@ -0,0 +1 @@ +[{"Def": ["identity", ["x"], "x"]}, {"Call": ["identity", true]}] \ No newline at end of file diff --git a/test/expr_def_call_not.json b/test/expr_def_call_not.json new file mode 100644 index 0000000..55b2011 --- /dev/null +++ b/test/expr_def_call_not.json @@ -0,0 +1 @@ +[{"Def": ["myNot", ["x"], {"Not": ["x"]}]}, {"Call": ["myNot", true]}] \ No newline at end of file diff --git a/test/expr_def_call_recursive.json b/test/expr_def_call_recursive.json new file mode 100644 index 0000000..ee2c8b8 --- /dev/null +++ b/test/expr_def_call_recursive.json @@ -0,0 +1 @@ +[{"Def": ["rec", ["x", "depth"], {"If": ["depth", "x", {"Call": ["rec", {"Not": ["x"]}, true]}]}]}, {"Call": ["rec", true, false]}] \ No newline at end of file diff --git a/test/unit_test.cpp b/test/unit_test.cpp index cc123e3..e563143 100644 --- a/test/unit_test.cpp +++ b/test/unit_test.cpp @@ -14,6 +14,7 @@ void export_json(const rel_t *ent, json &j); rel_t *eval(rel_t *func, rel_t *arg); rel_t *eval(rel_t *func, rel_t *arg1, rel_t *arg2); rel_t *interpret(const json &expr); +void clear_func_env(); static int tests_passed = 0; static int tests_failed = 0; @@ -566,6 +567,256 @@ void test_interpret_if_error_cases() check(interpret(if_null_cond) == rel_t::E, "interpret({If: [null, true, false]}) = E"); } +// === Тесты рекурсивных функций (Def/Call) === + +void test_def_call_vocabulary() +{ + check(rel_t::Def != nullptr, "Def is not null"); + check(rel_t::Call != nullptr, "Call is not null"); + + // Def и Call должны отличаться от других операций + check(rel_t::Def != rel_t::Not, "Def != Not"); + check(rel_t::Def != rel_t::And, "Def != And"); + check(rel_t::Def != rel_t::Or, "Def != Or"); + check(rel_t::Def != rel_t::If, "Def != If"); + check(rel_t::Def != rel_t::Call, "Def != Call"); + check(rel_t::Call != rel_t::Not, "Call != Not"); + check(rel_t::Call != rel_t::If, "Call != If"); + + // Def и Call являются сущностями (sub == E) + check(rel_t::Def->sub == rel_t::E, "Def->sub == E"); + check(rel_t::Call->sub == rel_t::E, "Call->sub == E"); +} + +void test_def_call_simple() +{ + clear_func_env(); + + // Определяем функцию identity: f(x) = x + // [{"Def": ["identity", ["x"], "x"]}, {"Call": ["identity", true]}] + json program = json::array({ + {{"Def", json::array({"identity", json::array({"x"}), "x"})}}, + {{"Call", json::array({"identity", true})}} + }); + check(interpret(program) == rel_t::True, "identity(true) = True"); + + clear_func_env(); + + // identity(false) = false + json program2 = json::array({ + {{"Def", json::array({"identity", json::array({"x"}), "x"})}}, + {{"Call", json::array({"identity", false})}} + }); + check(interpret(program2) == rel_t::False, "identity(false) = False"); +} + +void test_def_call_not_function() +{ + clear_func_env(); + + // Определяем функцию myNot: f(x) = Not[x] + // [{"Def": ["myNot", ["x"], {"Not": ["x"]}]}, {"Call": ["myNot", true]}] + json program = json::array({ + {{"Def", json::array({"myNot", json::array({"x"}), {{"Not", json::array({"x"})}}})}}, + {{"Call", json::array({"myNot", true})}} + }); + check(interpret(program) == rel_t::False, "myNot(true) = False"); + + clear_func_env(); + + json program2 = json::array({ + {{"Def", json::array({"myNot", json::array({"x"}), {{"Not", json::array({"x"})}}})}}, + {{"Call", json::array({"myNot", false})}} + }); + check(interpret(program2) == rel_t::True, "myNot(false) = True"); +} + +void test_def_call_two_params() +{ + clear_func_env(); + + // Определяем функцию myAnd: f(a, b) = And[a, b] + json program = json::array({ + {{"Def", json::array({"myAnd", json::array({"a", "b"}), {{"And", json::array({"a", "b"})}}})}}, + {{"Call", json::array({"myAnd", true, false})}} + }); + check(interpret(program) == rel_t::False, "myAnd(true, false) = False"); + + clear_func_env(); + + json program2 = json::array({ + {{"Def", json::array({"myAnd", json::array({"a", "b"}), {{"And", json::array({"a", "b"})}}})}}, + {{"Call", json::array({"myAnd", true, true})}} + }); + check(interpret(program2) == rel_t::True, "myAnd(true, true) = True"); +} + +void test_def_call_recursive() +{ + clear_func_env(); + + // Рекурсивная функция: toggle вызывает себя для отрицания аргумента + // Использует If для базового случая (терминации) + // toggleOnce(x) = If[x, false, true] — простая версия Not + // Но для демонстрации рекурсии: + // rec(x, depth) = If[depth, x, Call["rec", Not[x], true]] + // rec(true, false) → else: rec(Not[true], true) = rec(false, true) → then: false + json program = json::array({ + {{"Def", json::array({ + "rec", + json::array({"x", "depth"}), + {{"If", json::array({ + "depth", + "x", + {{"Call", json::array({"rec", {{"Not", json::array({"x"})}}, true})}} + })}} + })}}, + {{"Call", json::array({"rec", true, false})}} + }); + check(interpret(program) == rel_t::False, "rec(true, false) = False (one recursion step)"); + + clear_func_env(); + + // rec(false, false) → else: rec(Not[false], true) = rec(true, true) → then: true + json program2 = json::array({ + {{"Def", json::array({ + "rec", + json::array({"x", "depth"}), + {{"If", json::array({ + "depth", + "x", + {{"Call", json::array({"rec", {{"Not", json::array({"x"})}}, true})}} + })}} + })}}, + {{"Call", json::array({"rec", false, false})}} + }); + check(interpret(program2) == rel_t::True, "rec(false, false) = True (one recursion step)"); +} + +void test_def_call_multiple_functions() +{ + clear_func_env(); + + // Определяем две функции и вызываем одну из другой + // f(x) = Not[x], g(x) = Call["f", x] + json program = json::array({ + {{"Def", json::array({"f", json::array({"x"}), {{"Not", json::array({"x"})}}})}}, + {{"Def", json::array({"g", json::array({"x"}), {{"Call", json::array({"f", "x"})}}})}}, + {{"Call", json::array({"g", true})}} + }); + check(interpret(program) == rel_t::False, "g(true) = f(true) = Not[true] = False"); +} + +void test_def_call_nested_recursion() +{ + clear_func_env(); + + // Рекурсивная функция с вложенным If + // f(x) = If[x, true, Call["f", true]] + // f(true) → then: true (базовый случай) + json program = json::array({ + {{"Def", json::array({ + "f", + json::array({"x"}), + {{"If", json::array({"x", true, {{"Call", json::array({"f", true})}}})}} + })}}, + {{"Call", json::array({"f", true})}} + }); + check(interpret(program) == rel_t::True, "f(true) = true (base case)"); + + clear_func_env(); + + // f(false) → else: Call["f", true] → then: true + json program2 = json::array({ + {{"Def", json::array({ + "f", + json::array({"x"}), + {{"If", json::array({"x", true, {{"Call", json::array({"f", true})}}})}} + })}}, + {{"Call", json::array({"f", false})}} + }); + check(interpret(program2) == rel_t::True, "f(false) = f(true) = true (one recursion step)"); +} + +void test_def_call_error_cases() +{ + clear_func_env(); + + // Def с неправильным количеством аргументов + json def_no_args = {{"Def", json::array()}}; + check(interpret(def_no_args) == rel_t::E, "interpret({Def: []}) = E"); + + json def_one_arg = {{"Def", json::array({"name"})}}; + check(interpret(def_one_arg) == rel_t::E, "interpret({Def: [name]}) = E"); + + // Def с нестроковым именем + json def_bad_name = {{"Def", json::array({42, json::array({"x"}), true})}}; + check(interpret(def_bad_name) == rel_t::E, "interpret({Def: [42, ...]}) = E"); + + // Def с нестроковыми параметрами + json def_bad_params = {{"Def", json::array({"f", json::array({42}), true})}}; + check(interpret(def_bad_params) == rel_t::E, "interpret({Def: [f, [42], ...]}) = E"); + + // Call несуществующей функции + json call_undef = {{"Call", json::array({"undefined"})}}; + check(interpret(call_undef) == rel_t::E, "interpret({Call: [undefined]}) = E"); + + // Call с неправильным количеством аргументов + json program = json::array({ + {{"Def", json::array({"f", json::array({"x"}), "x"})}}, + {{"Call", json::array({"f", true, false})}} + }); + check(interpret(program) == rel_t::E, "interpret({Call: [f, true, false]}) = E (wrong arity)"); + + clear_func_env(); + + // Call с нестроковым именем функции + json call_bad_name = {{"Call", json::array({42})}}; + check(interpret(call_bad_name) == rel_t::E, "interpret({Call: [42]}) = E"); + + // Call с пустым массивом аргументов + json call_empty = {{"Call", json::array()}}; + check(interpret(call_empty) == rel_t::E, "interpret({Call: []}) = E"); + + clear_func_env(); +} + +void test_def_call_recursion_depth_limit() +{ + clear_func_env(); + + // Бесконечная рекурсия: f(x) = Call["f", x] (без базового случая) + // Должна завершиться E из-за лимита глубины рекурсии + json program = json::array({ + {{"Def", json::array({"inf", json::array({"x"}), {{"Call", json::array({"inf", "x"})}}})}}, + {{"Call", json::array({"inf", true})}} + }); + check(interpret(program) == rel_t::E, "infinite recursion returns E (depth limit)"); + + clear_func_env(); +} + +void test_interpret_array_sequential() +{ + // Массив выражений: последовательное выполнение + // [true, false, true] → результат последнего = true + json program = json::array({true, false, true}); + // Note: this is interpreted as a data array, not sequential execution + // because it starts with primitives, not operators + // For sequential execution, array must contain operator objects + + clear_func_env(); + + // Массив с Def и примитивами + json program2 = json::array({ + {{"Def", json::array({"id", json::array({"x"}), "x"})}}, + {{"Call", json::array({"id", true})}} + }); + check(interpret(program2) == rel_t::True, "sequential: [Def, Call] = True"); + + clear_func_env(); +} + // === Тесты счётчиков памяти === void test_memory_counters() @@ -622,6 +873,16 @@ int main() test_interpret_if_with_expressions(); test_interpret_if_nested(); test_interpret_if_error_cases(); + test_def_call_vocabulary(); + test_def_call_simple(); + test_def_call_not_function(); + test_def_call_two_params(); + test_def_call_recursive(); + test_def_call_multiple_functions(); + test_def_call_nested_recursion(); + test_def_call_error_cases(); + test_def_call_recursion_depth_limit(); + test_interpret_array_sequential(); test_memory_counters(); cout << endl; From 76b508714cc14566f36a6e0ad99e07357c063e77 Mon Sep 17 00:00:00 2001 From: konard Date: Fri, 6 Feb 2026 18:24:54 +0100 Subject: [PATCH 3/3] Revert "Initial commit with task details" This reverts commit 87077e2da174129b0b8c2ce59b5e8a6dd2771135. --- 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 63f2ac3..0000000 --- a/CLAUDE.md +++ /dev/null @@ -1,7 +0,0 @@ -Issue to solve: https://github.com/netkeep80/avm/issues/18 -Your prepared branch: issue-18-6104c510297a -Your prepared working directory: /tmp/gh-issue-solver-1770397966511 -Your forked repository: konard/netkeep80-avm -Original repository (upstream): netkeep80/avm - -Proceed.