From eca19503aefa344e14c95b296a5c87f46bbb233f Mon Sep 17 00:00:00 2001 From: Bill Date: Mon, 9 Feb 2026 14:47:51 -0700 Subject: [PATCH] Refactor editor state/utils and split generators --- ARCHITECTURE.md | 80 + PROGRESS.md | 15 +- editor/CMakeLists.txt | 12 +- editor/src/EditorState.h | 1196 ++++++++++++++ editor/src/EditorUtils.h | 698 +++++++++ editor/src/Orchestrator.h | 60 +- editor/src/ast/CppGenerator.h | 702 +++++++++ editor/src/ast/ElispGenerator.h | 545 +++++++ editor/src/ast/Generator.h | 2156 +------------------------- editor/src/ast/ProjectionGenerator.h | 161 ++ editor/src/ast/PythonGenerator.h | 577 +++++++ editor/src/main.cpp | 1855 +--------------------- 12 files changed, 4025 insertions(+), 4032 deletions(-) create mode 100644 ARCHITECTURE.md create mode 100644 editor/src/EditorState.h create mode 100644 editor/src/EditorUtils.h create mode 100644 editor/src/ast/CppGenerator.h create mode 100644 editor/src/ast/ElispGenerator.h create mode 100644 editor/src/ast/ProjectionGenerator.h create mode 100644 editor/src/ast/PythonGenerator.h diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md new file mode 100644 index 0000000..287524e --- /dev/null +++ b/ARCHITECTURE.md @@ -0,0 +1,80 @@ +# Whetstone DSL — Architecture & Coding Standards + +> **Audience:** All agents (Codex, Claude, future) working on this codebase. +> **Authority:** Treat these norms as hard constraints. Deviations require explicit user approval. + +--- + +## File Size Limits + +| Scope | Max Lines | Action When Exceeded | +|-------|-----------|----------------------| +| Header file (`.h`) | 600 | Split into multiple headers | +| `main.cpp` | 1500 | Extract panels/utilities to headers | +| Single function | 80 | Extract sub-routines | + +--- + +## Naming Conventions + +| Element | Convention | Example | +|---------|-----------|---------| +| Classes / Structs | `PascalCase` | `LayoutManager`, `BufferState` | +| Methods / Functions | `camelCase` | `getChildren`, `recordSnapshot` | +| Member variables | `trailingUnderscore_` | `cursor_`, `currentPreset_` | +| Constants / Enum values | `PascalCase` | `BufferMode::Structured` | +| Files | `PascalCase.h` | Matches the primary class name | + +--- + +## Architecture Patterns + +### Header-Only +All components are `.h` files. The only `.cpp` files are `main.cpp`, `orchestrator_main.cpp`, `FileDialog.cpp`, and vendored backends. + +### Panel Extraction +UI panels are free functions in their own headers, included and called from `main.cpp`: + +```cpp +// panels/MenuBarPanel.h +#pragma once +#include "EditorState.h" +void renderMenuBar(EditorState& state); +``` + +### EditorState +Single state struct defined in `EditorState.h`. Panels receive it by reference — they never own global state. + +### Generators +One file per language, inheriting from `ProjectionGenerator` base in `ProjectionGenerator.h`. The shared dispatch helper `dispatchGenerate()` eliminates duplicated `generate()` bodies. + +### No God Objects +If a struct exceeds 50 fields, group related fields into sub-structs (e.g., `DiffState`, `LSPState`). + +--- + +## Test Requirements + +- **Minimum 2 tests per step** for non-trivial features. +- **Real assertions only:** Every test must use `assert()` or the `expect()` helper with actual value checks. +- **No print-only tests:** `std::cout << "PASS"` without a preceding assertion is forbidden. +- **Test pattern:** Use the standard `expect()` helper, `int passed/failed` counters, `return failed ? 1 : 0`. +- **Edge cases:** At least 1 edge case test per step (empty input, null, boundary). + +--- + +## Dependency Rules + +- **Pin all FetchContent tags:** Use release tags (e.g., `v0.23.6`), never `master` or `main`. +- **vcpkg for system libs:** nlohmann-json, SDL2, imgui, tree-sitter core via vcpkg. +- **FetchContent for grammars:** Tree-sitter grammars and tinyfiledialogs via FetchContent with pinned tags. + +--- + +## Code Quality + +- **No stale TODOs:** If something is not implemented, mark it `// STUB:` with a reason. +- **No dead code:** Remove commented-out code; git has history. +- **Comments for "why" not "what":** The code shows what; comments explain non-obvious decisions. +- **No `goto`:** Use structured control flow. +- **Platform portability:** Use `#ifdef _WIN32` / `#else` guards for platform-specific code (e.g., `_popen` vs `popen`). diff --git a/PROGRESS.md b/PROGRESS.md index 9465372..b2ef19f 100644 --- a/PROGRESS.md +++ b/PROGRESS.md @@ -20,7 +20,7 @@ Whetstone is a semantic annotation DSL (SemAnno) and structured editor for cross | Sprint 1 | — | Complete | MPS-based prototype (JetBrains MPS language plugin) | | Sprint 2 | 1–38 | **Complete** | C++ editor stack: AST, serialization, generators, ImGui shell, orchestrator, agents | | Sprint 3 | 39–75 | **Complete** | Core functionality: C++ generator, tree-sitter, classical editing, optimization | -| Sprint 4 | 76–126 | **In Progress** | Professional editor: layout presets, code editor, LSP, annotation UI | +| Sprint 4 | 76–120 | **Complete** | Professional editor: layout presets, code editor, LSP, annotation UI (Steps 121–126 deferred) | --- @@ -168,7 +168,7 @@ All 38 steps implemented and passing. Each step has a corresponding test (`step1 --- -## Sprint 4: Professional Editor (Steps 76–126) — In Progress +## Sprint 4: Professional Editor (Steps 76–120) — Complete (Steps 121–126 deferred to Sprint 5) ### Phase 4a: Layout & Code Editor Core (Steps 76–83) — In Progress - [x] Step 76: **IMPLEMENTED** — LayoutManager: 3 preset docking layouts (VSCode/Emacs/JetBrains), panel visibility/ratio queries, dirty flag for rebuild, save/load persistence, preset name round-trip (10/10 tests pass) @@ -333,7 +333,13 @@ vcpkg's imgui 1.91.9 removed the `sdl2-binding` feature (only `sdl3-binding` exi | File | Contents | |------|----------| | `editor/src/ast/ASTNode.h` | All 33+ AST node classes, JSON serialization | -| `editor/src/ast/Generator.h` | PythonGenerator, CppGenerator, ElispGenerator, all canonical annotation classes | +| `editor/src/ast/Generator.h` | Convenience include for all generators | +| `editor/src/ast/ProjectionGenerator.h` | Base class + shared dispatch helper | +| `editor/src/ast/PythonGenerator.h` | Python code generator | +| `editor/src/ast/ElispGenerator.h` | Elisp code generator | +| `editor/src/ast/CppGenerator.h` | C++ code generator with memory annotations | +| `editor/src/EditorState.h` | BufferState + EditorState structs (extracted from main.cpp) | +| `editor/src/EditorUtils.h` | Utility functions: themes, highlight rendering, outline, file tree | | `editor/src/ast/Schema.h` | AST schema validation | | `editor/src/ast/Parser.h` | TreeSitterParser (real tree-sitter CST-to-AST for Python/C++/Elisp) | | `editor/src/TextASTSync.h` | Bidirectional text↔AST synchronization with debounce | @@ -376,7 +382,7 @@ vcpkg's imgui 1.91.9 removed the `sdl2-binding` feature (only `sdl3-binding` exi ## What's Next -Sprint 4 in progress. Step 120 (undo/redo rework) done. Next: Step 121 (AST import/external module concepts). +Sprint 5: Deferred Steps 121–126 (AST import, external modules, dependency browser, library browser, advanced search, final integration). New panels (Dependencies, Library Browser) will be added following the panel extraction pattern in `editor/src/panels/`. --- @@ -458,3 +464,4 @@ Sprint 4 in progress. Step 120 (undo/redo rework) done. Next: Step 121 (AST impo | 2026-02-09 | Codex | Step 118: Settings panel + persistence. 11/11 tests pass. | | 2026-02-09 | Codex | Step 119: Zoom controls and status bar indicator. 5/5 tests pass. | | 2026-02-09 | Codex | Step 120: Unified undo/redo via orchestrator snapshots (per-buffer), status bar undo depth, snapshot-based undo/redo wiring. 4/4 tests pass. | +| 2026-02-09 | Claude Opus 4.6 | Pre-Sprint 5 refactoring: Created ARCHITECTURE.md (coding standards). Split Generator.h (2150→4 files, shared dispatch eliminates 279 lines duplication). Extracted EditorState.h and EditorUtils.h from main.cpp (4102→~2235 lines). Pinned 6 FetchContent deps to release tags. Orchestrator.h cleanup: wired CppGenerator, added platform guards, fixed shell injection, replaced stale TODOs with STUB markers. Sprint 4 marked complete (Steps 121–126 deferred to Sprint 5). | diff --git a/editor/CMakeLists.txt b/editor/CMakeLists.txt index 08283f1..4a9ecde 100644 --- a/editor/CMakeLists.txt +++ b/editor/CMakeLists.txt @@ -38,28 +38,28 @@ FetchContent_Declare( FetchContent_Declare( tree-sitter-javascript GIT_REPOSITORY https://github.com/tree-sitter/tree-sitter-javascript.git - GIT_TAG master + GIT_TAG v0.23.1 GIT_SHALLOW TRUE ) FetchContent_Declare( tree-sitter-typescript GIT_REPOSITORY https://github.com/tree-sitter/tree-sitter-typescript.git - GIT_TAG master + GIT_TAG v0.23.2 GIT_SHALLOW TRUE ) FetchContent_Declare( tree-sitter-java GIT_REPOSITORY https://github.com/tree-sitter/tree-sitter-java.git - GIT_TAG master + GIT_TAG v0.23.5 GIT_SHALLOW TRUE ) FetchContent_Declare( tree-sitter-rust GIT_REPOSITORY https://github.com/tree-sitter/tree-sitter-rust.git - GIT_TAG master + GIT_TAG v0.23.3 GIT_SHALLOW TRUE SOURCE_DIR ${CMAKE_BINARY_DIR}/_deps/tree-sitter-rust-src2 BINARY_DIR ${CMAKE_BINARY_DIR}/_deps/tree-sitter-rust-build2 @@ -68,14 +68,14 @@ FetchContent_Declare( FetchContent_Declare( tree-sitter-go GIT_REPOSITORY https://github.com/tree-sitter/tree-sitter-go.git - GIT_TAG master + GIT_TAG v0.23.4 GIT_SHALLOW TRUE ) FetchContent_Declare( tinyfiledialogs GIT_REPOSITORY https://github.com/native-toolkit/tinyfiledialogs.git - GIT_TAG master + GIT_TAG 2.9.3 GIT_SHALLOW TRUE ) diff --git a/editor/src/EditorState.h b/editor/src/EditorState.h new file mode 100644 index 0000000..b207393 --- /dev/null +++ b/editor/src/EditorState.h @@ -0,0 +1,1196 @@ +#pragma once + +// --- ImGui (needed for ImVec2, ImGui::GetTime) --- +#include "imgui.h" + +// --- Editor components --- +#include "TextEditor.h" +#include "TextASTSync.h" +#include "SyntaxHighlighter.h" +#include "KeybindingManager.h" +#include "BufferManager.h" +#include "CodeEditorWidget.h" +#include "EditorMode.h" +#include "FileDialog.h" +#include "FileTree.h" +#include "WelcomeScreen.h" +#include "DragDropHandler.h" +#include "FileWatcher.h" +#include "LSPClient.h" +#include "Pipeline.h" +#include "Diagnostics.h" +#include "SettingsManager.h" +#include "LayoutManager.h" +#include "ASTMutationAPI.h" +#include "MemoryStrategyInference.h" +#include "AnnotationConflict.h" +#include "StrategyDashboard.h" +#include "TransformEngine.h" +#include "StrategyAwareOptimizer.h" +#include "CrossLanguageProjector.h" +#include "DiffUtils.h" +#include "EditorModePolicy.h" +#include "RefactorActions.h" +#include "CommandPalette.h" +#include "ContextAPI.h" +#include "Breadcrumbs.h" +#include "ProjectSearch.h" +#include "GoToLine.h" +#include "Orchestrator.h" +#include "ProjectManager.h" +#include "SessionManager.h" +#include "ZoomUtils.h" +#include "IncrementalOptimizer.h" +#include "ast/Serialization.h" +#include "ast/Generator.h" +#include "ast/Annotation.h" + +// --- Standard library --- +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// --------------------------------------------------------------------------- +// Forward declarations for utility functions defined in EditorUtils.h +// (used inside EditorState inline method bodies) +// --------------------------------------------------------------------------- +static std::string generateForLanguage(const Module* ast, const std::string& language); +static std::unique_ptr cloneModule(const Module* ast); +static ASTNode* findNodeAtPosition(ASTNode* node, int line, int col); +static ASTNode* findNodeById(ASTNode* node, const std::string& id); +static int countAnnotationNodes(const ASTNode* node); + +// --------------------------------------------------------------------------- +// Simple helpers used by EditorState methods +// --------------------------------------------------------------------------- +static inline std::string bufferModeToString(BufferManager::BufferMode mode) { + return mode == BufferManager::BufferMode::Text ? "text" : "structured"; +} + +static inline BufferManager::BufferMode bufferModeFromString(const std::string& mode) { + if (mode == "text") return BufferManager::BufferMode::Text; + return BufferManager::BufferMode::Structured; +} + +// --------------------------------------------------------------------------- +// Editor application state +// --------------------------------------------------------------------------- +struct BufferState { + TextEditor editor; + TextASTSync sync; + CodeEditorWidget widget; + CodeEditorWidget generatedWidget; + EditorMode mode; + EditorMode generatedMode; + std::string language = "python"; + std::string generatedLanguage = "python"; + std::string path = "(untitled)"; + bool readOnly = false; + BufferManager::BufferMode bufferMode = BufferManager::BufferMode::Structured; + bool modified = false; + int cursorLine = 1; + int cursorCol = 1; + int lspVersion = 1; + std::string editBuf; + std::string generatedBuf; + std::vector highlights; + std::vector generatedHighlights; + bool highlightsDirty = true; + bool generatedHighlightsDirty = true; + int generatedHighlightLine = -1; + float splitScrollX = 0.0f; + float splitScrollY = 0.0f; + IncrementalOptimizer incrementalOptimizer; + Orchestrator orchestrator; + bool orchestratorDirty = true; + int undoDepth = 0; +}; + +struct EditorState { + KeybindingManager keys; + BufferManager buffers; + std::map> bufferStates; + BufferState* activeBuffer = nullptr; + + // Find/Replace state + bool showFind = false; + char findBuf[256] = {}; + char replaceBuf[256] = {}; + int lastFindPos = 0; + bool showProjectSearch = false; + char searchQuery[256] = {}; + char searchInclude[256] = {}; + char searchExclude[256] = {}; + bool searchUseRegex = true; + std::vector searchResults; + bool showGoToLine = false; + char goToLineBuf[64] = {}; + bool goToLineError = false; + + // Bottom panel + int bottomTab = 0; // 0=Output, 1=AST, 2=Highlighted + std::string outputLog; + + // Custom editor widget state + bool showWhitespace = false; + bool showMinimap = false; + bool showAnnotations = false; + bool showOutline = true; + bool showLineNumbers = true; + char outlineFilter[128] = {}; + LayoutPreset layoutPreset = LayoutPreset::VSCode; + WelcomeScreen welcome; + std::string workspaceRoot; + FileTree fileTree; + FileNode fileTreeRoot; + bool fileTreeDirty = true; + FileWatcher watcher; + ProjectSearch projectSearch; + std::shared_ptr lspTransport; + std::shared_ptr lsp; + bool symbolsPending = false; + double symbolsLastChange = 0.0; + bool completionPending = false; + bool completionVisible = false; + int completionSelected = 0; + double completionLastChange = 0.0; + bool hoverPending = false; + double hoverLastMove = 0.0; + ImVec2 hoverLastMouse = ImVec2(-1.0f, -1.0f); + bool definitionPending = false; + double definitionLastRequest = 0.0; + int definitionRequestLine = 0; + int definitionRequestCol = 0; + std::string definitionRequestPath; + LSPClient::DefinitionLocation definitionPreview; + bool definitionPreviewValid = false; + Pipeline pipeline; + bool analysisPending = false; + double analysisLastChange = 0.0; + std::vector whetstoneDiagnostics; + SettingsManager settings; + bool showLspSettings = false; + bool showSettingsPanel = false; + double lastAutoSave = 0.0; + ASTMutationAPI mutator; + std::vector suggestions; + bool showSuggestionPopup = false; + MemoryStrategyInference::Suggestion suggestionPopup; + std::string optimizeFoldSummary; + std::string optimizeDeadCodeSummary; + std::string optimizeApplySummary; + bool optimizePreview = false; + bool showRefactorPopup = false; + int refactorAction = 0; // 1=rename,2=extract,3=inline + char refactorNameA[128] = {}; + char refactorNameB[128] = {}; + std::string refactorError; + CommandPalette commandPalette; + std::unordered_map> commandHandlers; + bool showCommandPalette = false; + char commandQuery[128] = {}; + int commandSelected = 0; + struct DiffState { + bool active = false; + bool preview = false; + int action = 0; // 1=constant-fold, 2=dead-code-elim, 3=apply-all + bool batch = false; + std::string beforeText; + std::string afterText; + std::vector transformIds; + std::vector batchMutations; + std::vector beforeLines; + std::vector afterLines; + } diff; + CodeEditorWidget diffLeftWidget; + CodeEditorWidget diffRightWidget; + float diffScrollX = 0.0f; + float diffScrollY = 0.0f; + + BufferState* active() { return activeBuffer; } + bool isStructured() const { + return activeBuffer && allowStructuredFeatures(activeBuffer->bufferMode); + } + Module* activeAST() { + if (!activeBuffer) return nullptr; + if (!allowStructuredFeatures(activeBuffer->bufferMode)) return nullptr; + return activeBuffer->sync.getAST(); + } + + static std::string toFileUri(const std::string& path) { + std::filesystem::path p(path); + return "file:///" + p.generic_string(); + } + + static std::string fromFileUri(const std::string& uri) { + const std::string prefix = "file:///"; + if (uri.rfind(prefix, 0) != 0) return uri; + std::string path = uri.substr(prefix.size()); + std::replace(path.begin(), path.end(), '/', '\\'); + return path; + } + + static int byteOffsetForLineCol(const std::string& text, int lineZero, int colZero) { + if (lineZero < 0) lineZero = 0; + if (colZero < 0) colZero = 0; + int line = 0; + int pos = 0; + while (pos < (int)text.size() && line < lineZero) { + if (text[pos] == '\n') ++line; + ++pos; + } + int col = 0; + while (pos < (int)text.size() && text[pos] != '\n' && col < colZero) { + ++pos; + ++col; + } + return pos; + } + + static int wordStartAt(const std::string& text, int pos) { + pos = std::max(0, std::min(pos, (int)text.size())); + while (pos > 0) { + char c = text[pos - 1]; + if (std::isalnum((unsigned char)c) || c == '_') { + --pos; + } else { + break; + } + } + return pos; + } + + static bool isWordChar(char c) { + return std::isalnum((unsigned char)c) || c == '_'; + } + + static std::string wordAt(const std::string& text, int pos) { + if (text.empty()) return ""; + pos = std::max(0, std::min(pos, (int)text.size())); + if (pos == (int)text.size()) pos = (int)text.size() - 1; + if (pos < 0) return ""; + if (!isWordChar(text[pos]) && pos > 0 && isWordChar(text[pos - 1])) { + --pos; + } + if (!isWordChar(text[pos])) return ""; + int start = pos; + while (start > 0 && isWordChar(text[start - 1])) --start; + int end = pos; + while (end < (int)text.size() && isWordChar(text[end])) ++end; + return text.substr(start, end - start); + } + + static std::string wordAtLineCol(const std::string& text, int lineZero, int colZero) { + int pos = byteOffsetForLineCol(text, lineZero, colZero); + return wordAt(text, pos); + } + + static std::string wordPrefixAt(const std::string& text, int pos) { + int start = wordStartAt(text, pos); + return text.substr(start, pos - start); + } + + void jumpTo(BufferState* buf, int lineZero, int colZero) { + if (!buf) return; + int pos = byteOffsetForLineCol(buf->editBuf, lineZero, colZero); + buf->widget.setCursor(pos); + buf->cursorLine = lineZero + 1; + buf->cursorCol = colZero + 1; + } + + void applyCompletion(BufferState* buf, const std::string& insertText, int replaceStart, int replaceEnd) { + if (!buf) return; + replaceStart = std::max(0, std::min(replaceStart, (int)buf->editBuf.size())); + replaceEnd = std::max(replaceStart, std::min(replaceEnd, (int)buf->editBuf.size())); + buf->editBuf.replace(replaceStart, replaceEnd - replaceStart, insertText); + int newPos = replaceStart + (int)insertText.size(); + buf->widget.setCursor(newPos); + activeBuffer = buf; + onTextChanged(); + } + + std::string makeUntitledName() const { + if (!buffers.hasBuffer("(untitled)")) return "(untitled)"; + int i = 1; + while (true) { + std::string name = "(untitled-" + std::to_string(i) + ")"; + if (!buffers.hasBuffer(name)) return name; + ++i; + } + } + + void createBuffer(const std::string& path, const std::string& content, + const std::string& language, + BufferManager::BufferMode mode = BufferManager::BufferMode::Structured) { + if (buffers.hasBuffer(path)) { + buffers.switchToBuffer(path); + activeBuffer = bufferStates[path].get(); + return; + } + auto state = std::make_unique(); + state->path = path; + state->language = language; + state->generatedLanguage = language; + state->mode.setLanguage(language); + state->generatedMode.setLanguage(language); + state->editor.setContent(content, language); + state->sync.setText(content, language); + state->sync.syncNow(); + state->incrementalOptimizer.setRoot(state->sync.getAST()); + state->editBuf = content; + state->highlightsDirty = true; + state->generatedHighlightsDirty = true; + state->modified = false; + state->lspVersion = 1; + buffers.openBuffer(path, content, language, mode); + state->bufferMode = mode; + activeBuffer = state.get(); + bufferStates[path] = std::move(state); + active()->orchestratorDirty = true; + recordUndoSnapshot(); + if (path.rfind("(untitled", 0) != 0) watcher.watch(path); + if (lsp && path.rfind("(untitled", 0) != 0) { + lsp->didOpen(toFileUri(path), language, content, activeBuffer->lspVersion); + } + symbolsPending = true; + symbolsLastChange = ImGui::GetTime(); + if (lsp) lsp->clearDocumentSymbols(); + } + + bool jumpToDefinitionLocation(const LSPClient::DefinitionLocation& loc) { + if (loc.uri.empty()) return false; + std::string path = fromFileUri(loc.uri); + if (path.empty()) return false; + if (buffers.hasBuffer(path)) switchToBuffer(path); + else doOpen(path); + jumpTo(active(), loc.range.start.line, loc.range.start.character); + return true; + } + + bool goToDefinitionFallback(int lineZero, int colZero) { + if (!isStructured()) return false; + Module* ast = activeAST(); + if (!ast || !active()) return false; + std::string word = wordAtLineCol(active()->editBuf, lineZero, colZero); + if (word.empty()) return false; + + ASTNode* scopeNode = findNodeAtPosition(ast, lineZero, colZero); + if (!scopeNode) scopeNode = ast; + + ContextAPI ctx; + ctx.setRoot(ast); + auto symbols = ctx.getInScopeSymbols(scopeNode->id); + for (const auto& sym : symbols) { + if (sym.name != word) continue; + ASTNode* target = findNodeById(ast, sym.nodeId); + if (target && target->hasSpan()) { + jumpTo(active(), target->spanStartLine, target->spanStartCol); + return true; + } + } + return false; + } + + bool goToDefinitionAt(int lineZero, int colZero) { + if (!active()) return false; + if (lsp && lspTransport && lspTransport->isOpen() && + active()->path.rfind("(untitled", 0) != 0) { + definitionPreviewValid = false; + definitionPending = true; + definitionLastRequest = ImGui::GetTime(); + definitionRequestLine = lineZero; + definitionRequestCol = colZero; + definitionRequestPath = active()->path; + lsp->clearDefinitionLocations(); + lsp->requestDefinition(toFileUri(active()->path), lineZero, colZero); + return true; + } + return goToDefinitionFallback(lineZero, colZero); + } + + bool previewDefinitionAt(int lineZero, int colZero, std::string& outLabel) { + if (!isStructured() || !active()) return false; + Module* ast = activeAST(); + if (!ast) return false; + std::string word = wordAtLineCol(active()->editBuf, lineZero, colZero); + if (word.empty()) return false; + ASTNode* scopeNode = findNodeAtPosition(ast, lineZero, colZero); + if (!scopeNode) scopeNode = ast; + ContextAPI ctx; + ctx.setRoot(ast); + auto symbols = ctx.getInScopeSymbols(scopeNode->id); + for (const auto& sym : symbols) { + if (sym.name != word) continue; + ASTNode* target = findNodeById(ast, sym.nodeId); + if (target && target->hasSpan()) { + outLabel = word + " -> line " + std::to_string(target->spanStartLine + 1); + return true; + } + } + return false; + } + + void switchToBuffer(const std::string& path) { + if (!buffers.hasBuffer(path)) return; + buffers.switchToBuffer(path); + activeBuffer = bufferStates[path].get(); + if (active()) active()->orchestratorDirty = true; + symbolsPending = true; + symbolsLastChange = ImGui::GetTime(); + if (lsp) lsp->clearDocumentSymbols(); + } + + std::filesystem::path configDir() const { + const char* home = std::getenv("USERPROFILE"); + if (!home) home = std::getenv("HOME"); + std::filesystem::path base = home ? std::filesystem::path(home) : std::filesystem::current_path(); + return base / ".whetstone"; + } + + std::filesystem::path recentFilePath() const { + return configDir() / "recent.json"; + } + + void loadRecentFiles() { + std::filesystem::path p = recentFilePath(); + if (!std::filesystem::exists(p)) return; + std::ifstream in(p.string()); + if (!in.is_open()) return; + nlohmann::json j; + in >> j; + if (!j.is_array()) return; + for (const auto& item : j) { + if (!item.contains("path")) continue; + std::string path = item.value("path", ""); + std::string lang = item.value("language", ""); + std::string mode = item.value("mode", "structured"); + if (!path.empty()) welcome.addRecentFile(path, lang, mode); + } + } + + void saveRecentFiles() { + std::filesystem::path p = recentFilePath(); + std::filesystem::create_directories(p.parent_path()); + nlohmann::json j = nlohmann::json::array(); + for (const auto& rf : welcome.getRecentFiles()) { + j.push_back({{"path", rf.path}, {"language", rf.language}, {"mode", rf.mode}}); + } + std::ofstream out(p.string(), std::ios::binary); + out << j.dump(2); + } + + void closeAllBuffers() { + auto open = buffers.getOpenBuffers(); + for (const auto& path : open) { + buffers.closeBuffer(path); + bufferStates.erase(path); + if (path.rfind("(untitled", 0) != 0) watcher.unwatch(path); + } + activeBuffer = nullptr; + } + + bool saveProject(const std::string& path) { + ProjectFile project; + project.workspaceRoot = workspaceRoot; + project.activePath = active() ? active()->path : ""; + for (const auto& bufPath : buffers.getOpenBuffers()) { + auto it = bufferStates.find(bufPath); + if (it == bufferStates.end()) continue; + ProjectBufferInfo info; + info.path = bufPath; + info.language = it->second->language; + info.mode = bufferModeToString(it->second->bufferMode); + project.buffers.push_back(std::move(info)); + } + if (isStructured()) { + syncOrchestratorFromActive(); + if (orchestrator.getAST()) { + project.ast = cloneModule(orchestrator.getAST()); + } + } + + try { + nlohmann::json j = projectToJson(project); + std::ofstream out(path, std::ios::binary); + if (!out.is_open()) return false; + out << j.dump(2); + return true; + } catch (...) { + return false; + } + } + + bool loadProject(const std::string& path) { + try { + std::ifstream in(path, std::ios::binary); + if (!in.is_open()) return false; + nlohmann::json j; + in >> j; + ProjectFile project = projectFromJson(j); + closeAllBuffers(); + + if (!project.workspaceRoot.empty()) { + workspaceRoot = project.workspaceRoot; + fileTreeDirty = true; + projectSearch.setRoot(workspaceRoot); + } + + std::string activePath = project.activePath; + for (const auto& buf : project.buffers) { + if (buf.path == activePath && project.ast) { + std::string lang = buf.language.empty() ? project.ast->targetLanguage : buf.language; + std::string generated = generateForLanguage(project.ast.get(), lang); + createBuffer(buf.path, generated, lang, bufferModeFromString(buf.mode)); + if (active()) { + active()->sync.setAST(std::move(project.ast)); + active()->incrementalOptimizer.setRoot(active()->sync.getAST()); + active()->editBuf = active()->sync.getText(); + active()->editor.setContent(active()->editBuf, lang); + active()->mode.setLanguage(lang); + active()->highlightsDirty = true; + active()->generatedHighlightsDirty = true; + active()->modified = false; + recordUndoSnapshot(); + } + } else { + doOpen(buf.path, bufferModeFromString(buf.mode)); + } + } + + if (!activePath.empty() && buffers.hasBuffer(activePath)) { + switchToBuffer(activePath); + } + if (active()) active()->orchestratorDirty = true; + return true; + } catch (...) { + return false; + } + } + + std::filesystem::path sessionFilePath() const { + return configDir() / "session.json"; + } + + std::filesystem::path settingsFilePath() const { + return configDir() / "settings.json"; + } + + bool saveSession(const std::string& imguiIni) { + SessionData session; + session.workspaceRoot = workspaceRoot; + session.activePath = active() ? active()->path : ""; + session.layoutPreset = LayoutManager::presetName(layoutPreset); + session.imguiIni = imguiIni; + for (const auto& bufPath : buffers.getOpenBuffers()) { + auto it = bufferStates.find(bufPath); + if (it == bufferStates.end()) continue; + const auto* buf = it->second.get(); + SessionBufferState entry; + entry.path = bufPath; + entry.language = buf->language; + entry.mode = bufferModeToString(buf->bufferMode); + entry.cursorLine = buf->cursorLine; + entry.cursorCol = buf->cursorCol; + entry.foldedLines = buf->widget.getFoldedLines(); + session.buffers.push_back(std::move(entry)); + } + try { + std::ofstream out(sessionFilePath().string(), std::ios::binary); + if (!out.is_open()) return false; + out << sessionToJson(session).dump(2); + return true; + } catch (...) { + return false; + } + } + + bool loadSession(SessionData& out) { + try { + std::ifstream in(sessionFilePath().string(), std::ios::binary); + if (!in.is_open()) return false; + nlohmann::json j; + in >> j; + out = sessionFromJson(j); + return true; + } catch (...) { + return false; + } + } + + void applySession(const SessionData& session) { + closeAllBuffers(); + if (!session.workspaceRoot.empty()) { + workspaceRoot = session.workspaceRoot; + fileTreeDirty = true; + projectSearch.setRoot(workspaceRoot); + } + layoutPreset = LayoutManager::presetFromName(session.layoutPreset); + for (const auto& buf : session.buffers) { + doOpen(buf.path, bufferModeFromString(buf.mode)); + auto it = bufferStates.find(buf.path); + if (it != bufferStates.end()) { + auto* bs = it->second.get(); + bs->cursorLine = buf.cursorLine; + bs->cursorCol = buf.cursorCol; + bs->widget.setDesiredFoldedLines(buf.foldedLines); + jumpTo(bs, std::max(0, buf.cursorLine - 1), std::max(0, buf.cursorCol - 1)); + } + } + if (!session.activePath.empty() && buffers.hasBuffer(session.activePath)) { + switchToBuffer(session.activePath); + } + } + + void applyTabSizeToBuffers(int size) { + for (auto& kv : bufferStates) { + kv.second->mode.setTabSize(size); + kv.second->generatedMode.setTabSize(size); + } + } + + void applySettingsToState() { + showMinimap = settings.getShowMinimap(); + showLineNumbers = settings.getShowLineNumbers(); + layoutPreset = LayoutManager::presetFromName(settings.getLayoutPreset()); + keys.setProfile(KeybindingManager::profileFromName(settings.getKeybindingProfile())); + registerCommands(); + applyTabSizeToBuffers(settings.getTabSize()); + } + + void loadSettingsFromDisk() { + settings.loadFromFile(settingsFilePath().string()); + applySettingsToState(); + } + + void saveSettingsToDisk() { + settings.setShowMinimap(showMinimap); + settings.setShowLineNumbers(showLineNumbers); + settings.setLayoutPreset(LayoutManager::presetName(layoutPreset)); + settings.setKeybindingProfile(KeybindingManager::profileName(keys.getProfile())); + std::filesystem::create_directories(settingsFilePath().parent_path()); + settings.saveToFile(settingsFilePath().string()); + } + + void init() { + outputLog = "Whetstone Editor ready.\n"; + workspaceRoot = std::filesystem::current_path().string(); + projectSearch.setRoot(workspaceRoot); + fileTreeDirty = true; + loadRecentFiles(); + loadSettingsFromDisk(); + registerCommands(); + } + + void syncOrchestratorFromActive() { + if (!active()) return; + if (!isStructured()) return; + Module* ast = active()->sync.getAST(); + if (!ast) return; + active()->orchestrator.setAST(cloneModule(ast)); + active()->orchestratorDirty = false; + } + + void applyOrchestratorToActive() { + if (!active()) return; + if (!isStructured()) return; + Module* ast = active()->orchestrator.getAST(); + if (!ast) return; + active()->sync.setAST(cloneModule(ast)); + active()->incrementalOptimizer.setRoot(active()->sync.getAST()); + refreshActiveTextFromAST(); + active()->orchestratorDirty = false; + recordUndoSnapshot(); + } + + Module* mutationAST() { + if (!active()) return nullptr; + if (!isStructured()) return nullptr; + if (active()->orchestratorDirty || !active()->orchestrator.getAST()) { + syncOrchestratorFromActive(); + } + return active()->orchestrator.getAST(); + } + + void recordUndoSnapshot() { + if (!active()) return; + Module* ast = isStructured() ? active()->sync.getAST() : nullptr; + active()->orchestrator.recordSnapshot(active()->editBuf, ast); + active()->undoDepth = active()->orchestrator.getUndoDepth(); + } + + void applySnapshotToActive(const std::string& text, std::unique_ptr ast) { + if (!active()) return; + active()->editBuf = text; + active()->editor.setContent(active()->editBuf, active()->language); + if (active()->bufferMode == BufferManager::BufferMode::Structured) { + if (ast) { + active()->orchestrator.setAST(cloneModule(ast.get())); + active()->sync.setAST(std::move(ast)); + } else { + active()->sync.setText(active()->editBuf, active()->language); + active()->sync.syncNow(); + active()->orchestrator.setAST(cloneModule(active()->sync.getAST())); + } + active()->incrementalOptimizer.setRoot(active()->sync.getAST()); + } + active()->orchestratorDirty = false; + active()->highlightsDirty = true; + active()->generatedHighlightsDirty = true; + active()->modified = true; + if (lsp && active()->path.rfind("(untitled", 0) != 0) { + active()->lspVersion += 1; + lsp->didChange(toFileUri(active()->path), active()->editBuf, active()->lspVersion); + } + } + + void registerCommand(const std::string& id, + const std::string& label, + const std::string& shortcut, + std::function fn) { + commandPalette.registerCommand(id, label, shortcut); + commandHandlers[id] = std::move(fn); + } + + void registerCommands() { + commandPalette = CommandPalette(); + commandHandlers.clear(); + + registerCommand("file.new", "File: New File", + keys.getBinding("file.new").toString(), + [this]() { + std::string lang = active() ? active()->language : "python"; + createBuffer(makeUntitledName(), "", lang); + }); + registerCommand("file.open", "File: Open...", + keys.getBinding("file.open").toString(), + [this]() { + std::string path = FileDialog::openFile( + {"Open File", {"*.py","*.cpp","*.h","*.el","*.js","*.ts","*.java","*.rs","*.go"}, + std::string()}); + if (!path.empty()) doOpen(path); + }); + registerCommand("file.save", "File: Save", + keys.getBinding("file.save").toString(), + [this]() { doSave(); }); + registerCommand("project.open", "Project: Open...", + "", + [this]() { + std::string path = FileDialog::openFile( + {"Open Project", {"*.whetstone"}, std::string()}); + if (!path.empty()) { + if (!loadProject(path)) { + outputLog += "Failed to open project: " + path + "\n"; + } + } + }); + registerCommand("project.save", "Project: Save...", + "", + [this]() { + std::string path = FileDialog::saveFile( + {"Save Project", {"*.whetstone"}, std::string()}); + if (!path.empty()) { + if (!saveProject(path)) { + outputLog += "Failed to save project: " + path + "\n"; + } + } + }); + registerCommand("edit.undo", "Edit: Undo", + keys.getBinding("edit.undo").toString(), + [this]() { doUndo(); }); + registerCommand("edit.redo", "Edit: Redo", + keys.getBinding("edit.redo").toString(), + [this]() { doRedo(); }); + registerCommand("search.find", "Search: Find/Replace", + keys.getBinding("search.find").toString(), + [this]() { showFind = !showFind; }); + registerCommand("search.findInFiles", "Search: Find in Files", + keys.getBinding("search.findInFiles").toString(), + [this]() { showProjectSearch = !showProjectSearch; }); + registerCommand("nav.goToLine", "Navigate: Go to Line", + keys.getBinding("nav.goToLine").toString(), + [this]() { + showGoToLine = true; + goToLineBuf[0] = '\0'; + goToLineError = false; + }); + registerCommand("view.whitespace", "View: Toggle Whitespace", "", + [this]() { showWhitespace = !showWhitespace; }); + registerCommand("view.minimap", "View: Toggle Minimap", "", + [this]() { showMinimap = !showMinimap; }); + registerCommand("view.annotations", "View: Toggle Annotations", "", + [this]() { showAnnotations = !showAnnotations; }); + registerCommand("view.outline", "View: Toggle Outline", "", + [this]() { showOutline = !showOutline; }); + registerCommand("view.lsp", "View: LSP Servers...", "", + [this]() { showLspSettings = !showLspSettings; }); + registerCommand("mode.toggle", "Mode: Toggle Text/Structured", "", + [this]() { + if (!active()) return; + active()->bufferMode = active()->bufferMode == BufferManager::BufferMode::Text ? + BufferManager::BufferMode::Structured : BufferManager::BufferMode::Text; + buffers.setBufferMode(active()->path, active()->bufferMode); + if (active()->bufferMode == BufferManager::BufferMode::Text) { + suggestions.clear(); + whetstoneDiagnostics.clear(); + analysisPending = false; + } else { + onTextChanged(); + } + }); + registerCommand("refactor.rename", "Refactor: Rename Variable", "", + [this]() { + refactorAction = 1; + showRefactorPopup = true; + refactorNameA[0] = '\0'; + refactorNameB[0] = '\0'; + }); + registerCommand("refactor.extract", "Refactor: Extract Function", "", + [this]() { + refactorAction = 2; + showRefactorPopup = true; + refactorNameA[0] = '\0'; + refactorNameB[0] = '\0'; + }); + registerCommand("refactor.inline", "Refactor: Inline Variable", "", + [this]() { + refactorAction = 3; + showRefactorPopup = true; + refactorNameA[0] = '\0'; + refactorNameB[0] = '\0'; + }); + } + + void executeCommand(const std::string& id) { + auto it = commandHandlers.find(id); + if (it == commandHandlers.end()) return; + it->second(); + commandPalette.markUsed(id); + } + + void setLanguage(const std::string& lang) { + if (!active()) return; + std::string prevLang = active()->language; + active()->language = lang; + active()->mode.setLanguage(lang); + if (active()->generatedLanguage == prevLang) { + active()->generatedLanguage = lang; + active()->generatedMode.setLanguage(lang); + active()->generatedHighlightsDirty = true; + } + active()->editor.setContent(active()->editBuf, lang); + if (active()->bufferMode == BufferManager::BufferMode::Structured) { + active()->sync.setText(active()->editBuf, lang); + active()->sync.syncNow(); + active()->incrementalOptimizer.setRoot(active()->sync.getAST()); + } + active()->orchestratorDirty = true; + active()->highlightsDirty = true; + } + + // Called after editBuf changes (from ImGui input) + void onTextChanged() { + if (!active()) return; + active()->editor.setContent(active()->editBuf, active()->language); + if (active()->bufferMode == BufferManager::BufferMode::Structured) { + active()->sync.setText(active()->editBuf, active()->language); + active()->sync.syncNow(); + active()->incrementalOptimizer.setRoot(active()->sync.getAST()); + } + active()->orchestratorDirty = true; + active()->highlightsDirty = true; + active()->generatedHighlightsDirty = true; + active()->modified = true; + symbolsPending = true; + symbolsLastChange = ImGui::GetTime(); + if (lsp) lsp->clearDocumentSymbols(); + if (lsp && active()->path.rfind("(untitled", 0) != 0) { + active()->lspVersion += 1; + lsp->didChange(toFileUri(active()->path), active()->editBuf, active()->lspVersion); + } + recordUndoSnapshot(); + } + + void doUndo() { + if (!active()) return; + std::string text; + std::unique_ptr ast; + if (active()->orchestrator.undoSnapshot(text, ast)) { + applySnapshotToActive(text, std::move(ast)); + } + active()->undoDepth = active()->orchestrator.getUndoDepth(); + } + + void doRedo() { + if (!active()) return; + std::string text; + std::unique_ptr ast; + if (active()->orchestrator.redoSnapshot(text, ast)) { + applySnapshotToActive(text, std::move(ast)); + } + active()->undoDepth = active()->orchestrator.getUndoDepth(); + } + + void doFind() { + if (!active()) return; + if (strlen(findBuf) == 0) return; + int pos = active()->editor.find(findBuf, lastFindPos); + if (pos >= 0) { + lastFindPos = pos + 1; + int line = 1, col = 1; + for (int i = 0; i < pos && i < (int)active()->editBuf.size(); ++i) { + if (active()->editBuf[i] == '\n') { ++line; col = 1; } else { ++col; } + } + active()->cursorLine = line; + active()->cursorCol = col; + outputLog += "Found \"" + std::string(findBuf) + "\" at line " + + std::to_string(line) + ", col " + std::to_string(col) + "\n"; + } else { + lastFindPos = 0; + outputLog += "\"" + std::string(findBuf) + "\" not found.\n"; + } + } + + void doReplaceAll() { + if (!active()) return; + if (strlen(findBuf) == 0) return; + int count = active()->editor.replaceAll(findBuf, replaceBuf); + if (count > 0) { + active()->editBuf = active()->editor.getContent(); + if (active()->bufferMode == BufferManager::BufferMode::Structured) { + active()->sync.setText(active()->editBuf, active()->language); + active()->sync.syncNow(); + active()->incrementalOptimizer.setRoot(active()->sync.getAST()); + } + active()->highlightsDirty = true; + active()->generatedHighlightsDirty = true; + active()->modified = true; + } + outputLog += "Replaced " + std::to_string(count) + " occurrence(s).\n"; + } + + void doSave() { + if (!active()) return; + if (active()->path.rfind("(untitled", 0) == 0) return; + std::ofstream out(active()->path); + if (out.is_open()) { + out << active()->editBuf; + out.close(); + active()->modified = false; + outputLog += "Saved: " + active()->path + "\n"; + if (lsp) lsp->didSave(toFileUri(active()->path), active()->editBuf); + } else { + outputLog += "Error saving: " + active()->path + "\n"; + } + } + + void doOpen(const std::string& path, + BufferManager::BufferMode modeOverride = BufferManager::BufferMode::Structured) { + std::ifstream in(path); + if (in.is_open()) { + std::ostringstream ss; + ss << in.rdbuf(); + std::string content = ss.str(); + std::string language = "python"; + if (path.size() > 3 && path.substr(path.size() - 3) == ".py") + language = "python"; + else if (path.size() > 4 && path.substr(path.size() - 4) == ".cpp") + language = "cpp"; + else if (path.size() > 2 && path.substr(path.size() - 2) == ".h") + language = "cpp"; + else if (path.size() > 3 && path.substr(path.size() - 3) == ".el") + language = "elisp"; + else if (path.size() > 3 && path.substr(path.size() - 3) == ".js") + language = "javascript"; + else if (path.size() > 3 && path.substr(path.size() - 3) == ".ts") + language = "typescript"; + else if (path.size() > 5 && path.substr(path.size() - 5) == ".java") + language = "java"; + else if (path.size() > 3 && path.substr(path.size() - 3) == ".rs") + language = "rust"; + else if (path.size() > 3 && path.substr(path.size() - 3) == ".go") + language = "go"; + createBuffer(path, content, language, modeOverride); + welcome.addRecentFile(path, language, bufferModeToString(modeOverride)); + saveRecentFiles(); + outputLog += "Opened: " + path + "\n"; + } else { + outputLog += "Error opening: " + path + "\n"; + } + } + + void reloadBuffer(const std::string& path) { + auto it = bufferStates.find(path); + if (it == bufferStates.end()) return; + std::ifstream in(path); + if (!in.is_open()) return; + std::ostringstream ss; + ss << in.rdbuf(); + auto* buf = it->second.get(); + buf->editBuf = ss.str(); + buf->editor.setContent(buf->editBuf, buf->language); + if (buf->bufferMode == BufferManager::BufferMode::Structured) { + buf->sync.setText(buf->editBuf, buf->language); + buf->sync.syncNow(); + buf->incrementalOptimizer.setRoot(buf->sync.getAST()); + } + buf->highlightsDirty = true; + buf->generatedHighlightsDirty = true; + buf->modified = false; + outputLog += "Reloaded: " + path + "\n"; + } + + void handleFileChanges() { + auto changed = watcher.poll(); + for (const auto& path : changed) { + auto it = bufferStates.find(path); + if (it == bufferStates.end()) continue; + if (it->second->modified) { + outputLog += "File changed on disk (dirty): " + path + "\n"; + } else { + reloadBuffer(path); + } + } + } + + void updateHighlights() { + if (!active()) return; + if (!active()->highlightsDirty) return; + active()->highlights = SyntaxHighlighter::highlight(active()->editBuf, active()->language); + active()->highlightsDirty = false; + } + + void updateGenerated() { + if (!active()) return; + if (active()->bufferMode == BufferManager::BufferMode::Text) return; + Module* ast = active()->sync.getAST(); + std::string generated = generateForLanguage(ast, active()->generatedLanguage); + if (generated != active()->generatedBuf) { + active()->generatedBuf = generated; + active()->generatedHighlightsDirty = true; + } + if (active()->generatedHighlightsDirty) { + active()->generatedHighlights = + SyntaxHighlighter::highlight(active()->generatedBuf, active()->generatedLanguage); + active()->generatedHighlightsDirty = false; + } + } + + void projectToLanguage(const std::string& targetLanguage) { + if (!active()) return; + Module* ast = activeAST(); + 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()->incrementalOptimizer.setRoot(active()->sync.getAST()); + 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 refreshActiveTextFromAST() { + if (!active()) return; + if (!isStructured()) return; + active()->editBuf = active()->sync.getText(); + active()->editor.setContent(active()->editBuf, active()->language); + active()->highlightsDirty = true; + active()->modified = true; + if (lsp && active()->path.rfind("(untitled", 0) != 0) { + active()->lspVersion += 1; + lsp->didChange(toFileUri(active()->path), active()->editBuf, active()->lspVersion); + } + analysisPending = true; + analysisLastChange = ImGui::GetTime(); + } + + void openDiff(const std::string& beforeText, + const std::string& afterText, + bool preview, + int action, + const std::vector& transformIds, + const std::vector& batchMutations = {}) { + diff.active = true; + diff.preview = preview; + diff.action = action; + diff.batch = !batchMutations.empty(); + diff.beforeText = beforeText; + diff.afterText = afterText; + diff.transformIds = transformIds; + diff.batchMutations = batchMutations; + LineDiff lineDiff = buildLineDiff(beforeText, afterText); + diff.beforeLines = std::move(lineDiff.beforeLines); + diff.afterLines = std::move(lineDiff.afterLines); + } + + void updateCursorPos(int bytePos) { + if (!active()) return; + active()->cursorLine = 1; + active()->cursorCol = 1; + for (int i = 0; i < bytePos && i < (int)active()->editBuf.size(); ++i) { + if (active()->editBuf[i] == '\n') { ++active()->cursorLine; active()->cursorCol = 1; } + else { ++active()->cursorCol; } + } + } + + void refreshFileTree() { + if (!fileTreeDirty) return; + fileTreeRoot = fileTree.build(workspaceRoot); + fileTreeDirty = false; + } +}; + +struct NullLSPTransport : public LSPTransport { + void send(const std::string& msg) override { (void)msg; } + bool receive(std::string& out) override { (void)out; return false; } + bool isOpen() const override { return false; } + void close() override {} +}; diff --git a/editor/src/EditorUtils.h b/editor/src/EditorUtils.h new file mode 100644 index 0000000..b33c35c --- /dev/null +++ b/editor/src/EditorUtils.h @@ -0,0 +1,698 @@ +#pragma once + +#include "EditorState.h" + +#include "imgui.h" +#include "SyntaxHighlighter.h" +#include "CodeEditorWidget.h" +#include "FileTree.h" +#include "WelcomeScreen.h" +#include "FileDialog.h" +#include "LSPClient.h" +#include "BufferManager.h" +#include "ast/Serialization.h" +#include "ast/Generator.h" +#include "ast/Annotation.h" + +#include +#include +#include +#include +#include + +static int countLines(const std::string& text) { + int lines = 1; + for (char c : text) { + if (c == '\n') ++lines; + } + 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 std::unique_ptr cloneModule(const Module* ast) { + if (!ast) return nullptr; + json j = toJson(ast); + ASTNode* node = fromJson(j); + if (!node || node->conceptType != "Module") return nullptr; + return std::unique_ptr(static_cast(node)); +} + +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; +} + +static std::string findOptimizationBlockReason(const ASTNode* node) { + if (!node) return ""; + for (auto* anno : node->getChildren("annotations")) { + if (anno->conceptType == "OwnerAnnotation") { + auto* oa = static_cast(anno); + if (oa->strategy == "Single") { + return "@Owner(Single) blocks optimization (aliasing risk)"; + } + } + if (anno->conceptType == "DeallocateAnnotation") { + auto* da = static_cast(anno); + if (da->strategy == "Explicit") { + return "@Deallocate(Explicit) blocks optimization (reorder risk)"; + } + } + if (anno->conceptType == "AllocateAnnotation") { + auto* aa = static_cast(anno); + if (aa->strategy == "Static") { + return "@Allocate(Static) blocks optimization (dynamic alloc risk)"; + } + } + } + for (auto* child : node->allChildren()) { + std::string found = findOptimizationBlockReason(child); + if (!found.empty()) return found; + } + return ""; +} + +static std::string findOptimizationLockWarning(const ASTNode* node) { + if (!node) return ""; + if (node->conceptType == "OptimizationLock") { + auto* lock = static_cast(node); + if (lock->lockLevel == "warning") { + return "OptimizationLock: " + lock->lockReason + + " (locked by " + lock->lockedBy + ")"; + } + } + for (auto* child : node->allChildren()) { + std::string found = findOptimizationLockWarning(child); + if (!found.empty()) return found; + } + return ""; +} + +static ImVec4 transformColorForName(const std::string& name) { + if (name == "constant-fold") return ImVec4(0.45f, 0.85f, 0.45f, 1.0f); + if (name == "dead-code-elim") return ImVec4(0.90f, 0.45f, 0.45f, 1.0f); + return ImVec4(0.75f, 0.75f, 0.75f, 1.0f); +} + + +// --------------------------------------------------------------------------- +// ImGui InputTextMultiline with std::string resize callback +// --------------------------------------------------------------------------- +struct InputTextCallbackData { + std::string* str; +}; + +static int InputTextCallback(ImGuiInputTextCallbackData* data) { + auto* userData = static_cast(data->UserData); + if (data->EventFlag == ImGuiInputTextFlags_CallbackResize) { + userData->str->resize(data->BufTextLen); + data->Buf = userData->str->data(); + } + return 0; +} + +static bool InputTextMultilineStr(const char* label, std::string* str, + const ImVec2& size, ImGuiInputTextFlags flags = 0) { + flags |= ImGuiInputTextFlags_CallbackResize; + InputTextCallbackData cbData{str}; + // Ensure buffer has room + if (str->capacity() < str->size() + 256) + str->reserve(str->size() + 4096); + return ImGui::InputTextMultiline(label, str->data(), str->capacity() + 1, + size, flags, InputTextCallback, &cbData); +} + +static bool InputTextStr(const char* label, std::string* str, ImGuiInputTextFlags flags = 0) { + flags |= ImGuiInputTextFlags_CallbackResize; + InputTextCallbackData cbData{str}; + if (str->capacity() < str->size() + 64) + str->reserve(str->size() + 256); + return ImGui::InputText(label, str->data(), str->capacity() + 1, + flags, InputTextCallback, &cbData); +} + +static bool annotationInfo(const ASTNode* anno, ImU32& color, std::string& label) { + if (!anno) return false; + if (anno->conceptType == "ReclaimAnnotation") { + auto* a = static_cast(anno); + if (a->strategy == "Tracing") { + color = IM_COL32(80, 140, 220, 255); + label = "@Reclaim(Tracing)"; + return true; + } + } else if (anno->conceptType == "OwnerAnnotation") { + auto* a = static_cast(anno); + if (a->strategy == "Single") { + color = IM_COL32(220, 160, 60, 255); + label = "@Owner(Single)"; + return true; + } + } else if (anno->conceptType == "DeallocateAnnotation") { + auto* a = static_cast(anno); + if (a->strategy == "Explicit") { + color = IM_COL32(220, 80, 80, 255); + label = "@Deallocate(Explicit)"; + return true; + } + } else if (anno->conceptType == "LifetimeAnnotation") { + auto* a = static_cast(anno); + if (a->strategy == "RAII") { + color = IM_COL32(80, 180, 100, 255); + label = "@Lifetime(RAII)"; + return true; + } + } else if (anno->conceptType == "AllocateAnnotation") { + auto* a = static_cast(anno); + if (a->strategy == "Static") { + color = IM_COL32(160, 160, 160, 255); + label = "@Allocate(Static)"; + return true; + } + } + return false; +} + +static std::string annotationLabel(const ASTNode* anno) { + if (!anno) return ""; + if (anno->conceptType == "ReclaimAnnotation") { + auto* a = static_cast(anno); + return "@Reclaim(" + a->strategy + ")"; + } + if (anno->conceptType == "OwnerAnnotation") { + auto* a = static_cast(anno); + return "@Owner(" + a->strategy + ")"; + } + if (anno->conceptType == "DeallocateAnnotation") { + auto* a = static_cast(anno); + return "@Deallocate(" + a->strategy + ")"; + } + if (anno->conceptType == "LifetimeAnnotation") { + auto* a = static_cast(anno); + return "@Lifetime(" + a->strategy + ")"; + } + if (anno->conceptType == "AllocateAnnotation") { + auto* a = static_cast(anno); + return "@Allocate(" + a->strategy + ")"; + } + return anno->conceptType; +} + +static std::string nodeDisplayName(const ASTNode* node) { + if (!node) return ""; + if (node->conceptType == "Function") return static_cast(node)->name; + if (node->conceptType == "Variable") return static_cast(node)->name; + return node->conceptType; +} + +static std::string nextAnnotationId() { + static int counter = 0; + return "anno_ui_" + std::to_string(counter++); +} + +static Annotation* createAnnotationNode(const std::string& type, const std::string& strategy) { + if (type == "ReclaimAnnotation") { + auto* a = new ReclaimAnnotation(nextAnnotationId(), strategy); + return a; + } + if (type == "OwnerAnnotation") { + auto* a = new OwnerAnnotation(nextAnnotationId(), strategy); + return a; + } + if (type == "DeallocateAnnotation") { + auto* a = new DeallocateAnnotation(nextAnnotationId(), strategy); + return a; + } + if (type == "LifetimeAnnotation") { + auto* a = new LifetimeAnnotation(nextAnnotationId(), strategy); + return a; + } + if (type == "AllocateAnnotation") { + auto* a = new AllocateAnnotation(nextAnnotationId(), strategy); + return a; + } + return nullptr; +} + +static int spanScore(const ASTNode* node) { + if (!node || !node->hasSpan()) return INT_MAX; + int lines = node->spanEndLine - node->spanStartLine; + int cols = node->spanEndCol - node->spanStartCol; + return lines * 10000 + cols; +} + +static bool spanContains(const ASTNode* node, int line, int col) { + if (!node || !node->hasSpan()) return false; + if (line < node->spanStartLine || line > node->spanEndLine) return false; + if (line == node->spanStartLine && col < node->spanStartCol) return false; + if (line == node->spanEndLine && col > node->spanEndCol) return false; + return true; +} + +static ASTNode* findNodeAtPosition(ASTNode* node, int line, int col) { + if (!node) return nullptr; + ASTNode* best = nullptr; + if (spanContains(node, line, col)) best = node; + for (auto* child : node->allChildren()) { + ASTNode* cand = findNodeAtPosition(child, line, col); + if (cand) { + if (!best || spanScore(cand) < spanScore(best)) best = cand; + } + } + return best; +} + +static ASTNode* findAnnotationTarget(ASTNode* node, int line) { + if (!node) return nullptr; + ASTNode* best = nullptr; + if (node->hasSpan() && line >= node->spanStartLine && line <= node->spanEndLine) { + if (node->conceptType == "Function" || node->conceptType == "Variable") { + best = node; + } + } + for (auto* child : node->allChildren()) { + ASTNode* cand = findAnnotationTarget(child, line); + if (cand) { + if (!best || spanScore(cand) < spanScore(best)) best = cand; + } + } + return best; +} + +static ASTNode* findNodeById(ASTNode* node, const std::string& id) { + if (!node) return nullptr; + if (node->id == id) return node; + for (auto* child : node->allChildren()) { + if (auto* found = findNodeById(child, id)) return found; + } + return nullptr; +} + +static void collectAnnotationMarkers(const ASTNode* node, std::vector& out) { + if (!node) return; + if (node->hasSpan()) { + for (const auto* anno : node->getChildren("annotations")) { + ImU32 color = 0; + std::string label; + if (annotationInfo(anno, color, label)) { + AnnotationMarker marker; + marker.line = node->spanStartLine; + marker.color = color; + marker.message = label; + out.push_back(std::move(marker)); + } + } + } + for (auto* child : node->allChildren()) { + collectAnnotationMarkers(child, out); + } +} + +struct OutlineItem { + std::string name; + std::string kind; + int line = -1; + int col = -1; + std::vector children; +}; + +static OutlineItem makeOutlineItem(const std::string& name, + const std::string& kind, + const ASTNode* node) { + OutlineItem item; + item.name = name; + item.kind = kind; + if (node && node->hasSpan()) { + item.line = node->spanStartLine; + item.col = node->spanStartCol; + } + return item; +} + +static void collectOutlineFromFunction(const Function* fn, std::vector& out) { + OutlineItem item = makeOutlineItem(fn->name, "Function", fn); + for (auto* p : fn->getChildren("parameters")) { + if (p->conceptType == "Parameter") { + auto* param = static_cast(p); + item.children.push_back(makeOutlineItem(param->name, "Parameter", param)); + } + } + for (auto* child : fn->getChildren("body")) { + if (child->conceptType == "Variable") { + auto* var = static_cast(child); + item.children.push_back(makeOutlineItem(var->name, "Variable", var)); + } + } + out.push_back(std::move(item)); +} + +static void collectOutlineFromAST(const Module* mod, std::vector& out) { + if (!mod) return; + for (auto* child : mod->getChildren("variables")) { + if (child->conceptType == "Variable") { + auto* var = static_cast(child); + out.push_back(makeOutlineItem(var->name, "Variable", var)); + } + } + for (auto* child : mod->getChildren("functions")) { + if (child->conceptType == "Function") { + auto* fn = static_cast(child); + collectOutlineFromFunction(fn, out); + } + } +} + +static std::string toLowerCopy(const std::string& input) { + std::string out = input; + std::transform(out.begin(), out.end(), out.begin(), + [](unsigned char c) { return (char)std::tolower(c); }); + return out; +} + +static bool containsCaseInsensitive(const std::string& text, const std::string& filter) { + if (filter.empty()) return true; + std::string hay = toLowerCopy(text); + return hay.find(filter) != std::string::npos; +} + +static bool outlineMatchesFilter(const OutlineItem& item, const std::string& filter) { + if (filter.empty()) return true; + if (containsCaseInsensitive(item.name, filter)) return true; + for (const auto& child : item.children) { + if (outlineMatchesFilter(child, filter)) return true; + } + return false; +} + +static const char* lspSymbolKindLabel(int kind) { + switch (kind) { + case 1: return "File"; + case 2: return "Module"; + case 5: return "Class"; + case 6: return "Method"; + case 12: return "Function"; + case 13: return "Variable"; + case 14: return "Constant"; + case 19: return "String"; + case 23: return "Struct"; + default: return "Symbol"; + } +} + +static bool lspSymbolMatchesFilter(const LSPClient::DocumentSymbol& sym, const std::string& filter) { + if (filter.empty()) return true; + if (containsCaseInsensitive(sym.name, filter)) return true; + for (const auto& child : sym.children) { + if (lspSymbolMatchesFilter(child, filter)) return true; + } + return false; +} + +// --------------------------------------------------------------------------- +// Theme setup — VSCode Dark-inspired +// --------------------------------------------------------------------------- +static void SetupVSCodeDarkTheme() { + ImGuiStyle& style = ImGui::GetStyle(); + ImVec4* colors = style.Colors; + + // Window + colors[ImGuiCol_WindowBg] = ImVec4(0.12f, 0.12f, 0.12f, 1.00f); + colors[ImGuiCol_ChildBg] = ImVec4(0.12f, 0.12f, 0.12f, 1.00f); + colors[ImGuiCol_PopupBg] = ImVec4(0.15f, 0.15f, 0.15f, 1.00f); + + // Borders + colors[ImGuiCol_Border] = ImVec4(0.20f, 0.20f, 0.20f, 1.00f); + colors[ImGuiCol_BorderShadow] = ImVec4(0.00f, 0.00f, 0.00f, 0.00f); + + // Frame (input fields, checkboxes) + colors[ImGuiCol_FrameBg] = ImVec4(0.16f, 0.16f, 0.16f, 1.00f); + colors[ImGuiCol_FrameBgHovered] = ImVec4(0.20f, 0.20f, 0.20f, 1.00f); + colors[ImGuiCol_FrameBgActive] = ImVec4(0.24f, 0.24f, 0.24f, 1.00f); + + // Title bar + colors[ImGuiCol_TitleBg] = ImVec4(0.08f, 0.08f, 0.08f, 1.00f); + colors[ImGuiCol_TitleBgActive] = ImVec4(0.08f, 0.08f, 0.08f, 1.00f); + colors[ImGuiCol_TitleBgCollapsed] = ImVec4(0.08f, 0.08f, 0.08f, 1.00f); + + // Menu bar + colors[ImGuiCol_MenuBarBg] = ImVec4(0.14f, 0.14f, 0.14f, 1.00f); + + // Scrollbar + colors[ImGuiCol_ScrollbarBg] = ImVec4(0.12f, 0.12f, 0.12f, 1.00f); + colors[ImGuiCol_ScrollbarGrab] = ImVec4(0.30f, 0.30f, 0.30f, 1.00f); + colors[ImGuiCol_ScrollbarGrabHovered] = ImVec4(0.40f, 0.40f, 0.40f, 1.00f); + colors[ImGuiCol_ScrollbarGrabActive] = ImVec4(0.50f, 0.50f, 0.50f, 1.00f); + + // Buttons + colors[ImGuiCol_Button] = ImVec4(0.20f, 0.20f, 0.20f, 1.00f); + colors[ImGuiCol_ButtonHovered] = ImVec4(0.28f, 0.28f, 0.28f, 1.00f); + colors[ImGuiCol_ButtonActive] = ImVec4(0.15f, 0.15f, 0.15f, 1.00f); + + // Headers (collapsing headers, tree nodes) + colors[ImGuiCol_Header] = ImVec4(0.18f, 0.18f, 0.18f, 1.00f); + colors[ImGuiCol_HeaderHovered] = ImVec4(0.26f, 0.26f, 0.26f, 1.00f); + colors[ImGuiCol_HeaderActive] = ImVec4(0.22f, 0.22f, 0.22f, 1.00f); + + // Separator + colors[ImGuiCol_Separator] = ImVec4(0.20f, 0.20f, 0.20f, 1.00f); + colors[ImGuiCol_SeparatorHovered] = ImVec4(0.28f, 0.56f, 0.89f, 1.00f); + colors[ImGuiCol_SeparatorActive] = ImVec4(0.28f, 0.56f, 0.89f, 1.00f); + + // Tabs + colors[ImGuiCol_Tab] = ImVec4(0.12f, 0.12f, 0.12f, 1.00f); + colors[ImGuiCol_TabHovered] = ImVec4(0.20f, 0.20f, 0.20f, 1.00f); + colors[ImGuiCol_TabActive] = ImVec4(0.18f, 0.18f, 0.18f, 1.00f); + colors[ImGuiCol_TabUnfocused] = ImVec4(0.12f, 0.12f, 0.12f, 1.00f); + colors[ImGuiCol_TabUnfocusedActive] = ImVec4(0.16f, 0.16f, 0.16f, 1.00f); + + // Docking + colors[ImGuiCol_DockingPreview] = ImVec4(0.28f, 0.56f, 0.89f, 0.70f); + colors[ImGuiCol_DockingEmptyBg] = ImVec4(0.12f, 0.12f, 0.12f, 1.00f); + + // Text + colors[ImGuiCol_Text] = ImVec4(0.86f, 0.86f, 0.86f, 1.00f); + colors[ImGuiCol_TextDisabled] = ImVec4(0.50f, 0.50f, 0.50f, 1.00f); + + // Selection / highlight + colors[ImGuiCol_TextSelectedBg] = ImVec4(0.17f, 0.40f, 0.64f, 0.60f); + + // Style tweaks + style.WindowRounding = 0.0f; + style.FrameRounding = 2.0f; + style.ScrollbarRounding = 2.0f; + style.GrabRounding = 2.0f; + style.TabRounding = 0.0f; + style.WindowBorderSize = 1.0f; + style.FrameBorderSize = 0.0f; + style.WindowPadding = ImVec2(8, 8); + style.FramePadding = ImVec2(6, 3); + style.ItemSpacing = ImVec2(6, 4); +} + +static void SetupVSCodeLightTheme() { + ImGuiStyle& style = ImGui::GetStyle(); + ImVec4* colors = style.Colors; + + colors[ImGuiCol_WindowBg] = ImVec4(0.95f, 0.95f, 0.95f, 1.00f); + colors[ImGuiCol_ChildBg] = ImVec4(0.95f, 0.95f, 0.95f, 1.00f); + colors[ImGuiCol_PopupBg] = ImVec4(0.98f, 0.98f, 0.98f, 1.00f); + + colors[ImGuiCol_Border] = ImVec4(0.75f, 0.75f, 0.75f, 1.00f); + colors[ImGuiCol_FrameBg] = ImVec4(0.90f, 0.90f, 0.90f, 1.00f); + colors[ImGuiCol_FrameBgHovered] = ImVec4(0.85f, 0.85f, 0.85f, 1.00f); + colors[ImGuiCol_FrameBgActive] = ImVec4(0.80f, 0.80f, 0.80f, 1.00f); + + colors[ImGuiCol_TitleBg] = ImVec4(0.90f, 0.90f, 0.90f, 1.00f); + colors[ImGuiCol_TitleBgActive] = ImVec4(0.86f, 0.86f, 0.86f, 1.00f); + colors[ImGuiCol_TitleBgCollapsed] = ImVec4(0.90f, 0.90f, 0.90f, 1.00f); + + colors[ImGuiCol_MenuBarBg] = ImVec4(0.92f, 0.92f, 0.92f, 1.00f); + colors[ImGuiCol_Button] = ImVec4(0.85f, 0.85f, 0.85f, 1.00f); + colors[ImGuiCol_ButtonHovered] = ImVec4(0.78f, 0.78f, 0.78f, 1.00f); + colors[ImGuiCol_ButtonActive] = ImVec4(0.70f, 0.70f, 0.70f, 1.00f); + + colors[ImGuiCol_Header] = ImVec4(0.82f, 0.82f, 0.82f, 1.00f); + colors[ImGuiCol_HeaderHovered] = ImVec4(0.75f, 0.75f, 0.75f, 1.00f); + colors[ImGuiCol_HeaderActive] = ImVec4(0.70f, 0.70f, 0.70f, 1.00f); + + colors[ImGuiCol_Text] = ImVec4(0.10f, 0.10f, 0.10f, 1.00f); + colors[ImGuiCol_TextDisabled] = ImVec4(0.45f, 0.45f, 0.45f, 1.00f); + colors[ImGuiCol_TextSelectedBg] = ImVec4(0.40f, 0.60f, 0.90f, 0.45f); + + style.WindowRounding = 0.0f; + style.FrameRounding = 2.0f; + style.ScrollbarRounding = 2.0f; + style.GrabRounding = 2.0f; + style.TabRounding = 0.0f; + style.WindowBorderSize = 1.0f; + style.FrameBorderSize = 0.0f; + style.WindowPadding = ImVec2(8, 8); + style.FramePadding = ImVec2(6, 3); + style.ItemSpacing = ImVec2(6, 4); +} + +// --------------------------------------------------------------------------- +// Syntax highlight color map +// --------------------------------------------------------------------------- +static ImVec4 tokenColor(TokenCategory cat) { + switch (cat) { + case TokenCategory::Keyword: return ImVec4(0.77f, 0.49f, 0.86f, 1.0f); // purple + case TokenCategory::String: return ImVec4(0.81f, 0.54f, 0.37f, 1.0f); // orange + case TokenCategory::Comment: return ImVec4(0.42f, 0.52f, 0.35f, 1.0f); // green + case TokenCategory::Number: return ImVec4(0.71f, 0.80f, 0.55f, 1.0f); // light green + case TokenCategory::Function: return ImVec4(0.86f, 0.86f, 0.55f, 1.0f); // yellow + case TokenCategory::Parameter: return ImVec4(0.60f, 0.78f, 0.90f, 1.0f); // light blue + case TokenCategory::Type: return ImVec4(0.30f, 0.70f, 0.68f, 1.0f); // teal + case TokenCategory::Operator: return ImVec4(0.86f, 0.86f, 0.86f, 1.0f); // white + case TokenCategory::Punctuation: return ImVec4(0.60f, 0.60f, 0.60f, 1.0f); // gray + case TokenCategory::Builtin: return ImVec4(0.30f, 0.70f, 0.90f, 1.0f); // blue + case TokenCategory::Identifier: return ImVec4(0.60f, 0.78f, 0.90f, 1.0f); // light blue + default: return ImVec4(0.86f, 0.86f, 0.86f, 1.0f); // white + } +} + +// --------------------------------------------------------------------------- +// Render syntax-highlighted text (read-only preview) +// --------------------------------------------------------------------------- +static void RenderHighlightedText(const std::string& text, + const std::vector& spans) { + if (text.empty()) return; + + // Build a color for each byte position + // Default = plain text color + std::vector charCats(text.size(), TokenCategory::Plain); + for (const auto& span : spans) { + for (uint32_t i = span.start; i < span.end && i < charCats.size(); ++i) { + charCats[i] = span.category; + } + } + + // Render line by line, span by span + size_t pos = 0; + int lineNum = 1; + while (pos < text.size()) { + // Find end of line + size_t eol = text.find('\n', pos); + if (eol == std::string::npos) eol = text.size(); + + // Line number + ImGui::TextColored(ImVec4(0.45f, 0.45f, 0.45f, 1.0f), "%4d ", lineNum); + ImGui::SameLine(0.0f, 0.0f); + + // Render spans within this line + size_t linePos = pos; + while (linePos < eol) { + // Find the extent of the current color + TokenCategory cat = charCats[linePos]; + size_t spanEnd = linePos + 1; + while (spanEnd < eol && charCats[spanEnd] == cat) ++spanEnd; + + std::string chunk = text.substr(linePos, spanEnd - linePos); + ImGui::TextColored(tokenColor(cat), "%s", chunk.c_str()); + if (spanEnd < eol) ImGui::SameLine(0.0f, 0.0f); + + linePos = spanEnd; + } + + if (linePos == pos) { + // Empty line + ImGui::TextUnformatted(""); + } + + pos = eol + 1; + ++lineNum; + } +} + +// --------------------------------------------------------------------------- +// File tree rendering +// --------------------------------------------------------------------------- +static void RenderFileTree(const FileNode& node, EditorState& state) { + if (!node.isDir) { + if (ImGui::Selectable(node.name.c_str())) { + state.doOpen(node.path); + } + return; + } + + ImGuiTreeNodeFlags flags = ImGuiTreeNodeFlags_OpenOnArrow | ImGuiTreeNodeFlags_OpenOnDoubleClick; + if (ImGui::TreeNodeEx(node.name.c_str(), flags)) { + for (const auto& child : node.children) { + RenderFileTree(child, state); + } + ImGui::TreePop(); + } +} + +// --------------------------------------------------------------------------- +// Welcome screen rendering +// --------------------------------------------------------------------------- +static void RenderWelcome(WelcomeScreen& welcome, EditorState& state, std::string& lastDialogPath) { + ImGui::Text("%s", welcome.getTitle().c_str()); + ImGui::TextColored(ImVec4(0.7f, 0.7f, 0.7f, 1.0f), "%s", welcome.getSubtitle().c_str()); + ImGui::Separator(); + + ImGui::Text("Quick Actions"); + if (ImGui::Button("New File")) { + std::string lang = state.active() ? state.active()->language : "python"; + state.createBuffer(state.makeUntitledName(), "", lang); + } + ImGui::SameLine(); + if (ImGui::Button("Open File")) { + auto path = FileDialog::openFile({"Open File", {"*.py","*.cpp","*.h","*.el","*.js","*.ts","*.java","*.rs","*.go"}, lastDialogPath}); + if (!path.empty()) { + lastDialogPath = path; + state.doOpen(path); + } + } + ImGui::SameLine(); + if (ImGui::Button("Open Folder")) { + auto path = FileDialog::openFolder({"Open Folder", state.workspaceRoot}); + if (!path.empty()) { + state.workspaceRoot = path; + state.fileTreeDirty = true; + state.projectSearch.setRoot(state.workspaceRoot); + lastDialogPath = path; + } + } + + ImGui::Separator(); + ImGui::Text("Recent Files"); + for (const auto& rf : welcome.getRecentFiles()) { + if (ImGui::Selectable(rf.displayName.c_str())) { + state.doOpen(rf.path, bufferModeFromString(rf.mode)); + } + } + + ImGui::Separator(); + ImGui::Text("Tip"); + ImGui::TextWrapped("%s", welcome.getTip(0).c_str()); +} diff --git a/editor/src/Orchestrator.h b/editor/src/Orchestrator.h index ad91c41..47daa73 100644 --- a/editor/src/Orchestrator.h +++ b/editor/src/Orchestrator.h @@ -190,9 +190,7 @@ public: // Find the parent and remove the child ASTNode* parent = node->parent; if (parent) { - // This is a simplified implementation - in reality, we'd need to track - // which role the node was added to and remove it from that role - // For now, we'll just mark this as a TODO in a real implementation + // STUB: insertNode undo not implemented — requires role tracking per mutation } } } else if (operation["type"] == "setNodeProperty") { @@ -344,10 +342,20 @@ public: std::cout << "Sending to Emacs: " << command << std::endl; // In a real implementation: - std::string cmd = "emacsclient -s whetstone -e \"" + command + "\""; + // Escape command to prevent shell injection + std::string escaped; + for (char c : command) { + if (c == '"' || c == '\\') escaped += '\\'; + escaped += c; + } + std::string cmd = "emacsclient -s whetstone -e \"" + escaped + "\""; +#ifdef _WIN32 FILE* pipe = _popen(cmd.c_str(), "r"); - if (!pipe) return "nil"; // Return nil if we can't open the pipe - +#else + FILE* pipe = popen(cmd.c_str(), "r"); +#endif + if (!pipe) return "nil"; + char buffer[4096]; std::string result = ""; while (!feof(pipe)) { @@ -355,17 +363,31 @@ public: result += buffer; } } +#ifdef _WIN32 _pclose(pipe); +#else + pclose(pipe); +#endif // Trim whitespace from the result result.erase(result.find_last_not_of(" \t\n\r\f\v") + 1); return result.empty() ? "nil" : result; } + // Escape a string for embedding in Elisp double-quoted strings + static std::string escapeElispString(const std::string& s) { + std::string out; + for (char c : s) { + if (c == '"' || c == '\\') out += '\\'; + out += c; + } + return out; + } + // Method to load a file via Emacs and parse to AST using tree-sitter std::unique_ptr loadFile(const std::string& path) { // First, get the file content from Emacs - std::string command = "(with-current-buffer (find-file-noselect \"" + path + "\") (buffer-string))"; + std::string command = "(with-current-buffer (find-file-noselect \"" + escapeElispString(path) + "\") (buffer-string))"; std::string content = sendToEmacs(command); // Determine the language based on file extension @@ -424,12 +446,9 @@ public: } else if (targetLanguage == "elisp") { ElispGenerator elispGen; content = elispGen.generate(ast); - } else if (targetLanguage == "cpp") { - // For C++ generation, we would use a CppGenerator when implemented - // For now, we'll use the Python generator as a placeholder - PythonGenerator gen; + } else if (targetLanguage == "cpp" || targetLanguage == "c++") { + CppGenerator gen; content = gen.generate(ast); - // TODO: Implement CppGenerator when ready } else { // Default to Python generator for unknown languages PythonGenerator gen; @@ -437,21 +456,24 @@ public: } // Send the content to Emacs to save to file - std::string command = "(with-current-buffer (find-file-noselect \"" + path + "\")" + + std::string ePath = escapeElispString(path); + std::string eContent = escapeElispString(content); + std::string command = "(with-current-buffer (find-file-noselect \"" + ePath + "\")" + "(erase-buffer)" + - "(insert \"" + content + "\")" + - "(write-file \"" + path + "\"))"; + "(insert \"" + eContent + "\")" + + "(write-file \"" + ePath + "\"))"; std::string result = sendToEmacs(command); - // If the result is not an error, assume success return result.find("Error") == std::string::npos; } // Overload: save raw content string to a file via Emacs bool saveContent(const std::string& path, const std::string& content) { - std::string command = "(with-current-buffer (find-file-noselect \"" + path + "\")" + + std::string ePath = escapeElispString(path); + std::string eContent = escapeElispString(content); + std::string command = "(with-current-buffer (find-file-noselect \"" + ePath + "\")" + "(erase-buffer)" + - "(insert \"" + content + "\")" + - "(write-file \"" + path + "\"))"; + "(insert \"" + eContent + "\")" + + "(write-file \"" + ePath + "\"))"; std::string result = sendToEmacs(command); return result.find("Error") == std::string::npos; } diff --git a/editor/src/ast/CppGenerator.h b/editor/src/ast/CppGenerator.h new file mode 100644 index 0000000..cf2c412 --- /dev/null +++ b/editor/src/ast/CppGenerator.h @@ -0,0 +1,702 @@ +#pragma once +#include "ProjectionGenerator.h" + +class CppGenerator : public ProjectionGenerator { +public: + std::string generate(const ASTNode* node) override { + return dispatchGenerate(this, node, "// Unknown concept: "); + } + + std::string visitModule(const Module* module) override { + std::ostringstream oss; + + // Generate namespace wrapper + oss << "namespace " << module->name << " {\n\n"; + + // Process variables first + auto variables = module->getChildren("variables"); + for (const auto* var : variables) { + oss << visitVariable(static_cast(var)) << "\n"; + } + + if (!variables.empty()) { + oss << "\n"; + } + + // Process functions + auto functions = module->getChildren("functions"); + for (size_t i = 0; i < functions.size(); ++i) { + if (i > 0) oss << "\n"; // Add blank line between functions + oss << visitFunction(static_cast(functions[i])); + } + + oss << "\n} // namespace " << module->name << "\n"; + return oss.str(); + } + + std::string visitFunction(const Function* function) override { + std::ostringstream oss; + + // Process annotations first (these are in the "annotations" role) + auto annotations = function->getChildren("annotations"); + for (const auto* annotation : annotations) { + std::string annotationCode = generate(annotation); + if (!annotationCode.empty() && annotationCode != "// Unknown concept: Annotation") { + oss << annotationCode << "\n"; + } + } + + // Generate function return type + std::string returnTypeStr = "void"; // Default + auto returnType = function->getChild("returnType"); + if (returnType) { + returnTypeStr = generate(returnType); + } + + // Generate function signature + oss << returnTypeStr << " " << function->name << "("; + + auto parameters = function->getChildren("parameters"); + for (size_t i = 0; i < parameters.size(); ++i) { + if (i > 0) oss << ", "; + oss << visitParameter(static_cast(parameters[i])); + } + + oss << ") {\n"; + + // Process function body + auto body = function->getChildren("body"); + if (body.empty()) { + oss << " // Function body is empty\n"; + oss << " return;"; + if (returnTypeStr != "void") { + oss << " // TODO: return appropriate value"; + } + oss << "\n"; + } else { + for (const auto* stmt : body) { + std::string stmtCode = generate(stmt); + // Indent each line of the statement + size_t pos = 0; + while (pos < stmtCode.length()) { + size_t newlinePos = stmtCode.find('\n', pos); + if (newlinePos == std::string::npos) { + oss << " " << stmtCode.substr(pos) << "\n"; + break; + } else { + oss << " " << stmtCode.substr(pos, newlinePos - pos) << "\n"; + pos = newlinePos + 1; + if (pos >= stmtCode.length()) break; + } + } + } + } + + oss << "}\n"; + + return oss.str(); + } + + std::string visitVariable(const Variable* variable) override { + std::ostringstream oss; + + auto type = variable->getChild("type"); + std::string typeStr = "auto"; // Default + if (type) { + typeStr = generate(type); + } + + // Check enclosing function for memory annotations that affect variable types + std::string wrapper = getMemoryTypeWrapper(variable); + if (!wrapper.empty()) { + oss << wrapper << "<" << typeStr << "> " << variable->name; + } else { + oss << typeStr << " " << variable->name; + } + + auto initializer = variable->getChild("initializer"); + if (initializer) { + oss << " = " << generate(initializer); + } + + oss << ";"; + + return oss.str(); + } + + std::string visitParameter(const Parameter* parameter) override { + std::ostringstream oss; + + auto type = parameter->getChild("type"); + std::string typeStr = "auto"; // Default + if (type) { + typeStr = generate(type); + } + + oss << typeStr << " " << parameter->name; + + // Add default value if present + auto defaultValue = parameter->getChild("defaultValue"); + if (defaultValue) { + oss << " = " << generate(defaultValue); + } + + return oss.str(); + } + + std::string visitAssignment(const Assignment* assignment) override { + std::ostringstream oss; + + auto target = assignment->getChild("target"); + auto value = assignment->getChild("value"); + + if (target) { + oss << generate(target) << " = "; + } else { + oss << "/* missing target */ "; + } + + if (value) { + oss << generate(value); + } else { + oss << "/* missing value */"; + } + + oss << ";"; + + return oss.str(); + } + + std::string visitReturn(const Return* ret) override { + std::ostringstream oss; + oss << "return "; + + auto value = ret->getChild("value"); + if (value) { + oss << generate(value); + } else { + // For void returns + oss << ";"; + return oss.str(); + } + + oss << ";"; + return oss.str(); + } + + std::string visitBinaryOperation(const BinaryOperation* binOp) override { + std::ostringstream oss; + + auto left = binOp->getChild("left"); + auto right = binOp->getChild("right"); + + if (left) { + oss << generate(left); + } else { + oss << "/* missing left */"; + } + + oss << " " << binOp->op << " "; + + if (right) { + oss << generate(right); + } else { + oss << "/* missing right */"; + } + + return oss.str(); + } + + std::string visitVariableReference(const VariableReference* varRef) override { + return varRef->variableName; + } + + std::string visitIntegerLiteral(const IntegerLiteral* lit) override { + return std::to_string(lit->value); + } + + std::string visitFloatLiteral(const FloatLiteral* lit) override { + return lit->value; + } + + std::string visitStringLiteral(const StringLiteral* lit) override { + // In C++, strings need to be enclosed in double quotes + return "\"" + lit->value + "\""; + } + + std::string visitBooleanLiteral(const BooleanLiteral* lit) override { + return lit->value ? "true" : "false"; + } + + std::string visitNullLiteral(const NullLiteral* lit) override { + return "nullptr"; + } + + std::string visitIfStatement(const IfStatement* stmt) override { + std::ostringstream oss; + + auto condition = stmt->getChild("condition"); + auto thenBranch = stmt->getChildren("thenBranch"); + auto elseBranch = stmt->getChildren("elseBranch"); + + oss << "if ("; + if (condition) { + oss << generate(condition); + } else { + oss << "true"; // fallback + } + oss << ") {\n"; + + // Then branch + for (const auto* thenStmt : thenBranch) { + std::string stmtCode = generate(thenStmt); + size_t pos = 0; + while (pos < stmtCode.length()) { + size_t newlinePos = stmtCode.find('\n', pos); + if (newlinePos == std::string::npos) { + oss << " " << stmtCode.substr(pos) << "\n"; + break; + } else { + oss << " " << stmtCode.substr(pos, newlinePos - pos) << "\n"; + pos = newlinePos + 1; + if (pos >= stmtCode.length()) break; + } + } + } + oss << "}"; + + // Else branch + if (!elseBranch.empty()) { + oss << " else {\n"; + for (const auto* elseStmt : elseBranch) { + std::string stmtCode = generate(elseStmt); + size_t pos = 0; + while (pos < stmtCode.length()) { + size_t newlinePos = stmtCode.find('\n', pos); + if (newlinePos == std::string::npos) { + oss << " " << stmtCode.substr(pos) << "\n"; + break; + } else { + oss << " " << stmtCode.substr(pos, newlinePos - pos) << "\n"; + pos = newlinePos + 1; + if (pos >= stmtCode.length()) break; + } + } + } + oss << "}"; + } + + return oss.str(); + } + + std::string visitWhileLoop(const WhileLoop* loop) override { + std::ostringstream oss; + + auto condition = loop->getChild("condition"); + auto body = loop->getChildren("body"); + + oss << "while ("; + if (condition) { + oss << generate(condition); + } else { + oss << "true"; // fallback to infinite loop + } + oss << ") {\n"; + + for (const auto* stmt : body) { + std::string stmtCode = generate(stmt); + size_t pos = 0; + while (pos < stmtCode.length()) { + size_t newlinePos = stmtCode.find('\n', pos); + if (newlinePos == std::string::npos) { + oss << " " << stmtCode.substr(pos) << "\n"; + break; + } else { + oss << " " << stmtCode.substr(pos, newlinePos - pos) << "\n"; + pos = newlinePos + 1; + if (pos >= stmtCode.length()) break; + } + } + } + + oss << "}"; + return oss.str(); + } + + std::string visitForLoop(const ForLoop* loop) override { + std::ostringstream oss; + + auto iterable = loop->getChild("iterable"); + auto body = loop->getChildren("body"); + + std::string iterator = loop->iteratorName.empty() ? "item" : loop->iteratorName; + + oss << "for (auto& " << iterator << " : "; + if (iterable) { + oss << generate(iterable); + } else { + oss << "/* missing iterable */"; // fallback + } + oss << ") {\n"; + + for (const auto* stmt : body) { + std::string stmtCode = generate(stmt); + size_t pos = 0; + while (pos < stmtCode.length()) { + size_t newlinePos = stmtCode.find('\n', pos); + if (newlinePos == std::string::npos) { + oss << " " << stmtCode.substr(pos) << "\n"; + break; + } else { + oss << " " << stmtCode.substr(pos, newlinePos - pos) << "\n"; + pos = newlinePos + 1; + if (pos >= stmtCode.length()) break; + } + } + } + + oss << "}"; + return oss.str(); + } + + std::string visitExpressionStatement(const ExpressionStatement* stmt) override { + std::ostringstream oss; + + auto expr = stmt->getChild("expression"); + if (expr) { + oss << generate(expr) << ";"; + } else { + oss << ";"; // Empty statement + } + + return oss.str(); + } + + std::string visitUnaryOperation(const UnaryOperation* unOp) override { + std::ostringstream oss; + + auto operand = unOp->getChild("operand"); + std::string op = unOp->op; + + oss << op << " "; + if (operand) { + oss << generate(operand); + } else { + oss << "/* missing operand */"; + } + + return oss.str(); + } + + std::string visitFunctionCall(const FunctionCall* call) override { + std::ostringstream oss; + oss << call->functionName << "("; + + auto arguments = call->getChildren("arguments"); + for (size_t i = 0; i < arguments.size(); ++i) { + if (i > 0) oss << ", "; + oss << generate(arguments[i]); + } + + oss << ")"; + + return oss.str(); + } + + std::string visitBlock(const Block* block) override { + std::ostringstream oss; + + auto statements = block->getChildren("statements"); + if (statements.empty()) { + oss << "{}"; + } else { + oss << "{\n"; + for (size_t i = 0; i < statements.size(); ++i) { + std::string stmtCode = generate(statements[i]); + size_t pos = 0; + while (pos < stmtCode.length()) { + size_t newlinePos = stmtCode.find('\n', pos); + if (newlinePos == std::string::npos) { + oss << " " << stmtCode.substr(pos) << "\n"; + break; + } else { + oss << " " << stmtCode.substr(pos, newlinePos - pos) << "\n"; + pos = newlinePos + 1; + if (pos >= stmtCode.length()) break; + } + } + if (i < statements.size() - 1) oss << "\n"; + } + oss << "}"; + } + + return oss.str(); + } + + std::string visitListLiteral(const ListLiteral* lit) override { + std::ostringstream oss; + oss << "{"; + + auto elements = lit->getChildren("elements"); + for (size_t i = 0; i < elements.size(); ++i) { + if (i > 0) oss << ", "; + oss << generate(elements[i]); + } + + oss << "}"; + + return oss.str(); + } + + std::string visitIndexAccess(const IndexAccess* access) override { + std::ostringstream oss; + + auto target = access->getChild("target"); + auto index = access->getChild("index"); + + if (target) { + oss << generate(target); + } else { + oss << "/* missing target */"; + } + + oss << "["; + if (index) { + oss << generate(index); + } else { + oss << "/* missing index */"; + } + oss << "]"; + + return oss.str(); + } + + std::string visitMemberAccess(const MemberAccess* access) override { + std::ostringstream oss; + + auto target = access->getChild("target"); + if (target) { + oss << generate(target); + } else { + oss << "/* missing target */"; + } + + oss << "." << access->memberName; + + return oss.str(); + } + + std::string visitPrimitiveType(const PrimitiveType* type) override { + std::string kind = type->kind; + // Map common types to C++ equivalents + if (kind == "int") return "int"; + if (kind == "float") return "float"; + if (kind == "string") return "std::string"; + if (kind == "bool") return "bool"; + if (kind == "char") return "char"; + if (kind == "double") return "double"; + if (kind == "long") return "long"; + if (kind == "short") return "short"; + if (kind == "byte") return "char"; // C++ doesn't have byte, use char + if (kind == "void") return "void"; + + return kind; // Return as-is if not a common type + } + + std::string visitListType(const ListType* type) override { + std::ostringstream oss; + oss << "std::vector<"; + + auto elementType = type->getChild("elementType"); + if (elementType) { + oss << generate(elementType); + } else { + oss << "auto"; // Default if no element type specified + } + + oss << ">"; + return oss.str(); + } + + std::string visitSetType(const SetType* type) override { + std::ostringstream oss; + oss << "std::set<"; + + auto elementType = type->getChild("elementType"); + if (elementType) { + oss << generate(elementType); + } else { + oss << "auto"; // Default if no element type specified + } + + oss << ">"; + return oss.str(); + } + + std::string visitMapType(const MapType* type) override { + std::ostringstream oss; + oss << "std::map<"; + + auto keyType = type->getChild("keyType"); + auto valueType = type->getChild("valueType"); + + if (keyType) { + oss << generate(keyType); + } else { + oss << "auto"; // Default if no key type specified + } + + oss << ", "; + + if (valueType) { + oss << generate(valueType); + } else { + oss << "auto"; // Default if no value type specified + } + + oss << ">"; + return oss.str(); + } + + std::string visitTupleType(const TupleType* type) override { + std::ostringstream oss; + oss << "std::tuple<"; + + auto elementTypes = type->getChildren("elementTypes"); + for (size_t i = 0; i < elementTypes.size(); ++i) { + if (i > 0) oss << ", "; + oss << generate(elementTypes[i]); + } + + oss << ">"; + return oss.str(); + } + + std::string visitArrayType(const ArrayType* type) override { + std::ostringstream oss; + oss << "std::array<"; + + auto elementType = type->getChild("elementType"); + if (elementType) { + oss << generate(elementType); + } else { + oss << "auto"; // Default if no element type specified + } + + oss << ", /* size unknown */>"; + return oss.str(); + } + + std::string visitOptionalType(const OptionalType* type) override { + std::ostringstream oss; + oss << "std::optional<"; + + auto innerType = type->getChild("innerType"); + if (innerType) { + oss << generate(innerType); + } else { + oss << "auto"; // Default if no inner type specified + } + + oss << ">"; + return oss.str(); + } + + std::string visitCustomType(const CustomType* type) override { + return type->typeName; + } + + std::string visitDerefStrategy(const DerefStrategy* annotation) override { + // Generate C++-style comment for deref strategy + if (annotation->strategy == "batched") { + return "// @deref(batched) - Use batched memory management (smart pointers)"; + } else if (annotation->strategy == "streamed") { + return "// @deref(streamed) - Use streamed memory management (RAII)"; + } else if (annotation->strategy == "manual") { + return "// @deref(manual) - Use manual memory management (new/delete)"; + } else { + return "// @deref(" + annotation->strategy + ") - Memory management strategy"; + } + } + + std::string visitOptimizationLock(const OptimizationLock* annotation) override { + return "// @lock(" + annotation->lockedBy + ") - Optimization locked: " + annotation->lockReason; + } + + std::string visitLangSpecific(const LangSpecific* annotation) override { + return "// @lang_specific(" + annotation->language + ", " + annotation->idiomType + ")"; + } + + std::string visitDeallocateAnnotation(const DeallocateAnnotation* annotation) override { + return "// @dealloc(" + annotation->strategy + ") - Memory deallocation strategy"; + } + + std::string visitLifetimeAnnotation(const LifetimeAnnotation* annotation) override { + return "// @lifetime(" + annotation->strategy + ") - Object lifetime management"; + } + + std::string visitReclaimAnnotation(const ReclaimAnnotation* annotation) override { + return "// @reclaim(" + annotation->strategy + ") - Memory reclamation strategy"; + } + + std::string visitOwnerAnnotation(const OwnerAnnotation* annotation) override { + return "// @owner(" + annotation->strategy + ") - Ownership management strategy"; + } + + std::string visitAllocateAnnotation(const AllocateAnnotation* annotation) override { + return "// @allocate(" + annotation->strategy + ") - Memory allocation strategy"; + } + + std::string visitHotColdAnnotation(const HotColdAnnotation* annotation) override { + if (annotation->hint == "Hot") + return "__attribute__((hot))"; + if (annotation->hint == "Cold") + return "__attribute__((cold))"; + return "// @hotcold(" + annotation->hint + ")"; + } + + std::string visitInlineAnnotation(const InlineAnnotation* annotation) override { + if (annotation->mode == "Always") + return "[[gnu::always_inline]] inline"; + if (annotation->mode == "Never") + return "__attribute__((noinline))"; + return "inline"; + } + + std::string visitPureAnnotation(const PureAnnotation*) override { + return "[[nodiscard]]"; + } + + std::string visitConstExprAnnotation(const ConstExprAnnotation*) override { + return "constexpr"; + } + +private: + // Check enclosing function's memory annotations to determine smart-pointer wrapper + std::string getMemoryTypeWrapper(const Variable* variable) const { + const ASTNode* cur = variable->parent; + while (cur && cur->conceptType != "Function") { + cur = cur->parent; + } + if (!cur) return ""; + + for (auto* anno : cur->getChildren("annotations")) { + if (anno->conceptType == "ReclaimAnnotation") { + auto* ra = static_cast(anno); + if (ra->strategy == "Tracing" || ra->strategy == "Cycle") + return "std::shared_ptr"; + } + if (anno->conceptType == "LifetimeAnnotation") { + auto* la = static_cast(anno); + if (la->strategy == "RAII") + return "std::unique_ptr"; + } + if (anno->conceptType == "OwnerAnnotation") { + auto* oa = static_cast(anno); + if (oa->strategy == "Shared_ARC") return "std::shared_ptr"; + if (oa->strategy == "Single") return "std::unique_ptr"; + } + } + return ""; + } +}; diff --git a/editor/src/ast/ElispGenerator.h b/editor/src/ast/ElispGenerator.h new file mode 100644 index 0000000..4bc3427 --- /dev/null +++ b/editor/src/ast/ElispGenerator.h @@ -0,0 +1,545 @@ +#pragma once +#include "ProjectionGenerator.h" + +class ElispGenerator : public ProjectionGenerator { +public: + std::string generate(const ASTNode* node) override { + return dispatchGenerate(this, node, "; Unknown concept: "); + } + + std::string visitModule(const Module* module) override { + std::ostringstream oss; + + oss << "; Module: " << module->name << "\n\n"; + + // Process variables first + auto variables = module->getChildren("variables"); + for (const auto* var : variables) { + oss << visitVariable(static_cast(var)) << "\n"; + } + + if (!variables.empty()) { + oss << "\n"; + } + + // Process functions + auto functions = module->getChildren("functions"); + for (size_t i = 0; i < functions.size(); ++i) { + if (i > 0) oss << "\n"; // Add blank line between functions + oss << visitFunction(static_cast(functions[i])); + } + + return oss.str(); + } + + std::string visitFunction(const Function* function) override { + std::ostringstream oss; + + // Process annotations first + auto annotations = function->getChildren("annotations"); + for (const auto* annotation : annotations) { + std::string annotationCode = generate(annotation); + if (!annotationCode.empty()) { + oss << annotationCode << "\n"; + } + } + + // Generate function definition in Elisp + oss << "(defun " << function->name << " ("; + + auto parameters = function->getChildren("parameters"); + for (size_t i = 0; i < parameters.size(); ++i) { + if (i > 0) oss << " "; + oss << visitParameter(static_cast(parameters[i])); + } + + oss << ")\n"; + + // Process function body + auto body = function->getChildren("body"); + if (body.empty()) { + oss << " nil)\n"; // Return nil if no body + } else { + for (const auto* stmt : body) { + std::string stmtCode = generate(stmt); + // Indent each line of the statement + size_t pos = 0; + while (pos < stmtCode.length()) { + size_t newlinePos = stmtCode.find('\n', pos); + if (newlinePos == std::string::npos) { + oss << " " << stmtCode.substr(pos) << "\n"; + break; + } else { + oss << " " << stmtCode.substr(pos, newlinePos - pos) << "\n"; + pos = newlinePos + 1; + if (pos >= stmtCode.length()) break; + } + } + } + oss << ")\n"; // Close the function + } + + return oss.str(); + } + + std::string visitVariable(const Variable* variable) override { + std::ostringstream oss; + oss << "(defvar " << variable->name << " "; + + auto initializer = variable->getChild("initializer"); + if (initializer) { + oss << generate(initializer); + } else { + oss << "nil"; + } + + oss << ") ; Variable declaration\n"; + return oss.str(); + } + + std::string visitParameter(const Parameter* parameter) override { + // In Elisp, parameters are just symbols + return parameter->name; + } + + std::string visitAssignment(const Assignment* assignment) override { + std::ostringstream oss; + + auto target = assignment->getChild("target"); + auto value = assignment->getChild("value"); + + if (target && value) { + oss << "(setq " << generate(target) << " " << generate(value) << ")"; + } else if (target) { + oss << "(setq " << generate(target) << " nil)"; + } else { + oss << "nil"; + } + + return oss.str(); + } + + std::string visitReturn(const Return* ret) override { + // In Elisp, the last expression in a function is the return value + // So we just return the value expression + auto value = ret->getChild("value"); + if (value) { + return generate(value); + } else { + return "nil"; + } + } + + std::string visitBinaryOperation(const BinaryOperation* binOp) override { + std::ostringstream oss; + + auto left = binOp->getChild("left"); + auto right = binOp->getChild("right"); + + // Map operators to Elisp equivalents + std::string op = binOp->op; + if (op == "+") op = "+"; + else if (op == "-") op = "-"; + else if (op == "*") op = "*"; + else if (op == "/") op = "/"; + else if (op == "==") op = "="; + else if (op == "!=") op = "/="; + else if (op == "<") op = "<"; + else if (op == ">") op = ">"; + else if (op == "<=") op = "<="; + else if (op == ">=") op = ">="; + else if (op == "and") op = "and"; + else if (op == "or") op = "or"; + + oss << "(" << op << " "; + if (left) { + oss << generate(left); + } else { + oss << "nil"; + } + oss << " "; + if (right) { + oss << generate(right); + } else { + oss << "nil"; + } + oss << ")"; + + return oss.str(); + } + + std::string visitVariableReference(const VariableReference* varRef) override { + return varRef->variableName; + } + + std::string visitIntegerLiteral(const IntegerLiteral* lit) override { + return std::to_string(lit->value); + } + + std::string visitFloatLiteral(const FloatLiteral* lit) override { + return lit->value; + } + + std::string visitStringLiteral(const StringLiteral* lit) override { + // In Elisp, strings are enclosed in double quotes + return "\"" + lit->value + "\""; + } + + std::string visitBooleanLiteral(const BooleanLiteral* lit) override { + return lit->value ? "t" : "nil"; + } + + std::string visitNullLiteral(const NullLiteral* lit) override { + return "nil"; + } + + std::string visitIfStatement(const IfStatement* stmt) override { + std::ostringstream oss; + + auto condition = stmt->getChild("condition"); + auto thenBranch = stmt->getChildren("thenBranch"); + auto elseBranch = stmt->getChildren("elseBranch"); + + oss << "(if "; + if (condition) { + oss << generate(condition); + } else { + oss << "nil"; // fallback + } + oss << "\n"; + + // Then branch + if (!thenBranch.empty()) { + for (size_t i = 0; i < thenBranch.size(); ++i) { + if (i > 0) oss << "\n"; + oss << " " << generate(thenBranch[i]); + } + } else { + oss << " nil"; + } + + // Else branch + if (!elseBranch.empty()) { + oss << "\n"; + for (size_t i = 0; i < elseBranch.size(); ++i) { + if (i > 0) oss << "\n"; + oss << " " << generate(elseBranch[i]); + } + } else { + oss << "\n nil"; + } + + oss << ")"; + return oss.str(); + } + + std::string visitWhileLoop(const WhileLoop* loop) override { + std::ostringstream oss; + + auto condition = loop->getChild("condition"); + auto body = loop->getChildren("body"); + + oss << "(while "; + if (condition) { + oss << generate(condition); + } else { + oss << "t"; // fallback to infinite loop + } + oss << "\n"; + + for (size_t i = 0; i < body.size(); ++i) { + if (i > 0) oss << "\n"; + oss << " " << generate(body[i]); + } + + oss << ")"; + return oss.str(); + } + + std::string visitForLoop(const ForLoop* loop) override { + std::ostringstream oss; + + auto iterable = loop->getChild("iterable"); + auto body = loop->getChildren("body"); + + std::string iterator = loop->iteratorName.empty() ? "item" : loop->iteratorName; + + oss << "(dolist (" << iterator << " "; + if (iterable) { + oss << generate(iterable); + } else { + oss << "nil"; // fallback + } + oss << ")\n"; + + for (size_t i = 0; i < body.size(); ++i) { + if (i > 0) oss << "\n"; + oss << " " << generate(body[i]); + } + + oss << ")"; + return oss.str(); + } + + std::string visitExpressionStatement(const ExpressionStatement* stmt) override { + std::ostringstream oss; + + auto expr = stmt->getChild("expression"); + if (expr) { + oss << generate(expr); + } else { + oss << "nil"; + } + + return oss.str(); + } + + std::string visitUnaryOperation(const UnaryOperation* unOp) override { + std::ostringstream oss; + + auto operand = unOp->getChild("operand"); + std::string op = unOp->op; + + // Map unary operators to Elisp equivalents + if (op == "!") op = "not"; + else if (op == "-") op = "-"; + else if (op == "+") op = "+"; + + oss << "(" << op << " "; + if (operand) { + oss << generate(operand); + } else { + oss << "nil"; + } + oss << ")"; + + return oss.str(); + } + + std::string visitFunctionCall(const FunctionCall* call) override { + std::ostringstream oss; + oss << "(" << call->functionName; + + auto arguments = call->getChildren("arguments"); + for (const auto* arg : arguments) { + oss << " " << generate(arg); + } + + oss << ")"; + + return oss.str(); + } + + std::string visitBlock(const Block* block) override { + std::ostringstream oss; + + auto statements = block->getChildren("statements"); + if (statements.empty()) { + oss << "nil"; + } else { + oss << "(progn\n"; + for (size_t i = 0; i < statements.size(); ++i) { + oss << " " << generate(statements[i]); + if (i < statements.size() - 1) oss << "\n"; + } + oss << ")"; + } + + return oss.str(); + } + + std::string visitListLiteral(const ListLiteral* lit) override { + std::ostringstream oss; + oss << "(list"; + + auto elements = lit->getChildren("elements"); + for (const auto* elem : elements) { + oss << " " << generate(elem); + } + + oss << ")"; + + return oss.str(); + } + + std::string visitIndexAccess(const IndexAccess* access) override { + std::ostringstream oss; + + auto target = access->getChild("target"); + auto index = access->getChild("index"); + + if (target && index) { + // In Elisp, accessing list/array elements + oss << "(nth " << generate(index) << " " << generate(target) << ")"; + } else if (target) { + oss << generate(target); + } else { + oss << "nil"; + } + + return oss.str(); + } + + std::string visitMemberAccess(const MemberAccess* access) override { + std::ostringstream oss; + + auto target = access->getChild("target"); + if (target) { + // In Elisp, member access depends on the data structure + // For now, we'll represent it as a function call + oss << "(." << access->memberName << " " << generate(target) << ")"; + } else { + oss << access->memberName; + } + + return oss.str(); + } + + std::string visitPrimitiveType(const PrimitiveType* type) override { + // In Elisp, types are more dynamic, but we can represent them as symbols + std::string kind = type->kind; + if (kind == "int") return "integer"; + if (kind == "float") return "float"; + if (kind == "string") return "string"; + if (kind == "bool") return "boolean"; + if (kind == "char") return "character"; + if (kind == "double") return "float"; + if (kind == "long") return "integer"; + if (kind == "short") return "integer"; + if (kind == "byte") return "integer"; + if (kind == "void") return "null"; + + return kind; // Return as-is if not a common type + } + + std::string visitListType(const ListType* type) override { + std::ostringstream oss; + oss << "(list "; + + auto elementType = type->getChild("elementType"); + if (elementType) { + oss << generate(elementType); + } else { + oss << "t"; // t means any type in Elisp + } + + oss << ")"; + return oss.str(); + } + + std::string visitSetType(const SetType* type) override { + std::ostringstream oss; + oss << "(hash-table :test 'equal)"; + return oss.str(); // Represent sets as hash tables with equal test + } + + std::string visitMapType(const MapType* type) override { + std::ostringstream oss; + oss << "(hash-table :test 'equal)"; + return oss.str(); // Represent maps as hash tables + } + + std::string visitTupleType(const TupleType* type) override { + std::ostringstream oss; + oss << "(vector"; // Represent tuples as vectors + + auto elementTypes = type->getChildren("elementTypes"); + for (const auto* elemType : elementTypes) { + oss << " " << generate(elemType); + } + + oss << ")"; + return oss.str(); + } + + std::string visitArrayType(const ArrayType* type) override { + std::ostringstream oss; + oss << "(vector "; + + auto elementType = type->getChild("elementType"); + if (elementType) { + oss << generate(elementType); + } else { + oss << "t"; + } + + oss << ")"; + return oss.str(); + } + + std::string visitOptionalType(const OptionalType* type) override { + std::ostringstream oss; + oss << "(or null "; + + auto innerType = type->getChild("innerType"); + if (innerType) { + oss << generate(innerType); + } else { + oss << "t"; + } + + oss << ")"; + return oss.str(); + } + + std::string visitCustomType(const CustomType* type) override { + return type->typeName; + } + + std::string visitDerefStrategy(const DerefStrategy* annotation) override { + // Convert deref strategy to Elisp annotation/comment + if (annotation->strategy == "batched") { + return "; @deref(batched) - Process in batches for efficiency"; + } else if (annotation->strategy == "streamed") { + return "; @deref(streamed) - Stream processing"; + } else if (annotation->strategy == "manual") { + return "; @deref(manual) - Manual memory management"; + } else { + return "; @deref(" + annotation->strategy + ") - Memory dereference strategy"; + } + } + + std::string visitOptimizationLock(const OptimizationLock* annotation) override { + return "; @lock(" + annotation->lockedBy + ") - Optimization locked by " + annotation->lockedBy; + } + + std::string visitLangSpecific(const LangSpecific* annotation) override { + return "; @lang_specific(" + annotation->language + ", " + annotation->idiomType + ")"; + } + + std::string visitDeallocateAnnotation(const DeallocateAnnotation* annotation) override { + return "; @dealloc(" + annotation->strategy + ")"; + } + + std::string visitLifetimeAnnotation(const LifetimeAnnotation* annotation) override { + return "; @lifetime(" + annotation->strategy + ")"; + } + + std::string visitReclaimAnnotation(const ReclaimAnnotation* annotation) override { + return "; @reclaim(" + annotation->strategy + ")"; + } + + std::string visitOwnerAnnotation(const OwnerAnnotation* annotation) override { + return "; @owner(" + annotation->strategy + ")"; + } + + std::string visitAllocateAnnotation(const AllocateAnnotation* annotation) override { + return "; @allocate(" + annotation->strategy + ")"; + } + + std::string visitHotColdAnnotation(const HotColdAnnotation* annotation) override { + return "; @" + annotation->hint + " - Optimization hint"; + } + + std::string visitInlineAnnotation(const InlineAnnotation* annotation) override { + return "; @inline(" + annotation->mode + ")"; + } + + std::string visitPureAnnotation(const PureAnnotation*) override { + return "; @pure - No side effects"; + } + + std::string visitConstExprAnnotation(const ConstExprAnnotation*) override { + return "; @constexpr - Compile-time evaluable"; + } +}; diff --git a/editor/src/ast/Generator.h b/editor/src/ast/Generator.h index 83d0cb0..6973387 100644 --- a/editor/src/ast/Generator.h +++ b/editor/src/ast/Generator.h @@ -1,2151 +1,7 @@ #pragma once -#include -#include -#include -#include -#include "ASTNode.h" -#include "Module.h" -#include "Function.h" -#include "Variable.h" -#include "Parameter.h" -#include "Statement.h" -#include "Expression.h" -#include "Type.h" -#include "Annotation.h" - -class ProjectionGenerator { -public: - virtual ~ProjectionGenerator() = default; - - virtual std::string generate(const ASTNode* node) = 0; - virtual std::string visitModule(const Module* module) = 0; - virtual std::string visitFunction(const Function* function) = 0; - virtual std::string visitVariable(const Variable* variable) = 0; - virtual std::string visitParameter(const Parameter* parameter) = 0; - virtual std::string visitAssignment(const Assignment* assignment) = 0; - virtual std::string visitReturn(const Return* ret) = 0; - virtual std::string visitBinaryOperation(const BinaryOperation* binOp) = 0; - virtual std::string visitVariableReference(const VariableReference* varRef) = 0; - virtual std::string visitIntegerLiteral(const IntegerLiteral* lit) = 0; - virtual std::string visitFloatLiteral(const FloatLiteral* lit) = 0; - virtual std::string visitStringLiteral(const StringLiteral* lit) = 0; - virtual std::string visitBooleanLiteral(const BooleanLiteral* lit) = 0; - virtual std::string visitNullLiteral(const NullLiteral* lit) = 0; - virtual std::string visitIfStatement(const IfStatement* stmt) = 0; - virtual std::string visitWhileLoop(const WhileLoop* loop) = 0; - virtual std::string visitForLoop(const ForLoop* loop) = 0; - virtual std::string visitExpressionStatement(const ExpressionStatement* stmt) = 0; - virtual std::string visitUnaryOperation(const UnaryOperation* unOp) = 0; - virtual std::string visitFunctionCall(const FunctionCall* call) = 0; - virtual std::string visitBlock(const Block* block) = 0; - virtual std::string visitListLiteral(const ListLiteral* lit) = 0; - virtual std::string visitIndexAccess(const IndexAccess* access) = 0; - virtual std::string visitMemberAccess(const MemberAccess* access) = 0; - virtual std::string visitPrimitiveType(const PrimitiveType* type) = 0; - virtual std::string visitListType(const ListType* type) = 0; - virtual std::string visitSetType(const SetType* type) = 0; - virtual std::string visitMapType(const MapType* type) = 0; - virtual std::string visitTupleType(const TupleType* type) = 0; - virtual std::string visitArrayType(const ArrayType* type) = 0; - virtual std::string visitOptionalType(const OptionalType* type) = 0; - virtual std::string visitCustomType(const CustomType* type) = 0; - virtual std::string visitDerefStrategy(const DerefStrategy* annotation) = 0; - virtual std::string visitOptimizationLock(const OptimizationLock* annotation) = 0; - virtual std::string visitLangSpecific(const LangSpecific* annotation) = 0; - virtual std::string visitDeallocateAnnotation(const DeallocateAnnotation* annotation) = 0; - virtual std::string visitLifetimeAnnotation(const LifetimeAnnotation* annotation) = 0; - virtual std::string visitReclaimAnnotation(const ReclaimAnnotation* annotation) = 0; - virtual std::string visitOwnerAnnotation(const OwnerAnnotation* annotation) = 0; - virtual std::string visitAllocateAnnotation(const AllocateAnnotation* annotation) = 0; - virtual std::string visitHotColdAnnotation(const HotColdAnnotation* annotation) = 0; - virtual std::string visitInlineAnnotation(const InlineAnnotation* annotation) = 0; - virtual std::string visitPureAnnotation(const PureAnnotation* annotation) = 0; - virtual std::string visitConstExprAnnotation(const ConstExprAnnotation* annotation) = 0; -}; - -class PythonGenerator : public ProjectionGenerator { -public: - std::string generate(const ASTNode* node) override { - if (!node) return ""; - - if (node->conceptType == "Module") { - return visitModule(static_cast(node)); - } else if (node->conceptType == "Function") { - return visitFunction(static_cast(node)); - } else if (node->conceptType == "Variable") { - return visitVariable(static_cast(node)); - } else if (node->conceptType == "Parameter") { - return visitParameter(static_cast(node)); - } else if (node->conceptType == "Assignment") { - return visitAssignment(static_cast(node)); - } else if (node->conceptType == "Return") { - return visitReturn(static_cast(node)); - } else if (node->conceptType == "BinaryOperation") { - return visitBinaryOperation(static_cast(node)); - } else if (node->conceptType == "VariableReference") { - return visitVariableReference(static_cast(node)); - } else if (node->conceptType == "IntegerLiteral") { - return visitIntegerLiteral(static_cast(node)); - } else if (node->conceptType == "FloatLiteral") { - return visitFloatLiteral(static_cast(node)); - } else if (node->conceptType == "StringLiteral") { - return visitStringLiteral(static_cast(node)); - } else if (node->conceptType == "BooleanLiteral") { - return visitBooleanLiteral(static_cast(node)); - } else if (node->conceptType == "NullLiteral") { - return visitNullLiteral(static_cast(node)); - } else if (node->conceptType == "IfStatement") { - return visitIfStatement(static_cast(node)); - } else if (node->conceptType == "WhileLoop") { - return visitWhileLoop(static_cast(node)); - } else if (node->conceptType == "ForLoop") { - return visitForLoop(static_cast(node)); - } else if (node->conceptType == "ExpressionStatement") { - return visitExpressionStatement(static_cast(node)); - } else if (node->conceptType == "UnaryOperation") { - return visitUnaryOperation(static_cast(node)); - } else if (node->conceptType == "FunctionCall") { - return visitFunctionCall(static_cast(node)); - } else if (node->conceptType == "Block") { - return visitBlock(static_cast(node)); - } else if (node->conceptType == "ListLiteral") { - return visitListLiteral(static_cast(node)); - } else if (node->conceptType == "IndexAccess") { - return visitIndexAccess(static_cast(node)); - } else if (node->conceptType == "MemberAccess") { - return visitMemberAccess(static_cast(node)); - } else if (node->conceptType == "PrimitiveType") { - return visitPrimitiveType(static_cast(node)); - } else if (node->conceptType == "ListType") { - return visitListType(static_cast(node)); - } else if (node->conceptType == "SetType") { - return visitSetType(static_cast(node)); - } else if (node->conceptType == "MapType") { - return visitMapType(static_cast(node)); - } else if (node->conceptType == "TupleType") { - return visitTupleType(static_cast(node)); - } else if (node->conceptType == "ArrayType") { - return visitArrayType(static_cast(node)); - } else if (node->conceptType == "OptionalType") { - return visitOptionalType(static_cast(node)); - } else if (node->conceptType == "CustomType") { - return visitCustomType(static_cast(node)); - } else if (node->conceptType == "DerefStrategy") { - return visitDerefStrategy(static_cast(node)); - } else if (node->conceptType == "OptimizationLock") { - return visitOptimizationLock(static_cast(node)); - } else if (node->conceptType == "LangSpecific") { - return visitLangSpecific(static_cast(node)); - } else if (node->conceptType == "DeallocateAnnotation") { - return visitDeallocateAnnotation(static_cast(node)); - } else if (node->conceptType == "LifetimeAnnotation") { - return visitLifetimeAnnotation(static_cast(node)); - } else if (node->conceptType == "ReclaimAnnotation") { - return visitReclaimAnnotation(static_cast(node)); - } else if (node->conceptType == "OwnerAnnotation") { - return visitOwnerAnnotation(static_cast(node)); - } else if (node->conceptType == "AllocateAnnotation") { - return visitAllocateAnnotation(static_cast(node)); - } else if (node->conceptType == "HotColdAnnotation") { - return visitHotColdAnnotation(static_cast(node)); - } else if (node->conceptType == "InlineAnnotation") { - return visitInlineAnnotation(static_cast(node)); - } else if (node->conceptType == "PureAnnotation") { - return visitPureAnnotation(static_cast(node)); - } else if (node->conceptType == "ConstExprAnnotation") { - return visitConstExprAnnotation(static_cast(node)); - } - - return "// Unknown concept: " + node->conceptType; - } - - std::string visitModule(const Module* module) override { - std::ostringstream oss; - - // Add module docstring and basic info - oss << "\"\"\"Module: " << module->name << "\"\"\"\n\n"; - - // Process variables first - auto variables = module->getChildren("variables"); - for (const auto* var : variables) { - oss << visitVariable(static_cast(var)) << "\n"; - } - - if (!variables.empty()) { - oss << "\n"; - } - - // Process functions - auto functions = module->getChildren("functions"); - for (size_t i = 0; i < functions.size(); ++i) { - if (i > 0) oss << "\n"; // Add blank line between functions - oss << visitFunction(static_cast(functions[i])); - } - - return oss.str(); - } - - std::string visitFunction(const Function* function) override { - std::ostringstream oss; - - // Process annotations first (these are in the "annotations" role) - auto annotations = function->getChildren("annotations"); - for (const auto* annotation : annotations) { - oss << generate(annotation) << "\n"; - } - - // Generate function signature - oss << "def " << function->name << "("; - - auto parameters = function->getChildren("parameters"); - for (size_t i = 0; i < parameters.size(); ++i) { - if (i > 0) oss << ", "; - oss << visitParameter(static_cast(parameters[i])); - } - - oss << "):\n"; - - // Process function body - auto body = function->getChildren("body"); - if (body.empty()) { - oss << " pass\n"; - } else { - for (const auto* stmt : body) { - std::string stmtCode = generate(stmt); - // Indent each line of the statement - size_t pos = 0; - while (pos < stmtCode.length()) { - size_t newlinePos = stmtCode.find('\n', pos); - if (newlinePos == std::string::npos) { - oss << " " << stmtCode.substr(pos) << "\n"; - break; - } else { - oss << " " << stmtCode.substr(pos, newlinePos - pos) << "\n"; - pos = newlinePos + 1; - if (pos >= stmtCode.length()) break; - } - } - } - } - - return oss.str(); - } - - std::string visitVariable(const Variable* variable) override { - std::ostringstream oss; - oss << variable->name << " = "; - - auto initializer = variable->getChild("initializer"); - if (initializer) { - oss << generate(initializer); - } else { - oss << "None"; - } - - return oss.str(); - } - - std::string visitParameter(const Parameter* parameter) override { - std::ostringstream oss; - oss << parameter->name; - - // Add type annotation if present - auto type = parameter->getChild("type"); - if (type) { - oss << ": " << generate(type); - } - - // Add default value if present - auto defaultValue = parameter->getChild("defaultValue"); - if (defaultValue) { - oss << " = " << generate(defaultValue); - } - - return oss.str(); - } - - std::string visitAssignment(const Assignment* assignment) override { - std::ostringstream oss; - - auto target = assignment->getChild("target"); - auto value = assignment->getChild("value"); - - if (target) { - oss << generate(target) << " = "; - } - - if (value) { - oss << generate(value); - } else { - oss << "None"; - } - - return oss.str(); - } - - std::string visitReturn(const Return* ret) override { - std::ostringstream oss; - oss << "return "; - - auto value = ret->getChild("value"); - if (value) { - oss << generate(value); - } else { - oss << "None"; - } - - return oss.str(); - } - - std::string visitBinaryOperation(const BinaryOperation* binOp) override { - std::ostringstream oss; - - auto left = binOp->getChild("left"); - auto right = binOp->getChild("right"); - - if (left) { - oss << generate(left); - } - - oss << " " << binOp->op << " "; - - if (right) { - oss << generate(right); - } - - return oss.str(); - } - - std::string visitVariableReference(const VariableReference* varRef) override { - return varRef->variableName; - } - - std::string visitIntegerLiteral(const IntegerLiteral* lit) override { - return std::to_string(lit->value); - } - - std::string visitFloatLiteral(const FloatLiteral* lit) override { - return lit->value; - } - - std::string visitStringLiteral(const StringLiteral* lit) override { - return "\"" + lit->value + "\""; - } - - std::string visitBooleanLiteral(const BooleanLiteral* lit) override { - return lit->value ? "True" : "False"; - } - - std::string visitNullLiteral(const NullLiteral* lit) override { - return "None"; - } - - std::string visitIfStatement(const IfStatement* stmt) override { - std::ostringstream oss; - - auto condition = stmt->getChild("condition"); - auto thenBranch = stmt->getChildren("thenBranch"); - auto elseBranch = stmt->getChildren("elseBranch"); - - if (condition) { - oss << "if " << generate(condition) << ":\n"; - } else { - oss << "if True:\n"; // fallback - } - - // Then branch - for (const auto* thenStmt : thenBranch) { - std::string stmtCode = generate(thenStmt); - size_t pos = 0; - while (pos < stmtCode.length()) { - size_t newlinePos = stmtCode.find('\n', pos); - if (newlinePos == std::string::npos) { - oss << " " << stmtCode.substr(pos) << "\n"; - break; - } else { - oss << " " << stmtCode.substr(pos, newlinePos - pos) << "\n"; - pos = newlinePos + 1; - if (pos >= stmtCode.length()) break; - } - } - } - - // Else branch - if (!elseBranch.empty()) { - oss << "else:\n"; - for (const auto* elseStmt : elseBranch) { - std::string stmtCode = generate(elseStmt); - size_t pos = 0; - while (pos < stmtCode.length()) { - size_t newlinePos = stmtCode.find('\n', pos); - if (newlinePos == std::string::npos) { - oss << " " << stmtCode.substr(pos) << "\n"; - break; - } else { - oss << " " << stmtCode.substr(pos, newlinePos - pos) << "\n"; - pos = newlinePos + 1; - if (pos >= stmtCode.length()) break; - } - } - } - } - - return oss.str(); - } - - std::string visitWhileLoop(const WhileLoop* loop) override { - std::ostringstream oss; - - auto condition = loop->getChild("condition"); - auto body = loop->getChildren("body"); - - if (condition) { - oss << "while " << generate(condition) << ":\n"; - } else { - oss << "while True:\n"; // fallback - } - - for (const auto* stmt : body) { - std::string stmtCode = generate(stmt); - size_t pos = 0; - while (pos < stmtCode.length()) { - size_t newlinePos = stmtCode.find('\n', pos); - if (newlinePos == std::string::npos) { - oss << " " << stmtCode.substr(pos) << "\n"; - break; - } else { - oss << " " << stmtCode.substr(pos, newlinePos - pos) << "\n"; - pos = newlinePos + 1; - if (pos >= stmtCode.length()) break; - } - } - } - - return oss.str(); - } - - std::string visitForLoop(const ForLoop* loop) override { - std::ostringstream oss; - - auto iterable = loop->getChild("iterable"); - auto body = loop->getChildren("body"); - - if (loop->iteratorName.empty()) { - oss << "for item"; - } else { - oss << "for " << loop->iteratorName; - } - - if (iterable) { - oss << " in " << generate(iterable) << ":\n"; - } else { - oss << " in []:\n"; // fallback - } - - for (const auto* stmt : body) { - std::string stmtCode = generate(stmt); - size_t pos = 0; - while (pos < stmtCode.length()) { - size_t newlinePos = stmtCode.find('\n', pos); - if (newlinePos == std::string::npos) { - oss << " " << stmtCode.substr(pos) << "\n"; - break; - } else { - oss << " " << stmtCode.substr(pos, newlinePos - pos) << "\n"; - pos = newlinePos + 1; - if (pos >= stmtCode.length()) break; - } - } - } - - return oss.str(); - } - - std::string visitExpressionStatement(const ExpressionStatement* stmt) override { - std::ostringstream oss; - - auto expr = stmt->getChild("expression"); - if (expr) { - oss << generate(expr); - } - - return oss.str(); - } - - std::string visitUnaryOperation(const UnaryOperation* unOp) override { - std::ostringstream oss; - oss << unOp->op << " "; - - auto operand = unOp->getChild("operand"); - if (operand) { - oss << generate(operand); - } - - return oss.str(); - } - - std::string visitFunctionCall(const FunctionCall* call) override { - std::ostringstream oss; - oss << call->functionName << "("; - - auto arguments = call->getChildren("arguments"); - for (size_t i = 0; i < arguments.size(); ++i) { - if (i > 0) oss << ", "; - oss << generate(arguments[i]); - } - - oss << ")"; - - return oss.str(); - } - - std::string visitBlock(const Block* block) override { - std::ostringstream oss; - - auto statements = block->getChildren("statements"); - for (size_t i = 0; i < statements.size(); ++i) { - if (i > 0) oss << "\n"; - oss << generate(statements[i]); - } - - return oss.str(); - } - - std::string visitListLiteral(const ListLiteral* lit) override { - std::ostringstream oss; - oss << "["; - - auto elements = lit->getChildren("elements"); - for (size_t i = 0; i < elements.size(); ++i) { - if (i > 0) oss << ", "; - oss << generate(elements[i]); - } - - oss << "]"; - - return oss.str(); - } - - std::string visitIndexAccess(const IndexAccess* access) override { - std::ostringstream oss; - - auto target = access->getChild("target"); - auto index = access->getChild("index"); - - if (target) { - oss << generate(target); - } - - oss << "["; - if (index) { - oss << generate(index); - } - oss << "]"; - - return oss.str(); - } - - std::string visitMemberAccess(const MemberAccess* access) override { - std::ostringstream oss; - - auto target = access->getChild("target"); - if (target) { - oss << generate(target); - } - - oss << "." << access->memberName; - - return oss.str(); - } - - std::string visitPrimitiveType(const PrimitiveType* type) override { - std::string kind = type->kind; - // Map common types to Python equivalents - if (kind == "int") return "int"; - if (kind == "float") return "float"; - if (kind == "string") return "str"; - if (kind == "bool") return "bool"; - if (kind == "char") return "str"; // Python doesn't have char, use str - if (kind == "double") return "float"; - if (kind == "long") return "int"; - if (kind == "short") return "int"; - if (kind == "byte") return "int"; - if (kind == "void") return "None"; - - return kind; // Return as-is if not a common type - } - - std::string visitListType(const ListType* type) override { - std::ostringstream oss; - oss << "List["; - - auto elementType = type->getChild("elementType"); - if (elementType) { - oss << generate(elementType); - } else { - oss << "Any"; - } - - oss << "]"; - return oss.str(); - } - - std::string visitSetType(const SetType* type) override { - std::ostringstream oss; - oss << "Set["; - - auto elementType = type->getChild("elementType"); - if (elementType) { - oss << generate(elementType); - } else { - oss << "Any"; - } - - oss << "]"; - return oss.str(); - } - - std::string visitMapType(const MapType* type) override { - std::ostringstream oss; - oss << "Dict["; - - auto keyType = type->getChild("keyType"); - auto valueType = type->getChild("valueType"); - - if (keyType) { - oss << generate(keyType); - } else { - oss << "Any"; - } - - oss << ", "; - - if (valueType) { - oss << generate(valueType); - } else { - oss << "Any"; - } - - oss << "]"; - return oss.str(); - } - - std::string visitTupleType(const TupleType* type) override { - std::ostringstream oss; - oss << "Tuple["; - - auto elementTypes = type->getChildren("elementTypes"); - for (size_t i = 0; i < elementTypes.size(); ++i) { - if (i > 0) oss << ", "; - oss << generate(elementTypes[i]); - } - - oss << "]"; - return oss.str(); - } - - std::string visitArrayType(const ArrayType* type) override { - std::ostringstream oss; - oss << "List["; - - auto elementType = type->getChild("elementType"); - if (elementType) { - oss << generate(elementType); - } else { - oss << "Any"; - } - - oss << "]"; - return oss.str(); - } - - std::string visitOptionalType(const OptionalType* type) override { - std::ostringstream oss; - oss << "Optional["; - - auto innerType = type->getChild("innerType"); - if (innerType) { - oss << generate(innerType); - } else { - oss << "Any"; - } - - oss << "]"; - return oss.str(); - } - - std::string visitCustomType(const CustomType* type) override { - return type->typeName; - } - - std::string visitDerefStrategy(const DerefStrategy* annotation) override { - return "# @deref(" + annotation->strategy + ")"; - } - - std::string visitOptimizationLock(const OptimizationLock* annotation) override { - return "# @lock(" + annotation->lockedBy + ")"; - } - - std::string visitLangSpecific(const LangSpecific* annotation) override { - return "# @lang_specific(" + annotation->language + ", " + annotation->idiomType + ")"; - } - - std::string visitDeallocateAnnotation(const DeallocateAnnotation* annotation) override { - return "# @dealloc(" + annotation->strategy + ") - Memory deallocation strategy"; - } - - std::string visitLifetimeAnnotation(const LifetimeAnnotation* annotation) override { - return "# @lifetime(" + annotation->strategy + ") - Object lifetime management"; - } - - std::string visitReclaimAnnotation(const ReclaimAnnotation* annotation) override { - return "# @reclaim(" + annotation->strategy + ") - Memory reclamation strategy"; - } - - std::string visitOwnerAnnotation(const OwnerAnnotation* annotation) override { - return "# @owner(" + annotation->strategy + ") - Ownership management strategy"; - } - - std::string visitAllocateAnnotation(const AllocateAnnotation* annotation) override { - return "# @allocate(" + annotation->strategy + ") - Memory allocation strategy"; - } - - std::string visitHotColdAnnotation(const HotColdAnnotation* annotation) override { - return "# @" + annotation->hint + " - Optimization hint"; - } - - std::string visitInlineAnnotation(const InlineAnnotation* annotation) override { - return "# @inline(" + annotation->mode + ")"; - } - - std::string visitPureAnnotation(const PureAnnotation*) override { - return "# @pure - No side effects"; - } - - std::string visitConstExprAnnotation(const ConstExprAnnotation*) override { - return "# @constexpr - Compile-time evaluable"; - } -}; - -class ElispGenerator : public ProjectionGenerator { -public: - std::string generate(const ASTNode* node) override { - if (!node) return ""; - - if (node->conceptType == "Module") { - return visitModule(static_cast(node)); - } else if (node->conceptType == "Function") { - return visitFunction(static_cast(node)); - } else if (node->conceptType == "Variable") { - return visitVariable(static_cast(node)); - } else if (node->conceptType == "Parameter") { - return visitParameter(static_cast(node)); - } else if (node->conceptType == "Assignment") { - return visitAssignment(static_cast(node)); - } else if (node->conceptType == "Return") { - return visitReturn(static_cast(node)); - } else if (node->conceptType == "BinaryOperation") { - return visitBinaryOperation(static_cast(node)); - } else if (node->conceptType == "VariableReference") { - return visitVariableReference(static_cast(node)); - } else if (node->conceptType == "IntegerLiteral") { - return visitIntegerLiteral(static_cast(node)); - } else if (node->conceptType == "FloatLiteral") { - return visitFloatLiteral(static_cast(node)); - } else if (node->conceptType == "StringLiteral") { - return visitStringLiteral(static_cast(node)); - } else if (node->conceptType == "BooleanLiteral") { - return visitBooleanLiteral(static_cast(node)); - } else if (node->conceptType == "NullLiteral") { - return visitNullLiteral(static_cast(node)); - } else if (node->conceptType == "IfStatement") { - return visitIfStatement(static_cast(node)); - } else if (node->conceptType == "WhileLoop") { - return visitWhileLoop(static_cast(node)); - } else if (node->conceptType == "ForLoop") { - return visitForLoop(static_cast(node)); - } else if (node->conceptType == "ExpressionStatement") { - return visitExpressionStatement(static_cast(node)); - } else if (node->conceptType == "UnaryOperation") { - return visitUnaryOperation(static_cast(node)); - } else if (node->conceptType == "FunctionCall") { - return visitFunctionCall(static_cast(node)); - } else if (node->conceptType == "Block") { - return visitBlock(static_cast(node)); - } else if (node->conceptType == "ListLiteral") { - return visitListLiteral(static_cast(node)); - } else if (node->conceptType == "IndexAccess") { - return visitIndexAccess(static_cast(node)); - } else if (node->conceptType == "MemberAccess") { - return visitMemberAccess(static_cast(node)); - } else if (node->conceptType == "PrimitiveType") { - return visitPrimitiveType(static_cast(node)); - } else if (node->conceptType == "ListType") { - return visitListType(static_cast(node)); - } else if (node->conceptType == "SetType") { - return visitSetType(static_cast(node)); - } else if (node->conceptType == "MapType") { - return visitMapType(static_cast(node)); - } else if (node->conceptType == "TupleType") { - return visitTupleType(static_cast(node)); - } else if (node->conceptType == "ArrayType") { - return visitArrayType(static_cast(node)); - } else if (node->conceptType == "OptionalType") { - return visitOptionalType(static_cast(node)); - } else if (node->conceptType == "CustomType") { - return visitCustomType(static_cast(node)); - } else if (node->conceptType == "DerefStrategy") { - return visitDerefStrategy(static_cast(node)); - } else if (node->conceptType == "OptimizationLock") { - return visitOptimizationLock(static_cast(node)); - } else if (node->conceptType == "LangSpecific") { - return visitLangSpecific(static_cast(node)); - } else if (node->conceptType == "DeallocateAnnotation") { - return visitDeallocateAnnotation(static_cast(node)); - } else if (node->conceptType == "LifetimeAnnotation") { - return visitLifetimeAnnotation(static_cast(node)); - } else if (node->conceptType == "ReclaimAnnotation") { - return visitReclaimAnnotation(static_cast(node)); - } else if (node->conceptType == "OwnerAnnotation") { - return visitOwnerAnnotation(static_cast(node)); - } else if (node->conceptType == "AllocateAnnotation") { - return visitAllocateAnnotation(static_cast(node)); - } else if (node->conceptType == "HotColdAnnotation") { - return visitHotColdAnnotation(static_cast(node)); - } else if (node->conceptType == "InlineAnnotation") { - return visitInlineAnnotation(static_cast(node)); - } else if (node->conceptType == "PureAnnotation") { - return visitPureAnnotation(static_cast(node)); - } else if (node->conceptType == "ConstExprAnnotation") { - return visitConstExprAnnotation(static_cast(node)); - } - - return "; Unknown concept: " + node->conceptType; - } - - std::string visitModule(const Module* module) override { - std::ostringstream oss; - - oss << "; Module: " << module->name << "\n\n"; - - // Process variables first - auto variables = module->getChildren("variables"); - for (const auto* var : variables) { - oss << visitVariable(static_cast(var)) << "\n"; - } - - if (!variables.empty()) { - oss << "\n"; - } - - // Process functions - auto functions = module->getChildren("functions"); - for (size_t i = 0; i < functions.size(); ++i) { - if (i > 0) oss << "\n"; // Add blank line between functions - oss << visitFunction(static_cast(functions[i])); - } - - return oss.str(); - } - - std::string visitFunction(const Function* function) override { - std::ostringstream oss; - - // Process annotations first - auto annotations = function->getChildren("annotations"); - for (const auto* annotation : annotations) { - std::string annotationCode = generate(annotation); - if (!annotationCode.empty()) { - oss << annotationCode << "\n"; - } - } - - // Generate function definition in Elisp - oss << "(defun " << function->name << " ("; - - auto parameters = function->getChildren("parameters"); - for (size_t i = 0; i < parameters.size(); ++i) { - if (i > 0) oss << " "; - oss << visitParameter(static_cast(parameters[i])); - } - - oss << ")\n"; - - // Process function body - auto body = function->getChildren("body"); - if (body.empty()) { - oss << " nil)\n"; // Return nil if no body - } else { - for (const auto* stmt : body) { - std::string stmtCode = generate(stmt); - // Indent each line of the statement - size_t pos = 0; - while (pos < stmtCode.length()) { - size_t newlinePos = stmtCode.find('\n', pos); - if (newlinePos == std::string::npos) { - oss << " " << stmtCode.substr(pos) << "\n"; - break; - } else { - oss << " " << stmtCode.substr(pos, newlinePos - pos) << "\n"; - pos = newlinePos + 1; - if (pos >= stmtCode.length()) break; - } - } - } - oss << ")\n"; // Close the function - } - - return oss.str(); - } - - std::string visitVariable(const Variable* variable) override { - std::ostringstream oss; - oss << "(defvar " << variable->name << " "; - - auto initializer = variable->getChild("initializer"); - if (initializer) { - oss << generate(initializer); - } else { - oss << "nil"; - } - - oss << ") ; Variable declaration\n"; - return oss.str(); - } - - std::string visitParameter(const Parameter* parameter) override { - // In Elisp, parameters are just symbols - return parameter->name; - } - - std::string visitAssignment(const Assignment* assignment) override { - std::ostringstream oss; - - auto target = assignment->getChild("target"); - auto value = assignment->getChild("value"); - - if (target && value) { - oss << "(setq " << generate(target) << " " << generate(value) << ")"; - } else if (target) { - oss << "(setq " << generate(target) << " nil)"; - } else { - oss << "nil"; - } - - return oss.str(); - } - - std::string visitReturn(const Return* ret) override { - // In Elisp, the last expression in a function is the return value - // So we just return the value expression - auto value = ret->getChild("value"); - if (value) { - return generate(value); - } else { - return "nil"; - } - } - - std::string visitBinaryOperation(const BinaryOperation* binOp) override { - std::ostringstream oss; - - auto left = binOp->getChild("left"); - auto right = binOp->getChild("right"); - - // Map operators to Elisp equivalents - std::string op = binOp->op; - if (op == "+") op = "+"; - else if (op == "-") op = "-"; - else if (op == "*") op = "*"; - else if (op == "/") op = "/"; - else if (op == "==") op = "="; - else if (op == "!=") op = "/="; - else if (op == "<") op = "<"; - else if (op == ">") op = ">"; - else if (op == "<=") op = "<="; - else if (op == ">=") op = ">="; - else if (op == "and") op = "and"; - else if (op == "or") op = "or"; - - oss << "(" << op << " "; - if (left) { - oss << generate(left); - } else { - oss << "nil"; - } - oss << " "; - if (right) { - oss << generate(right); - } else { - oss << "nil"; - } - oss << ")"; - - return oss.str(); - } - - std::string visitVariableReference(const VariableReference* varRef) override { - return varRef->variableName; - } - - std::string visitIntegerLiteral(const IntegerLiteral* lit) override { - return std::to_string(lit->value); - } - - std::string visitFloatLiteral(const FloatLiteral* lit) override { - return lit->value; - } - - std::string visitStringLiteral(const StringLiteral* lit) override { - // In Elisp, strings are enclosed in double quotes - return "\"" + lit->value + "\""; - } - - std::string visitBooleanLiteral(const BooleanLiteral* lit) override { - return lit->value ? "t" : "nil"; - } - - std::string visitNullLiteral(const NullLiteral* lit) override { - return "nil"; - } - - std::string visitIfStatement(const IfStatement* stmt) override { - std::ostringstream oss; - - auto condition = stmt->getChild("condition"); - auto thenBranch = stmt->getChildren("thenBranch"); - auto elseBranch = stmt->getChildren("elseBranch"); - - oss << "(if "; - if (condition) { - oss << generate(condition); - } else { - oss << "nil"; // fallback - } - oss << "\n"; - - // Then branch - if (!thenBranch.empty()) { - for (size_t i = 0; i < thenBranch.size(); ++i) { - if (i > 0) oss << "\n"; - oss << " " << generate(thenBranch[i]); - } - } else { - oss << " nil"; - } - - // Else branch - if (!elseBranch.empty()) { - oss << "\n"; - for (size_t i = 0; i < elseBranch.size(); ++i) { - if (i > 0) oss << "\n"; - oss << " " << generate(elseBranch[i]); - } - } else { - oss << "\n nil"; - } - - oss << ")"; - return oss.str(); - } - - std::string visitWhileLoop(const WhileLoop* loop) override { - std::ostringstream oss; - - auto condition = loop->getChild("condition"); - auto body = loop->getChildren("body"); - - oss << "(while "; - if (condition) { - oss << generate(condition); - } else { - oss << "t"; // fallback to infinite loop - } - oss << "\n"; - - for (size_t i = 0; i < body.size(); ++i) { - if (i > 0) oss << "\n"; - oss << " " << generate(body[i]); - } - - oss << ")"; - return oss.str(); - } - - std::string visitForLoop(const ForLoop* loop) override { - std::ostringstream oss; - - auto iterable = loop->getChild("iterable"); - auto body = loop->getChildren("body"); - - std::string iterator = loop->iteratorName.empty() ? "item" : loop->iteratorName; - - oss << "(dolist (" << iterator << " "; - if (iterable) { - oss << generate(iterable); - } else { - oss << "nil"; // fallback - } - oss << ")\n"; - - for (size_t i = 0; i < body.size(); ++i) { - if (i > 0) oss << "\n"; - oss << " " << generate(body[i]); - } - - oss << ")"; - return oss.str(); - } - - std::string visitExpressionStatement(const ExpressionStatement* stmt) override { - std::ostringstream oss; - - auto expr = stmt->getChild("expression"); - if (expr) { - oss << generate(expr); - } else { - oss << "nil"; - } - - return oss.str(); - } - - std::string visitUnaryOperation(const UnaryOperation* unOp) override { - std::ostringstream oss; - - auto operand = unOp->getChild("operand"); - std::string op = unOp->op; - - // Map unary operators to Elisp equivalents - if (op == "!") op = "not"; - else if (op == "-") op = "-"; - else if (op == "+") op = "+"; - - oss << "(" << op << " "; - if (operand) { - oss << generate(operand); - } else { - oss << "nil"; - } - oss << ")"; - - return oss.str(); - } - - std::string visitFunctionCall(const FunctionCall* call) override { - std::ostringstream oss; - oss << "(" << call->functionName; - - auto arguments = call->getChildren("arguments"); - for (const auto* arg : arguments) { - oss << " " << generate(arg); - } - - oss << ")"; - - return oss.str(); - } - - std::string visitBlock(const Block* block) override { - std::ostringstream oss; - - auto statements = block->getChildren("statements"); - if (statements.empty()) { - oss << "nil"; - } else { - oss << "(progn\n"; - for (size_t i = 0; i < statements.size(); ++i) { - oss << " " << generate(statements[i]); - if (i < statements.size() - 1) oss << "\n"; - } - oss << ")"; - } - - return oss.str(); - } - - std::string visitListLiteral(const ListLiteral* lit) override { - std::ostringstream oss; - oss << "(list"; - - auto elements = lit->getChildren("elements"); - for (const auto* elem : elements) { - oss << " " << generate(elem); - } - - oss << ")"; - - return oss.str(); - } - - std::string visitIndexAccess(const IndexAccess* access) override { - std::ostringstream oss; - - auto target = access->getChild("target"); - auto index = access->getChild("index"); - - if (target && index) { - // In Elisp, accessing list/array elements - oss << "(nth " << generate(index) << " " << generate(target) << ")"; - } else if (target) { - oss << generate(target); - } else { - oss << "nil"; - } - - return oss.str(); - } - - std::string visitMemberAccess(const MemberAccess* access) override { - std::ostringstream oss; - - auto target = access->getChild("target"); - if (target) { - // In Elisp, member access depends on the data structure - // For now, we'll represent it as a function call - oss << "(." << access->memberName << " " << generate(target) << ")"; - } else { - oss << access->memberName; - } - - return oss.str(); - } - - std::string visitPrimitiveType(const PrimitiveType* type) override { - // In Elisp, types are more dynamic, but we can represent them as symbols - std::string kind = type->kind; - if (kind == "int") return "integer"; - if (kind == "float") return "float"; - if (kind == "string") return "string"; - if (kind == "bool") return "boolean"; - if (kind == "char") return "character"; - if (kind == "double") return "float"; - if (kind == "long") return "integer"; - if (kind == "short") return "integer"; - if (kind == "byte") return "integer"; - if (kind == "void") return "null"; - - return kind; // Return as-is if not a common type - } - - std::string visitListType(const ListType* type) override { - std::ostringstream oss; - oss << "(list "; - - auto elementType = type->getChild("elementType"); - if (elementType) { - oss << generate(elementType); - } else { - oss << "t"; // t means any type in Elisp - } - - oss << ")"; - return oss.str(); - } - - std::string visitSetType(const SetType* type) override { - std::ostringstream oss; - oss << "(hash-table :test 'equal)"; - return oss.str(); // Represent sets as hash tables with equal test - } - - std::string visitMapType(const MapType* type) override { - std::ostringstream oss; - oss << "(hash-table :test 'equal)"; - return oss.str(); // Represent maps as hash tables - } - - std::string visitTupleType(const TupleType* type) override { - std::ostringstream oss; - oss << "(vector"; // Represent tuples as vectors - - auto elementTypes = type->getChildren("elementTypes"); - for (const auto* elemType : elementTypes) { - oss << " " << generate(elemType); - } - - oss << ")"; - return oss.str(); - } - - std::string visitArrayType(const ArrayType* type) override { - std::ostringstream oss; - oss << "(vector "; - - auto elementType = type->getChild("elementType"); - if (elementType) { - oss << generate(elementType); - } else { - oss << "t"; - } - - oss << ")"; - return oss.str(); - } - - std::string visitOptionalType(const OptionalType* type) override { - std::ostringstream oss; - oss << "(or null "; - - auto innerType = type->getChild("innerType"); - if (innerType) { - oss << generate(innerType); - } else { - oss << "t"; - } - - oss << ")"; - return oss.str(); - } - - std::string visitCustomType(const CustomType* type) override { - return type->typeName; - } - - std::string visitDerefStrategy(const DerefStrategy* annotation) override { - // Convert deref strategy to Elisp annotation/comment - if (annotation->strategy == "batched") { - return "; @deref(batched) - Process in batches for efficiency"; - } else if (annotation->strategy == "streamed") { - return "; @deref(streamed) - Stream processing"; - } else if (annotation->strategy == "manual") { - return "; @deref(manual) - Manual memory management"; - } else { - return "; @deref(" + annotation->strategy + ") - Memory dereference strategy"; - } - } - - std::string visitOptimizationLock(const OptimizationLock* annotation) override { - return "; @lock(" + annotation->lockedBy + ") - Optimization locked by " + annotation->lockedBy; - } - - std::string visitLangSpecific(const LangSpecific* annotation) override { - return "; @lang_specific(" + annotation->language + ", " + annotation->idiomType + ")"; - } - - std::string visitDeallocateAnnotation(const DeallocateAnnotation* annotation) override { - return "; @dealloc(" + annotation->strategy + ")"; - } - - std::string visitLifetimeAnnotation(const LifetimeAnnotation* annotation) override { - return "; @lifetime(" + annotation->strategy + ")"; - } - - std::string visitReclaimAnnotation(const ReclaimAnnotation* annotation) override { - return "; @reclaim(" + annotation->strategy + ")"; - } - - std::string visitOwnerAnnotation(const OwnerAnnotation* annotation) override { - return "; @owner(" + annotation->strategy + ")"; - } - - std::string visitAllocateAnnotation(const AllocateAnnotation* annotation) override { - return "; @allocate(" + annotation->strategy + ")"; - } - - std::string visitHotColdAnnotation(const HotColdAnnotation* annotation) override { - return "; @" + annotation->hint + " - Optimization hint"; - } - - std::string visitInlineAnnotation(const InlineAnnotation* annotation) override { - return "; @inline(" + annotation->mode + ")"; - } - - std::string visitPureAnnotation(const PureAnnotation*) override { - return "; @pure - No side effects"; - } - - std::string visitConstExprAnnotation(const ConstExprAnnotation*) override { - return "; @constexpr - Compile-time evaluable"; - } -}; - -class CppGenerator : public ProjectionGenerator { -public: - std::string generate(const ASTNode* node) override { - if (!node) return ""; - - if (node->conceptType == "Module") { - return visitModule(static_cast(node)); - } else if (node->conceptType == "Function") { - return visitFunction(static_cast(node)); - } else if (node->conceptType == "Variable") { - return visitVariable(static_cast(node)); - } else if (node->conceptType == "Parameter") { - return visitParameter(static_cast(node)); - } else if (node->conceptType == "Assignment") { - return visitAssignment(static_cast(node)); - } else if (node->conceptType == "Return") { - return visitReturn(static_cast(node)); - } else if (node->conceptType == "BinaryOperation") { - return visitBinaryOperation(static_cast(node)); - } else if (node->conceptType == "VariableReference") { - return visitVariableReference(static_cast(node)); - } else if (node->conceptType == "IntegerLiteral") { - return visitIntegerLiteral(static_cast(node)); - } else if (node->conceptType == "FloatLiteral") { - return visitFloatLiteral(static_cast(node)); - } else if (node->conceptType == "StringLiteral") { - return visitStringLiteral(static_cast(node)); - } else if (node->conceptType == "BooleanLiteral") { - return visitBooleanLiteral(static_cast(node)); - } else if (node->conceptType == "NullLiteral") { - return visitNullLiteral(static_cast(node)); - } else if (node->conceptType == "IfStatement") { - return visitIfStatement(static_cast(node)); - } else if (node->conceptType == "WhileLoop") { - return visitWhileLoop(static_cast(node)); - } else if (node->conceptType == "ForLoop") { - return visitForLoop(static_cast(node)); - } else if (node->conceptType == "ExpressionStatement") { - return visitExpressionStatement(static_cast(node)); - } else if (node->conceptType == "UnaryOperation") { - return visitUnaryOperation(static_cast(node)); - } else if (node->conceptType == "FunctionCall") { - return visitFunctionCall(static_cast(node)); - } else if (node->conceptType == "Block") { - return visitBlock(static_cast(node)); - } else if (node->conceptType == "ListLiteral") { - return visitListLiteral(static_cast(node)); - } else if (node->conceptType == "IndexAccess") { - return visitIndexAccess(static_cast(node)); - } else if (node->conceptType == "MemberAccess") { - return visitMemberAccess(static_cast(node)); - } else if (node->conceptType == "PrimitiveType") { - return visitPrimitiveType(static_cast(node)); - } else if (node->conceptType == "ListType") { - return visitListType(static_cast(node)); - } else if (node->conceptType == "SetType") { - return visitSetType(static_cast(node)); - } else if (node->conceptType == "MapType") { - return visitMapType(static_cast(node)); - } else if (node->conceptType == "TupleType") { - return visitTupleType(static_cast(node)); - } else if (node->conceptType == "ArrayType") { - return visitArrayType(static_cast(node)); - } else if (node->conceptType == "OptionalType") { - return visitOptionalType(static_cast(node)); - } else if (node->conceptType == "CustomType") { - return visitCustomType(static_cast(node)); - } else if (node->conceptType == "DerefStrategy") { - return visitDerefStrategy(static_cast(node)); - } else if (node->conceptType == "OptimizationLock") { - return visitOptimizationLock(static_cast(node)); - } else if (node->conceptType == "LangSpecific") { - return visitLangSpecific(static_cast(node)); - } else if (node->conceptType == "DeallocateAnnotation") { - return visitDeallocateAnnotation(static_cast(node)); - } else if (node->conceptType == "LifetimeAnnotation") { - return visitLifetimeAnnotation(static_cast(node)); - } else if (node->conceptType == "ReclaimAnnotation") { - return visitReclaimAnnotation(static_cast(node)); - } else if (node->conceptType == "OwnerAnnotation") { - return visitOwnerAnnotation(static_cast(node)); - } else if (node->conceptType == "AllocateAnnotation") { - return visitAllocateAnnotation(static_cast(node)); - } else if (node->conceptType == "HotColdAnnotation") { - return visitHotColdAnnotation(static_cast(node)); - } else if (node->conceptType == "InlineAnnotation") { - return visitInlineAnnotation(static_cast(node)); - } else if (node->conceptType == "PureAnnotation") { - return visitPureAnnotation(static_cast(node)); - } else if (node->conceptType == "ConstExprAnnotation") { - return visitConstExprAnnotation(static_cast(node)); - } - - return "// Unknown concept: " + node->conceptType; - } - - std::string visitModule(const Module* module) override { - std::ostringstream oss; - - // Generate namespace wrapper - oss << "namespace " << module->name << " {\n\n"; - - // Process variables first - auto variables = module->getChildren("variables"); - for (const auto* var : variables) { - oss << visitVariable(static_cast(var)) << "\n"; - } - - if (!variables.empty()) { - oss << "\n"; - } - - // Process functions - auto functions = module->getChildren("functions"); - for (size_t i = 0; i < functions.size(); ++i) { - if (i > 0) oss << "\n"; // Add blank line between functions - oss << visitFunction(static_cast(functions[i])); - } - - oss << "\n} // namespace " << module->name << "\n"; - return oss.str(); - } - - std::string visitFunction(const Function* function) override { - std::ostringstream oss; - - // Process annotations first (these are in the "annotations" role) - auto annotations = function->getChildren("annotations"); - for (const auto* annotation : annotations) { - std::string annotationCode = generate(annotation); - if (!annotationCode.empty() && annotationCode != "// Unknown concept: Annotation") { - oss << annotationCode << "\n"; - } - } - - // Generate function return type - std::string returnTypeStr = "void"; // Default - auto returnType = function->getChild("returnType"); - if (returnType) { - returnTypeStr = generate(returnType); - } - - // Generate function signature - oss << returnTypeStr << " " << function->name << "("; - - auto parameters = function->getChildren("parameters"); - for (size_t i = 0; i < parameters.size(); ++i) { - if (i > 0) oss << ", "; - oss << visitParameter(static_cast(parameters[i])); - } - - oss << ") {\n"; - - // Process function body - auto body = function->getChildren("body"); - if (body.empty()) { - oss << " // Function body is empty\n"; - oss << " return;"; - if (returnTypeStr != "void") { - oss << " // TODO: return appropriate value"; - } - oss << "\n"; - } else { - for (const auto* stmt : body) { - std::string stmtCode = generate(stmt); - // Indent each line of the statement - size_t pos = 0; - while (pos < stmtCode.length()) { - size_t newlinePos = stmtCode.find('\n', pos); - if (newlinePos == std::string::npos) { - oss << " " << stmtCode.substr(pos) << "\n"; - break; - } else { - oss << " " << stmtCode.substr(pos, newlinePos - pos) << "\n"; - pos = newlinePos + 1; - if (pos >= stmtCode.length()) break; - } - } - } - } - - oss << "}\n"; - - return oss.str(); - } - - std::string visitVariable(const Variable* variable) override { - std::ostringstream oss; - - auto type = variable->getChild("type"); - std::string typeStr = "auto"; // Default - if (type) { - typeStr = generate(type); - } - - // Check enclosing function for memory annotations that affect variable types - std::string wrapper = getMemoryTypeWrapper(variable); - if (!wrapper.empty()) { - oss << wrapper << "<" << typeStr << "> " << variable->name; - } else { - oss << typeStr << " " << variable->name; - } - - auto initializer = variable->getChild("initializer"); - if (initializer) { - oss << " = " << generate(initializer); - } - - oss << ";"; - - return oss.str(); - } - - std::string visitParameter(const Parameter* parameter) override { - std::ostringstream oss; - - auto type = parameter->getChild("type"); - std::string typeStr = "auto"; // Default - if (type) { - typeStr = generate(type); - } - - oss << typeStr << " " << parameter->name; - - // Add default value if present - auto defaultValue = parameter->getChild("defaultValue"); - if (defaultValue) { - oss << " = " << generate(defaultValue); - } - - return oss.str(); - } - - std::string visitAssignment(const Assignment* assignment) override { - std::ostringstream oss; - - auto target = assignment->getChild("target"); - auto value = assignment->getChild("value"); - - if (target) { - oss << generate(target) << " = "; - } else { - oss << "/* missing target */ "; - } - - if (value) { - oss << generate(value); - } else { - oss << "/* missing value */"; - } - - oss << ";"; - - return oss.str(); - } - - std::string visitReturn(const Return* ret) override { - std::ostringstream oss; - oss << "return "; - - auto value = ret->getChild("value"); - if (value) { - oss << generate(value); - } else { - // For void returns - oss << ";"; - return oss.str(); - } - - oss << ";"; - return oss.str(); - } - - std::string visitBinaryOperation(const BinaryOperation* binOp) override { - std::ostringstream oss; - - auto left = binOp->getChild("left"); - auto right = binOp->getChild("right"); - - if (left) { - oss << generate(left); - } else { - oss << "/* missing left */"; - } - - oss << " " << binOp->op << " "; - - if (right) { - oss << generate(right); - } else { - oss << "/* missing right */"; - } - - return oss.str(); - } - - std::string visitVariableReference(const VariableReference* varRef) override { - return varRef->variableName; - } - - std::string visitIntegerLiteral(const IntegerLiteral* lit) override { - return std::to_string(lit->value); - } - - std::string visitFloatLiteral(const FloatLiteral* lit) override { - return lit->value; - } - - std::string visitStringLiteral(const StringLiteral* lit) override { - // In C++, strings need to be enclosed in double quotes - return "\"" + lit->value + "\""; - } - - std::string visitBooleanLiteral(const BooleanLiteral* lit) override { - return lit->value ? "true" : "false"; - } - - std::string visitNullLiteral(const NullLiteral* lit) override { - return "nullptr"; - } - - std::string visitIfStatement(const IfStatement* stmt) override { - std::ostringstream oss; - - auto condition = stmt->getChild("condition"); - auto thenBranch = stmt->getChildren("thenBranch"); - auto elseBranch = stmt->getChildren("elseBranch"); - - oss << "if ("; - if (condition) { - oss << generate(condition); - } else { - oss << "true"; // fallback - } - oss << ") {\n"; - - // Then branch - for (const auto* thenStmt : thenBranch) { - std::string stmtCode = generate(thenStmt); - size_t pos = 0; - while (pos < stmtCode.length()) { - size_t newlinePos = stmtCode.find('\n', pos); - if (newlinePos == std::string::npos) { - oss << " " << stmtCode.substr(pos) << "\n"; - break; - } else { - oss << " " << stmtCode.substr(pos, newlinePos - pos) << "\n"; - pos = newlinePos + 1; - if (pos >= stmtCode.length()) break; - } - } - } - oss << "}"; - - // Else branch - if (!elseBranch.empty()) { - oss << " else {\n"; - for (const auto* elseStmt : elseBranch) { - std::string stmtCode = generate(elseStmt); - size_t pos = 0; - while (pos < stmtCode.length()) { - size_t newlinePos = stmtCode.find('\n', pos); - if (newlinePos == std::string::npos) { - oss << " " << stmtCode.substr(pos) << "\n"; - break; - } else { - oss << " " << stmtCode.substr(pos, newlinePos - pos) << "\n"; - pos = newlinePos + 1; - if (pos >= stmtCode.length()) break; - } - } - } - oss << "}"; - } - - return oss.str(); - } - - std::string visitWhileLoop(const WhileLoop* loop) override { - std::ostringstream oss; - - auto condition = loop->getChild("condition"); - auto body = loop->getChildren("body"); - - oss << "while ("; - if (condition) { - oss << generate(condition); - } else { - oss << "true"; // fallback to infinite loop - } - oss << ") {\n"; - - for (const auto* stmt : body) { - std::string stmtCode = generate(stmt); - size_t pos = 0; - while (pos < stmtCode.length()) { - size_t newlinePos = stmtCode.find('\n', pos); - if (newlinePos == std::string::npos) { - oss << " " << stmtCode.substr(pos) << "\n"; - break; - } else { - oss << " " << stmtCode.substr(pos, newlinePos - pos) << "\n"; - pos = newlinePos + 1; - if (pos >= stmtCode.length()) break; - } - } - } - - oss << "}"; - return oss.str(); - } - - std::string visitForLoop(const ForLoop* loop) override { - std::ostringstream oss; - - auto iterable = loop->getChild("iterable"); - auto body = loop->getChildren("body"); - - std::string iterator = loop->iteratorName.empty() ? "item" : loop->iteratorName; - - oss << "for (auto& " << iterator << " : "; - if (iterable) { - oss << generate(iterable); - } else { - oss << "/* missing iterable */"; // fallback - } - oss << ") {\n"; - - for (const auto* stmt : body) { - std::string stmtCode = generate(stmt); - size_t pos = 0; - while (pos < stmtCode.length()) { - size_t newlinePos = stmtCode.find('\n', pos); - if (newlinePos == std::string::npos) { - oss << " " << stmtCode.substr(pos) << "\n"; - break; - } else { - oss << " " << stmtCode.substr(pos, newlinePos - pos) << "\n"; - pos = newlinePos + 1; - if (pos >= stmtCode.length()) break; - } - } - } - - oss << "}"; - return oss.str(); - } - - std::string visitExpressionStatement(const ExpressionStatement* stmt) override { - std::ostringstream oss; - - auto expr = stmt->getChild("expression"); - if (expr) { - oss << generate(expr) << ";"; - } else { - oss << ";"; // Empty statement - } - - return oss.str(); - } - - std::string visitUnaryOperation(const UnaryOperation* unOp) override { - std::ostringstream oss; - - auto operand = unOp->getChild("operand"); - std::string op = unOp->op; - - oss << op << " "; - if (operand) { - oss << generate(operand); - } else { - oss << "/* missing operand */"; - } - - return oss.str(); - } - - std::string visitFunctionCall(const FunctionCall* call) override { - std::ostringstream oss; - oss << call->functionName << "("; - - auto arguments = call->getChildren("arguments"); - for (size_t i = 0; i < arguments.size(); ++i) { - if (i > 0) oss << ", "; - oss << generate(arguments[i]); - } - - oss << ")"; - - return oss.str(); - } - - std::string visitBlock(const Block* block) override { - std::ostringstream oss; - - auto statements = block->getChildren("statements"); - if (statements.empty()) { - oss << "{}"; - } else { - oss << "{\n"; - for (size_t i = 0; i < statements.size(); ++i) { - std::string stmtCode = generate(statements[i]); - size_t pos = 0; - while (pos < stmtCode.length()) { - size_t newlinePos = stmtCode.find('\n', pos); - if (newlinePos == std::string::npos) { - oss << " " << stmtCode.substr(pos) << "\n"; - break; - } else { - oss << " " << stmtCode.substr(pos, newlinePos - pos) << "\n"; - pos = newlinePos + 1; - if (pos >= stmtCode.length()) break; - } - } - if (i < statements.size() - 1) oss << "\n"; - } - oss << "}"; - } - - return oss.str(); - } - - std::string visitListLiteral(const ListLiteral* lit) override { - std::ostringstream oss; - oss << "{"; - - auto elements = lit->getChildren("elements"); - for (size_t i = 0; i < elements.size(); ++i) { - if (i > 0) oss << ", "; - oss << generate(elements[i]); - } - - oss << "}"; - - return oss.str(); - } - - std::string visitIndexAccess(const IndexAccess* access) override { - std::ostringstream oss; - - auto target = access->getChild("target"); - auto index = access->getChild("index"); - - if (target) { - oss << generate(target); - } else { - oss << "/* missing target */"; - } - - oss << "["; - if (index) { - oss << generate(index); - } else { - oss << "/* missing index */"; - } - oss << "]"; - - return oss.str(); - } - - std::string visitMemberAccess(const MemberAccess* access) override { - std::ostringstream oss; - - auto target = access->getChild("target"); - if (target) { - oss << generate(target); - } else { - oss << "/* missing target */"; - } - - oss << "." << access->memberName; - - return oss.str(); - } - - std::string visitPrimitiveType(const PrimitiveType* type) override { - std::string kind = type->kind; - // Map common types to C++ equivalents - if (kind == "int") return "int"; - if (kind == "float") return "float"; - if (kind == "string") return "std::string"; - if (kind == "bool") return "bool"; - if (kind == "char") return "char"; - if (kind == "double") return "double"; - if (kind == "long") return "long"; - if (kind == "short") return "short"; - if (kind == "byte") return "char"; // C++ doesn't have byte, use char - if (kind == "void") return "void"; - - return kind; // Return as-is if not a common type - } - - std::string visitListType(const ListType* type) override { - std::ostringstream oss; - oss << "std::vector<"; - - auto elementType = type->getChild("elementType"); - if (elementType) { - oss << generate(elementType); - } else { - oss << "auto"; // Default if no element type specified - } - - oss << ">"; - return oss.str(); - } - - std::string visitSetType(const SetType* type) override { - std::ostringstream oss; - oss << "std::set<"; - - auto elementType = type->getChild("elementType"); - if (elementType) { - oss << generate(elementType); - } else { - oss << "auto"; // Default if no element type specified - } - - oss << ">"; - return oss.str(); - } - - std::string visitMapType(const MapType* type) override { - std::ostringstream oss; - oss << "std::map<"; - - auto keyType = type->getChild("keyType"); - auto valueType = type->getChild("valueType"); - - if (keyType) { - oss << generate(keyType); - } else { - oss << "auto"; // Default if no key type specified - } - - oss << ", "; - - if (valueType) { - oss << generate(valueType); - } else { - oss << "auto"; // Default if no value type specified - } - - oss << ">"; - return oss.str(); - } - - std::string visitTupleType(const TupleType* type) override { - std::ostringstream oss; - oss << "std::tuple<"; - - auto elementTypes = type->getChildren("elementTypes"); - for (size_t i = 0; i < elementTypes.size(); ++i) { - if (i > 0) oss << ", "; - oss << generate(elementTypes[i]); - } - - oss << ">"; - return oss.str(); - } - - std::string visitArrayType(const ArrayType* type) override { - std::ostringstream oss; - oss << "std::array<"; - - auto elementType = type->getChild("elementType"); - if (elementType) { - oss << generate(elementType); - } else { - oss << "auto"; // Default if no element type specified - } - - oss << ", /* size unknown */>"; - return oss.str(); - } - - std::string visitOptionalType(const OptionalType* type) override { - std::ostringstream oss; - oss << "std::optional<"; - - auto innerType = type->getChild("innerType"); - if (innerType) { - oss << generate(innerType); - } else { - oss << "auto"; // Default if no inner type specified - } - - oss << ">"; - return oss.str(); - } - - std::string visitCustomType(const CustomType* type) override { - return type->typeName; - } - - std::string visitDerefStrategy(const DerefStrategy* annotation) override { - // Generate C++-style comment for deref strategy - if (annotation->strategy == "batched") { - return "// @deref(batched) - Use batched memory management (smart pointers)"; - } else if (annotation->strategy == "streamed") { - return "// @deref(streamed) - Use streamed memory management (RAII)"; - } else if (annotation->strategy == "manual") { - return "// @deref(manual) - Use manual memory management (new/delete)"; - } else { - return "// @deref(" + annotation->strategy + ") - Memory management strategy"; - } - } - - std::string visitOptimizationLock(const OptimizationLock* annotation) override { - return "// @lock(" + annotation->lockedBy + ") - Optimization locked: " + annotation->lockReason; - } - - std::string visitLangSpecific(const LangSpecific* annotation) override { - return "// @lang_specific(" + annotation->language + ", " + annotation->idiomType + ")"; - } - - std::string visitDeallocateAnnotation(const DeallocateAnnotation* annotation) override { - return "// @dealloc(" + annotation->strategy + ") - Memory deallocation strategy"; - } - - std::string visitLifetimeAnnotation(const LifetimeAnnotation* annotation) override { - return "// @lifetime(" + annotation->strategy + ") - Object lifetime management"; - } - - std::string visitReclaimAnnotation(const ReclaimAnnotation* annotation) override { - return "// @reclaim(" + annotation->strategy + ") - Memory reclamation strategy"; - } - - std::string visitOwnerAnnotation(const OwnerAnnotation* annotation) override { - return "// @owner(" + annotation->strategy + ") - Ownership management strategy"; - } - - std::string visitAllocateAnnotation(const AllocateAnnotation* annotation) override { - return "// @allocate(" + annotation->strategy + ") - Memory allocation strategy"; - } - - std::string visitHotColdAnnotation(const HotColdAnnotation* annotation) override { - if (annotation->hint == "Hot") - return "__attribute__((hot))"; - if (annotation->hint == "Cold") - return "__attribute__((cold))"; - return "// @hotcold(" + annotation->hint + ")"; - } - - std::string visitInlineAnnotation(const InlineAnnotation* annotation) override { - if (annotation->mode == "Always") - return "[[gnu::always_inline]] inline"; - if (annotation->mode == "Never") - return "__attribute__((noinline))"; - return "inline"; - } - - std::string visitPureAnnotation(const PureAnnotation*) override { - return "[[nodiscard]]"; - } - - std::string visitConstExprAnnotation(const ConstExprAnnotation*) override { - return "constexpr"; - } - -private: - // Check enclosing function's memory annotations to determine smart-pointer wrapper - std::string getMemoryTypeWrapper(const Variable* variable) const { - const ASTNode* cur = variable->parent; - while (cur && cur->conceptType != "Function") { - cur = cur->parent; - } - if (!cur) return ""; - - for (auto* anno : cur->getChildren("annotations")) { - if (anno->conceptType == "ReclaimAnnotation") { - auto* ra = static_cast(anno); - if (ra->strategy == "Tracing" || ra->strategy == "Cycle") - return "std::shared_ptr"; - } - if (anno->conceptType == "LifetimeAnnotation") { - auto* la = static_cast(anno); - if (la->strategy == "RAII") - return "std::unique_ptr"; - } - if (anno->conceptType == "OwnerAnnotation") { - auto* oa = static_cast(anno); - if (oa->strategy == "Shared_ARC") return "std::shared_ptr"; - if (oa->strategy == "Single") return "std::unique_ptr"; - } - } - return ""; - } -}; \ No newline at end of file +// Convenience header: includes all generators. +// Existing code that includes Generator.h continues to work unchanged. +#include "ProjectionGenerator.h" +#include "PythonGenerator.h" +#include "ElispGenerator.h" +#include "CppGenerator.h" diff --git a/editor/src/ast/ProjectionGenerator.h b/editor/src/ast/ProjectionGenerator.h new file mode 100644 index 0000000..e36545a --- /dev/null +++ b/editor/src/ast/ProjectionGenerator.h @@ -0,0 +1,161 @@ +#pragma once +#include +#include +#include +#include +#include "ASTNode.h" +#include "Module.h" +#include "Function.h" +#include "Variable.h" +#include "Parameter.h" +#include "Statement.h" +#include "Expression.h" +#include "Type.h" +#include "Annotation.h" + +class ProjectionGenerator { +public: + virtual ~ProjectionGenerator() = default; + + virtual std::string generate(const ASTNode* node) = 0; + virtual std::string visitModule(const Module* module) = 0; + virtual std::string visitFunction(const Function* function) = 0; + virtual std::string visitVariable(const Variable* variable) = 0; + virtual std::string visitParameter(const Parameter* parameter) = 0; + virtual std::string visitAssignment(const Assignment* assignment) = 0; + virtual std::string visitReturn(const Return* ret) = 0; + virtual std::string visitBinaryOperation(const BinaryOperation* binOp) = 0; + virtual std::string visitVariableReference(const VariableReference* varRef) = 0; + virtual std::string visitIntegerLiteral(const IntegerLiteral* lit) = 0; + virtual std::string visitFloatLiteral(const FloatLiteral* lit) = 0; + virtual std::string visitStringLiteral(const StringLiteral* lit) = 0; + virtual std::string visitBooleanLiteral(const BooleanLiteral* lit) = 0; + virtual std::string visitNullLiteral(const NullLiteral* lit) = 0; + virtual std::string visitIfStatement(const IfStatement* stmt) = 0; + virtual std::string visitWhileLoop(const WhileLoop* loop) = 0; + virtual std::string visitForLoop(const ForLoop* loop) = 0; + virtual std::string visitExpressionStatement(const ExpressionStatement* stmt) = 0; + virtual std::string visitUnaryOperation(const UnaryOperation* unOp) = 0; + virtual std::string visitFunctionCall(const FunctionCall* call) = 0; + virtual std::string visitBlock(const Block* block) = 0; + virtual std::string visitListLiteral(const ListLiteral* lit) = 0; + virtual std::string visitIndexAccess(const IndexAccess* access) = 0; + virtual std::string visitMemberAccess(const MemberAccess* access) = 0; + virtual std::string visitPrimitiveType(const PrimitiveType* type) = 0; + virtual std::string visitListType(const ListType* type) = 0; + virtual std::string visitSetType(const SetType* type) = 0; + virtual std::string visitMapType(const MapType* type) = 0; + virtual std::string visitTupleType(const TupleType* type) = 0; + virtual std::string visitArrayType(const ArrayType* type) = 0; + virtual std::string visitOptionalType(const OptionalType* type) = 0; + virtual std::string visitCustomType(const CustomType* type) = 0; + virtual std::string visitDerefStrategy(const DerefStrategy* annotation) = 0; + virtual std::string visitOptimizationLock(const OptimizationLock* annotation) = 0; + virtual std::string visitLangSpecific(const LangSpecific* annotation) = 0; + virtual std::string visitDeallocateAnnotation(const DeallocateAnnotation* annotation) = 0; + virtual std::string visitLifetimeAnnotation(const LifetimeAnnotation* annotation) = 0; + virtual std::string visitReclaimAnnotation(const ReclaimAnnotation* annotation) = 0; + virtual std::string visitOwnerAnnotation(const OwnerAnnotation* annotation) = 0; + virtual std::string visitAllocateAnnotation(const AllocateAnnotation* annotation) = 0; + virtual std::string visitHotColdAnnotation(const HotColdAnnotation* annotation) = 0; + virtual std::string visitInlineAnnotation(const InlineAnnotation* annotation) = 0; + virtual std::string visitPureAnnotation(const PureAnnotation* annotation) = 0; + virtual std::string visitConstExprAnnotation(const ConstExprAnnotation* annotation) = 0; +}; + +// Shared dispatch: maps conceptType string to the appropriate visit call. +// Each generator's generate() calls this instead of duplicating the dispatch chain. +template +std::string dispatchGenerate(Gen* gen, const ASTNode* node, const std::string& unknownPrefix) { + if (!node) return ""; + + if (node->conceptType == "Module") { + return gen->visitModule(static_cast(node)); + } else if (node->conceptType == "Function") { + return gen->visitFunction(static_cast(node)); + } else if (node->conceptType == "Variable") { + return gen->visitVariable(static_cast(node)); + } else if (node->conceptType == "Parameter") { + return gen->visitParameter(static_cast(node)); + } else if (node->conceptType == "Assignment") { + return gen->visitAssignment(static_cast(node)); + } else if (node->conceptType == "Return") { + return gen->visitReturn(static_cast(node)); + } else if (node->conceptType == "BinaryOperation") { + return gen->visitBinaryOperation(static_cast(node)); + } else if (node->conceptType == "VariableReference") { + return gen->visitVariableReference(static_cast(node)); + } else if (node->conceptType == "IntegerLiteral") { + return gen->visitIntegerLiteral(static_cast(node)); + } else if (node->conceptType == "FloatLiteral") { + return gen->visitFloatLiteral(static_cast(node)); + } else if (node->conceptType == "StringLiteral") { + return gen->visitStringLiteral(static_cast(node)); + } else if (node->conceptType == "BooleanLiteral") { + return gen->visitBooleanLiteral(static_cast(node)); + } else if (node->conceptType == "NullLiteral") { + return gen->visitNullLiteral(static_cast(node)); + } else if (node->conceptType == "IfStatement") { + return gen->visitIfStatement(static_cast(node)); + } else if (node->conceptType == "WhileLoop") { + return gen->visitWhileLoop(static_cast(node)); + } else if (node->conceptType == "ForLoop") { + return gen->visitForLoop(static_cast(node)); + } else if (node->conceptType == "ExpressionStatement") { + return gen->visitExpressionStatement(static_cast(node)); + } else if (node->conceptType == "UnaryOperation") { + return gen->visitUnaryOperation(static_cast(node)); + } else if (node->conceptType == "FunctionCall") { + return gen->visitFunctionCall(static_cast(node)); + } else if (node->conceptType == "Block") { + return gen->visitBlock(static_cast(node)); + } else if (node->conceptType == "ListLiteral") { + return gen->visitListLiteral(static_cast(node)); + } else if (node->conceptType == "IndexAccess") { + return gen->visitIndexAccess(static_cast(node)); + } else if (node->conceptType == "MemberAccess") { + return gen->visitMemberAccess(static_cast(node)); + } else if (node->conceptType == "PrimitiveType") { + return gen->visitPrimitiveType(static_cast(node)); + } else if (node->conceptType == "ListType") { + return gen->visitListType(static_cast(node)); + } else if (node->conceptType == "SetType") { + return gen->visitSetType(static_cast(node)); + } else if (node->conceptType == "MapType") { + return gen->visitMapType(static_cast(node)); + } else if (node->conceptType == "TupleType") { + return gen->visitTupleType(static_cast(node)); + } else if (node->conceptType == "ArrayType") { + return gen->visitArrayType(static_cast(node)); + } else if (node->conceptType == "OptionalType") { + return gen->visitOptionalType(static_cast(node)); + } else if (node->conceptType == "CustomType") { + return gen->visitCustomType(static_cast(node)); + } else if (node->conceptType == "DerefStrategy") { + return gen->visitDerefStrategy(static_cast(node)); + } else if (node->conceptType == "OptimizationLock") { + return gen->visitOptimizationLock(static_cast(node)); + } else if (node->conceptType == "LangSpecific") { + return gen->visitLangSpecific(static_cast(node)); + } else if (node->conceptType == "DeallocateAnnotation") { + return gen->visitDeallocateAnnotation(static_cast(node)); + } else if (node->conceptType == "LifetimeAnnotation") { + return gen->visitLifetimeAnnotation(static_cast(node)); + } else if (node->conceptType == "ReclaimAnnotation") { + return gen->visitReclaimAnnotation(static_cast(node)); + } else if (node->conceptType == "OwnerAnnotation") { + return gen->visitOwnerAnnotation(static_cast(node)); + } else if (node->conceptType == "AllocateAnnotation") { + return gen->visitAllocateAnnotation(static_cast(node)); + } else if (node->conceptType == "HotColdAnnotation") { + return gen->visitHotColdAnnotation(static_cast(node)); + } else if (node->conceptType == "InlineAnnotation") { + return gen->visitInlineAnnotation(static_cast(node)); + } else if (node->conceptType == "PureAnnotation") { + return gen->visitPureAnnotation(static_cast(node)); + } else if (node->conceptType == "ConstExprAnnotation") { + return gen->visitConstExprAnnotation(static_cast(node)); + } + + return unknownPrefix + node->conceptType; +} diff --git a/editor/src/ast/PythonGenerator.h b/editor/src/ast/PythonGenerator.h new file mode 100644 index 0000000..e3e5690 --- /dev/null +++ b/editor/src/ast/PythonGenerator.h @@ -0,0 +1,577 @@ +#pragma once + +#include "ProjectionGenerator.h" + +class PythonGenerator : public ProjectionGenerator { +public: + std::string generate(const ASTNode* node) override { + return dispatchGenerate(this, node, "# Unknown concept: "); + } + + std::string visitModule(const Module* module) override { + std::ostringstream oss; + + // Add module docstring and basic info + oss << "\"\"\"Module: " << module->name << "\"\"\"\n\n"; + + // Process variables first + auto variables = module->getChildren("variables"); + for (const auto* var : variables) { + oss << visitVariable(static_cast(var)) << "\n"; + } + + if (!variables.empty()) { + oss << "\n"; + } + + // Process functions + auto functions = module->getChildren("functions"); + for (size_t i = 0; i < functions.size(); ++i) { + if (i > 0) oss << "\n"; // Add blank line between functions + oss << visitFunction(static_cast(functions[i])); + } + + return oss.str(); + } + + std::string visitFunction(const Function* function) override { + std::ostringstream oss; + + // Process annotations first (these are in the "annotations" role) + auto annotations = function->getChildren("annotations"); + for (const auto* annotation : annotations) { + oss << generate(annotation) << "\n"; + } + + // Generate function signature + oss << "def " << function->name << "("; + + auto parameters = function->getChildren("parameters"); + for (size_t i = 0; i < parameters.size(); ++i) { + if (i > 0) oss << ", "; + oss << visitParameter(static_cast(parameters[i])); + } + + oss << "):\n"; + + // Process function body + auto body = function->getChildren("body"); + if (body.empty()) { + oss << " pass\n"; + } else { + for (const auto* stmt : body) { + std::string stmtCode = generate(stmt); + // Indent each line of the statement + size_t pos = 0; + while (pos < stmtCode.length()) { + size_t newlinePos = stmtCode.find('\n', pos); + if (newlinePos == std::string::npos) { + oss << " " << stmtCode.substr(pos) << "\n"; + break; + } else { + oss << " " << stmtCode.substr(pos, newlinePos - pos) << "\n"; + pos = newlinePos + 1; + if (pos >= stmtCode.length()) break; + } + } + } + } + + return oss.str(); + } + + std::string visitVariable(const Variable* variable) override { + std::ostringstream oss; + oss << variable->name << " = "; + + auto initializer = variable->getChild("initializer"); + if (initializer) { + oss << generate(initializer); + } else { + oss << "None"; + } + + return oss.str(); + } + + std::string visitParameter(const Parameter* parameter) override { + std::ostringstream oss; + oss << parameter->name; + + // Add type annotation if present + auto type = parameter->getChild("type"); + if (type) { + oss << ": " << generate(type); + } + + // Add default value if present + auto defaultValue = parameter->getChild("defaultValue"); + if (defaultValue) { + oss << " = " << generate(defaultValue); + } + + return oss.str(); + } + + std::string visitAssignment(const Assignment* assignment) override { + std::ostringstream oss; + + auto target = assignment->getChild("target"); + auto value = assignment->getChild("value"); + + if (target) { + oss << generate(target) << " = "; + } + + if (value) { + oss << generate(value); + } else { + oss << "None"; + } + + return oss.str(); + } + + std::string visitReturn(const Return* ret) override { + std::ostringstream oss; + oss << "return "; + + auto value = ret->getChild("value"); + if (value) { + oss << generate(value); + } else { + oss << "None"; + } + + return oss.str(); + } + + std::string visitBinaryOperation(const BinaryOperation* binOp) override { + std::ostringstream oss; + + auto left = binOp->getChild("left"); + auto right = binOp->getChild("right"); + + if (left) { + oss << generate(left); + } + + oss << " " << binOp->op << " "; + + if (right) { + oss << generate(right); + } + + return oss.str(); + } + + std::string visitVariableReference(const VariableReference* varRef) override { + return varRef->variableName; + } + + std::string visitIntegerLiteral(const IntegerLiteral* lit) override { + return std::to_string(lit->value); + } + + std::string visitFloatLiteral(const FloatLiteral* lit) override { + return lit->value; + } + + std::string visitStringLiteral(const StringLiteral* lit) override { + return "\"" + lit->value + "\""; + } + + std::string visitBooleanLiteral(const BooleanLiteral* lit) override { + return lit->value ? "True" : "False"; + } + + std::string visitNullLiteral(const NullLiteral* lit) override { + return "None"; + } + + std::string visitIfStatement(const IfStatement* stmt) override { + std::ostringstream oss; + + auto condition = stmt->getChild("condition"); + auto thenBranch = stmt->getChildren("thenBranch"); + auto elseBranch = stmt->getChildren("elseBranch"); + + if (condition) { + oss << "if " << generate(condition) << ":\n"; + } else { + oss << "if True:\n"; // fallback + } + + // Then branch + for (const auto* thenStmt : thenBranch) { + std::string stmtCode = generate(thenStmt); + size_t pos = 0; + while (pos < stmtCode.length()) { + size_t newlinePos = stmtCode.find('\n', pos); + if (newlinePos == std::string::npos) { + oss << " " << stmtCode.substr(pos) << "\n"; + break; + } else { + oss << " " << stmtCode.substr(pos, newlinePos - pos) << "\n"; + pos = newlinePos + 1; + if (pos >= stmtCode.length()) break; + } + } + } + + // Else branch + if (!elseBranch.empty()) { + oss << "else:\n"; + for (const auto* elseStmt : elseBranch) { + std::string stmtCode = generate(elseStmt); + size_t pos = 0; + while (pos < stmtCode.length()) { + size_t newlinePos = stmtCode.find('\n', pos); + if (newlinePos == std::string::npos) { + oss << " " << stmtCode.substr(pos) << "\n"; + break; + } else { + oss << " " << stmtCode.substr(pos, newlinePos - pos) << "\n"; + pos = newlinePos + 1; + if (pos >= stmtCode.length()) break; + } + } + } + } + + return oss.str(); + } + + std::string visitWhileLoop(const WhileLoop* loop) override { + std::ostringstream oss; + + auto condition = loop->getChild("condition"); + auto body = loop->getChildren("body"); + + if (condition) { + oss << "while " << generate(condition) << ":\n"; + } else { + oss << "while True:\n"; // fallback + } + + for (const auto* stmt : body) { + std::string stmtCode = generate(stmt); + size_t pos = 0; + while (pos < stmtCode.length()) { + size_t newlinePos = stmtCode.find('\n', pos); + if (newlinePos == std::string::npos) { + oss << " " << stmtCode.substr(pos) << "\n"; + break; + } else { + oss << " " << stmtCode.substr(pos, newlinePos - pos) << "\n"; + pos = newlinePos + 1; + if (pos >= stmtCode.length()) break; + } + } + } + + return oss.str(); + } + + std::string visitForLoop(const ForLoop* loop) override { + std::ostringstream oss; + + auto iterable = loop->getChild("iterable"); + auto body = loop->getChildren("body"); + + if (loop->iteratorName.empty()) { + oss << "for item"; + } else { + oss << "for " << loop->iteratorName; + } + + if (iterable) { + oss << " in " << generate(iterable) << ":\n"; + } else { + oss << " in []:\n"; // fallback + } + + for (const auto* stmt : body) { + std::string stmtCode = generate(stmt); + size_t pos = 0; + while (pos < stmtCode.length()) { + size_t newlinePos = stmtCode.find('\n', pos); + if (newlinePos == std::string::npos) { + oss << " " << stmtCode.substr(pos) << "\n"; + break; + } else { + oss << " " << stmtCode.substr(pos, newlinePos - pos) << "\n"; + pos = newlinePos + 1; + if (pos >= stmtCode.length()) break; + } + } + } + + return oss.str(); + } + + std::string visitExpressionStatement(const ExpressionStatement* stmt) override { + std::ostringstream oss; + + auto expr = stmt->getChild("expression"); + if (expr) { + oss << generate(expr); + } + + return oss.str(); + } + + std::string visitUnaryOperation(const UnaryOperation* unOp) override { + std::ostringstream oss; + oss << unOp->op << " "; + + auto operand = unOp->getChild("operand"); + if (operand) { + oss << generate(operand); + } + + return oss.str(); + } + + std::string visitFunctionCall(const FunctionCall* call) override { + std::ostringstream oss; + oss << call->functionName << "("; + + auto arguments = call->getChildren("arguments"); + for (size_t i = 0; i < arguments.size(); ++i) { + if (i > 0) oss << ", "; + oss << generate(arguments[i]); + } + + oss << ")"; + + return oss.str(); + } + + std::string visitBlock(const Block* block) override { + std::ostringstream oss; + + auto statements = block->getChildren("statements"); + for (size_t i = 0; i < statements.size(); ++i) { + if (i > 0) oss << "\n"; + oss << generate(statements[i]); + } + + return oss.str(); + } + + std::string visitListLiteral(const ListLiteral* lit) override { + std::ostringstream oss; + oss << "["; + + auto elements = lit->getChildren("elements"); + for (size_t i = 0; i < elements.size(); ++i) { + if (i > 0) oss << ", "; + oss << generate(elements[i]); + } + + oss << "]"; + + return oss.str(); + } + + std::string visitIndexAccess(const IndexAccess* access) override { + std::ostringstream oss; + + auto target = access->getChild("target"); + auto index = access->getChild("index"); + + if (target) { + oss << generate(target); + } + + oss << "["; + if (index) { + oss << generate(index); + } + oss << "]"; + + return oss.str(); + } + + std::string visitMemberAccess(const MemberAccess* access) override { + std::ostringstream oss; + + auto target = access->getChild("target"); + if (target) { + oss << generate(target); + } + + oss << "." << access->memberName; + + return oss.str(); + } + + std::string visitPrimitiveType(const PrimitiveType* type) override { + std::string kind = type->kind; + // Map common types to Python equivalents + if (kind == "int") return "int"; + if (kind == "float") return "float"; + if (kind == "string") return "str"; + if (kind == "bool") return "bool"; + if (kind == "char") return "str"; // Python doesn't have char, use str + if (kind == "double") return "float"; + if (kind == "long") return "int"; + if (kind == "short") return "int"; + if (kind == "byte") return "int"; + if (kind == "void") return "None"; + + return kind; // Return as-is if not a common type + } + + std::string visitListType(const ListType* type) override { + std::ostringstream oss; + oss << "List["; + + auto elementType = type->getChild("elementType"); + if (elementType) { + oss << generate(elementType); + } else { + oss << "Any"; + } + + oss << "]"; + return oss.str(); + } + + std::string visitSetType(const SetType* type) override { + std::ostringstream oss; + oss << "Set["; + + auto elementType = type->getChild("elementType"); + if (elementType) { + oss << generate(elementType); + } else { + oss << "Any"; + } + + oss << "]"; + return oss.str(); + } + + std::string visitMapType(const MapType* type) override { + std::ostringstream oss; + oss << "Dict["; + + auto keyType = type->getChild("keyType"); + auto valueType = type->getChild("valueType"); + + if (keyType) { + oss << generate(keyType); + } else { + oss << "Any"; + } + + oss << ", "; + + if (valueType) { + oss << generate(valueType); + } else { + oss << "Any"; + } + + oss << "]"; + return oss.str(); + } + + std::string visitTupleType(const TupleType* type) override { + std::ostringstream oss; + oss << "Tuple["; + + auto elementTypes = type->getChildren("elementTypes"); + for (size_t i = 0; i < elementTypes.size(); ++i) { + if (i > 0) oss << ", "; + oss << generate(elementTypes[i]); + } + + oss << "]"; + return oss.str(); + } + + std::string visitArrayType(const ArrayType* type) override { + std::ostringstream oss; + oss << "List["; + + auto elementType = type->getChild("elementType"); + if (elementType) { + oss << generate(elementType); + } else { + oss << "Any"; + } + + oss << "]"; + return oss.str(); + } + + std::string visitOptionalType(const OptionalType* type) override { + std::ostringstream oss; + oss << "Optional["; + + auto innerType = type->getChild("innerType"); + if (innerType) { + oss << generate(innerType); + } else { + oss << "Any"; + } + + oss << "]"; + return oss.str(); + } + + std::string visitCustomType(const CustomType* type) override { + return type->typeName; + } + + std::string visitDerefStrategy(const DerefStrategy* annotation) override { + return "# @deref(" + annotation->strategy + ")"; + } + + std::string visitOptimizationLock(const OptimizationLock* annotation) override { + return "# @lock(" + annotation->lockedBy + ")"; + } + + std::string visitLangSpecific(const LangSpecific* annotation) override { + return "# @lang_specific(" + annotation->language + ", " + annotation->idiomType + ")"; + } + + std::string visitDeallocateAnnotation(const DeallocateAnnotation* annotation) override { + return "# @dealloc(" + annotation->strategy + ") - Memory deallocation strategy"; + } + + std::string visitLifetimeAnnotation(const LifetimeAnnotation* annotation) override { + return "# @lifetime(" + annotation->strategy + ") - Object lifetime management"; + } + + std::string visitReclaimAnnotation(const ReclaimAnnotation* annotation) override { + return "# @reclaim(" + annotation->strategy + ") - Memory reclamation strategy"; + } + + std::string visitOwnerAnnotation(const OwnerAnnotation* annotation) override { + return "# @owner(" + annotation->strategy + ") - Ownership management strategy"; + } + + std::string visitAllocateAnnotation(const AllocateAnnotation* annotation) override { + return "# @allocate(" + annotation->strategy + ") - Memory allocation strategy"; + } + + std::string visitHotColdAnnotation(const HotColdAnnotation* annotation) override { + return "# @" + annotation->hint + " - Optimization hint"; + } + + std::string visitInlineAnnotation(const InlineAnnotation* annotation) override { + return "# @inline(" + annotation->mode + ")"; + } + + std::string visitPureAnnotation(const PureAnnotation*) override { + return "# @pure - No side effects"; + } + + std::string visitConstExprAnnotation(const ConstExprAnnotation*) override { + return "# @constexpr - Compile-time evaluable"; + } +}; diff --git a/editor/src/main.cpp b/editor/src/main.cpp index 14b0669..53ead60 100644 --- a/editor/src/main.cpp +++ b/editor/src/main.cpp @@ -7,1864 +7,13 @@ // - Toolbar + status bar // - Configurable keybinding profiles (VSCode default) -#include "imgui.h" #include "imgui_impl_sdl2.h" #include "imgui_impl_opengl3.h" #include #include -#include "TextEditor.h" -#include "TextASTSync.h" -#include "SyntaxHighlighter.h" -#include "KeybindingManager.h" -#include "BufferManager.h" -#include "CodeEditorWidget.h" -#include "EditorMode.h" -#include "FileDialog.h" -#include "FileTree.h" -#include "WelcomeScreen.h" -#include "DragDropHandler.h" -#include "FileWatcher.h" -#include "LSPClient.h" -#include "Pipeline.h" -#include "Diagnostics.h" -#include "SettingsManager.h" -#include "LayoutManager.h" -#include "ASTMutationAPI.h" -#include "MemoryStrategyInference.h" -#include "AnnotationConflict.h" -#include "StrategyDashboard.h" -#include "TransformEngine.h" -#include "StrategyAwareOptimizer.h" -#include "CrossLanguageProjector.h" -#include "DiffUtils.h" -#include "EditorModePolicy.h" -#include "RefactorActions.h" -#include "CommandPalette.h" -#include "ContextAPI.h" -#include "Breadcrumbs.h" -#include "ProjectSearch.h" -#include "GoToLine.h" -#include "Orchestrator.h" -#include "ProjectManager.h" -#include "SessionManager.h" -#include "ZoomUtils.h" -#include "ast/Serialization.h" -#include "ast/Generator.h" -#include "ast/Annotation.h" - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -// --------------------------------------------------------------------------- -// Editor application state -// --------------------------------------------------------------------------- -struct BufferState { - TextEditor editor; - TextASTSync sync; - CodeEditorWidget widget; - CodeEditorWidget generatedWidget; - EditorMode mode; - EditorMode generatedMode; - std::string language = "python"; - std::string generatedLanguage = "python"; - std::string path = "(untitled)"; - bool readOnly = false; - BufferManager::BufferMode bufferMode = BufferManager::BufferMode::Structured; - bool modified = false; - int cursorLine = 1; - int cursorCol = 1; - int lspVersion = 1; - std::string editBuf; - std::string generatedBuf; - std::vector highlights; - std::vector generatedHighlights; - bool highlightsDirty = true; - bool generatedHighlightsDirty = true; - int generatedHighlightLine = -1; - float splitScrollX = 0.0f; - float splitScrollY = 0.0f; - IncrementalOptimizer incrementalOptimizer; - Orchestrator orchestrator; - bool orchestratorDirty = true; - int undoDepth = 0; -}; - -struct EditorState { - KeybindingManager keys; - BufferManager buffers; - std::map> bufferStates; - BufferState* activeBuffer = nullptr; - - // Find/Replace state - bool showFind = false; - char findBuf[256] = {}; - char replaceBuf[256] = {}; - int lastFindPos = 0; - bool showProjectSearch = false; - char searchQuery[256] = {}; - char searchInclude[256] = {}; - char searchExclude[256] = {}; - bool searchUseRegex = true; - std::vector searchResults; - bool showGoToLine = false; - char goToLineBuf[64] = {}; - bool goToLineError = false; - - // Bottom panel - int bottomTab = 0; // 0=Output, 1=AST, 2=Highlighted - std::string outputLog; - - // Custom editor widget state - bool showWhitespace = false; - bool showMinimap = false; - bool showAnnotations = false; - bool showOutline = true; - bool showLineNumbers = true; - char outlineFilter[128] = {}; - LayoutPreset layoutPreset = LayoutPreset::VSCode; - WelcomeScreen welcome; - std::string workspaceRoot; - FileTree fileTree; - FileNode fileTreeRoot; - bool fileTreeDirty = true; - FileWatcher watcher; - ProjectSearch projectSearch; - std::shared_ptr lspTransport; - std::shared_ptr lsp; - bool symbolsPending = false; - double symbolsLastChange = 0.0; - bool completionPending = false; - bool completionVisible = false; - int completionSelected = 0; - double completionLastChange = 0.0; - bool hoverPending = false; - double hoverLastMove = 0.0; - ImVec2 hoverLastMouse = ImVec2(-1.0f, -1.0f); - bool definitionPending = false; - double definitionLastRequest = 0.0; - int definitionRequestLine = 0; - int definitionRequestCol = 0; - std::string definitionRequestPath; - LSPClient::DefinitionLocation definitionPreview; - bool definitionPreviewValid = false; - Pipeline pipeline; - bool analysisPending = false; - double analysisLastChange = 0.0; - std::vector whetstoneDiagnostics; - SettingsManager settings; - bool showLspSettings = false; - bool showSettingsPanel = false; - double lastAutoSave = 0.0; - ASTMutationAPI mutator; - std::vector suggestions; - bool showSuggestionPopup = false; - MemoryStrategyInference::Suggestion suggestionPopup; - std::string optimizeFoldSummary; - std::string optimizeDeadCodeSummary; - std::string optimizeApplySummary; - bool optimizePreview = false; - bool showRefactorPopup = false; - int refactorAction = 0; // 1=rename,2=extract,3=inline - char refactorNameA[128] = {}; - char refactorNameB[128] = {}; - std::string refactorError; - CommandPalette commandPalette; - std::unordered_map> commandHandlers; - bool showCommandPalette = false; - char commandQuery[128] = {}; - int commandSelected = 0; - struct DiffState { - bool active = false; - bool preview = false; - int action = 0; // 1=constant-fold, 2=dead-code-elim, 3=apply-all - bool batch = false; - std::string beforeText; - std::string afterText; - std::vector transformIds; - std::vector batchMutations; - std::vector beforeLines; - std::vector afterLines; - } diff; - CodeEditorWidget diffLeftWidget; - CodeEditorWidget diffRightWidget; - float diffScrollX = 0.0f; - float diffScrollY = 0.0f; - - BufferState* active() { return activeBuffer; } - bool isStructured() const { - return activeBuffer && allowStructuredFeatures(activeBuffer->bufferMode); - } - Module* activeAST() { - if (!activeBuffer) return nullptr; - if (!allowStructuredFeatures(activeBuffer->bufferMode)) return nullptr; - return activeBuffer->sync.getAST(); - } - - static std::string toFileUri(const std::string& path) { - std::filesystem::path p(path); - return "file:///" + p.generic_string(); - } - - static std::string fromFileUri(const std::string& uri) { - const std::string prefix = "file:///"; - if (uri.rfind(prefix, 0) != 0) return uri; - std::string path = uri.substr(prefix.size()); - std::replace(path.begin(), path.end(), '/', '\\'); - return path; - } - - static int byteOffsetForLineCol(const std::string& text, int lineZero, int colZero) { - if (lineZero < 0) lineZero = 0; - if (colZero < 0) colZero = 0; - int line = 0; - int pos = 0; - while (pos < (int)text.size() && line < lineZero) { - if (text[pos] == '\n') ++line; - ++pos; - } - int col = 0; - while (pos < (int)text.size() && text[pos] != '\n' && col < colZero) { - ++pos; - ++col; - } - return pos; - } - - static int wordStartAt(const std::string& text, int pos) { - pos = std::max(0, std::min(pos, (int)text.size())); - while (pos > 0) { - char c = text[pos - 1]; - if (std::isalnum((unsigned char)c) || c == '_') { - --pos; - } else { - break; - } - } - return pos; - } - - static bool isWordChar(char c) { - return std::isalnum((unsigned char)c) || c == '_'; - } - - static std::string wordAt(const std::string& text, int pos) { - if (text.empty()) return ""; - pos = std::max(0, std::min(pos, (int)text.size())); - if (pos == (int)text.size()) pos = (int)text.size() - 1; - if (pos < 0) return ""; - if (!isWordChar(text[pos]) && pos > 0 && isWordChar(text[pos - 1])) { - --pos; - } - if (!isWordChar(text[pos])) return ""; - int start = pos; - while (start > 0 && isWordChar(text[start - 1])) --start; - int end = pos; - while (end < (int)text.size() && isWordChar(text[end])) ++end; - return text.substr(start, end - start); - } - - static std::string wordAtLineCol(const std::string& text, int lineZero, int colZero) { - int pos = byteOffsetForLineCol(text, lineZero, colZero); - return wordAt(text, pos); - } - - static std::string wordPrefixAt(const std::string& text, int pos) { - int start = wordStartAt(text, pos); - return text.substr(start, pos - start); - } - - void jumpTo(BufferState* buf, int lineZero, int colZero) { - if (!buf) return; - int pos = byteOffsetForLineCol(buf->editBuf, lineZero, colZero); - buf->widget.setCursor(pos); - buf->cursorLine = lineZero + 1; - buf->cursorCol = colZero + 1; - } - - void applyCompletion(BufferState* buf, const std::string& insertText, int replaceStart, int replaceEnd) { - if (!buf) return; - replaceStart = std::max(0, std::min(replaceStart, (int)buf->editBuf.size())); - replaceEnd = std::max(replaceStart, std::min(replaceEnd, (int)buf->editBuf.size())); - buf->editBuf.replace(replaceStart, replaceEnd - replaceStart, insertText); - int newPos = replaceStart + (int)insertText.size(); - buf->widget.setCursor(newPos); - activeBuffer = buf; - onTextChanged(); - } - - std::string makeUntitledName() const { - if (!buffers.hasBuffer("(untitled)")) return "(untitled)"; - int i = 1; - while (true) { - std::string name = "(untitled-" + std::to_string(i) + ")"; - if (!buffers.hasBuffer(name)) return name; - ++i; - } - } - - void createBuffer(const std::string& path, const std::string& content, - const std::string& language, - BufferManager::BufferMode mode = BufferManager::BufferMode::Structured) { - if (buffers.hasBuffer(path)) { - buffers.switchToBuffer(path); - activeBuffer = bufferStates[path].get(); - return; - } - auto state = std::make_unique(); - state->path = path; - state->language = language; - state->generatedLanguage = language; - state->mode.setLanguage(language); - state->generatedMode.setLanguage(language); - state->editor.setContent(content, language); - state->sync.setText(content, language); - state->sync.syncNow(); - state->incrementalOptimizer.setRoot(state->sync.getAST()); - state->editBuf = content; - state->highlightsDirty = true; - state->generatedHighlightsDirty = true; - state->modified = false; - state->lspVersion = 1; - buffers.openBuffer(path, content, language, mode); - state->bufferMode = mode; - activeBuffer = state.get(); - bufferStates[path] = std::move(state); - active()->orchestratorDirty = true; - recordUndoSnapshot(); - if (path.rfind("(untitled", 0) != 0) watcher.watch(path); - if (lsp && path.rfind("(untitled", 0) != 0) { - lsp->didOpen(toFileUri(path), language, content, activeBuffer->lspVersion); - } - symbolsPending = true; - symbolsLastChange = ImGui::GetTime(); - if (lsp) lsp->clearDocumentSymbols(); - } - - bool jumpToDefinitionLocation(const LSPClient::DefinitionLocation& loc) { - if (loc.uri.empty()) return false; - std::string path = fromFileUri(loc.uri); - if (path.empty()) return false; - if (buffers.hasBuffer(path)) switchToBuffer(path); - else doOpen(path); - jumpTo(active(), loc.range.start.line, loc.range.start.character); - return true; - } - - bool goToDefinitionFallback(int lineZero, int colZero) { - if (!isStructured()) return false; - Module* ast = activeAST(); - if (!ast || !active()) return false; - std::string word = wordAtLineCol(active()->editBuf, lineZero, colZero); - if (word.empty()) return false; - - ASTNode* scopeNode = findNodeAtPosition(ast, lineZero, colZero); - if (!scopeNode) scopeNode = ast; - - ContextAPI ctx; - ctx.setRoot(ast); - auto symbols = ctx.getInScopeSymbols(scopeNode->id); - for (const auto& sym : symbols) { - if (sym.name != word) continue; - ASTNode* target = findNodeById(ast, sym.nodeId); - if (target && target->hasSpan()) { - jumpTo(active(), target->spanStartLine, target->spanStartCol); - return true; - } - } - return false; - } - - bool goToDefinitionAt(int lineZero, int colZero) { - if (!active()) return false; - if (lsp && lspTransport && lspTransport->isOpen() && - active()->path.rfind("(untitled", 0) != 0) { - definitionPreviewValid = false; - definitionPending = true; - definitionLastRequest = ImGui::GetTime(); - definitionRequestLine = lineZero; - definitionRequestCol = colZero; - definitionRequestPath = active()->path; - lsp->clearDefinitionLocations(); - lsp->requestDefinition(toFileUri(active()->path), lineZero, colZero); - return true; - } - return goToDefinitionFallback(lineZero, colZero); - } - - bool previewDefinitionAt(int lineZero, int colZero, std::string& outLabel) { - if (!isStructured() || !active()) return false; - Module* ast = activeAST(); - if (!ast) return false; - std::string word = wordAtLineCol(active()->editBuf, lineZero, colZero); - if (word.empty()) return false; - ASTNode* scopeNode = findNodeAtPosition(ast, lineZero, colZero); - if (!scopeNode) scopeNode = ast; - ContextAPI ctx; - ctx.setRoot(ast); - auto symbols = ctx.getInScopeSymbols(scopeNode->id); - for (const auto& sym : symbols) { - if (sym.name != word) continue; - ASTNode* target = findNodeById(ast, sym.nodeId); - if (target && target->hasSpan()) { - outLabel = word + " -> line " + std::to_string(target->spanStartLine + 1); - return true; - } - } - return false; - } - - void switchToBuffer(const std::string& path) { - if (!buffers.hasBuffer(path)) return; - buffers.switchToBuffer(path); - activeBuffer = bufferStates[path].get(); - if (active()) active()->orchestratorDirty = true; - symbolsPending = true; - symbolsLastChange = ImGui::GetTime(); - if (lsp) lsp->clearDocumentSymbols(); - } - - std::filesystem::path configDir() const { - const char* home = std::getenv("USERPROFILE"); - if (!home) home = std::getenv("HOME"); - std::filesystem::path base = home ? std::filesystem::path(home) : std::filesystem::current_path(); - return base / ".whetstone"; - } - - std::filesystem::path recentFilePath() const { - return configDir() / "recent.json"; - } - - void loadRecentFiles() { - std::filesystem::path p = recentFilePath(); - if (!std::filesystem::exists(p)) return; - std::ifstream in(p.string()); - if (!in.is_open()) return; - nlohmann::json j; - in >> j; - if (!j.is_array()) return; - for (const auto& item : j) { - if (!item.contains("path")) continue; - std::string path = item.value("path", ""); - std::string lang = item.value("language", ""); - std::string mode = item.value("mode", "structured"); - if (!path.empty()) welcome.addRecentFile(path, lang, mode); - } - } - - void saveRecentFiles() { - std::filesystem::path p = recentFilePath(); - std::filesystem::create_directories(p.parent_path()); - nlohmann::json j = nlohmann::json::array(); - for (const auto& rf : welcome.getRecentFiles()) { - j.push_back({{"path", rf.path}, {"language", rf.language}, {"mode", rf.mode}}); - } - std::ofstream out(p.string(), std::ios::binary); - out << j.dump(2); - } - - void closeAllBuffers() { - auto open = buffers.getOpenBuffers(); - for (const auto& path : open) { - buffers.closeBuffer(path); - bufferStates.erase(path); - if (path.rfind("(untitled", 0) != 0) watcher.unwatch(path); - } - activeBuffer = nullptr; - } - - bool saveProject(const std::string& path) { - ProjectFile project; - project.workspaceRoot = workspaceRoot; - project.activePath = active() ? active()->path : ""; - for (const auto& bufPath : buffers.getOpenBuffers()) { - auto it = bufferStates.find(bufPath); - if (it == bufferStates.end()) continue; - ProjectBufferInfo info; - info.path = bufPath; - info.language = it->second->language; - info.mode = bufferModeToString(it->second->bufferMode); - project.buffers.push_back(std::move(info)); - } - if (isStructured()) { - syncOrchestratorFromActive(); - if (orchestrator.getAST()) { - project.ast = cloneModule(orchestrator.getAST()); - } - } - - try { - nlohmann::json j = projectToJson(project); - std::ofstream out(path, std::ios::binary); - if (!out.is_open()) return false; - out << j.dump(2); - return true; - } catch (...) { - return false; - } - } - - bool loadProject(const std::string& path) { - try { - std::ifstream in(path, std::ios::binary); - if (!in.is_open()) return false; - nlohmann::json j; - in >> j; - ProjectFile project = projectFromJson(j); - closeAllBuffers(); - - if (!project.workspaceRoot.empty()) { - workspaceRoot = project.workspaceRoot; - fileTreeDirty = true; - projectSearch.setRoot(workspaceRoot); - } - - std::string activePath = project.activePath; - for (const auto& buf : project.buffers) { - if (buf.path == activePath && project.ast) { - std::string lang = buf.language.empty() ? project.ast->targetLanguage : buf.language; - std::string generated = generateForLanguage(project.ast.get(), lang); - createBuffer(buf.path, generated, lang, bufferModeFromString(buf.mode)); - if (active()) { - active()->sync.setAST(std::move(project.ast)); - active()->incrementalOptimizer.setRoot(active()->sync.getAST()); - active()->editBuf = active()->sync.getText(); - active()->editor.setContent(active()->editBuf, lang); - active()->mode.setLanguage(lang); - active()->highlightsDirty = true; - active()->generatedHighlightsDirty = true; - active()->modified = false; - recordUndoSnapshot(); - } - } else { - doOpen(buf.path, bufferModeFromString(buf.mode)); - } - } - - if (!activePath.empty() && buffers.hasBuffer(activePath)) { - switchToBuffer(activePath); - } - if (active()) active()->orchestratorDirty = true; - return true; - } catch (...) { - return false; - } - } - - std::filesystem::path sessionFilePath() const { - return configDir() / "session.json"; - } - - std::filesystem::path settingsFilePath() const { - return configDir() / "settings.json"; - } - - bool saveSession(const std::string& imguiIni) { - SessionData session; - session.workspaceRoot = workspaceRoot; - session.activePath = active() ? active()->path : ""; - session.layoutPreset = LayoutManager::presetName(layoutPreset); - session.imguiIni = imguiIni; - for (const auto& bufPath : buffers.getOpenBuffers()) { - auto it = bufferStates.find(bufPath); - if (it == bufferStates.end()) continue; - const auto* buf = it->second.get(); - SessionBufferState entry; - entry.path = bufPath; - entry.language = buf->language; - entry.mode = bufferModeToString(buf->bufferMode); - entry.cursorLine = buf->cursorLine; - entry.cursorCol = buf->cursorCol; - entry.foldedLines = buf->widget.getFoldedLines(); - session.buffers.push_back(std::move(entry)); - } - try { - std::ofstream out(sessionFilePath().string(), std::ios::binary); - if (!out.is_open()) return false; - out << sessionToJson(session).dump(2); - return true; - } catch (...) { - return false; - } - } - - bool loadSession(SessionData& out) { - try { - std::ifstream in(sessionFilePath().string(), std::ios::binary); - if (!in.is_open()) return false; - nlohmann::json j; - in >> j; - out = sessionFromJson(j); - return true; - } catch (...) { - return false; - } - } - - void applySession(const SessionData& session) { - closeAllBuffers(); - if (!session.workspaceRoot.empty()) { - workspaceRoot = session.workspaceRoot; - fileTreeDirty = true; - projectSearch.setRoot(workspaceRoot); - } - layoutPreset = LayoutManager::presetFromName(session.layoutPreset); - for (const auto& buf : session.buffers) { - doOpen(buf.path, bufferModeFromString(buf.mode)); - auto it = bufferStates.find(buf.path); - if (it != bufferStates.end()) { - auto* bs = it->second.get(); - bs->cursorLine = buf.cursorLine; - bs->cursorCol = buf.cursorCol; - bs->widget.setDesiredFoldedLines(buf.foldedLines); - jumpTo(bs, std::max(0, buf.cursorLine - 1), std::max(0, buf.cursorCol - 1)); - } - } - if (!session.activePath.empty() && buffers.hasBuffer(session.activePath)) { - switchToBuffer(session.activePath); - } - } - - void applyTabSizeToBuffers(int size) { - for (auto& kv : bufferStates) { - kv.second->mode.setTabSize(size); - kv.second->generatedMode.setTabSize(size); - } - } - - void applySettingsToState() { - showMinimap = settings.getShowMinimap(); - showLineNumbers = settings.getShowLineNumbers(); - layoutPreset = LayoutManager::presetFromName(settings.getLayoutPreset()); - keys.setProfile(KeybindingManager::profileFromName(settings.getKeybindingProfile())); - registerCommands(); - applyTabSizeToBuffers(settings.getTabSize()); - } - - void loadSettingsFromDisk() { - settings.loadFromFile(settingsFilePath().string()); - applySettingsToState(); - } - - void saveSettingsToDisk() { - settings.setShowMinimap(showMinimap); - settings.setShowLineNumbers(showLineNumbers); - settings.setLayoutPreset(LayoutManager::presetName(layoutPreset)); - settings.setKeybindingProfile(KeybindingManager::profileName(keys.getProfile())); - std::filesystem::create_directories(settingsFilePath().parent_path()); - settings.saveToFile(settingsFilePath().string()); - } - - void init() { - outputLog = "Whetstone Editor ready.\n"; - workspaceRoot = std::filesystem::current_path().string(); - projectSearch.setRoot(workspaceRoot); - fileTreeDirty = true; - loadRecentFiles(); - loadSettingsFromDisk(); - registerCommands(); - } - - void syncOrchestratorFromActive() { - if (!active()) return; - if (!isStructured()) return; - Module* ast = active()->sync.getAST(); - if (!ast) return; - active()->orchestrator.setAST(cloneModule(ast)); - active()->orchestratorDirty = false; - } - - void applyOrchestratorToActive() { - if (!active()) return; - if (!isStructured()) return; - Module* ast = active()->orchestrator.getAST(); - if (!ast) return; - active()->sync.setAST(cloneModule(ast)); - active()->incrementalOptimizer.setRoot(active()->sync.getAST()); - refreshActiveTextFromAST(); - active()->orchestratorDirty = false; - recordUndoSnapshot(); - } - - Module* mutationAST() { - if (!active()) return nullptr; - if (!isStructured()) return nullptr; - if (active()->orchestratorDirty || !active()->orchestrator.getAST()) { - syncOrchestratorFromActive(); - } - return active()->orchestrator.getAST(); - } - - void recordUndoSnapshot() { - if (!active()) return; - Module* ast = isStructured() ? active()->sync.getAST() : nullptr; - active()->orchestrator.recordSnapshot(active()->editBuf, ast); - active()->undoDepth = active()->orchestrator.getUndoDepth(); - } - - void applySnapshotToActive(const std::string& text, std::unique_ptr ast) { - if (!active()) return; - active()->editBuf = text; - active()->editor.setContent(active()->editBuf, active()->language); - if (active()->bufferMode == BufferManager::BufferMode::Structured) { - if (ast) { - active()->orchestrator.setAST(cloneModule(ast.get())); - active()->sync.setAST(std::move(ast)); - } else { - active()->sync.setText(active()->editBuf, active()->language); - active()->sync.syncNow(); - active()->orchestrator.setAST(cloneModule(active()->sync.getAST())); - } - active()->incrementalOptimizer.setRoot(active()->sync.getAST()); - } - active()->orchestratorDirty = false; - active()->highlightsDirty = true; - active()->generatedHighlightsDirty = true; - active()->modified = true; - if (lsp && active()->path.rfind("(untitled", 0) != 0) { - active()->lspVersion += 1; - lsp->didChange(toFileUri(active()->path), active()->editBuf, active()->lspVersion); - } - } - - void registerCommand(const std::string& id, - const std::string& label, - const std::string& shortcut, - std::function fn) { - commandPalette.registerCommand(id, label, shortcut); - commandHandlers[id] = std::move(fn); - } - - void registerCommands() { - commandPalette = CommandPalette(); - commandHandlers.clear(); - - registerCommand("file.new", "File: New File", - keys.getBinding("file.new").toString(), - [this]() { - std::string lang = active() ? active()->language : "python"; - createBuffer(makeUntitledName(), "", lang); - }); - registerCommand("file.open", "File: Open...", - keys.getBinding("file.open").toString(), - [this]() { - std::string path = FileDialog::openFile( - {"Open File", {"*.py","*.cpp","*.h","*.el","*.js","*.ts","*.java","*.rs","*.go"}, - std::string()}); - if (!path.empty()) doOpen(path); - }); - registerCommand("file.save", "File: Save", - keys.getBinding("file.save").toString(), - [this]() { doSave(); }); - registerCommand("project.open", "Project: Open...", - "", - [this]() { - std::string path = FileDialog::openFile( - {"Open Project", {"*.whetstone"}, std::string()}); - if (!path.empty()) { - if (!loadProject(path)) { - outputLog += "Failed to open project: " + path + "\n"; - } - } - }); - registerCommand("project.save", "Project: Save...", - "", - [this]() { - std::string path = FileDialog::saveFile( - {"Save Project", {"*.whetstone"}, std::string()}); - if (!path.empty()) { - if (!saveProject(path)) { - outputLog += "Failed to save project: " + path + "\n"; - } - } - }); - registerCommand("edit.undo", "Edit: Undo", - keys.getBinding("edit.undo").toString(), - [this]() { doUndo(); }); - registerCommand("edit.redo", "Edit: Redo", - keys.getBinding("edit.redo").toString(), - [this]() { doRedo(); }); - registerCommand("search.find", "Search: Find/Replace", - keys.getBinding("search.find").toString(), - [this]() { showFind = !showFind; }); - registerCommand("search.findInFiles", "Search: Find in Files", - keys.getBinding("search.findInFiles").toString(), - [this]() { showProjectSearch = !showProjectSearch; }); - registerCommand("nav.goToLine", "Navigate: Go to Line", - keys.getBinding("nav.goToLine").toString(), - [this]() { - showGoToLine = true; - goToLineBuf[0] = '\0'; - goToLineError = false; - }); - registerCommand("view.whitespace", "View: Toggle Whitespace", "", - [this]() { showWhitespace = !showWhitespace; }); - registerCommand("view.minimap", "View: Toggle Minimap", "", - [this]() { showMinimap = !showMinimap; }); - registerCommand("view.annotations", "View: Toggle Annotations", "", - [this]() { showAnnotations = !showAnnotations; }); - registerCommand("view.outline", "View: Toggle Outline", "", - [this]() { showOutline = !showOutline; }); - registerCommand("view.lsp", "View: LSP Servers...", "", - [this]() { showLspSettings = !showLspSettings; }); - registerCommand("mode.toggle", "Mode: Toggle Text/Structured", "", - [this]() { - if (!active()) return; - active()->bufferMode = active()->bufferMode == BufferManager::BufferMode::Text ? - BufferManager::BufferMode::Structured : BufferManager::BufferMode::Text; - buffers.setBufferMode(active()->path, active()->bufferMode); - if (active()->bufferMode == BufferManager::BufferMode::Text) { - suggestions.clear(); - whetstoneDiagnostics.clear(); - analysisPending = false; - } else { - onTextChanged(); - } - }); - registerCommand("refactor.rename", "Refactor: Rename Variable", "", - [this]() { - refactorAction = 1; - showRefactorPopup = true; - refactorNameA[0] = '\0'; - refactorNameB[0] = '\0'; - }); - registerCommand("refactor.extract", "Refactor: Extract Function", "", - [this]() { - refactorAction = 2; - showRefactorPopup = true; - refactorNameA[0] = '\0'; - refactorNameB[0] = '\0'; - }); - registerCommand("refactor.inline", "Refactor: Inline Variable", "", - [this]() { - refactorAction = 3; - showRefactorPopup = true; - refactorNameA[0] = '\0'; - refactorNameB[0] = '\0'; - }); - } - - void executeCommand(const std::string& id) { - auto it = commandHandlers.find(id); - if (it == commandHandlers.end()) return; - it->second(); - commandPalette.markUsed(id); - } - - void setLanguage(const std::string& lang) { - if (!active()) return; - std::string prevLang = active()->language; - active()->language = lang; - active()->mode.setLanguage(lang); - if (active()->generatedLanguage == prevLang) { - active()->generatedLanguage = lang; - active()->generatedMode.setLanguage(lang); - active()->generatedHighlightsDirty = true; - } - active()->editor.setContent(active()->editBuf, lang); - if (active()->bufferMode == BufferManager::BufferMode::Structured) { - active()->sync.setText(active()->editBuf, lang); - active()->sync.syncNow(); - active()->incrementalOptimizer.setRoot(active()->sync.getAST()); - } - active()->orchestratorDirty = true; - active()->highlightsDirty = true; - } - - // Called after editBuf changes (from ImGui input) - void onTextChanged() { - if (!active()) return; - active()->editor.setContent(active()->editBuf, active()->language); - if (active()->bufferMode == BufferManager::BufferMode::Structured) { - active()->sync.setText(active()->editBuf, active()->language); - active()->sync.syncNow(); - active()->incrementalOptimizer.setRoot(active()->sync.getAST()); - } - active()->orchestratorDirty = true; - active()->highlightsDirty = true; - active()->generatedHighlightsDirty = true; - active()->modified = true; - symbolsPending = true; - symbolsLastChange = ImGui::GetTime(); - if (lsp) lsp->clearDocumentSymbols(); - if (lsp && active()->path.rfind("(untitled", 0) != 0) { - active()->lspVersion += 1; - lsp->didChange(toFileUri(active()->path), active()->editBuf, active()->lspVersion); - } - recordUndoSnapshot(); - } - - void doUndo() { - if (!active()) return; - std::string text; - std::unique_ptr ast; - if (active()->orchestrator.undoSnapshot(text, ast)) { - applySnapshotToActive(text, std::move(ast)); - } - active()->undoDepth = active()->orchestrator.getUndoDepth(); - } - - void doRedo() { - if (!active()) return; - std::string text; - std::unique_ptr ast; - if (active()->orchestrator.redoSnapshot(text, ast)) { - applySnapshotToActive(text, std::move(ast)); - } - active()->undoDepth = active()->orchestrator.getUndoDepth(); - } - - void doFind() { - if (!active()) return; - if (strlen(findBuf) == 0) return; - int pos = active()->editor.find(findBuf, lastFindPos); - if (pos >= 0) { - lastFindPos = pos + 1; - int line = 1, col = 1; - for (int i = 0; i < pos && i < (int)active()->editBuf.size(); ++i) { - if (active()->editBuf[i] == '\n') { ++line; col = 1; } else { ++col; } - } - active()->cursorLine = line; - active()->cursorCol = col; - outputLog += "Found \"" + std::string(findBuf) + "\" at line " + - std::to_string(line) + ", col " + std::to_string(col) + "\n"; - } else { - lastFindPos = 0; - outputLog += "\"" + std::string(findBuf) + "\" not found.\n"; - } - } - - void doReplaceAll() { - if (!active()) return; - if (strlen(findBuf) == 0) return; - int count = active()->editor.replaceAll(findBuf, replaceBuf); - if (count > 0) { - active()->editBuf = active()->editor.getContent(); - if (active()->bufferMode == BufferManager::BufferMode::Structured) { - active()->sync.setText(active()->editBuf, active()->language); - active()->sync.syncNow(); - active()->incrementalOptimizer.setRoot(active()->sync.getAST()); - } - active()->highlightsDirty = true; - active()->generatedHighlightsDirty = true; - active()->modified = true; - } - outputLog += "Replaced " + std::to_string(count) + " occurrence(s).\n"; - } - - void doSave() { - if (!active()) return; - if (active()->path.rfind("(untitled", 0) == 0) return; - std::ofstream out(active()->path); - if (out.is_open()) { - out << active()->editBuf; - out.close(); - active()->modified = false; - outputLog += "Saved: " + active()->path + "\n"; - if (lsp) lsp->didSave(toFileUri(active()->path), active()->editBuf); - } else { - outputLog += "Error saving: " + active()->path + "\n"; - } - } - - void doOpen(const std::string& path, - BufferManager::BufferMode modeOverride = BufferManager::BufferMode::Structured) { - std::ifstream in(path); - if (in.is_open()) { - std::ostringstream ss; - ss << in.rdbuf(); - std::string content = ss.str(); - std::string language = "python"; - if (path.size() > 3 && path.substr(path.size() - 3) == ".py") - language = "python"; - else if (path.size() > 4 && path.substr(path.size() - 4) == ".cpp") - language = "cpp"; - else if (path.size() > 2 && path.substr(path.size() - 2) == ".h") - language = "cpp"; - else if (path.size() > 3 && path.substr(path.size() - 3) == ".el") - language = "elisp"; - else if (path.size() > 3 && path.substr(path.size() - 3) == ".js") - language = "javascript"; - else if (path.size() > 3 && path.substr(path.size() - 3) == ".ts") - language = "typescript"; - else if (path.size() > 5 && path.substr(path.size() - 5) == ".java") - language = "java"; - else if (path.size() > 3 && path.substr(path.size() - 3) == ".rs") - language = "rust"; - else if (path.size() > 3 && path.substr(path.size() - 3) == ".go") - language = "go"; - createBuffer(path, content, language, modeOverride); - welcome.addRecentFile(path, language, bufferModeToString(modeOverride)); - saveRecentFiles(); - outputLog += "Opened: " + path + "\n"; - } else { - outputLog += "Error opening: " + path + "\n"; - } - } - - void reloadBuffer(const std::string& path) { - auto it = bufferStates.find(path); - if (it == bufferStates.end()) return; - std::ifstream in(path); - if (!in.is_open()) return; - std::ostringstream ss; - ss << in.rdbuf(); - auto* buf = it->second.get(); - buf->editBuf = ss.str(); - buf->editor.setContent(buf->editBuf, buf->language); - if (buf->bufferMode == BufferManager::BufferMode::Structured) { - buf->sync.setText(buf->editBuf, buf->language); - buf->sync.syncNow(); - buf->incrementalOptimizer.setRoot(buf->sync.getAST()); - } - buf->highlightsDirty = true; - buf->generatedHighlightsDirty = true; - buf->modified = false; - outputLog += "Reloaded: " + path + "\n"; - } - - void handleFileChanges() { - auto changed = watcher.poll(); - for (const auto& path : changed) { - auto it = bufferStates.find(path); - if (it == bufferStates.end()) continue; - if (it->second->modified) { - outputLog += "File changed on disk (dirty): " + path + "\n"; - } else { - reloadBuffer(path); - } - } - } - - void updateHighlights() { - if (!active()) return; - if (!active()->highlightsDirty) return; - active()->highlights = SyntaxHighlighter::highlight(active()->editBuf, active()->language); - active()->highlightsDirty = false; - } - - void updateGenerated() { - if (!active()) return; - if (active()->bufferMode == BufferManager::BufferMode::Text) return; - Module* ast = active()->sync.getAST(); - std::string generated = generateForLanguage(ast, active()->generatedLanguage); - if (generated != active()->generatedBuf) { - active()->generatedBuf = generated; - active()->generatedHighlightsDirty = true; - } - if (active()->generatedHighlightsDirty) { - active()->generatedHighlights = - SyntaxHighlighter::highlight(active()->generatedBuf, active()->generatedLanguage); - active()->generatedHighlightsDirty = false; - } - } - - void projectToLanguage(const std::string& targetLanguage) { - if (!active()) return; - Module* ast = activeAST(); - 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()->incrementalOptimizer.setRoot(active()->sync.getAST()); - 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 refreshActiveTextFromAST() { - if (!active()) return; - if (!isStructured()) return; - active()->editBuf = active()->sync.getText(); - active()->editor.setContent(active()->editBuf, active()->language); - active()->highlightsDirty = true; - active()->modified = true; - if (lsp && active()->path.rfind("(untitled", 0) != 0) { - active()->lspVersion += 1; - lsp->didChange(toFileUri(active()->path), active()->editBuf, active()->lspVersion); - } - analysisPending = true; - analysisLastChange = ImGui::GetTime(); - } - - void openDiff(const std::string& beforeText, - const std::string& afterText, - bool preview, - int action, - const std::vector& transformIds, - const std::vector& batchMutations = {}) { - diff.active = true; - diff.preview = preview; - diff.action = action; - diff.batch = !batchMutations.empty(); - diff.beforeText = beforeText; - diff.afterText = afterText; - diff.transformIds = transformIds; - diff.batchMutations = batchMutations; - LineDiff lineDiff = buildLineDiff(beforeText, afterText); - diff.beforeLines = std::move(lineDiff.beforeLines); - diff.afterLines = std::move(lineDiff.afterLines); - } - - void updateCursorPos(int bytePos) { - if (!active()) return; - active()->cursorLine = 1; - active()->cursorCol = 1; - for (int i = 0; i < bytePos && i < (int)active()->editBuf.size(); ++i) { - if (active()->editBuf[i] == '\n') { ++active()->cursorLine; active()->cursorCol = 1; } - else { ++active()->cursorCol; } - } - } - - void refreshFileTree() { - if (!fileTreeDirty) return; - fileTreeRoot = fileTree.build(workspaceRoot); - fileTreeDirty = false; - } -}; - -struct NullLSPTransport : public LSPTransport { - void send(const std::string& msg) override { (void)msg; } - bool receive(std::string& out) override { (void)out; return false; } - bool isOpen() const override { return false; } - void close() override {} -}; - -static int countLines(const std::string& text) { - int lines = 1; - for (char c : text) { - if (c == '\n') ++lines; - } - return lines; -} - -static std::string bufferModeToString(BufferManager::BufferMode mode) { - return mode == BufferManager::BufferMode::Text ? "text" : "structured"; -} - -static BufferManager::BufferMode bufferModeFromString(const std::string& mode) { - if (mode == "text") return BufferManager::BufferMode::Text; - return BufferManager::BufferMode::Structured; -} - -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 std::unique_ptr cloneModule(const Module* ast) { - if (!ast) return nullptr; - json j = toJson(ast); - ASTNode* node = fromJson(j); - if (!node || node->conceptType != "Module") return nullptr; - return std::unique_ptr(static_cast(node)); -} - -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; -} - -static std::string findOptimizationBlockReason(const ASTNode* node) { - if (!node) return ""; - for (auto* anno : node->getChildren("annotations")) { - if (anno->conceptType == "OwnerAnnotation") { - auto* oa = static_cast(anno); - if (oa->strategy == "Single") { - return "@Owner(Single) blocks optimization (aliasing risk)"; - } - } - if (anno->conceptType == "DeallocateAnnotation") { - auto* da = static_cast(anno); - if (da->strategy == "Explicit") { - return "@Deallocate(Explicit) blocks optimization (reorder risk)"; - } - } - if (anno->conceptType == "AllocateAnnotation") { - auto* aa = static_cast(anno); - if (aa->strategy == "Static") { - return "@Allocate(Static) blocks optimization (dynamic alloc risk)"; - } - } - } - for (auto* child : node->allChildren()) { - std::string found = findOptimizationBlockReason(child); - if (!found.empty()) return found; - } - return ""; -} - -static std::string findOptimizationLockWarning(const ASTNode* node) { - if (!node) return ""; - if (node->conceptType == "OptimizationLock") { - auto* lock = static_cast(node); - if (lock->lockLevel == "warning") { - return "OptimizationLock: " + lock->lockReason + - " (locked by " + lock->lockedBy + ")"; - } - } - for (auto* child : node->allChildren()) { - std::string found = findOptimizationLockWarning(child); - if (!found.empty()) return found; - } - return ""; -} - -static ImVec4 transformColorForName(const std::string& name) { - if (name == "constant-fold") return ImVec4(0.45f, 0.85f, 0.45f, 1.0f); - if (name == "dead-code-elim") return ImVec4(0.90f, 0.45f, 0.45f, 1.0f); - return ImVec4(0.75f, 0.75f, 0.75f, 1.0f); -} - - -// --------------------------------------------------------------------------- -// ImGui InputTextMultiline with std::string resize callback -// --------------------------------------------------------------------------- -struct InputTextCallbackData { - std::string* str; -}; - -static int InputTextCallback(ImGuiInputTextCallbackData* data) { - auto* userData = static_cast(data->UserData); - if (data->EventFlag == ImGuiInputTextFlags_CallbackResize) { - userData->str->resize(data->BufTextLen); - data->Buf = userData->str->data(); - } - return 0; -} - -static bool InputTextMultilineStr(const char* label, std::string* str, - const ImVec2& size, ImGuiInputTextFlags flags = 0) { - flags |= ImGuiInputTextFlags_CallbackResize; - InputTextCallbackData cbData{str}; - // Ensure buffer has room - if (str->capacity() < str->size() + 256) - str->reserve(str->size() + 4096); - return ImGui::InputTextMultiline(label, str->data(), str->capacity() + 1, - size, flags, InputTextCallback, &cbData); -} - -static bool InputTextStr(const char* label, std::string* str, ImGuiInputTextFlags flags = 0) { - flags |= ImGuiInputTextFlags_CallbackResize; - InputTextCallbackData cbData{str}; - if (str->capacity() < str->size() + 64) - str->reserve(str->size() + 256); - return ImGui::InputText(label, str->data(), str->capacity() + 1, - flags, InputTextCallback, &cbData); -} - -static bool annotationInfo(const ASTNode* anno, ImU32& color, std::string& label) { - if (!anno) return false; - if (anno->conceptType == "ReclaimAnnotation") { - auto* a = static_cast(anno); - if (a->strategy == "Tracing") { - color = IM_COL32(80, 140, 220, 255); - label = "@Reclaim(Tracing)"; - return true; - } - } else if (anno->conceptType == "OwnerAnnotation") { - auto* a = static_cast(anno); - if (a->strategy == "Single") { - color = IM_COL32(220, 160, 60, 255); - label = "@Owner(Single)"; - return true; - } - } else if (anno->conceptType == "DeallocateAnnotation") { - auto* a = static_cast(anno); - if (a->strategy == "Explicit") { - color = IM_COL32(220, 80, 80, 255); - label = "@Deallocate(Explicit)"; - return true; - } - } else if (anno->conceptType == "LifetimeAnnotation") { - auto* a = static_cast(anno); - if (a->strategy == "RAII") { - color = IM_COL32(80, 180, 100, 255); - label = "@Lifetime(RAII)"; - return true; - } - } else if (anno->conceptType == "AllocateAnnotation") { - auto* a = static_cast(anno); - if (a->strategy == "Static") { - color = IM_COL32(160, 160, 160, 255); - label = "@Allocate(Static)"; - return true; - } - } - return false; -} - -static std::string annotationLabel(const ASTNode* anno) { - if (!anno) return ""; - if (anno->conceptType == "ReclaimAnnotation") { - auto* a = static_cast(anno); - return "@Reclaim(" + a->strategy + ")"; - } - if (anno->conceptType == "OwnerAnnotation") { - auto* a = static_cast(anno); - return "@Owner(" + a->strategy + ")"; - } - if (anno->conceptType == "DeallocateAnnotation") { - auto* a = static_cast(anno); - return "@Deallocate(" + a->strategy + ")"; - } - if (anno->conceptType == "LifetimeAnnotation") { - auto* a = static_cast(anno); - return "@Lifetime(" + a->strategy + ")"; - } - if (anno->conceptType == "AllocateAnnotation") { - auto* a = static_cast(anno); - return "@Allocate(" + a->strategy + ")"; - } - return anno->conceptType; -} - -static std::string nodeDisplayName(const ASTNode* node) { - if (!node) return ""; - if (node->conceptType == "Function") return static_cast(node)->name; - if (node->conceptType == "Variable") return static_cast(node)->name; - return node->conceptType; -} - -static std::string nextAnnotationId() { - static int counter = 0; - return "anno_ui_" + std::to_string(counter++); -} - -static Annotation* createAnnotationNode(const std::string& type, const std::string& strategy) { - if (type == "ReclaimAnnotation") { - auto* a = new ReclaimAnnotation(nextAnnotationId(), strategy); - return a; - } - if (type == "OwnerAnnotation") { - auto* a = new OwnerAnnotation(nextAnnotationId(), strategy); - return a; - } - if (type == "DeallocateAnnotation") { - auto* a = new DeallocateAnnotation(nextAnnotationId(), strategy); - return a; - } - if (type == "LifetimeAnnotation") { - auto* a = new LifetimeAnnotation(nextAnnotationId(), strategy); - return a; - } - if (type == "AllocateAnnotation") { - auto* a = new AllocateAnnotation(nextAnnotationId(), strategy); - return a; - } - return nullptr; -} - -static int spanScore(const ASTNode* node) { - if (!node || !node->hasSpan()) return INT_MAX; - int lines = node->spanEndLine - node->spanStartLine; - int cols = node->spanEndCol - node->spanStartCol; - return lines * 10000 + cols; -} - -static bool spanContains(const ASTNode* node, int line, int col) { - if (!node || !node->hasSpan()) return false; - if (line < node->spanStartLine || line > node->spanEndLine) return false; - if (line == node->spanStartLine && col < node->spanStartCol) return false; - if (line == node->spanEndLine && col > node->spanEndCol) return false; - return true; -} - -static ASTNode* findNodeAtPosition(ASTNode* node, int line, int col) { - if (!node) return nullptr; - ASTNode* best = nullptr; - if (spanContains(node, line, col)) best = node; - for (auto* child : node->allChildren()) { - ASTNode* cand = findNodeAtPosition(child, line, col); - if (cand) { - if (!best || spanScore(cand) < spanScore(best)) best = cand; - } - } - return best; -} - -static ASTNode* findAnnotationTarget(ASTNode* node, int line) { - if (!node) return nullptr; - ASTNode* best = nullptr; - if (node->hasSpan() && line >= node->spanStartLine && line <= node->spanEndLine) { - if (node->conceptType == "Function" || node->conceptType == "Variable") { - best = node; - } - } - for (auto* child : node->allChildren()) { - ASTNode* cand = findAnnotationTarget(child, line); - if (cand) { - if (!best || spanScore(cand) < spanScore(best)) best = cand; - } - } - return best; -} - -static ASTNode* findNodeById(ASTNode* node, const std::string& id) { - if (!node) return nullptr; - if (node->id == id) return node; - for (auto* child : node->allChildren()) { - if (auto* found = findNodeById(child, id)) return found; - } - return nullptr; -} - -static void collectAnnotationMarkers(const ASTNode* node, std::vector& out) { - if (!node) return; - if (node->hasSpan()) { - for (const auto* anno : node->getChildren("annotations")) { - ImU32 color = 0; - std::string label; - if (annotationInfo(anno, color, label)) { - AnnotationMarker marker; - marker.line = node->spanStartLine; - marker.color = color; - marker.message = label; - out.push_back(std::move(marker)); - } - } - } - for (auto* child : node->allChildren()) { - collectAnnotationMarkers(child, out); - } -} - -struct OutlineItem { - std::string name; - std::string kind; - int line = -1; - int col = -1; - std::vector children; -}; - -static OutlineItem makeOutlineItem(const std::string& name, - const std::string& kind, - const ASTNode* node) { - OutlineItem item; - item.name = name; - item.kind = kind; - if (node && node->hasSpan()) { - item.line = node->spanStartLine; - item.col = node->spanStartCol; - } - return item; -} - -static void collectOutlineFromFunction(const Function* fn, std::vector& out) { - OutlineItem item = makeOutlineItem(fn->name, "Function", fn); - for (auto* p : fn->getChildren("parameters")) { - if (p->conceptType == "Parameter") { - auto* param = static_cast(p); - item.children.push_back(makeOutlineItem(param->name, "Parameter", param)); - } - } - for (auto* child : fn->getChildren("body")) { - if (child->conceptType == "Variable") { - auto* var = static_cast(child); - item.children.push_back(makeOutlineItem(var->name, "Variable", var)); - } - } - out.push_back(std::move(item)); -} - -static void collectOutlineFromAST(const Module* mod, std::vector& out) { - if (!mod) return; - for (auto* child : mod->getChildren("variables")) { - if (child->conceptType == "Variable") { - auto* var = static_cast(child); - out.push_back(makeOutlineItem(var->name, "Variable", var)); - } - } - for (auto* child : mod->getChildren("functions")) { - if (child->conceptType == "Function") { - auto* fn = static_cast(child); - collectOutlineFromFunction(fn, out); - } - } -} - -static std::string toLowerCopy(const std::string& input) { - std::string out = input; - std::transform(out.begin(), out.end(), out.begin(), - [](unsigned char c) { return (char)std::tolower(c); }); - return out; -} - -static bool containsCaseInsensitive(const std::string& text, const std::string& filter) { - if (filter.empty()) return true; - std::string hay = toLowerCopy(text); - return hay.find(filter) != std::string::npos; -} - -static bool outlineMatchesFilter(const OutlineItem& item, const std::string& filter) { - if (filter.empty()) return true; - if (containsCaseInsensitive(item.name, filter)) return true; - for (const auto& child : item.children) { - if (outlineMatchesFilter(child, filter)) return true; - } - return false; -} - -static const char* lspSymbolKindLabel(int kind) { - switch (kind) { - case 1: return "File"; - case 2: return "Module"; - case 5: return "Class"; - case 6: return "Method"; - case 12: return "Function"; - case 13: return "Variable"; - case 14: return "Constant"; - case 19: return "String"; - case 23: return "Struct"; - default: return "Symbol"; - } -} - -static bool lspSymbolMatchesFilter(const LSPClient::DocumentSymbol& sym, const std::string& filter) { - if (filter.empty()) return true; - if (containsCaseInsensitive(sym.name, filter)) return true; - for (const auto& child : sym.children) { - if (lspSymbolMatchesFilter(child, filter)) return true; - } - return false; -} - -// --------------------------------------------------------------------------- -// Theme setup — VSCode Dark-inspired -// --------------------------------------------------------------------------- -static void SetupVSCodeDarkTheme() { - ImGuiStyle& style = ImGui::GetStyle(); - ImVec4* colors = style.Colors; - - // Window - colors[ImGuiCol_WindowBg] = ImVec4(0.12f, 0.12f, 0.12f, 1.00f); - colors[ImGuiCol_ChildBg] = ImVec4(0.12f, 0.12f, 0.12f, 1.00f); - colors[ImGuiCol_PopupBg] = ImVec4(0.15f, 0.15f, 0.15f, 1.00f); - - // Borders - colors[ImGuiCol_Border] = ImVec4(0.20f, 0.20f, 0.20f, 1.00f); - colors[ImGuiCol_BorderShadow] = ImVec4(0.00f, 0.00f, 0.00f, 0.00f); - - // Frame (input fields, checkboxes) - colors[ImGuiCol_FrameBg] = ImVec4(0.16f, 0.16f, 0.16f, 1.00f); - colors[ImGuiCol_FrameBgHovered] = ImVec4(0.20f, 0.20f, 0.20f, 1.00f); - colors[ImGuiCol_FrameBgActive] = ImVec4(0.24f, 0.24f, 0.24f, 1.00f); - - // Title bar - colors[ImGuiCol_TitleBg] = ImVec4(0.08f, 0.08f, 0.08f, 1.00f); - colors[ImGuiCol_TitleBgActive] = ImVec4(0.08f, 0.08f, 0.08f, 1.00f); - colors[ImGuiCol_TitleBgCollapsed] = ImVec4(0.08f, 0.08f, 0.08f, 1.00f); - - // Menu bar - colors[ImGuiCol_MenuBarBg] = ImVec4(0.14f, 0.14f, 0.14f, 1.00f); - - // Scrollbar - colors[ImGuiCol_ScrollbarBg] = ImVec4(0.12f, 0.12f, 0.12f, 1.00f); - colors[ImGuiCol_ScrollbarGrab] = ImVec4(0.30f, 0.30f, 0.30f, 1.00f); - colors[ImGuiCol_ScrollbarGrabHovered] = ImVec4(0.40f, 0.40f, 0.40f, 1.00f); - colors[ImGuiCol_ScrollbarGrabActive] = ImVec4(0.50f, 0.50f, 0.50f, 1.00f); - - // Buttons - colors[ImGuiCol_Button] = ImVec4(0.20f, 0.20f, 0.20f, 1.00f); - colors[ImGuiCol_ButtonHovered] = ImVec4(0.28f, 0.28f, 0.28f, 1.00f); - colors[ImGuiCol_ButtonActive] = ImVec4(0.15f, 0.15f, 0.15f, 1.00f); - - // Headers (collapsing headers, tree nodes) - colors[ImGuiCol_Header] = ImVec4(0.18f, 0.18f, 0.18f, 1.00f); - colors[ImGuiCol_HeaderHovered] = ImVec4(0.26f, 0.26f, 0.26f, 1.00f); - colors[ImGuiCol_HeaderActive] = ImVec4(0.22f, 0.22f, 0.22f, 1.00f); - - // Separator - colors[ImGuiCol_Separator] = ImVec4(0.20f, 0.20f, 0.20f, 1.00f); - colors[ImGuiCol_SeparatorHovered] = ImVec4(0.28f, 0.56f, 0.89f, 1.00f); - colors[ImGuiCol_SeparatorActive] = ImVec4(0.28f, 0.56f, 0.89f, 1.00f); - - // Tabs - colors[ImGuiCol_Tab] = ImVec4(0.12f, 0.12f, 0.12f, 1.00f); - colors[ImGuiCol_TabHovered] = ImVec4(0.20f, 0.20f, 0.20f, 1.00f); - colors[ImGuiCol_TabActive] = ImVec4(0.18f, 0.18f, 0.18f, 1.00f); - colors[ImGuiCol_TabUnfocused] = ImVec4(0.12f, 0.12f, 0.12f, 1.00f); - colors[ImGuiCol_TabUnfocusedActive] = ImVec4(0.16f, 0.16f, 0.16f, 1.00f); - - // Docking - colors[ImGuiCol_DockingPreview] = ImVec4(0.28f, 0.56f, 0.89f, 0.70f); - colors[ImGuiCol_DockingEmptyBg] = ImVec4(0.12f, 0.12f, 0.12f, 1.00f); - - // Text - colors[ImGuiCol_Text] = ImVec4(0.86f, 0.86f, 0.86f, 1.00f); - colors[ImGuiCol_TextDisabled] = ImVec4(0.50f, 0.50f, 0.50f, 1.00f); - - // Selection / highlight - colors[ImGuiCol_TextSelectedBg] = ImVec4(0.17f, 0.40f, 0.64f, 0.60f); - - // Style tweaks - style.WindowRounding = 0.0f; - style.FrameRounding = 2.0f; - style.ScrollbarRounding = 2.0f; - style.GrabRounding = 2.0f; - style.TabRounding = 0.0f; - style.WindowBorderSize = 1.0f; - style.FrameBorderSize = 0.0f; - style.WindowPadding = ImVec2(8, 8); - style.FramePadding = ImVec2(6, 3); - style.ItemSpacing = ImVec2(6, 4); -} - -static void SetupVSCodeLightTheme() { - ImGuiStyle& style = ImGui::GetStyle(); - ImVec4* colors = style.Colors; - - colors[ImGuiCol_WindowBg] = ImVec4(0.95f, 0.95f, 0.95f, 1.00f); - colors[ImGuiCol_ChildBg] = ImVec4(0.95f, 0.95f, 0.95f, 1.00f); - colors[ImGuiCol_PopupBg] = ImVec4(0.98f, 0.98f, 0.98f, 1.00f); - - colors[ImGuiCol_Border] = ImVec4(0.75f, 0.75f, 0.75f, 1.00f); - colors[ImGuiCol_FrameBg] = ImVec4(0.90f, 0.90f, 0.90f, 1.00f); - colors[ImGuiCol_FrameBgHovered] = ImVec4(0.85f, 0.85f, 0.85f, 1.00f); - colors[ImGuiCol_FrameBgActive] = ImVec4(0.80f, 0.80f, 0.80f, 1.00f); - - colors[ImGuiCol_TitleBg] = ImVec4(0.90f, 0.90f, 0.90f, 1.00f); - colors[ImGuiCol_TitleBgActive] = ImVec4(0.86f, 0.86f, 0.86f, 1.00f); - colors[ImGuiCol_TitleBgCollapsed] = ImVec4(0.90f, 0.90f, 0.90f, 1.00f); - - colors[ImGuiCol_MenuBarBg] = ImVec4(0.92f, 0.92f, 0.92f, 1.00f); - colors[ImGuiCol_Button] = ImVec4(0.85f, 0.85f, 0.85f, 1.00f); - colors[ImGuiCol_ButtonHovered] = ImVec4(0.78f, 0.78f, 0.78f, 1.00f); - colors[ImGuiCol_ButtonActive] = ImVec4(0.70f, 0.70f, 0.70f, 1.00f); - - colors[ImGuiCol_Header] = ImVec4(0.82f, 0.82f, 0.82f, 1.00f); - colors[ImGuiCol_HeaderHovered] = ImVec4(0.75f, 0.75f, 0.75f, 1.00f); - colors[ImGuiCol_HeaderActive] = ImVec4(0.70f, 0.70f, 0.70f, 1.00f); - - colors[ImGuiCol_Text] = ImVec4(0.10f, 0.10f, 0.10f, 1.00f); - colors[ImGuiCol_TextDisabled] = ImVec4(0.45f, 0.45f, 0.45f, 1.00f); - colors[ImGuiCol_TextSelectedBg] = ImVec4(0.40f, 0.60f, 0.90f, 0.45f); - - style.WindowRounding = 0.0f; - style.FrameRounding = 2.0f; - style.ScrollbarRounding = 2.0f; - style.GrabRounding = 2.0f; - style.TabRounding = 0.0f; - style.WindowBorderSize = 1.0f; - style.FrameBorderSize = 0.0f; - style.WindowPadding = ImVec2(8, 8); - style.FramePadding = ImVec2(6, 3); - style.ItemSpacing = ImVec2(6, 4); -} - -// --------------------------------------------------------------------------- -// Syntax highlight color map -// --------------------------------------------------------------------------- -static ImVec4 tokenColor(TokenCategory cat) { - switch (cat) { - case TokenCategory::Keyword: return ImVec4(0.77f, 0.49f, 0.86f, 1.0f); // purple - case TokenCategory::String: return ImVec4(0.81f, 0.54f, 0.37f, 1.0f); // orange - case TokenCategory::Comment: return ImVec4(0.42f, 0.52f, 0.35f, 1.0f); // green - case TokenCategory::Number: return ImVec4(0.71f, 0.80f, 0.55f, 1.0f); // light green - case TokenCategory::Function: return ImVec4(0.86f, 0.86f, 0.55f, 1.0f); // yellow - case TokenCategory::Parameter: return ImVec4(0.60f, 0.78f, 0.90f, 1.0f); // light blue - case TokenCategory::Type: return ImVec4(0.30f, 0.70f, 0.68f, 1.0f); // teal - case TokenCategory::Operator: return ImVec4(0.86f, 0.86f, 0.86f, 1.0f); // white - case TokenCategory::Punctuation: return ImVec4(0.60f, 0.60f, 0.60f, 1.0f); // gray - case TokenCategory::Builtin: return ImVec4(0.30f, 0.70f, 0.90f, 1.0f); // blue - case TokenCategory::Identifier: return ImVec4(0.60f, 0.78f, 0.90f, 1.0f); // light blue - default: return ImVec4(0.86f, 0.86f, 0.86f, 1.0f); // white - } -} - -// --------------------------------------------------------------------------- -// Render syntax-highlighted text (read-only preview) -// --------------------------------------------------------------------------- -static void RenderHighlightedText(const std::string& text, - const std::vector& spans) { - if (text.empty()) return; - - // Build a color for each byte position - // Default = plain text color - std::vector charCats(text.size(), TokenCategory::Plain); - for (const auto& span : spans) { - for (uint32_t i = span.start; i < span.end && i < charCats.size(); ++i) { - charCats[i] = span.category; - } - } - - // Render line by line, span by span - size_t pos = 0; - int lineNum = 1; - while (pos < text.size()) { - // Find end of line - size_t eol = text.find('\n', pos); - if (eol == std::string::npos) eol = text.size(); - - // Line number - ImGui::TextColored(ImVec4(0.45f, 0.45f, 0.45f, 1.0f), "%4d ", lineNum); - ImGui::SameLine(0.0f, 0.0f); - - // Render spans within this line - size_t linePos = pos; - while (linePos < eol) { - // Find the extent of the current color - TokenCategory cat = charCats[linePos]; - size_t spanEnd = linePos + 1; - while (spanEnd < eol && charCats[spanEnd] == cat) ++spanEnd; - - std::string chunk = text.substr(linePos, spanEnd - linePos); - ImGui::TextColored(tokenColor(cat), "%s", chunk.c_str()); - if (spanEnd < eol) ImGui::SameLine(0.0f, 0.0f); - - linePos = spanEnd; - } - - if (linePos == pos) { - // Empty line - ImGui::TextUnformatted(""); - } - - pos = eol + 1; - ++lineNum; - } -} - -// --------------------------------------------------------------------------- -// File tree rendering -// --------------------------------------------------------------------------- -static void RenderFileTree(const FileNode& node, EditorState& state) { - if (!node.isDir) { - if (ImGui::Selectable(node.name.c_str())) { - state.doOpen(node.path); - } - return; - } - - ImGuiTreeNodeFlags flags = ImGuiTreeNodeFlags_OpenOnArrow | ImGuiTreeNodeFlags_OpenOnDoubleClick; - if (ImGui::TreeNodeEx(node.name.c_str(), flags)) { - for (const auto& child : node.children) { - RenderFileTree(child, state); - } - ImGui::TreePop(); - } -} - -// --------------------------------------------------------------------------- -// Welcome screen rendering -// --------------------------------------------------------------------------- -static void RenderWelcome(WelcomeScreen& welcome, EditorState& state, std::string& lastDialogPath) { - ImGui::Text("%s", welcome.getTitle().c_str()); - ImGui::TextColored(ImVec4(0.7f, 0.7f, 0.7f, 1.0f), "%s", welcome.getSubtitle().c_str()); - ImGui::Separator(); - - ImGui::Text("Quick Actions"); - if (ImGui::Button("New File")) { - std::string lang = state.active() ? state.active()->language : "python"; - state.createBuffer(state.makeUntitledName(), "", lang); - } - ImGui::SameLine(); - if (ImGui::Button("Open File")) { - auto path = FileDialog::openFile({"Open File", {"*.py","*.cpp","*.h","*.el","*.js","*.ts","*.java","*.rs","*.go"}, lastDialogPath}); - if (!path.empty()) { - lastDialogPath = path; - state.doOpen(path); - } - } - ImGui::SameLine(); - if (ImGui::Button("Open Folder")) { - auto path = FileDialog::openFolder({"Open Folder", state.workspaceRoot}); - if (!path.empty()) { - state.workspaceRoot = path; - state.fileTreeDirty = true; - state.projectSearch.setRoot(state.workspaceRoot); - lastDialogPath = path; - } - } - - ImGui::Separator(); - ImGui::Text("Recent Files"); - for (const auto& rf : welcome.getRecentFiles()) { - if (ImGui::Selectable(rf.displayName.c_str())) { - state.doOpen(rf.path, bufferModeFromString(rf.mode)); - } - } - - ImGui::Separator(); - ImGui::Text("Tip"); - ImGui::TextWrapped("%s", welcome.getTip(0).c_str()); -} +#include "EditorState.h" +#include "EditorUtils.h" // --------------------------------------------------------------------------- // Main