From e0c5bf2a9ea42e76a191c04789134b29a2baab0c Mon Sep 17 00:00:00 2001 From: Bill Date: Sat, 7 Feb 2026 08:22:35 -0700 Subject: [PATCH] Step 47 TDD test: Tree-sitter Elisp integration Tests parseElisp() with defun parsing, parameter extraction, BinaryOperation from (+ x 1), auto-annotation with @Reclaim(Tracing), and cross-language equivalence with Python AST output. Co-Authored-By: Claude Opus 4.6 --- editor/CMakeLists.txt | 3 + editor/tests/step47_test.cpp | 166 +++++++++++++++++++++++++++++++++++ 2 files changed, 169 insertions(+) create mode 100644 editor/tests/step47_test.cpp diff --git a/editor/CMakeLists.txt b/editor/CMakeLists.txt index b41a331..e0e21ac 100644 --- a/editor/CMakeLists.txt +++ b/editor/CMakeLists.txt @@ -154,6 +154,9 @@ target_include_directories(step45_test PRIVATE src) add_executable(step46_test tests/step46_test.cpp) target_include_directories(step46_test PRIVATE src) +add_executable(step47_test tests/step47_test.cpp) +target_include_directories(step47_test PRIVATE src) + add_executable(whetstone_editor src/main.cpp) target_include_directories(whetstone_editor PRIVATE src) # find_package(SDL2 REQUIRED) # Commented out for now to avoid build issues diff --git a/editor/tests/step47_test.cpp b/editor/tests/step47_test.cpp new file mode 100644 index 0000000..f6eef27 --- /dev/null +++ b/editor/tests/step47_test.cpp @@ -0,0 +1,166 @@ +// Step 47 TDD Test: Tree-sitter Elisp integration +// +// Tests that TreeSitterParser::parseElisp() parses Emacs Lisp source: +// 1. defun → Function node with correct name +// 2. Parameters from defun arglist +// 3. defvar → Variable node +// 4. if → IfStatement +// 5. Auto-annotates with @Reclaim(Tracing) since Elisp uses GC +// 6. Module targetLanguage is "elisp" +// +// Will fail until TreeSitterParser::parseElisp() is implemented +// with real tree-sitter-elisp integration. + +#include +#include +#include +#include +#include "ast/Parser.h" +#include "ast/Annotation.h" + +int main() { + int passed = 0; + int failed = 0; + + // --- Test 1: Parse simple defun --- + { + std::string source = "(defun f (x) (+ x 1))"; + auto module = TreeSitterParser::parseElisp(source); + + assert(module != nullptr && "parseElisp should return a non-null module"); + assert(module->targetLanguage == "elisp" && "Module target language should be 'elisp'"); + + std::cout << "Test 1 PASS: parseElisp returns non-null module with elisp target" << std::endl; + ++passed; + } + + // --- Test 2: Parsed defun has correct name --- + { + std::string source = "(defun f (x) (+ x 1))"; + auto module = TreeSitterParser::parseElisp(source); + + auto functions = module->getChildren("functions"); + assert(!functions.empty() && "Module should contain at least one function"); + + auto* fn = static_cast(functions[0]); + assert(fn->name == "f" && "Function name should be 'f'"); + + std::cout << "Test 2 PASS: Elisp defun has name 'f'" << std::endl; + ++passed; + } + + // --- Test 3: defun has parameter from arglist --- + { + std::string source = "(defun f (x) (+ x 1))"; + auto module = TreeSitterParser::parseElisp(source); + + auto functions = module->getChildren("functions"); + auto* fn = static_cast(functions[0]); + + auto params = fn->getChildren("parameters"); + assert(!params.empty() && "Function should have at least one parameter"); + + auto* param = static_cast(params[0]); + assert(param->name == "x" && "Parameter name should be 'x'"); + + std::cout << "Test 3 PASS: Elisp defun has parameter 'x'" << std::endl; + ++passed; + } + + // --- Test 4: Function body contains BinaryOperation for (+ x 1) --- + { + std::string source = "(defun f (x) (+ x 1))"; + auto module = TreeSitterParser::parseElisp(source); + + auto functions = module->getChildren("functions"); + auto* fn = static_cast(functions[0]); + + auto body = fn->getChildren("body"); + assert(!body.empty() && "Function body should not be empty"); + + // The (+ x 1) should produce a Return or ExpressionStatement with BinaryOperation + // In Elisp, the last form is implicitly returned + bool hasBinOp = false; + for (auto* stmt : body) { + // Could be wrapped in Return or ExpressionStatement + ASTNode* expr = nullptr; + if (stmt->conceptType == "Return") { + expr = stmt->getChild("value"); + } else if (stmt->conceptType == "ExpressionStatement") { + expr = stmt->getChild("expression"); + } else if (stmt->conceptType == "BinaryOperation") { + expr = stmt; + } + + if (expr && expr->conceptType == "BinaryOperation") { + auto* binOp = static_cast(expr); + assert(binOp->op == "+" && "Operator should be '+'"); + hasBinOp = true; + break; + } + } + assert(hasBinOp && "Body should contain BinaryOperation(+)"); + + std::cout << "Test 4 PASS: Elisp (+ x 1) produces BinaryOperation" << std::endl; + ++passed; + } + + // --- Test 5: Elisp function auto-annotated with @Reclaim(Tracing) --- + { + std::string source = "(defun f (x) (+ x 1))"; + auto module = TreeSitterParser::parseElisp(source); + + auto functions = module->getChildren("functions"); + auto* fn = static_cast(functions[0]); + + auto annotations = fn->getChildren("annotations"); + bool hasReclaimTracing = false; + for (auto* anno : annotations) { + if (anno->conceptType == "ReclaimAnnotation") { + auto* reclaim = static_cast(anno); + if (reclaim->strategy == "Tracing") { + hasReclaimTracing = true; + } + } + } + assert(hasReclaimTracing && + "Elisp functions should be auto-annotated with @Reclaim(Tracing)"); + + std::cout << "Test 5 PASS: Elisp function auto-annotated with @Reclaim(Tracing)" << std::endl; + ++passed; + } + + // --- Test 6: Produces same AST structure as Python equivalent --- + { + // Both should produce: Function(f) with Parameter(x) and body BinaryOperation(+ x 1) + std::string elispSource = "(defun f (x) (+ x 1))"; + std::string pythonSource = "def f(x):\n return x + 1\n"; + + auto elispModule = TreeSitterParser::parseElisp(elispSource); + auto pythonModule = TreeSitterParser::parsePython(pythonSource); + + auto elispFns = elispModule->getChildren("functions"); + auto pythonFns = pythonModule->getChildren("functions"); + + assert(!elispFns.empty() && !pythonFns.empty() && + "Both should have functions"); + + auto* elispFn = static_cast(elispFns[0]); + auto* pythonFn = static_cast(pythonFns[0]); + + assert(elispFn->name == pythonFn->name && + "Both should produce function with same name"); + + auto elispParams = elispFn->getChildren("parameters"); + auto pythonParams = pythonFn->getChildren("parameters"); + assert(elispParams.size() == pythonParams.size() && + "Both should have same number of parameters"); + + std::cout << "Test 6 PASS: Elisp and Python produce equivalent AST structure" << std::endl; + ++passed; + } + + // --- Summary --- + std::cout << "\n=== Step 47 Results: " << passed << " passed, " << failed << " failed ===" << std::endl; + return failed > 0 ? 1 : 0; +}