diff --git a/editor/CMakeLists.txt b/editor/CMakeLists.txt index 0e33507..7bc63d3 100644 --- a/editor/CMakeLists.txt +++ b/editor/CMakeLists.txt @@ -2182,4 +2182,13 @@ target_link_libraries(step366_test PRIVATE tree_sitter_javascript tree_sitter_typescript tree_sitter_java tree_sitter_rust tree_sitter_go) +add_executable(step367_test tests/step367_test.cpp) +target_include_directories(step367_test PRIVATE src) +target_link_libraries(step367_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 2c0bf96..991c5c8 100644 --- a/editor/src/AnnotationInference.h +++ b/editor/src/AnnotationInference.h @@ -40,6 +40,8 @@ public: } if (mod->targetLanguage == "c") { inferCModule(mod, out); + } else if (mod->targetLanguage == "wat" || mod->targetLanguage == "wasm") { + inferWatModule(mod, out); } } @@ -395,6 +397,53 @@ private: } } + void inferWatModule(const Module* mod, std::vector& out) const { + if (!mod) return; + + for (auto* fnNode : mod->getChildren("functions")) { + if (fnNode->conceptType != "Function") continue; + if (!hasInferred(out, fnNode->id, "ExecAnnotation", "stack")) { + out.push_back({fnNode->id, "ExecAnnotation", "mode", "stack", + "WAT executes in a stack-based VM", 0.95}); + } + } + + if (!mod->getChildren("imports").empty() && + !hasInferred(out, mod->id, "LinkAnnotation", "import")) { + out.push_back({mod->id, "LinkAnnotation", "kind", "import", + "WAT module imports host/runtime functions", 0.90}); + } + + bool hasExport = false; + bool hasTable = false; + bool hasMemory = false; + for (auto* stmt : mod->getChildren("statements")) { + if (stmt->conceptType != "PragmaDirective") continue; + auto* p = static_cast(stmt); + std::string d = lowerAscii(p->directive); + hasExport = hasExport || d == "export"; + hasTable = hasTable || d == "table"; + hasMemory = hasMemory || d == "memory"; + } + + if (hasExport && !hasInferred(out, mod->id, "VisibilityAnnotation", "public")) { + out.push_back({mod->id, "VisibilityAnnotation", "level", "public", + "Exported WAT symbols are public API", 0.90}); + } + if (hasTable && !hasInferred(out, mod->id, "ShimAnnotation", "vtable")) { + out.push_back({mod->id, "ShimAnnotation", "kind", "vtable", + "WAT table dispatch resembles virtual table shims", 0.82}); + } + if (hasMemory && !hasInferred(out, mod->id, "AlignAnnotation", "4")) { + out.push_back({mod->id, "AlignAnnotation", "bytes", "4", + "Linear memory defaults to aligned word operations", 0.70}); + } + if (hasMemory && !hasInferred(out, mod->id, "LayoutAnnotation", "linear")) { + out.push_back({mod->id, "LayoutAnnotation", "mode", "linear", + "WAT memory is a linear buffer layout", 0.86}); + } + } + void inferCHeaderGuard(const Module* mod, std::vector& out) const { bool hasIfndef = false; bool hasEndif = false; diff --git a/editor/src/MemoryStrategyInference.h b/editor/src/MemoryStrategyInference.h index d3ae076..ffdcf8b 100644 --- a/editor/src/MemoryStrategyInference.h +++ b/editor/src/MemoryStrategyInference.h @@ -100,6 +100,9 @@ private: out.push_back({mod->id, "OwnerAnnotation", "Single", "Rust uses single-owner lifetime tracking", 0.95}); } + else if (lang == "wat" || lang == "wasm") { + inferWatModule(mod, out); + } else if (lang == "swift" || lang == "objc") { out.push_back({mod->id, "OwnerAnnotation", "Shared_ARC", lang + " uses automatic reference counting", 0.90}); @@ -130,6 +133,14 @@ private: "C uses explicit reclamation (free)", 0.95}); } + void inferWatModule(const Module* mod, + std::vector& out) const { + out.push_back({mod->id, "OwnerAnnotation", "Manual", + "WAT linear memory is manually managed", 0.92}); + out.push_back({mod->id, "ReclaimAnnotation", "Explicit", + "WAT has explicit memory lifetime operations", 0.88}); + } + void inferFunction(const Function* fn, const std::string& lang, std::vector& out) const { // Skip if function already has a memory annotation @@ -139,6 +150,10 @@ private: inferCFunction(fn, out); return; } + if (lang == "wat" || lang == "wasm") { + inferWatFunction(fn, out); + return; + } // Check body for patterns bool hasAlloc = false; @@ -213,6 +228,19 @@ private: } } + void inferWatFunction(const Function* fn, + std::vector& out) const { + bool hasMemoryOps = false; + for (auto* stmt : fn->getChildren("body")) { + hasMemoryOps = hasMemoryOps || containsWatMemoryOp(stmt); + } + if (hasMemoryOps) { + out.push_back({fn->id, "OwnerAnnotation", "Manual", + "WAT memory/load/store operations require manual ownership", + 0.90}); + } + } + static bool hasMemoryAnnotation(const ASTNode* node) { for (auto* anno : node->getChildren("annotations")) { const auto& ct = anno->conceptType; @@ -255,6 +283,23 @@ private: return false; } + static bool containsWatMemoryOp(const ASTNode* node) { + if (!node) return false; + if (node->conceptType == "FunctionCall") { + auto* fc = static_cast(node); + std::string fn = lowerAscii(fc->functionName); + if (fn.find("memory.") != std::string::npos || + fn.find(".load") != std::string::npos || + fn.find(".store") != std::string::npos || + fn == "memory.grow") + return true; + } + for (auto* child : node->allChildren()) { + if (containsWatMemoryOp(child)) return true; + } + return false; + } + static void inferCLocalLifetime(const ASTNode* node, std::vector& out) { if (!node) return; diff --git a/editor/tests/step367_test.cpp b/editor/tests/step367_test.cpp new file mode 100644 index 0000000..c5ef537 --- /dev/null +++ b/editor/tests/step367_test.cpp @@ -0,0 +1,199 @@ +// Step 367: WAT Annotation Mapping (12 tests) + +#include +#include +#include +#include +#include "MemoryStrategyInference.h" +#include "AnnotationInference.h" +#include "CrossLanguageProjector.h" +#include "SemannoFormat.h" +#include "Pipeline.h" +#include "ast/WatParser.h" + +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; +} + +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; +} + +int main() { + int passed = 0; + + // Test 1: WAT function gets Exec(stack) + { + Module mod("m1", "wat", "wat"); + mod.addChild("functions", new Function("f1", "run")); + AnnotationInference inf; + auto inferred = inf.inferAll(&mod); + assert(hasInference(inferred, "ExecAnnotation", "stack")); + std::cout << "Test 1 PASSED: function -> Exec(stack)\n"; + passed++; + } + + // Test 2: memory op gets Owner(Manual) + { + Module mod("m1", "wat", "wat"); + auto* fn = new Function("f1", "grow"); + auto* call = new FunctionCall(); + call->id = "c1"; + call->functionName = "memory.grow"; + fn->addChild("body", call); + mod.addChild("functions", fn); + + MemoryStrategyInference mem; + auto suggestions = mem.inferAnnotations(&mod); + assert(hasMemorySuggestion(suggestions, "OwnerAnnotation", "Manual")); + std::cout << "Test 2 PASSED: memory op -> Owner(Manual)\n"; + passed++; + } + + // Test 3: export -> Visibility(public) + { + Module mod("m1", "wat", "wat"); + mod.addChild("statements", new PragmaDirective("p1", "export")); + AnnotationInference inf; + auto inferred = inf.inferAll(&mod); + assert(hasInference(inferred, "VisibilityAnnotation", "public")); + std::cout << "Test 3 PASSED: export -> Visibility(public)\n"; + passed++; + } + + // Test 4: import -> Link(import) + { + Module mod("m1", "wat", "wat"); + mod.addChild("imports", new Import("i1", "env.print", "runtime", "")); + AnnotationInference inf; + auto inferred = inf.inferAll(&mod); + assert(hasInference(inferred, "LinkAnnotation", "import")); + std::cout << "Test 4 PASSED: import -> Link(import)\n"; + passed++; + } + + // Test 5: WAT defaults applied + { + Module mod("m1", "wat", "wat"); + MemoryStrategyInference mem; + auto suggestions = mem.inferAnnotations(&mod); + assert(hasMemorySuggestion(suggestions, "OwnerAnnotation", "Manual")); + assert(hasMemorySuggestion(suggestions, "ReclaimAnnotation", "Explicit")); + std::cout << "Test 5 PASSED: WAT defaults applied\n"; + passed++; + } + + // Test 6: table dispatch -> Shim(vtable) + { + Module mod("m1", "wat", "wat"); + mod.addChild("statements", new PragmaDirective("p1", "table")); + AnnotationInference inf; + auto inferred = inf.inferAll(&mod); + assert(hasInference(inferred, "ShimAnnotation", "vtable")); + std::cout << "Test 6 PASSED: table -> Shim(vtable)\n"; + passed++; + } + + // Test 7: memory declarations -> Align + Layout + { + Module mod("m1", "wat", "wat"); + mod.addChild("statements", new PragmaDirective("p1", "memory")); + AnnotationInference inf; + auto inferred = inf.inferAll(&mod); + assert(hasInference(inferred, "AlignAnnotation", "4")); + assert(hasInference(inferred, "LayoutAnnotation", "linear")); + std::cout << "Test 7 PASSED: memory -> Align/Layout\n"; + passed++; + } + + // Test 8: confidence values stay bounded + { + Module mod("m1", "wat", "wat"); + mod.addChild("functions", new Function("f1", "run")); + AnnotationInference inf; + auto inferred = inf.inferAll(&mod); + bool foundStrong = false; + for (const auto& a : inferred) { + assert(a.confidence >= 0.0 && a.confidence <= 1.0); + if (a.confidence >= 0.75) foundStrong = true; + } + assert(foundStrong); + std::cout << "Test 8 PASSED: confidence bounded and useful\n"; + passed++; + } + + // Test 9: WAT annotations preserved projecting to C + { + Module mod("m1", "wat", "wat"); + auto* fn = new Function("f1", "run"); + auto* exec = new ExecAnnotation(); + exec->id = "a1"; + exec->mode = "stack"; + fn->addChild("annotations", exec); + mod.addChild("functions", fn); + + CrossLanguageProjector projector; + auto cMod = projector.project(&mod, "c"); + assert(projector.annotationsPreserved(&mod, cMod.get())); + std::cout << "Test 9 PASSED: projection preserves WAT annotations\n"; + passed++; + } + + // Test 10: Semanno roundtrip for Exec(stack) + { + ExecAnnotation exec; + exec.id = "a1"; + exec.mode = "stack"; + exec.runtimeHint = "wasm"; + std::string line = SemannoEmitter::emit(&exec); + auto parsed = SemannoParser::parse(line); + assert(parsed.type == "exec"); + assert(parsed.properties["mode"] == "stack"); + std::cout << "Test 10 PASSED: semanno roundtrip\n"; + passed++; + } + + // Test 11: pipeline parse wat + infer stack annotation + { + Pipeline p; + std::vector diags; + auto mod = p.parse("(module (func $f (result i32)))", "wat", diags); + AnnotationInference inf; + auto inferred = inf.inferAll(mod.get()); + assert(hasInference(inferred, "ExecAnnotation", "stack")); + std::cout << "Test 11 PASSED: pipeline wat parse supports inference\n"; + passed++; + } + + // Test 12: annotation survives wat -> c -> wat projection + { + Module mod("m1", "wat", "wat"); + auto* fn = new Function("f1", "run"); + auto* vis = new VisibilityAnnotation(); + vis->id = "v1"; + vis->level = "public"; + fn->addChild("annotations", vis); + mod.addChild("functions", fn); + + CrossLanguageProjector projector; + auto cMod = projector.project(&mod, "c"); + auto watBack = projector.project(cMod.get(), "wat"); + assert(projector.annotationsPreserved(&mod, watBack.get())); + std::cout << "Test 12 PASSED: annotation roundtrip through projection\n"; + passed++; + } + + std::cout << "\nResults: " << passed << "/12\n"; + assert(passed == 12); + return 0; +} diff --git a/progress.md b/progress.md index 02e83b3..03e62c0 100644 --- a/progress.md +++ b/progress.md @@ -2286,6 +2286,48 @@ the shared pipeline. **Architecture gate check:** - `editor/src/ast/WatGenerator.h` is within header size limit (`219` lines) +### Step 367: WAT Annotation Mapping +**Status:** PASS (12/12 tests) + +Implemented WAT-specific annotation inference for execution model, linkage, and +memory-layout signals, plus WAT memory-strategy defaults. + +**Files created:** +- `editor/tests/step367_test.cpp` — 12 tests covering: + 1. function -> `Exec(stack)` + 2. memory operations -> `Owner(Manual)` + 3. export -> `Visibility(public)` + 4. import -> `Link(import)` + 5. WAT defaults (`Owner(Manual)`, `Reclaim(Explicit)`) + 6. table dispatch -> `Shim(vtable)` + 7. memory declaration -> `Align` + `Layout(linear)` + 8. confidence bounds + 9. annotation preservation through projection to C + 10. Semanno emit/parse roundtrip + 11. pipeline parse + inference integration + 12. WAT -> C -> WAT annotation roundtrip preservation + +**Files modified:** +- `editor/src/MemoryStrategyInference.h` — WAT defaults and memory-op rule: + - module defaults for `wat/wasm` + - function memory-op detection (`memory.*`, `*.load`, `*.store`) +- `editor/src/AnnotationInference.h` — WAT-specific pattern inference: + - function-level `Exec(stack)` + - import/export mapping to `Link`/`Visibility` + - table mapping to `Shim(vtable)` + - memory mapping to `Align` + `Layout(linear)` +- `editor/CMakeLists.txt` — `step367_test` target + +**Verification run:** +- `step367_test` — PASS (12/12) new step coverage +- `step366_test` — PASS (12/12) regression coverage +- `step365_test` — PASS (12/12) regression coverage +- `step364_test` — PASS (8/8) regression coverage + +**Architecture gate check:** +- `editor/src/AnnotationInference.h` remains within header size limit + (`589` lines <= `600`) + # Roadmap Planning — Sprints 12-25+ ## Status: Planning Complete (Sprints 12-19 detailed, 20-25 in roadmap.md)