Step 104: add cross-language projection action
This commit is contained in:
@@ -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. |
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
|
||||
37
editor/tests/step104_test.cpp
Normal file
37
editor/tests/step104_test.cpp
Normal 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;
|
||||
}
|
||||
Reference in New Issue
Block a user