Files
whetstone_DSL/editor/src/Orchestrator.h

320 lines
12 KiB
C
Raw Normal View History

2026-02-06 20:22:37 -07:00
#pragma once
#include "ast/Module.h"
#include "ast/Serialization.h"
#include <string>
#include <memory>
#include <fstream>
#include <iostream>
2026-02-06 20:44:07 -07:00
#include <nlohmann/json.hpp>
2026-02-06 20:22:37 -07:00
2026-02-06 21:45:02 -07:00
#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
2026-02-06 21:45:02 -07:00
2026-02-06 20:22:37 -07:00
class Orchestrator {
private:
std::unique_ptr<Module> currentAST;
std::string loadedFilePath;
2026-02-06 21:45:02 -07:00
// 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)
2026-02-06 20:22:37 -07:00
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;
}
2026-02-06 21:45:02 -07:00
// 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) {
// 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
}
}
} 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;
}
// Helper function to find a node by ID in the AST (needs to be implemented)
ASTNode* findNodeById(ASTNode* root, const std::string& id) {
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:
std::string cmd = "emacsclient -s whetstone -e \"" + command + "\"";
FILE* pipe = _popen(cmd.c_str(), "r");
if (!pipe) return "nil"; // Return nil if we can't open the pipe
char buffer[4096];
std::string result = "";
while (!feof(pipe)) {
if (fgets(buffer, sizeof(buffer), pipe) != NULL) {
result += buffer;
}
}
_pclose(pipe);
// Trim whitespace from the result
result.erase(result.find_last_not_of(" \t\n\r\f\v") + 1);
return result.empty() ? "nil" : result;
}
// 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 \"" + 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";
}
// 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 {
// 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
bool saveFile(const std::string& path, const ASTNode* ast) {
if (!ast) return false;
// Generate code from the AST based on the target language
std::string content;
if (ast->conceptType == "Module") {
// Use the appropriate generator based on the target language
const Module* module = static_cast<const Module*>(ast);
if (module->targetLanguage == "python") {
PythonGenerator gen;
content = gen.generate(ast);
} else if (module->targetLanguage == "elisp") {
ElispGenerator elispGen;
content = elispGen.generate(ast);
} else {
// Default to Python generator
PythonGenerator gen;
content = gen.generate(ast);
}
} else {
// If it's not a module, wrap it in a temporary module for generation
PythonGenerator gen;
content = gen.generate(ast);
}
// Send the content to Emacs to save to file
std::string command = "(with-temp-file \"" + path + "\" (insert \"" + content + "\"))";
std::string result = sendToEmacs(command);
// If the result is not an error, assume success
return result.find("Error") == std::string::npos;
}
2026-02-06 20:22:37 -07:00
};