Files
whetstone_DSL/editor/src/ContextAPI.h
Bill 7fb9e89e25 Phase 3e complete: Agent API Extend (Steps 59, 61-63)
Step 59: WebSocketAgentServer with session management, JSON-RPC routing, MockWebSocketTransport (10/10 tests)
Step 61: ASTMutationAPI with setProperty, updateNode, deleteNode, insertNode, journal, OptimizationLock warning (6/6 tests)
Step 62: ContextAPI with getInScopeSymbols, getCallHierarchy, getDependencyGraph (6/6 tests)
Step 63: BatchMutationAPI with atomic applySequence and reverse-order rollback (5/5 tests)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-08 20:57:39 -07:00

262 lines
9.4 KiB
C++

#pragma once
// Step 62: Context API
//
// ContextAPI: provides scope, call-hierarchy, and data-flow queries
// over the SemAnno AST.
//
// Methods:
// getInScopeSymbols — variables/functions/parameters visible at a node
// getCallHierarchy — callers and callees of a function
// getDependencyGraph — data-flow dependencies of a node (via VariableRef)
#include <string>
#include <vector>
#include <algorithm>
#include "ast/ASTNode.h"
#include "ast/Module.h"
#include "ast/Function.h"
#include "ast/Variable.h"
#include "ast/Parameter.h"
#include "ast/Expression.h"
class ContextAPI {
public:
struct Symbol {
std::string name;
std::string kind; // "function", "variable", "parameter"
std::string nodeId;
};
struct CallInfo {
std::string functionId;
std::string functionName;
std::vector<std::string> callerIds; // functions that call this one
std::vector<std::string> calleeIds; // functions called by this one
};
void setRoot(ASTNode* root) { root_ = root; }
// ---------------------------------------------------------------
// getInScopeSymbols — walk up from nodeId, collecting symbols
// ---------------------------------------------------------------
std::vector<Symbol> getInScopeSymbols(const std::string& nodeId) const {
ASTNode* node = findById(root_, nodeId);
if (!node) return {};
std::vector<Symbol> symbols;
// Walk up the ancestor chain (including node itself)
const ASTNode* cur = node;
while (cur) {
if (cur->conceptType == "Function") {
auto* fn = static_cast<const Function*>(cur);
// Parameters
for (auto* p : fn->getChildren("parameters")) {
if (p->conceptType == "Parameter") {
auto* param = static_cast<const Parameter*>(p);
addSymbolUnique(symbols, {param->name, "parameter", param->id});
}
}
// Body variables
for (auto* child : fn->getChildren("body")) {
if (child->conceptType == "Variable") {
auto* v = static_cast<const Variable*>(child);
addSymbolUnique(symbols, {v->name, "variable", v->id});
}
}
}
else if (cur->conceptType == "Module") {
auto* mod = static_cast<const Module*>(cur);
// Module-level variables
for (auto* child : mod->getChildren("variables")) {
if (child->conceptType == "Variable") {
auto* v = static_cast<const Variable*>(child);
addSymbolUnique(symbols, {v->name, "variable", v->id});
}
}
// Module-level functions
for (auto* child : mod->getChildren("functions")) {
if (child->conceptType == "Function") {
auto* fn = static_cast<const Function*>(child);
addSymbolUnique(symbols, {fn->name, "function", fn->id});
}
}
}
cur = cur->parent;
}
return symbols;
}
// ---------------------------------------------------------------
// getCallHierarchy
// ---------------------------------------------------------------
CallInfo getCallHierarchy(const std::string& functionId) const {
CallInfo info;
ASTNode* fnNode = findById(root_, functionId);
if (!fnNode || fnNode->conceptType != "Function") return info;
auto* fn = static_cast<Function*>(fnNode);
info.functionId = fn->id;
info.functionName = fn->name;
// Collect ALL FunctionCall nodes in the entire tree
std::vector<ASTNode*> allCalls;
collectByType(root_, "FunctionCall", allCalls);
// callerIds: functions that contain a call to fn->name
// calleeIds: functions called from within fn
for (auto* callNode : allCalls) {
auto* fc = static_cast<FunctionCall*>(callNode);
if (fc->functionName == fn->name) {
// Someone calls our function — find the enclosing Function
Function* caller = enclosingFunction(callNode);
if (caller && caller->id != fn->id) {
addUnique(info.callerIds, caller->id);
}
}
// Is this call inside our function?
if (isDescendantOf(callNode, fnNode)) {
// Resolve callee function ID by name
std::string calleeId = resolveFunctionId(fc->functionName);
if (!calleeId.empty()) {
addUnique(info.calleeIds, calleeId);
}
}
}
return info;
}
// ---------------------------------------------------------------
// getDependencyGraph — data-flow deps via VariableReference
// ---------------------------------------------------------------
std::vector<std::string> getDependencyGraph(const std::string& nodeId) const {
ASTNode* node = findById(root_, nodeId);
if (!node) return {};
// Collect all VariableReference nodes in the subtree
std::vector<ASTNode*> refs;
collectByType(node, "VariableReference", refs);
std::vector<std::string> deps;
for (auto* refNode : refs) {
auto* vr = static_cast<VariableReference*>(refNode);
// Resolve to the declaration node (Variable or Parameter)
std::string declId = resolveSymbolId(vr->variableName, refNode);
if (!declId.empty()) {
addUnique(deps, declId);
}
}
return deps;
}
private:
// --- helpers ---
ASTNode* findById(ASTNode* node, const std::string& id) const {
if (!node) return nullptr;
if (node->id == id) return node;
for (auto* child : node->allChildren()) {
auto* found = findById(child, id);
if (found) return found;
}
return nullptr;
}
static void collectByType(ASTNode* node, const std::string& type,
std::vector<ASTNode*>& out) {
if (!node) return;
if (node->conceptType == type) out.push_back(node);
for (auto* child : node->allChildren()) {
collectByType(child, type, out);
}
}
// Walk up to find the nearest enclosing Function
static Function* enclosingFunction(ASTNode* node) {
ASTNode* cur = node->parent;
while (cur) {
if (cur->conceptType == "Function")
return static_cast<Function*>(cur);
cur = cur->parent;
}
return nullptr;
}
// Check if child is a descendant of ancestor
static bool isDescendantOf(const ASTNode* child, const ASTNode* ancestor) {
const ASTNode* cur = child->parent;
while (cur) {
if (cur == ancestor) return true;
cur = cur->parent;
}
return false;
}
// Resolve a function name to its node ID (searching module-level functions)
std::string resolveFunctionId(const std::string& name) const {
if (!root_) return "";
// Find the module (root or first module child)
const ASTNode* mod = root_;
if (mod->conceptType != "Module") return "";
for (auto* child : mod->getChildren("functions")) {
if (child->conceptType == "Function") {
auto* fn = static_cast<const Function*>(child);
if (fn->name == name) return fn->id;
}
}
return "";
}
// Resolve a variable/parameter name to its declaration ID,
// searching up the scope chain from refNode.
std::string resolveSymbolId(const std::string& name,
const ASTNode* refNode) const {
const ASTNode* cur = refNode;
while (cur) {
if (cur->conceptType == "Function") {
auto* fn = static_cast<const Function*>(cur);
for (auto* p : fn->getChildren("parameters")) {
if (p->conceptType == "Parameter" &&
static_cast<const Parameter*>(p)->name == name)
return p->id;
}
for (auto* child : fn->getChildren("body")) {
if (child->conceptType == "Variable" &&
static_cast<const Variable*>(child)->name == name)
return child->id;
}
}
else if (cur->conceptType == "Module") {
for (auto* child : cur->getChildren("variables")) {
if (child->conceptType == "Variable" &&
static_cast<const Variable*>(child)->name == name)
return child->id;
}
}
cur = cur->parent;
}
return "";
}
static void addSymbolUnique(std::vector<Symbol>& symbols,
const Symbol& sym) {
for (const auto& s : symbols) {
if (s.nodeId == sym.nodeId) return;
}
symbols.push_back(sym);
}
static void addUnique(std::vector<std::string>& vec,
const std::string& val) {
if (std::find(vec.begin(), vec.end(), val) == vec.end())
vec.push_back(val);
}
ASTNode* root_ = nullptr;
};