Files
whetstone_DSL/editor/src/APIBoundaryPreserver.h

254 lines
9.5 KiB
C++

#pragma once
// Step 445: API Boundary Preservation — ensures migrated modules maintain
// their public API surface with FFI annotations and @Contract verification.
#include "MigrationPlanGenerator.h"
#include <string>
#include <vector>
struct FunctionSignature {
std::string name;
std::string returnType;
std::vector<std::string> paramTypes;
std::vector<std::string> paramNames;
bool isPublic = true;
};
struct TypeMapping {
std::string sourceName;
std::string sourceLanguage;
std::string targetName;
std::string targetLanguage;
std::string note; // compatibility note
};
struct APIMigrationContract {
std::string functionName;
std::string precondition;
std::string postcondition;
};
struct FFIBoundary {
std::string functionName;
std::string sourceLanguage;
std::string targetLanguage;
std::string annotation; // e.g. @FFI(extern "C"), #[no_mangle], etc.
};
struct APIBoundaryReport {
std::string moduleName;
std::string sourceLanguage;
std::string targetLanguage;
std::vector<FunctionSignature> publicAPI;
std::vector<TypeMapping> typeMappings;
std::vector<APIMigrationContract> contracts;
std::vector<FFIBoundary> ffiBoundaries;
bool apiPreserved = true;
bool hasFunction(const std::string& name) const {
for (const auto& f : publicAPI)
if (f.name == name) return true;
return false;
}
bool hasTypeMapping(const std::string& sourceName) const {
for (const auto& t : typeMappings)
if (t.sourceName == sourceName) return true;
return false;
}
bool hasContract(const std::string& funcName) const {
for (const auto& c : contracts)
if (c.functionName == funcName) return true;
return false;
}
bool hasFFI(const std::string& funcName) const {
for (const auto& f : ffiBoundaries)
if (f.functionName == funcName) return true;
return false;
}
};
class APIBoundaryPreserver {
public:
static APIBoundaryReport analyze(const std::string& source,
const MigrationUnit& unit) {
APIBoundaryReport report;
report.moduleName = unit.moduleName;
report.sourceLanguage = unit.sourceLanguage;
report.targetLanguage = unit.targetLanguage;
// Extract public API from source
report.publicAPI = extractPublicFunctions(source, unit.exports);
// Generate type mappings
report.typeMappings = generateTypeMappings(source, unit);
// Generate contracts for public functions
report.contracts = generateContracts(report.publicAPI, source);
// Generate FFI boundaries for mixed-language operation
report.ffiBoundaries = generateFFIBoundaries(report.publicAPI, unit);
report.apiPreserved = true;
return report;
}
static bool verifyPreservation(const APIBoundaryReport& original,
const APIBoundaryReport& migrated) {
// Every original public function must exist in migrated
for (const auto& f : original.publicAPI) {
if (!migrated.hasFunction(f.name)) return false;
}
return true;
}
private:
static std::vector<FunctionSignature> extractPublicFunctions(
const std::string& source, const std::vector<std::string>& exports) {
std::vector<FunctionSignature> sigs;
for (const auto& name : exports) {
FunctionSignature sig;
sig.name = name;
sig.isPublic = true;
// Extract return type and params from source (heuristic)
extractSignatureDetails(source, name, sig);
sigs.push_back(std::move(sig));
}
return sigs;
}
static void extractSignatureDetails(const std::string& source,
const std::string& name,
FunctionSignature& sig) {
// Find "type name(" pattern
size_t namePos = source.find(name + "(");
if (namePos == std::string::npos) {
sig.returnType = "int"; // default for C
return;
}
// Extract return type (word before function name)
size_t start = namePos;
while (start > 0 && source[start - 1] == ' ') --start;
size_t typeEnd = start;
while (start > 0 && (std::isalnum((unsigned char)source[start - 1]) ||
source[start - 1] == '_' || source[start - 1] == '*')) --start;
sig.returnType = source.substr(start, typeEnd - start);
if (sig.returnType.empty()) sig.returnType = "int";
// Extract parameter list
size_t parenOpen = source.find('(', namePos);
size_t parenClose = source.find(')', parenOpen);
if (parenOpen != std::string::npos && parenClose != std::string::npos) {
std::string params = source.substr(parenOpen + 1, parenClose - parenOpen - 1);
parseParams(params, sig);
}
}
static void parseParams(const std::string& params, FunctionSignature& sig) {
if (params.empty() || params == "void") return;
size_t pos = 0;
while (pos < params.size()) {
size_t comma = params.find(',', pos);
if (comma == std::string::npos) comma = params.size();
std::string param = trim(params.substr(pos, comma - pos));
if (!param.empty()) {
// Split "type name" — last word is name, rest is type
size_t lastSpace = param.rfind(' ');
if (lastSpace != std::string::npos) {
sig.paramTypes.push_back(trim(param.substr(0, lastSpace)));
sig.paramNames.push_back(trim(param.substr(lastSpace + 1)));
} else {
sig.paramTypes.push_back(param);
sig.paramNames.push_back("arg" + std::to_string(sig.paramNames.size()));
}
}
pos = comma + 1;
}
}
static std::vector<TypeMapping> generateTypeMappings(const std::string& source,
const MigrationUnit& unit) {
std::vector<TypeMapping> mappings;
std::string tgt = unit.targetLanguage;
// Common C → target type mappings
auto addIf = [&](const std::string& pattern, const std::string& src,
const std::string& dst, const std::string& note) {
if (source.find(pattern) != std::string::npos)
mappings.push_back({src, unit.sourceLanguage, dst, tgt, note});
};
if (tgt == "rust") {
addIf("int ", "int", "i32", "C int maps to Rust i32");
addIf("char*", "char*", "&str / String", "C string to Rust string type");
addIf("void*", "void*", "*mut c_void", "Opaque pointer in FFI");
addIf("struct ", "struct", "struct", "C struct maps to Rust struct");
} else if (tgt == "java") {
addIf("int ", "int", "int", "Direct mapping");
addIf("char*", "char*", "String", "C string to Java String");
addIf("struct ", "struct", "class", "C struct maps to Java class");
} else if (tgt == "python") {
addIf("int ", "int", "int", "Direct mapping");
addIf("char*", "char*", "str", "C string to Python str");
}
return mappings;
}
static std::vector<APIMigrationContract> generateContracts(
const std::vector<FunctionSignature>& api, const std::string& source) {
std::vector<APIMigrationContract> contracts;
for (const auto& f : api) {
APIMigrationContract c;
c.functionName = f.name;
// Generate pre/post conditions from signature
if (!f.paramTypes.empty()) {
// Pointer params get non-null precondition
for (size_t i = 0; i < f.paramTypes.size(); ++i) {
if (f.paramTypes[i].find('*') != std::string::npos) {
c.precondition += f.paramNames[i] + " != NULL; ";
}
}
}
if (f.returnType.find('*') != std::string::npos) {
c.postcondition = "result may be NULL (caller must check)";
} else if (f.returnType == "int") {
c.postcondition = "returns int result";
}
if (!c.precondition.empty() || !c.postcondition.empty())
contracts.push_back(std::move(c));
}
return contracts;
}
static std::vector<FFIBoundary> generateFFIBoundaries(
const std::vector<FunctionSignature>& api, const MigrationUnit& unit) {
std::vector<FFIBoundary> boundaries;
for (const auto& f : api) {
FFIBoundary ffi;
ffi.functionName = f.name;
ffi.sourceLanguage = unit.sourceLanguage;
ffi.targetLanguage = unit.targetLanguage;
if (unit.targetLanguage == "rust") {
ffi.annotation = "@FFI(#[no_mangle] pub extern \"C\")";
} else if (unit.targetLanguage == "java") {
ffi.annotation = "@FFI(JNI native)";
} else {
ffi.annotation = "@FFI(extern)";
}
boundaries.push_back(std::move(ffi));
}
return boundaries;
}
static std::string trim(const std::string& s) {
size_t start = 0, end = s.size();
while (start < end && std::isspace((unsigned char)s[start])) ++start;
while (end > start && std::isspace((unsigned char)s[end - 1])) --end;
return s.substr(start, end - start);
}
};