From 16b7384cd0f89faa9bbd54e1d193c59860c379d2 Mon Sep 17 00:00:00 2001 From: Bill Date: Sat, 7 Feb 2026 08:25:37 -0700 Subject: [PATCH] Step 60 TDD test: AST query API Tests ASTQueryAPI: getAST returns JSON, findNodesByType for Functions/ Parameters/VariableReferences, findNodesByProperty by name, findNodesWithAnnotation, and getSubtree with depth limit. Co-Authored-By: Claude Opus 4.6 --- editor/CMakeLists.txt | 3 + editor/tests/step60_test.cpp | 185 +++++++++++++++++++++++++++++++++++ 2 files changed, 188 insertions(+) create mode 100644 editor/tests/step60_test.cpp diff --git a/editor/CMakeLists.txt b/editor/CMakeLists.txt index 101ca82..55dd5b4 100644 --- a/editor/CMakeLists.txt +++ b/editor/CMakeLists.txt @@ -169,6 +169,9 @@ target_include_directories(step56_test PRIVATE src) add_executable(step57_test tests/step57_test.cpp) target_include_directories(step57_test PRIVATE src) +add_executable(step60_test tests/step60_test.cpp) +target_include_directories(step60_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/step60_test.cpp b/editor/tests/step60_test.cpp new file mode 100644 index 0000000..e6b53b8 --- /dev/null +++ b/editor/tests/step60_test.cpp @@ -0,0 +1,185 @@ +// Step 60 TDD Test: AST query API +// +// Tests the agent query API: +// 1. getAST(nodeId) returns AST subtree as JSON +// 2. findNodes by concept type returns matching nodes +// 3. findNodes by property value returns matching nodes +// 4. getSubtree with depth limit +// 5. findNodes with annotation presence filter +// +// Will fail until the AST query API is implemented. + +#include +#include +#include +#include +#include "ast/ASTNode.h" +#include "ast/Module.h" +#include "ast/Function.h" +#include "ast/Variable.h" +#include "ast/Parameter.h" +#include "ast/Statement.h" +#include "ast/Expression.h" +#include "ast/Annotation.h" + +static bool contains(const std::string& haystack, const std::string& needle) { + return haystack.find(needle) != std::string::npos; +} + +// Forward declaration — ASTQueryAPI +class ASTQueryAPI { +public: + // Set the root AST to query against + void setRoot(ASTNode* root); + + // Get subtree as JSON string + std::string getAST(const std::string& nodeId) const; + + // Get subtree with depth limit + std::string getSubtree(const std::string& nodeId, int depth) const; + + // Find nodes by concept type + std::vector findNodesByType(const std::string& conceptType) const; + + // Find nodes where a string property matches + std::vector findNodesByProperty(const std::string& propertyName, + const std::string& value) const; + + // Find nodes that have a specific annotation type + std::vector findNodesWithAnnotation(const std::string& annotationType) const; +}; + +int main() { + int passed = 0; + int failed = 0; + + // Build a test AST: Module with 2 functions, one with an annotation + Module mod("m1", "TestModule", "python"); + + Function* fn1 = new Function("f1", "add"); + Parameter* p1 = new Parameter("p1", "x"); + Parameter* p2 = new Parameter("p2", "y"); + fn1->addChild("parameters", p1); + fn1->addChild("parameters", p2); + Return* ret1 = new Return(); + ret1->id = "r1"; + BinaryOperation* binOp = new BinaryOperation("b1", "+"); + VariableReference* refX = new VariableReference("vr1", "x"); + VariableReference* refY = new VariableReference("vr2", "y"); + binOp->setChild("left", refX); + binOp->setChild("right", refY); + ret1->setChild("value", binOp); + fn1->addChild("body", ret1); + mod.addChild("functions", fn1); + + Function* fn2 = new Function("f2", "compute"); + DerefStrategy* deref = new DerefStrategy("d1", "batched"); + fn2->addChild("annotations", deref); + Variable* var = new Variable("v1", "result"); + var->id = "v1"; + fn2->addChild("body", var); + mod.addChild("functions", fn2); + + ASTQueryAPI api; + api.setRoot(&mod); + + // --- Test 1: getAST returns JSON for root --- + { + std::string json = api.getAST("m1"); + assert(!json.empty() && "getAST should return non-empty JSON"); + assert(contains(json, "TestModule") && "JSON should contain module name"); + assert(contains(json, "Module") && "JSON should contain concept type"); + + std::cout << "Test 1 PASS: getAST returns JSON with module info" << std::endl; + ++passed; + } + + // --- Test 2: getAST for a child node --- + { + std::string json = api.getAST("f1"); + assert(!json.empty() && "getAST should return JSON for child node"); + assert(contains(json, "add") && "JSON should contain function name 'add'"); + + std::cout << "Test 2 PASS: getAST returns JSON for child node" << std::endl; + ++passed; + } + + // --- Test 3: findNodesByType("Function") returns both functions --- + { + auto results = api.findNodesByType("Function"); + assert(results.size() == 2 && "Should find 2 Function nodes"); + + std::cout << "Test 3 PASS: findNodesByType finds all Functions" << std::endl; + ++passed; + } + + // --- Test 4: findNodesByType("Parameter") returns both parameters --- + { + auto results = api.findNodesByType("Parameter"); + assert(results.size() == 2 && "Should find 2 Parameter nodes"); + + std::cout << "Test 4 PASS: findNodesByType finds all Parameters" << std::endl; + ++passed; + } + + // --- Test 5: findNodesByType("VariableReference") returns 2 refs --- + { + auto results = api.findNodesByType("VariableReference"); + assert(results.size() == 2 && "Should find 2 VariableReference nodes"); + + std::cout << "Test 5 PASS: findNodesByType finds VariableReferences" << std::endl; + ++passed; + } + + // --- Test 6: findNodesByProperty returns matching node --- + { + auto results = api.findNodesByProperty("name", "compute"); + assert(results.size() >= 1 && "Should find at least 1 node with name='compute'"); + + auto* found = static_cast(results[0]); + assert(found->name == "compute" && "Found node should be the 'compute' function"); + + std::cout << "Test 6 PASS: findNodesByProperty finds by name" << std::endl; + ++passed; + } + + // --- Test 7: findNodesWithAnnotation returns annotated function --- + { + auto results = api.findNodesWithAnnotation("DerefStrategy"); + assert(results.size() >= 1 && "Should find at least 1 node with DerefStrategy annotation"); + + auto* found = static_cast(results[0]); + assert(found->name == "compute" && "Annotated function should be 'compute'"); + + std::cout << "Test 7 PASS: findNodesWithAnnotation finds annotated nodes" << std::endl; + ++passed; + } + + // --- Test 8: getSubtree with depth=1 limits children --- + { + std::string json = api.getSubtree("m1", 1); + assert(!json.empty() && "getSubtree should return JSON"); + assert(contains(json, "TestModule") && "Should contain module name"); + // At depth 1, should show functions but not their children details + assert(contains(json, "add") && "Should show function names at depth 1"); + + std::cout << "Test 8 PASS: getSubtree respects depth limit" << std::endl; + ++passed; + } + + // Cleanup + delete refY; + delete refX; + delete binOp; + delete ret1; + delete p2; + delete p1; + delete fn1; + delete deref; + delete var; + delete fn2; + + // --- Summary --- + std::cout << "\n=== Step 60 Results: " << passed << " passed, " << failed << " failed ===" << std::endl; + return failed > 0 ? 1 : 0; +}