Step 104: add cross-language projection action

This commit is contained in:
Bill
2026-02-09 10:57:25 -07:00
parent a50bf616b8
commit 8a85ba298d
4 changed files with 148 additions and 16 deletions

View File

@@ -204,6 +204,7 @@ All 38 steps implemented and passing. Each step has a corresponding test (`step1
- [x] Step 101: **IMPLEMENTED** — Annotation conflict highlighting with gutter linkage and quick-fix actions (1/1 tests pass)
- [x] Step 102: **IMPLEMENTED** — Memory strategy dashboard panel with counts and JSON export (1/1 tests pass)
- [x] Step 103: **IMPLEMENTED** — Side-by-side generated code view with scroll lock, target language selection, and line highlighting (1/1 tests pass)
- [x] Step 104: **IMPLEMENTED** — Cross-language projection button with read-only projected tabs and annotation summary (2/2 tests pass)
---
@@ -287,6 +288,7 @@ vcpkg's imgui 1.91.9 removed the `sdl2-binding` feature (only `sdl3-binding` exi
**Step 101:** Compile and pass (1/1)
**Step 102:** Compile and pass (1/1)
**Step 103:** Compile and pass (1/1)
**Step 104:** Compile and pass (2/2)
---
@@ -399,3 +401,4 @@ Sprint 4 in progress. Step 76 (LayoutManager) done. Next: Step 77 (custom code e
| 2026-02-09 | Codex | Step 101: Annotation conflict highlighting with quick-fix actions. 1/1 tests pass. |
| 2026-02-09 | Codex | Step 102: Memory strategy dashboard panel with counts and JSON export. 1/1 tests pass. |
| 2026-02-09 | Codex | Step 103: Side-by-side generated code view with scroll lock, target language selector, and line highlighting. 1/1 tests pass. |
| 2026-02-09 | Codex | Step 104: Cross-language projection button with read-only projected tabs and annotation summary. 2/2 tests pass. |

View File

@@ -562,6 +562,10 @@ add_executable(step103_test tests/step103_test.cpp)
target_include_directories(step103_test PRIVATE src)
target_link_libraries(step103_test PRIVATE nlohmann_json::nlohmann_json)
add_executable(step104_test tests/step104_test.cpp)
target_include_directories(step104_test PRIVATE src)
target_link_libraries(step104_test PRIVATE nlohmann_json::nlohmann_json)
find_package(SDL2 CONFIG REQUIRED)
find_package(OpenGL REQUIRED)
find_package(glad CONFIG REQUIRED)

View File

@@ -33,6 +33,7 @@
#include "MemoryStrategyInference.h"
#include "AnnotationConflict.h"
#include "StrategyDashboard.h"
#include "CrossLanguageProjector.h"
#include "ast/Generator.h"
#include "ast/Annotation.h"
@@ -64,6 +65,7 @@ struct BufferState {
std::string language = "python";
std::string generatedLanguage = "python";
std::string path = "(untitled)";
bool readOnly = false;
bool modified = false;
int cursorLine = 1;
int cursorCol = 1;
@@ -461,22 +463,7 @@ struct EditorState {
void updateGenerated() {
if (!active()) return;
Module* ast = active()->sync.getAST();
std::string generated;
if (ast) {
if (active()->generatedLanguage == "python") {
PythonGenerator gen;
generated = gen.generate(ast);
} else if (active()->generatedLanguage == "cpp") {
CppGenerator gen;
generated = gen.generate(ast);
} else if (active()->generatedLanguage == "elisp") {
ElispGenerator gen;
generated = gen.generate(ast);
} else {
PythonGenerator gen;
generated = gen.generate(ast);
}
}
std::string generated = generateForLanguage(ast, active()->generatedLanguage);
if (generated != active()->generatedBuf) {
active()->generatedBuf = generated;
active()->generatedHighlightsDirty = true;
@@ -488,6 +475,54 @@ struct EditorState {
}
}
void projectToLanguage(const std::string& targetLanguage) {
if (!active()) return;
Module* ast = active()->sync.getAST();
if (!ast) {
outputLog += "Project to " + targetLanguage + ": no AST available.\n";
return;
}
CrossLanguageProjector projector;
auto projected = projector.project(ast, targetLanguage);
if (!projected) {
outputLog += "Project to " + targetLanguage + ": failed to project AST.\n";
return;
}
const int srcAnnoCount = countAnnotationNodes(ast);
const int projAnnoCount = countAnnotationNodes(projected.get());
const bool preserved = projector.annotationsPreserved(ast, projected.get());
std::string generated = generateForLanguage(projected.get(), targetLanguage);
std::string baseName = "(untitled-projection:" + targetLanguage + ")";
std::string projName = baseName;
int suffix = 1;
while (buffers.hasBuffer(projName)) {
projName = baseName + "-" + std::to_string(suffix++);
}
createBuffer(projName, generated, targetLanguage);
if (active()) {
active()->readOnly = true;
active()->sync.setText(generated, targetLanguage);
active()->sync.setAST(std::move(projected));
active()->editBuf = generated;
active()->editor.setContent(active()->editBuf, targetLanguage);
active()->mode.setLanguage(targetLanguage);
active()->highlightsDirty = true;
active()->generatedLanguage = targetLanguage;
active()->generatedMode.setLanguage(targetLanguage);
active()->generatedHighlightsDirty = true;
active()->modified = false;
}
outputLog += "Projected to " + targetLanguage + " in " + projName +
" (annotations " + std::to_string(srcAnnoCount) + " -> " +
std::to_string(projAnnoCount) + ", types preserved: " +
(preserved ? "yes" : "no") + ").\n";
}
void updateCursorPos(int bytePos) {
if (!active()) return;
active()->cursorLine = 1;
@@ -520,6 +555,42 @@ static int countLines(const std::string& text) {
return lines;
}
static std::string generateForLanguage(const Module* ast, const std::string& language) {
if (!ast) return "";
if (language == "python") {
PythonGenerator gen;
return gen.generate(ast);
}
if (language == "cpp") {
CppGenerator gen;
return gen.generate(ast);
}
if (language == "elisp") {
ElispGenerator gen;
return gen.generate(ast);
}
PythonGenerator gen;
return gen.generate(ast);
}
static bool isAnnotationNode(const ASTNode* node) {
if (!node) return false;
if (node->conceptType.find("Annotation") != std::string::npos) return true;
if (node->conceptType == "DerefStrategy") return true;
if (node->conceptType == "OptimizationLock") return true;
if (node->conceptType == "LangSpecific") return true;
return false;
}
static int countAnnotationNodes(const ASTNode* node) {
if (!node) return 0;
int count = isAnnotationNode(node) ? 1 : 0;
for (auto* child : node->allChildren()) {
count += countAnnotationNodes(child);
}
return count;
}
// ---------------------------------------------------------------------------
// ImGui InputTextMultiline with std::string resize callback
@@ -1175,6 +1246,22 @@ int main(int, char**) {
}
ImGui::EndMenu();
}
const bool canProject = state.active() && state.active()->sync.getAST();
ImGui::SameLine();
ImGui::Dummy(ImVec2(12.0f, 0.0f));
ImGui::SameLine();
if (!canProject) ImGui::BeginDisabled();
if (ImGui::Button("Project to...")) {
ImGui::OpenPopup("ProjectToPopup");
}
if (ImGui::BeginPopup("ProjectToPopup")) {
if (ImGui::MenuItem("Python")) state.projectToLanguage("python");
if (ImGui::MenuItem("C++")) state.projectToLanguage("cpp");
if (ImGui::MenuItem("Elisp")) state.projectToLanguage("elisp");
ImGui::EndPopup();
}
if (!canProject) ImGui::EndDisabled();
ImGui::EndMainMenuBar();
}
@@ -1361,6 +1448,7 @@ int main(int, char**) {
CodeEditorOptions opts;
opts.showWhitespace = state.showWhitespace;
opts.readOnly = buf->readOnly;
opts.mode = &buf->mode;
opts.enableFolding = true;
opts.showMinimap = state.showMinimap;

View File

@@ -0,0 +1,37 @@
// Step 104 TDD Test: Cross-language projection
//
// Tests:
// 1. Projected module uses target language
// 2. Annotation types preserved across projection
#include <cassert>
#include <iostream>
#include "CrossLanguageProjector.h"
#include "ast/Module.h"
#include "ast/Function.h"
#include "ast/Annotation.h"
int main() {
int passed = 0;
int failed = 0;
Module mod("m1", "mod", "python");
auto* fn = new Function("f1", "foo");
auto* anno = new ReclaimAnnotation("a1", "Tracing");
fn->addChild("annotations", anno);
mod.addChild("functions", fn);
CrossLanguageProjector projector;
auto projected = projector.project(&mod, "cpp");
assert(projected && projected->targetLanguage == "cpp");
std::cout << "Test 1 PASS: target language set" << std::endl;
++passed;
assert(projector.annotationsPreserved(&mod, projected.get()));
std::cout << "Test 2 PASS: annotations preserved" << std::endl;
++passed;
std::cout << "\n=== Step 104 Results: " << passed << " passed, " << failed << " failed ===" << std::endl;
return failed > 0 ? 1 : 0;
}