Sprint 3 Step 60: AST query API implementation

- Implement ASTQueryAPI class with tree walking, JSON output,
  findNodesByType, findNodesByProperty, findNodesWithAnnotation
- Add nlohmann_json link for step60_test in CMakeLists.txt
- Add PROGRESS.md for cross-session continuity tracking

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Bill
2026-02-07 22:54:18 -07:00
parent 89bcf805bc
commit 7c0b0c759e
3 changed files with 365 additions and 15 deletions

View File

@@ -6,8 +6,6 @@
// 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 <iostream>
#include <string>
@@ -21,32 +19,130 @@
#include "ast/Statement.h"
#include "ast/Expression.h"
#include "ast/Annotation.h"
#include "ast/Serialization.h"
static bool contains(const std::string& haystack, const std::string& needle) {
return haystack.find(needle) != std::string::npos;
}
// Forward declaration — ASTQueryAPI
// --- ASTQueryAPI implementation for Step 60 ---
class ASTQueryAPI {
ASTNode* root_ = nullptr;
// Recursive helper: find node by ID
ASTNode* findById(ASTNode* node, const std::string& nodeId) const {
if (!node) return nullptr;
if (node->id == nodeId) return node;
for (auto* child : node->allChildren()) {
auto* found = findById(child, nodeId);
if (found) return found;
}
return nullptr;
}
// Recursive helper: collect all nodes into a flat list
void collectAll(ASTNode* node, std::vector<ASTNode*>& out) const {
if (!node) return;
out.push_back(node);
for (auto* child : node->allChildren()) {
collectAll(child, out);
}
}
// Get a named string property from a node (by property name)
static std::string getStringProperty(const ASTNode* node, const std::string& prop) {
const auto& ct = node->conceptType;
if (prop == "name") {
if (ct == "Module") return static_cast<const Module*>(node)->name;
if (ct == "Function") return static_cast<const Function*>(node)->name;
if (ct == "Variable") return static_cast<const Variable*>(node)->name;
if (ct == "Parameter") return static_cast<const Parameter*>(node)->name;
}
if (prop == "op") {
if (ct == "BinaryOperation") return static_cast<const BinaryOperation*>(node)->op;
if (ct == "UnaryOperation") return static_cast<const UnaryOperation*>(node)->op;
}
if (prop == "variableName" && ct == "VariableReference")
return static_cast<const VariableReference*>(node)->variableName;
if (prop == "strategy") {
if (ct == "DerefStrategy") return static_cast<const DerefStrategy*>(node)->strategy;
if (ct == "DeallocateAnnotation") return static_cast<const DeallocateAnnotation*>(node)->strategy;
if (ct == "LifetimeAnnotation") return static_cast<const LifetimeAnnotation*>(node)->strategy;
if (ct == "ReclaimAnnotation") return static_cast<const ReclaimAnnotation*>(node)->strategy;
if (ct == "OwnerAnnotation") return static_cast<const OwnerAnnotation*>(node)->strategy;
if (ct == "AllocateAnnotation") return static_cast<const AllocateAnnotation*>(node)->strategy;
}
return "";
}
// JSON with depth limit
json toJsonDepth(const ASTNode* node, int depth) const {
json j;
j["id"] = node->id;
j["concept"] = node->conceptType;
j["properties"] = propertiesToJson(node);
if (depth > 0) {
json children = json::object();
for (const auto& role : node->childRoles()) {
json arr = json::array();
for (const auto* child : node->getChildren(role)) {
arr.push_back(toJsonDepth(child, depth - 1));
}
children[role] = arr;
}
j["children"] = children;
}
return j;
}
public:
// Set the root AST to query against
void setRoot(ASTNode* root);
void setRoot(ASTNode* root) { root_ = root; }
// Get subtree as JSON string
std::string getAST(const std::string& nodeId) const;
std::string getAST(const std::string& nodeId) const {
ASTNode* node = findById(root_, nodeId);
if (!node) return "";
return toJson(node).dump();
}
// Get subtree with depth limit
std::string getSubtree(const std::string& nodeId, int depth) const;
std::string getSubtree(const std::string& nodeId, int depth) const {
ASTNode* node = findById(root_, nodeId);
if (!node) return "";
return toJsonDepth(node, depth).dump();
}
// Find nodes by concept type
std::vector<ASTNode*> findNodesByType(const std::string& conceptType) const;
std::vector<ASTNode*> findNodesByType(const std::string& conceptType) const {
std::vector<ASTNode*> all, result;
collectAll(root_, all);
for (auto* n : all) {
if (n->conceptType == conceptType) result.push_back(n);
}
return result;
}
// Find nodes where a string property matches
std::vector<ASTNode*> findNodesByProperty(const std::string& propertyName,
const std::string& value) const;
const std::string& value) const {
std::vector<ASTNode*> all, result;
collectAll(root_, all);
for (auto* n : all) {
if (getStringProperty(n, propertyName) == value) result.push_back(n);
}
return result;
}
// Find nodes that have a specific annotation type
std::vector<ASTNode*> findNodesWithAnnotation(const std::string& annotationType) const;
std::vector<ASTNode*> findNodesWithAnnotation(const std::string& annotationType) const {
std::vector<ASTNode*> all, result;
collectAll(root_, all);
for (auto* n : all) {
for (auto* anno : n->getChildren("annotations")) {
if (anno->conceptType == annotationType) {
result.push_back(n);
break;
}
}
}
return result;
}
};
int main() {