530 lines
20 KiB
C++
530 lines
20 KiB
C++
#pragma once
|
|
#include "ast/Module.h"
|
|
#include "ast/Serialization.h"
|
|
#include <string>
|
|
#include <memory>
|
|
#include <fstream>
|
|
#include <iostream>
|
|
#include <nlohmann/json.hpp>
|
|
|
|
#include <deque>
|
|
#include <cstdio> // For system() and popen() functions
|
|
#include "ast/Generator.h" // For the code generators
|
|
#include "ast/Parser.h" // For tree-sitter parser integration
|
|
#include "ast/Annotation.h" // For annotation classes like OptimizationLock
|
|
|
|
class Orchestrator {
|
|
private:
|
|
std::unique_ptr<Module> currentAST;
|
|
std::string loadedFilePath;
|
|
|
|
// Operation journal for undo/redo
|
|
std::deque<json> operationJournal;
|
|
size_t undoPosition = 0; // Points to the next operation to undo (0 = no operations to undo)
|
|
|
|
public:
|
|
Orchestrator() = default;
|
|
|
|
~Orchestrator() {
|
|
// On destruction, save the AST back if there's a loaded file
|
|
if (!loadedFilePath.empty() && currentAST) {
|
|
saveAST(loadedFilePath);
|
|
}
|
|
}
|
|
|
|
// Load AST from JSON file
|
|
bool loadAST(const std::string& filePath) {
|
|
try {
|
|
// Read the file
|
|
std::ifstream file(filePath);
|
|
if (!file.is_open()) {
|
|
return false;
|
|
}
|
|
|
|
std::string content((std::istreambuf_iterator<char>(file)),
|
|
std::istreambuf_iterator<char>());
|
|
file.close();
|
|
|
|
// Parse JSON
|
|
auto j = json::parse(content);
|
|
|
|
// Deserialize to AST
|
|
ASTNode* node = fromJson(j);
|
|
if (!node || node->conceptType != "Module") {
|
|
deleteTree(node);
|
|
return false;
|
|
}
|
|
|
|
currentAST.reset(static_cast<Module*>(node));
|
|
loadedFilePath = filePath;
|
|
return true;
|
|
} catch (...) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// Save AST to JSON file
|
|
bool saveAST(const std::string& filePath) {
|
|
if (!currentAST) {
|
|
return false;
|
|
}
|
|
|
|
try {
|
|
json j = toJson(currentAST.get());
|
|
std::ofstream file(filePath);
|
|
if (!file.is_open()) {
|
|
return false;
|
|
}
|
|
|
|
file << j.dump(2);
|
|
file.close();
|
|
return true;
|
|
} catch (...) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// Get current AST
|
|
Module* getAST() {
|
|
return currentAST.get();
|
|
}
|
|
|
|
// Set current AST
|
|
void setAST(std::unique_ptr<Module> ast) {
|
|
currentAST = std::move(ast);
|
|
}
|
|
|
|
// Get loaded file path
|
|
std::string getLoadedFilePath() const {
|
|
return loadedFilePath;
|
|
}
|
|
|
|
// Check if a node or any of its ancestors has an optimization lock
|
|
bool isNodeLocked(const ASTNode* node) const {
|
|
if (!node) return false;
|
|
|
|
// Check if this node has an optimization lock annotation
|
|
// In a real implementation, we'd check the annotations role for OptimizationLock nodes
|
|
// For now, we'll implement a basic check by looking for annotations
|
|
|
|
// Check this node's annotations
|
|
auto annotations = node->getChildren("annotations");
|
|
for (const auto* annotation : annotations) {
|
|
if (annotation->conceptType == "OptimizationLock") {
|
|
return true; // Found a lock annotation on this node
|
|
}
|
|
}
|
|
|
|
// Recursively check parent nodes
|
|
return isNodeLocked(node->parent);
|
|
}
|
|
|
|
// Get the lock annotation for a node (if any exists in the ancestry)
|
|
const OptimizationLock* getNodeLock(const ASTNode* node) const {
|
|
if (!node) return nullptr;
|
|
|
|
// Check if this node has an optimization lock annotation
|
|
auto annotations = node->getChildren("annotations");
|
|
for (const auto* annotation : annotations) {
|
|
if (annotation->conceptType == "OptimizationLock") {
|
|
return static_cast<const OptimizationLock*>(annotation);
|
|
}
|
|
}
|
|
|
|
// Recursively check parent nodes
|
|
return getNodeLock(node->parent);
|
|
}
|
|
|
|
// Get all locks on a node and its ancestors
|
|
std::vector<const OptimizationLock*> getLocks(const std::string& nodeId) const {
|
|
std::vector<const OptimizationLock*> locks;
|
|
|
|
// Find the node by ID
|
|
ASTNode* node = findNodeById(currentAST.get(), nodeId);
|
|
if (!node) return locks;
|
|
|
|
// Collect all locks from the node and up the ancestry
|
|
const ASTNode* current = node;
|
|
while (current) {
|
|
// Check if this node has any optimization lock annotations
|
|
auto annotations = current->getChildren("annotations");
|
|
for (const auto* annotation : annotations) {
|
|
if (annotation->conceptType == "OptimizationLock") {
|
|
locks.push_back(static_cast<const OptimizationLock*>(annotation));
|
|
}
|
|
}
|
|
current = current->parent;
|
|
}
|
|
|
|
return locks;
|
|
}
|
|
|
|
// Record an operation for undo/redo
|
|
void recordOperation(const json& operation) {
|
|
// Clear any operations after the current position (if we're in the middle of the undo stack)
|
|
while (operationJournal.size() > undoPosition) {
|
|
operationJournal.pop_back();
|
|
}
|
|
|
|
// Add the new operation
|
|
operationJournal.push_back(operation);
|
|
undoPosition = operationJournal.size(); // Point to after the last operation
|
|
}
|
|
|
|
// Undo the last operation
|
|
bool undoLastOperation() {
|
|
if (undoPosition == 0) {
|
|
return false; // Nothing to undo
|
|
}
|
|
|
|
// Move back one operation
|
|
undoPosition--;
|
|
json operation = operationJournal[undoPosition];
|
|
|
|
// Perform the inverse of the operation
|
|
if (operation["type"] == "insertNode") {
|
|
// For insertNode, the inverse is to delete the node
|
|
std::string nodeId = operation["nodeId"];
|
|
ASTNode* node = findNodeById(currentAST.get(), nodeId);
|
|
if (node) {
|
|
// Find the parent and remove the child
|
|
ASTNode* parent = node->parent;
|
|
if (parent) {
|
|
// STUB: insertNode undo not implemented — requires role tracking per mutation
|
|
}
|
|
}
|
|
} else if (operation["type"] == "setNodeProperty") {
|
|
// For setNodeProperty, the inverse is to set the property back to its old value
|
|
std::string nodeId = operation["nodeId"];
|
|
std::string property = operation["property"];
|
|
json oldValue = operation["oldValue"];
|
|
|
|
ASTNode* node = findNodeById(currentAST.get(), nodeId);
|
|
if (node) {
|
|
setNodeProperty(node, property, oldValue);
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
// Redo the last undone operation
|
|
bool redoLastOperation() {
|
|
if (undoPosition >= operationJournal.size()) {
|
|
return false; // Nothing to redo
|
|
}
|
|
|
|
// Perform the operation
|
|
json operation = operationJournal[undoPosition];
|
|
if (operation["type"] == "insertNode") {
|
|
// Re-insert the node
|
|
// This would require recreating the node and adding it back - simplified for this example
|
|
} else if (operation["type"] == "setNodeProperty") {
|
|
// Re-set the property to its new value
|
|
std::string nodeId = operation["nodeId"];
|
|
std::string property = operation["property"];
|
|
json newValue = operation["newValue"];
|
|
|
|
ASTNode* node = findNodeById(currentAST.get(), nodeId);
|
|
if (node) {
|
|
setNodeProperty(node, property, newValue);
|
|
}
|
|
}
|
|
|
|
// Move forward one operation
|
|
undoPosition++;
|
|
return true;
|
|
}
|
|
|
|
// Record a snapshot (text + AST) for unified undo/redo
|
|
void recordSnapshot(const std::string& text, const ASTNode* ast) {
|
|
json op;
|
|
op["type"] = "snapshot";
|
|
op["text"] = text;
|
|
if (ast) op["ast"] = toJson(ast);
|
|
recordOperation(op);
|
|
}
|
|
|
|
// Undo snapshot
|
|
bool undoSnapshot(std::string& outText, std::unique_ptr<Module>& outAst) {
|
|
if (undoPosition == 0) return false;
|
|
undoPosition--;
|
|
json operation = operationJournal[undoPosition];
|
|
if (operation.value("type", "") != "snapshot") return false;
|
|
outText = operation.value("text", "");
|
|
outAst.reset();
|
|
if (operation.contains("ast")) {
|
|
ASTNode* node = fromJson(operation["ast"]);
|
|
if (node && node->conceptType == "Module") {
|
|
outAst.reset(static_cast<Module*>(node));
|
|
} else {
|
|
deleteTree(node);
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// Redo snapshot
|
|
bool redoSnapshot(std::string& outText, std::unique_ptr<Module>& outAst) {
|
|
if (undoPosition >= operationJournal.size()) return false;
|
|
json operation = operationJournal[undoPosition];
|
|
if (operation.value("type", "") != "snapshot") return false;
|
|
undoPosition++;
|
|
outText = operation.value("text", "");
|
|
outAst.reset();
|
|
if (operation.contains("ast")) {
|
|
ASTNode* node = fromJson(operation["ast"]);
|
|
if (node && node->conceptType == "Module") {
|
|
outAst.reset(static_cast<Module*>(node));
|
|
} else {
|
|
deleteTree(node);
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
int getUndoDepth() const { return (int)undoPosition; }
|
|
int getRedoDepth() const { return (int)(operationJournal.size() - undoPosition); }
|
|
|
|
// Helper function to find a node by ID in the AST
|
|
ASTNode* findNodeById(ASTNode* root, const std::string& id) const {
|
|
if (!root) return nullptr;
|
|
if (root->id == id) return root;
|
|
|
|
// Recursively search in children
|
|
for (auto* child : root->allChildren()) {
|
|
ASTNode* found = findNodeById(child, id);
|
|
if (found) return found;
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
// Helper function to set a property on a node (needs to be implemented)
|
|
bool setNodeProperty(ASTNode* node, const std::string& property, const json& value) {
|
|
// This is a simplified implementation - in a real system, we'd have specific setters for each concept
|
|
// For now, we'll handle a few common properties
|
|
if (node->conceptType == "Function" && property == "name") {
|
|
if (value.is_string()) {
|
|
static_cast<Function*>(node)->name = value.get<std::string>();
|
|
return true;
|
|
}
|
|
} else if (node->conceptType == "Variable" && property == "name") {
|
|
if (value.is_string()) {
|
|
static_cast<Variable*>(node)->name = value.get<std::string>();
|
|
return true;
|
|
}
|
|
} else if (node->conceptType == "Parameter" && property == "name") {
|
|
if (value.is_string()) {
|
|
static_cast<Parameter*>(node)->name = value.get<std::string>();
|
|
return true;
|
|
}
|
|
}
|
|
// Add more property setters as needed
|
|
|
|
return false; // Property not supported
|
|
}
|
|
|
|
// Method to start Emacs daemon
|
|
bool startEmacsDaemon() {
|
|
// In a real implementation, this would spawn: emacs --daemon=whetstone --load whetstone-bridge.el
|
|
// For this implementation, we'll simulate the functionality
|
|
// This would typically use system() or a process spawning library
|
|
std::cout << "Starting Emacs daemon with: emacs --daemon=whetstone" << std::endl;
|
|
// In a real implementation: system("emacs --daemon=whetstone --load whetstone-bridge.el");
|
|
return true;
|
|
}
|
|
|
|
// Method to send a command to Emacs
|
|
std::string sendToEmacs(const std::string& command) {
|
|
// In a real implementation, this would send the command to Emacs via emacsclient
|
|
// This executes: emacsclient -s whetstone -e "command" and returns the result
|
|
std::cout << "Sending to Emacs: " << command << std::endl;
|
|
|
|
// In a real implementation:
|
|
// 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");
|
|
#else
|
|
FILE* pipe = popen(cmd.c_str(), "r");
|
|
#endif
|
|
if (!pipe) return "nil";
|
|
|
|
char buffer[4096];
|
|
std::string result = "";
|
|
while (!feof(pipe)) {
|
|
if (fgets(buffer, sizeof(buffer), pipe) != NULL) {
|
|
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<Module> loadFile(const std::string& path) {
|
|
// First, get the file content from Emacs
|
|
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
|
|
std::string targetLanguage = "python"; // Default
|
|
if (path.substr(path.find_last_of(".") + 1) == "cpp" || path.substr(path.find_last_of(".") + 1) == "hpp") {
|
|
targetLanguage = "cpp";
|
|
} else if (path.substr(path.find_last_of(".") + 1) == "py") {
|
|
targetLanguage = "python";
|
|
} else if (path.substr(path.find_last_of(".") + 1) == "el" || path.substr(path.find_last_of(".") + 1) == "elisp") {
|
|
targetLanguage = "elisp";
|
|
} else if (path.substr(path.find_last_of(".") + 1) == "js") {
|
|
targetLanguage = "javascript";
|
|
} else if (path.substr(path.find_last_of(".") + 1) == "ts") {
|
|
targetLanguage = "typescript";
|
|
} else if (path.substr(path.find_last_of(".") + 1) == "java") {
|
|
targetLanguage = "java";
|
|
} else if (path.substr(path.find_last_of(".") + 1) == "rs") {
|
|
targetLanguage = "rust";
|
|
} else if (path.substr(path.find_last_of(".") + 1) == "go") {
|
|
targetLanguage = "go";
|
|
}
|
|
|
|
// Parse the content using the appropriate tree-sitter parser
|
|
std::unique_ptr<Module> module;
|
|
if (targetLanguage == "python") {
|
|
module = TreeSitterParser::parsePython(content);
|
|
} else if (targetLanguage == "cpp") {
|
|
module = TreeSitterParser::parseCpp(content);
|
|
} else if (targetLanguage == "elisp") {
|
|
module = TreeSitterParser::parseElisp(content);
|
|
} else if (targetLanguage == "javascript") {
|
|
module = TreeSitterParser::parseJavaScript(content);
|
|
} else if (targetLanguage == "typescript") {
|
|
module = TreeSitterParser::parseTypeScript(content);
|
|
} else if (targetLanguage == "java") {
|
|
module = TreeSitterParser::parseJava(content);
|
|
} else if (targetLanguage == "rust") {
|
|
module = TreeSitterParser::parseRust(content);
|
|
} else if (targetLanguage == "go") {
|
|
module = TreeSitterParser::parseGo(content);
|
|
} else {
|
|
// For unknown languages, create a basic module with the content
|
|
module = std::make_unique<Module>();
|
|
module->id = "LoadedModule_" + std::to_string(reinterpret_cast<uintptr_t>(module.get()));
|
|
module->name = path;
|
|
module->targetLanguage = targetLanguage;
|
|
}
|
|
|
|
// Set the module name to match the file path
|
|
module->name = path;
|
|
|
|
return module;
|
|
}
|
|
|
|
// Method to save AST to a file via Emacs using appropriate generator
|
|
bool saveFile(const std::string& path, const ASTNode* ast) {
|
|
if (!ast) return false;
|
|
|
|
// Determine target language from the AST or file extension
|
|
std::string targetLanguage = "python"; // Default
|
|
if (ast->conceptType == "Module") {
|
|
const Module* module = static_cast<const Module*>(ast);
|
|
targetLanguage = module->targetLanguage;
|
|
} else {
|
|
// If not a module, try to determine from file extension
|
|
if (path.substr(path.find_last_of(".") + 1) == "cpp" || path.substr(path.find_last_of(".") + 1) == "hpp") {
|
|
targetLanguage = "cpp";
|
|
} else if (path.substr(path.find_last_of(".") + 1) == "py") {
|
|
targetLanguage = "python";
|
|
} else if (path.substr(path.find_last_of(".") + 1) == "el" || path.substr(path.find_last_of(".") + 1) == "elisp") {
|
|
targetLanguage = "elisp";
|
|
} else if (path.substr(path.find_last_of(".") + 1) == "js") {
|
|
targetLanguage = "javascript";
|
|
} else if (path.substr(path.find_last_of(".") + 1) == "ts") {
|
|
targetLanguage = "typescript";
|
|
} else if (path.substr(path.find_last_of(".") + 1) == "java") {
|
|
targetLanguage = "java";
|
|
} else if (path.substr(path.find_last_of(".") + 1) == "rs") {
|
|
targetLanguage = "rust";
|
|
} else if (path.substr(path.find_last_of(".") + 1) == "go") {
|
|
targetLanguage = "go";
|
|
}
|
|
}
|
|
|
|
// Generate code from the AST using the appropriate generator
|
|
std::string content;
|
|
if (targetLanguage == "python") {
|
|
PythonGenerator gen;
|
|
content = gen.generate(ast);
|
|
} else if (targetLanguage == "elisp") {
|
|
ElispGenerator elispGen;
|
|
content = elispGen.generate(ast);
|
|
} else if (targetLanguage == "cpp" || targetLanguage == "c++") {
|
|
CppGenerator gen;
|
|
content = gen.generate(ast);
|
|
} else if (targetLanguage == "javascript") {
|
|
JavaScriptGenerator gen;
|
|
content = gen.generate(ast);
|
|
} else if (targetLanguage == "typescript") {
|
|
TypeScriptGenerator gen;
|
|
content = gen.generate(ast);
|
|
} else if (targetLanguage == "java") {
|
|
JavaGenerator gen;
|
|
content = gen.generate(ast);
|
|
} else if (targetLanguage == "rust") {
|
|
RustGenerator gen;
|
|
content = gen.generate(ast);
|
|
} else if (targetLanguage == "go") {
|
|
GoGenerator gen;
|
|
content = gen.generate(ast);
|
|
} else {
|
|
// Default to Python generator for unknown languages
|
|
PythonGenerator gen;
|
|
content = gen.generate(ast);
|
|
}
|
|
|
|
// Send the content to Emacs to save to file
|
|
std::string ePath = escapeElispString(path);
|
|
std::string eContent = escapeElispString(content);
|
|
std::string command = "(with-current-buffer (find-file-noselect \"" + ePath + "\")" +
|
|
"(erase-buffer)" +
|
|
"(insert \"" + eContent + "\")" +
|
|
"(write-file \"" + ePath + "\"))";
|
|
std::string result = sendToEmacs(command);
|
|
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 ePath = escapeElispString(path);
|
|
std::string eContent = escapeElispString(content);
|
|
std::string command = "(with-current-buffer (find-file-noselect \"" + ePath + "\")" +
|
|
"(erase-buffer)" +
|
|
"(insert \"" + eContent + "\")" +
|
|
"(write-file \"" + ePath + "\"))";
|
|
std::string result = sendToEmacs(command);
|
|
return result.find("Error") == std::string::npos;
|
|
}
|
|
};
|