diff --git a/editor/CMakeLists.txt b/editor/CMakeLists.txt index d67d4ce..4deb62c 100644 --- a/editor/CMakeLists.txt +++ b/editor/CMakeLists.txt @@ -2227,4 +2227,13 @@ target_link_libraries(step371_test PRIVATE tree_sitter_javascript tree_sitter_typescript tree_sitter_java tree_sitter_rust tree_sitter_go) +add_executable(step372_test tests/step372_test.cpp) +target_include_directories(step372_test PRIVATE src) +target_link_libraries(step372_test PRIVATE + nlohmann_json::nlohmann_json + unofficial::tree-sitter::tree-sitter + tree_sitter_python tree_sitter_cpp tree_sitter_elisp + tree_sitter_javascript tree_sitter_typescript + tree_sitter_java tree_sitter_rust tree_sitter_go) + # Step 12: Dear ImGui shell scaffolding created (main.cpp exists but not built due to dependencies) diff --git a/editor/src/AnnotationInference.h b/editor/src/AnnotationInference.h index 991c5c8..0fc336b 100644 --- a/editor/src/AnnotationInference.h +++ b/editor/src/AnnotationInference.h @@ -14,7 +14,6 @@ #include #include #include - class AnnotationInference { public: struct InferredAnnotation { @@ -57,6 +56,15 @@ private: if (node->conceptType == "Function" || node->conceptType == "AsyncFunction" || node->conceptType == "MethodDeclaration") { inferFunction(node, out); + } else if (node->conceptType == "Variable") { + inferVariable(node, out); + } else if (node->conceptType == "MacroDefinition") { + inferMacro(node, out); + } else if (node->conceptType == "ClassDeclaration") { + if (!hasInferred(out, node->id, "VisibilityAnnotation", "public")) { + out.push_back({node->id, "VisibilityAnnotation", "level", "public", + "Class declarations default to public surface", 0.70}); + } } else if (node->conceptType == "ForLoop" || node->conceptType == "WhileLoop") { inferLoop(node, out); @@ -73,8 +81,6 @@ private: std::set existing; for (const auto* a : annos) existing.insert(a->conceptType); - auto body = fn->getChildren("body"); - // Async detection if (fn->conceptType == "AsyncFunction" && !existing.count("ExecAnnotation")) { out.push_back({fn->id, "ExecAnnotation", "mode", "async", @@ -100,31 +106,27 @@ private: "Tail-recursive call detected", 0.80}); } } - + inferLispFunctionPatterns(fn, existing, fnName, out); // Visibility inference for methods if (fn->conceptType == "MethodDeclaration" && !existing.count("VisibilityAnnotation")) { out.push_back({fn->id, "VisibilityAnnotation", "level", "public", "Default method visibility", 0.60}); } - // Exception handling detection if (!existing.count("ExceptionAnnotation") && hasExceptionHandling(fn)) { out.push_back({fn->id, "ExceptionAnnotation", "style", "unchecked", "try/catch pattern detected", 0.75}); } - // Blocking detection if (!existing.count("BlockingAnnotation") && hasBlockingCalls(fn)) { out.push_back({fn->id, "BlockingAnnotation", "kind", "io", "IO operations detected", 0.65}); } - // Parallel detection (goroutine-like patterns) if (!existing.count("ParallelAnnotation") && hasParallelPatterns(fn)) { out.push_back({fn->id, "ParallelAnnotation", "kind", "task", "Parallel dispatch pattern detected", 0.70}); } - // Complexity inference if (!existing.count("ComplexityAnnotation")) { int depth = nestingDepth(fn); @@ -135,7 +137,6 @@ private: "Nesting depth analysis", 0.55}); } } - void inferLoop(const ASTNode* loop, std::vector& out) const { auto annos = loop->getChildren("annotations"); std::set existing; @@ -182,9 +183,18 @@ private: return fc->functionName == name; } } + if (last->conceptType == "ExpressionStatement") { + auto* expr = last->getChild("expression"); + if (expr && expr->conceptType == "FunctionCall") { + auto* fc = static_cast(expr); + return fc->functionName == name; + } + } return false; } +#include "AnnotationInferenceLisp.h" + bool hasExceptionHandling(const ASTNode* node) const { if (!node) return false; // Look for try/catch patterns in function calls diff --git a/editor/src/AnnotationInferenceLisp.h b/editor/src/AnnotationInferenceLisp.h new file mode 100644 index 0000000..d100ffb --- /dev/null +++ b/editor/src/AnnotationInferenceLisp.h @@ -0,0 +1,98 @@ + void inferLispFunctionPatterns(const ASTNode* fn, + const std::set& existing, + const std::string& fnName, + std::vector& out) const { + if (hasCallNamed(fn, "values") && !existing.count("ContractAnnotation")) { + out.push_back({fn->id, "ContractAnnotation", "returns", "multiple-values", + "values form indicates multi-value return contract", 0.88}); + } + if (hasDeclaredTypePattern(fn) && !existing.count("ComplexityAnnotation")) { + out.push_back({fn->id, "ComplexityAnnotation", "timeComplexity", "declared-type", + "Type declaration forms constrain complexity interpretation", 0.72}); + } + if (hasOptimizePattern(fn) && !hasInferred(out, fn->id, "PolicyAnnotation", "critical")) { + out.push_back({fn->id, "PolicyAnnotation", "perf", "critical", + "optimize speed hint implies performance-critical policy", 0.90}); + out.push_back({fn->id, "PolicyAnnotation", "style", "literal", + "optimize declaration favors literal policy output", 0.86}); + } + if (fn->conceptType == "MethodDeclaration" && fnName.size() > 0 && + !hasInferred(out, fn->id, "SyntheticAnnotation", "clos-method")) { + out.push_back({fn->id, "SyntheticAnnotation", "generator", "clos-method", + "Method declaration maps to CLOS generic dispatch", 0.76}); + } + } + + void inferVariable(const ASTNode* varNode, std::vector& out) const { + auto* var = static_cast(varNode); + if (var->name.size() > 2 && var->name.front() == '*' && var->name.back() == '*' && + !hasInferred(out, var->id, "BindingAnnotation", "dynamic")) { + out.push_back({var->id, "BindingAnnotation", "time", "dynamic", + "Earmuff naming indicates dynamic/special binding", 0.95}); + } else if (varNode->parent && varNode->parent->conceptType == "Block" && + !hasInferred(out, var->id, "BindingAnnotation", "static")) { + out.push_back({var->id, "BindingAnnotation", "time", "static", + "Lexical block variable inferred as static binding", 0.70}); + } + } + + void inferMacro(const ASTNode* macroNode, std::vector& out) const { + if (!hasInferred(out, macroNode->id, "MetaAnnotation", "quoted")) { + out.push_back({macroNode->id, "MetaAnnotation", "state", "quoted", + "Macro definitions operate over quoted forms", 0.92}); + } + if (!hasInferred(out, macroNode->id, "SyntheticAnnotation", "macro")) { + out.push_back({macroNode->id, "SyntheticAnnotation", "generator", "macro", + "Macro expands synthetic code structures", 0.90}); + } + } + + bool hasCallNamed(const ASTNode* node, const std::string& name) const { + if (!node) return false; + if (node->conceptType == "FunctionCall") { + auto* fc = static_cast(node); + if (lowerAscii(fc->functionName) == lowerAscii(name)) return true; + } + for (auto* child : node->allChildren()) { + if (hasCallNamed(child, name)) return true; + } + return false; + } + + bool hasDeclaredTypePattern(const ASTNode* node) const { + if (!node) return false; + if (node->conceptType == "FunctionCall") { + auto* fc = static_cast(node); + if (lowerAscii(fc->functionName) == "declare") { + for (auto* arg : fc->getChildren("arguments")) { + if (arg->conceptType != "FunctionCall") continue; + auto* inner = static_cast(arg); + if (lowerAscii(inner->functionName) == "type") return true; + } + } + } + for (auto* child : node->allChildren()) { + if (hasDeclaredTypePattern(child)) return true; + } + return false; + } + + bool hasOptimizePattern(const ASTNode* node) const { + if (!node) return false; + if (node->conceptType == "FunctionCall") { + auto* fc = static_cast(node); + std::string fn = lowerAscii(fc->functionName); + if (fn == "optimize") return true; + if (fn == "declare") { + for (auto* arg : fc->getChildren("arguments")) { + if (arg->conceptType != "FunctionCall") continue; + auto* inner = static_cast(arg); + if (lowerAscii(inner->functionName) == "optimize") return true; + } + } + } + for (auto* child : node->allChildren()) { + if (hasOptimizePattern(child)) return true; + } + return false; + } diff --git a/editor/src/MemoryStrategyInference.h b/editor/src/MemoryStrategyInference.h index ffdcf8b..61359c9 100644 --- a/editor/src/MemoryStrategyInference.h +++ b/editor/src/MemoryStrategyInference.h @@ -80,11 +80,18 @@ private: if (lang == "python" || lang == "elisp" || lang == "ruby" || lang == "javascript" || lang == "java" || lang == "csharp" || - lang == "kotlin") { + lang == "kotlin" || lang == "common-lisp" || lang == "commonlisp" || + lang == "lisp" || lang == "cl") { // GC languages → @Reclaim(Tracing) on the module out.push_back({mod->id, "ReclaimAnnotation", "Tracing", lang + " uses tracing garbage collection", 0.95}); + if (lang == "common-lisp" || lang == "commonlisp" || + lang == "lisp" || lang == "cl") { + out.push_back({mod->id, "OwnerAnnotation", "Shared_GC", + "Common Lisp values are GC-managed shared objects", + 0.92}); + } } else if (lang == "go") { out.push_back({mod->id, "ReclaimAnnotation", "Escape", diff --git a/editor/tests/step372_test.cpp b/editor/tests/step372_test.cpp new file mode 100644 index 0000000..b03b2bb --- /dev/null +++ b/editor/tests/step372_test.cpp @@ -0,0 +1,242 @@ +// Step 372: Lisp Annotation Mapping (12 tests) + +#include +#include +#include +#include +#include "AnnotationInference.h" +#include "MemoryStrategyInference.h" +#include "CrossLanguageProjector.h" +#include "ast/PythonGenerator.h" +#include "ast/CppGenerator.h" +#include "ast/ClassDeclaration.h" + +static bool hasInference(const std::vector& inferred, + const std::string& annotationType, + const std::string& value) { + for (const auto& a : inferred) { + if (a.annotationType == annotationType && a.value == value) return true; + } + return false; +} + +static bool hasMemorySuggestion(const std::vector& suggestions, + const std::string& annotationType, + const std::string& strategy) { + for (const auto& s : suggestions) { + if (s.annotationType == annotationType && s.strategy == strategy) return true; + } + return false; +} + +int main() { + int passed = 0; + + // Test 1: macro -> @Meta(quoted) + { + Module mod("m1", "cl", "common-lisp"); + mod.addChild("statements", new MacroDefinition("mac1", "when1")); + AnnotationInference inf; + auto inferred = inf.inferAll(&mod); + assert(hasInference(inferred, "MetaAnnotation", "quoted")); + std::cout << "Test 1 PASSED: macro -> Meta(quoted)\n"; + passed++; + } + + // Test 2: dynamic variable -> @Binding(dynamic) + { + Module mod("m1", "cl", "common-lisp"); + mod.addChild("variables", new Variable("v1", "*runtime-mode*")); + AnnotationInference inf; + auto inferred = inf.inferAll(&mod); + assert(hasInference(inferred, "BindingAnnotation", "dynamic")); + std::cout << "Test 2 PASSED: dynamic variable -> Binding(dynamic)\n"; + passed++; + } + + // Test 3: values form -> @Contract(multiple-values) + { + Module mod("m1", "cl", "common-lisp"); + auto* fn = new Function("f1", "pair"); + auto* valuesCall = new FunctionCall(); + valuesCall->id = "c1"; + valuesCall->functionName = "values"; + valuesCall->addChild("arguments", new IntegerLiteral("i1", 1)); + valuesCall->addChild("arguments", new IntegerLiteral("i2", 2)); + auto* stmt = new ExpressionStatement(); + stmt->id = "s1"; + stmt->setChild("expression", valuesCall); + fn->addChild("body", stmt); + mod.addChild("functions", fn); + + AnnotationInference inf; + auto inferred = inf.inferAll(&mod); + assert(hasInference(inferred, "ContractAnnotation", "multiple-values")); + std::cout << "Test 3 PASSED: values -> Contract annotation\n"; + passed++; + } + + // Test 4: tail-recursive function -> @TailCall + { + Module mod("m1", "cl", "common-lisp"); + auto* fn = new Function("f1", "fact"); + auto* call = new FunctionCall(); + call->id = "c1"; + call->functionName = "fact"; + auto* stmt = new ExpressionStatement(); + stmt->id = "s1"; + stmt->setChild("expression", call); + fn->addChild("body", stmt); + mod.addChild("functions", fn); + + AnnotationInference inf; + auto inferred = inf.inferAll(&mod); + assert(hasInference(inferred, "TailCallAnnotation", "")); + std::cout << "Test 4 PASSED: tail recursion detected\n"; + passed++; + } + + // Test 5: CLOS class -> @Visibility(public) + { + Module mod("m1", "cl", "common-lisp"); + mod.addChild("classes", new ClassDeclaration("c1", "point")); + AnnotationInference inf; + auto inferred = inf.inferAll(&mod); + assert(hasInference(inferred, "VisibilityAnnotation", "public")); + std::cout << "Test 5 PASSED: CLOS class -> Visibility(public)\n"; + passed++; + } + + // Test 6: optimize declaration -> @Policy(perf=critical, style=literal) + { + Module mod("m1", "cl", "common-lisp"); + auto* fn = new Function("f1", "hot"); + auto* optimize = new FunctionCall(); + optimize->id = "c2"; + optimize->functionName = "optimize"; + auto* decl = new FunctionCall(); + decl->id = "c1"; + decl->functionName = "declare"; + decl->addChild("arguments", optimize); + auto* stmt = new ExpressionStatement(); + stmt->id = "s1"; + stmt->setChild("expression", decl); + fn->addChild("body", stmt); + mod.addChild("functions", fn); + + AnnotationInference inf; + auto inferred = inf.inferAll(&mod); + assert(hasInference(inferred, "PolicyAnnotation", "critical")); + assert(hasInference(inferred, "PolicyAnnotation", "literal")); + std::cout << "Test 6 PASSED: optimize -> Policy mapping\n"; + passed++; + } + + // Test 7: CL memory defaults -> GC + { + Module mod("m1", "cl", "common-lisp"); + MemoryStrategyInference mem; + auto suggestions = mem.inferAnnotations(&mod); + assert(hasMemorySuggestion(suggestions, "ReclaimAnnotation", "Tracing")); + assert(hasMemorySuggestion(suggestions, "OwnerAnnotation", "Shared_GC")); + std::cout << "Test 7 PASSED: CL memory defaults (GC)\n"; + passed++; + } + + // Test 8: confidence values are bounded and include strong signal + { + Module mod("m1", "cl", "common-lisp"); + mod.addChild("statements", new MacroDefinition("mac1", "m")); + mod.addChild("variables", new Variable("v1", "*x*")); + AnnotationInference inf; + auto inferred = inf.inferAll(&mod); + bool strong = false; + for (const auto& a : inferred) { + assert(a.confidence >= 0.0 && a.confidence <= 1.0); + if (a.confidence >= 0.90) strong = true; + } + assert(strong); + std::cout << "Test 8 PASSED: confidence bounds\n"; + passed++; + } + + // Test 9: CL @Binding(dynamic) -> Python projection preserves annotation + { + Module mod("m1", "cl", "common-lisp"); + auto* fn = new Function("f1", "f"); + auto* b = new BindingAnnotation(); + b->id = "a1"; + b->time = "dynamic"; + fn->addChild("annotations", b); + mod.addChild("functions", fn); + + CrossLanguageProjector projector; + auto pyMod = projector.project(&mod, "python"); + PythonGenerator gen; + auto out = gen.generate(pyMod.get()); + assert(projector.annotationsPreserved(&mod, pyMod.get())); + assert(out.find("def f(") != std::string::npos); + std::cout << "Test 9 PASSED: binding(dynamic) preserved to Python projection\n"; + passed++; + } + + // Test 10: CL @Binding(dynamic) -> C++ projection preserves annotation + { + Module mod("m1", "cl", "common-lisp"); + auto* fn = new Function("f1", "f"); + auto* b = new BindingAnnotation(); + b->id = "a1"; + b->time = "dynamic"; + fn->addChild("annotations", b); + mod.addChild("functions", fn); + + CrossLanguageProjector projector; + auto cppMod = projector.project(&mod, "cpp"); + CppGenerator gen; + auto out = gen.generate(cppMod.get()); + assert(projector.annotationsPreserved(&mod, cppMod.get())); + assert(out.find("f(") != std::string::npos); + std::cout << "Test 10 PASSED: binding(dynamic) preserved to C++ projection\n"; + passed++; + } + + // Test 11: (declare (type ...)) -> Complexity(declared-type) + { + Module mod("m1", "cl", "common-lisp"); + auto* fn = new Function("f1", "typed"); + auto* typeCall = new FunctionCall(); + typeCall->id = "c2"; + typeCall->functionName = "type"; + typeCall->addChild("arguments", new VariableReference("r1", "fixnum")); + auto* decl = new FunctionCall(); + decl->id = "c1"; + decl->functionName = "declare"; + decl->addChild("arguments", typeCall); + auto* stmt = new ExpressionStatement(); + stmt->id = "s1"; + stmt->setChild("expression", decl); + fn->addChild("body", stmt); + mod.addChild("functions", fn); + + AnnotationInference inf; + auto inferred = inf.inferAll(&mod); + assert(hasInference(inferred, "ComplexityAnnotation", "declared-type")); + std::cout << "Test 11 PASSED: declare(type) -> Complexity mapping\n"; + passed++; + } + + // Test 12: CLOS method -> synthetic method-combination hint + { + Module mod("m1", "cl", "common-lisp"); + mod.addChild("functions", new MethodDeclaration("m1", "area")); + AnnotationInference inf; + auto inferred = inf.inferAll(&mod); + assert(hasInference(inferred, "SyntheticAnnotation", "clos-method")); + std::cout << "Test 12 PASSED: CLOS method -> synthetic hint\n"; + passed++; + } + + std::cout << "\nResults: " << passed << "/12\n"; + assert(passed == 12); + return 0; +} diff --git a/progress.md b/progress.md index fc8daeb..0039bbc 100644 --- a/progress.md +++ b/progress.md @@ -2487,6 +2487,52 @@ for Common Lisp aliases in the pipeline. - `editor/src/ast/CommonLispGenerator.h` remains within header-size limit (`267` lines <= `600`) +### Step 372: Lisp Annotation Mapping +**Status:** PASS (12/12 tests) + +Added Lisp-focused annotation inference coverage for macros, dynamic bindings, +multi-value contracts, method dispatch signals, and optimize/declaration forms, +plus Common Lisp memory-strategy defaults. + +**Files created:** +- `editor/src/AnnotationInferenceLisp.h` — extracted Lisp-specific inference helpers: + - macro inference (`Meta(quoted)` + `Synthetic(macro)`) + - dynamic vs lexical binding inference (`Binding(dynamic|static)`) + - CL form pattern checks for `values`, `declare(type ...)`, and `optimize` + - CLOS method synthetic hint inference +- `editor/tests/step372_test.cpp` — 12 tests covering: + 1. macro -> `Meta(quoted)` + 2. dynamic variable -> `Binding(dynamic)` + 3. `values` -> multi-value `Contract` + 4. tail-recursive function -> `TailCall` + 5. CLOS class -> `Visibility(public)` + 6. optimize declaration -> `Policy(perf=critical, style=literal)` + 7. Common Lisp GC defaults (`Reclaim(Tracing)`, `Owner(Shared_GC)`) + 8. confidence bounds and strong signals + 9. `Binding(dynamic)` preservation through CL -> Python projection + 10. `Binding(dynamic)` preservation through CL -> C++ projection + 11. `declare(type ...)` -> `Complexity(declared-type)` + 12. method synthetic hint for CLOS dispatch + +**Files modified:** +- `editor/src/AnnotationInference.h` — integrate Lisp inference hooks while + preserving architecture line limits +- `editor/src/MemoryStrategyInference.h` — add Common Lisp module defaults for GC +- `editor/CMakeLists.txt` — `step372_test` target + +**Verification run:** +- `step372_test` — PASS (12/12) new step coverage +- `step371_test` — PASS (12/12) regression coverage +- `step370_test` — PASS (12/12) regression coverage +- `step369_test` — PASS (8/8) regression coverage +- `step367_test` — PASS (12/12) regression coverage + +**Architecture gate check:** +- `editor/src/AnnotationInference.h` remains within header-size limit + (`599` lines <= `600`) +- `editor/src/MemoryStrategyInference.h` remains within header-size limit + (`384` lines <= `600`) + # Roadmap Planning — Sprints 12-25+ ## Status: Planning Complete (Sprints 12-19 detailed, 20-25 in roadmap.md)