feat: add C# and Python ctypes code generators

- C# backend: [StructLayout(LayoutKind.Explicit)] with [FieldOffset], IntPtr pointers, fixed arrays, enums
- Python backend: ctypes.Structure with _fields_, POINTER() for typed pointers, c_void_p, padding
- Both support enums, vectors, bitfields, arrays, unions, static fields
- Export menu: C# Structs... and Python ctypes... entries
- Format combo auto-populates new options
- 14 new tests for both backends (all passing)
This commit is contained in:
IChooseYou
2026-03-10 15:20:56 -06:00
committed by IChooseYou
parent 6c8b7d3d97
commit ea85b7a621
5 changed files with 1297 additions and 4 deletions

View File

@@ -733,6 +733,8 @@ void MainWindow::createMenus() {
Qt5Qt6AddAction(exportMenu, "&C++ Header...", QKeySequence::UnknownKey, QIcon(), this, &MainWindow::exportCpp);
Qt5Qt6AddAction(exportMenu, "&Rust Structs...", QKeySequence::UnknownKey, QIcon(), this, &MainWindow::exportRust);
Qt5Qt6AddAction(exportMenu, "#&define Offsets...", QKeySequence::UnknownKey, QIcon(), this, &MainWindow::exportDefines);
Qt5Qt6AddAction(exportMenu, "C&# Structs...", QKeySequence::UnknownKey, QIcon(), this, &MainWindow::exportCSharp);
Qt5Qt6AddAction(exportMenu, "&Python ctypes...", QKeySequence::UnknownKey, QIcon(), this, &MainWindow::exportPython);
Qt5Qt6AddAction(exportMenu, "ReClass &XML...", QKeySequence::UnknownKey, QIcon(), this, &MainWindow::exportReclassXmlAction);
// Examples submenu — scan once at init
{
@@ -3291,6 +3293,51 @@ void MainWindow::exportDefines() {
setAppStatus("Exported to " + QFileInfo(path).fileName());
}
// ── Export C# structs ──
void MainWindow::exportCSharp() {
auto* tab = activeTab();
if (!tab) return;
QString path = QFileDialog::getSaveFileName(this,
"Export C# Structs", {}, "C# Source (*.cs);;All Files (*)");
if (path.isEmpty()) return;
const QHash<NodeKind, QString>* aliases =
tab->doc->typeAliases.isEmpty() ? nullptr : &tab->doc->typeAliases;
bool asserts = QSettings("Reclass", "Reclass").value("generatorAsserts", false).toBool();
QString text = renderCSharpAll(tab->doc->tree, aliases, asserts);
QFile file(path);
if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
QMessageBox::warning(this, "Export Failed",
"Could not write to: " + path);
return;
}
file.write(text.toUtf8());
setAppStatus("Exported to " + QFileInfo(path).fileName());
}
// ── Export Python ctypes ──
void MainWindow::exportPython() {
auto* tab = activeTab();
if (!tab) return;
QString path = QFileDialog::getSaveFileName(this,
"Export Python ctypes", {}, "Python Source (*.py);;All Files (*)");
if (path.isEmpty()) return;
QString text = renderPythonAll(tab->doc->tree);
QFile file(path);
if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
QMessageBox::warning(this, "Export Failed",
"Could not write to: " + path);
return;
}
file.write(text.toUtf8());
setAppStatus("Exported to " + QFileInfo(path).fileName());
}
// ── Export ReClass XML ──
void MainWindow::exportReclassXmlAction() {