161 lines
5.7 KiB
C++
161 lines
5.7 KiB
C++
#pragma once
|
|
#include <string>
|
|
#include <vector>
|
|
#include <fstream>
|
|
#include <filesystem>
|
|
#include <algorithm>
|
|
#include "ast/ASTNode.h"
|
|
#include "ast/Module.h"
|
|
#include "SemannoFormat.h"
|
|
|
|
struct SemannoSidecarSaveResult {
|
|
bool success = false;
|
|
std::string path;
|
|
int annotationCount = 0;
|
|
std::string error;
|
|
};
|
|
|
|
struct SemannoSidecarLoadResult {
|
|
bool success = false;
|
|
std::vector<SemannoEntry> entries;
|
|
int count = 0;
|
|
std::string error;
|
|
};
|
|
|
|
class SemannoSidecar {
|
|
public:
|
|
// Generates the path for a .semanno sidecar file.
|
|
// e.g., /path/to/project/src/file.py -> /path/to/project/.whetstone/src/file.py.semanno
|
|
static std::filesystem::path getSidecarPath(const std::filesystem::path& workspaceRoot, const std::filesystem::path& filePath) {
|
|
std::filesystem::path relativePath;
|
|
if (filePath.is_absolute()) {
|
|
std::error_code ec;
|
|
relativePath = std::filesystem::relative(filePath, workspaceRoot, ec);
|
|
if (ec || relativePath.empty()) {
|
|
relativePath = filePath.filename();
|
|
}
|
|
} else {
|
|
relativePath = filePath;
|
|
}
|
|
|
|
std::filesystem::path sidecarDir = workspaceRoot / ".whetstone";
|
|
return sidecarDir / (relativePath.generic_string() + ".semanno");
|
|
}
|
|
|
|
// Saves all semantic annotations from an AST module to a .semanno sidecar file.
|
|
static SemannoSidecarSaveResult saveSemannoSidecar(const std::string& workspaceRoot, const std::string& filePath, const Module* module) {
|
|
SemannoSidecarSaveResult result;
|
|
if (!module) {
|
|
result.error = "No AST to save";
|
|
return result;
|
|
}
|
|
|
|
std::vector<std::pair<int, std::string>> entries;
|
|
collectEntries(module, entries);
|
|
result.annotationCount = static_cast<int>(entries.size());
|
|
|
|
auto path = getSidecarPath(workspaceRoot, filePath);
|
|
result.path = path.string();
|
|
if (entries.empty()) {
|
|
try {
|
|
std::filesystem::create_directories(path.parent_path());
|
|
std::ofstream file(path);
|
|
if (!file.is_open()) {
|
|
result.error = "Cannot write to " + path.string();
|
|
return result;
|
|
}
|
|
} catch (const std::filesystem::filesystem_error& e) {
|
|
result.error = e.what();
|
|
return result;
|
|
}
|
|
result.success = true;
|
|
return result;
|
|
}
|
|
|
|
// Sort by line number to ensure a stable format
|
|
std::sort(entries.begin(), entries.end(), [](const auto& a, const auto& b) {
|
|
return a.first < b.first;
|
|
});
|
|
|
|
try {
|
|
std::filesystem::create_directories(path.parent_path());
|
|
|
|
std::ofstream file(path);
|
|
if (!file.is_open()) {
|
|
result.error = "Cannot write to " + path.string();
|
|
return result;
|
|
}
|
|
|
|
for (const auto& entry : entries) {
|
|
file << "L" << entry.first << ": " << entry.second << "\n";
|
|
}
|
|
result.success = true;
|
|
return result;
|
|
} catch (const std::filesystem::filesystem_error& e) {
|
|
result.error = e.what();
|
|
return result;
|
|
}
|
|
}
|
|
|
|
// Loads semantic annotations from a .semanno sidecar file.
|
|
static SemannoSidecarLoadResult loadSemannoSidecar(const std::string& workspaceRoot, const std::string& filePath) {
|
|
SemannoSidecarLoadResult result;
|
|
auto path = getSidecarPath(workspaceRoot, filePath);
|
|
|
|
std::ifstream file(path);
|
|
if (!file.is_open()) {
|
|
result.error = "No sidecar file: " + path.string();
|
|
return result;
|
|
}
|
|
|
|
std::string line;
|
|
while (std::getline(file, line)) {
|
|
if (line.empty() || line[0] != 'L') continue;
|
|
|
|
auto semanno = SemannoParser::parse(line);
|
|
if (!semanno.type.empty()) {
|
|
result.entries.push_back(semanno);
|
|
}
|
|
}
|
|
result.count = static_cast<int>(result.entries.size());
|
|
result.success = true;
|
|
return result;
|
|
}
|
|
|
|
private:
|
|
// Helper to recursively traverse the AST and collect annotation entries.
|
|
static void collectEntries(const ASTNode* node, std::vector<std::pair<int, std::string>>& entries) {
|
|
if (!node) return;
|
|
|
|
// An annotation's line number is determined by its parent node
|
|
if (node->parent && node->parent->spanStartLine >= 0) {
|
|
std::string semannoString = SemannoEmitter::emit(node);
|
|
if (!semannoString.empty()) {
|
|
// Link annotation comments to their parent source line in the sidecar.
|
|
entries.push_back({node->parent->spanStartLine, semannoString});
|
|
}
|
|
}
|
|
|
|
// Recurse through all children
|
|
for (const auto* child : node->allChildren()) {
|
|
collectEntries(child, entries);
|
|
}
|
|
}
|
|
};
|
|
|
|
inline std::string semannoSidecarPath(const std::string& workspaceRoot,
|
|
const std::string& filePath) {
|
|
return SemannoSidecar::getSidecarPath(workspaceRoot, filePath).string();
|
|
}
|
|
|
|
inline SemannoSidecarSaveResult saveSemannoSidecar(const std::string& workspaceRoot,
|
|
const std::string& filePath,
|
|
const Module* module) {
|
|
return SemannoSidecar::saveSemannoSidecar(workspaceRoot, filePath, module);
|
|
}
|
|
|
|
inline SemannoSidecarLoadResult loadSemannoSidecar(const std::string& workspaceRoot,
|
|
const std::string& filePath) {
|
|
return SemannoSidecar::loadSemannoSidecar(workspaceRoot, filePath);
|
|
}
|