mirror of
https://github.com/logos-co/logos-cpp-sdk.git
synced 2026-08-31 17:51:07 +00:00
247 lines
8.5 KiB
C++
247 lines
8.5 KiB
C++
#include "c_header_parser.h"
|
|
#include <QFile>
|
|
#include <QJsonDocument>
|
|
#include <QJsonObject>
|
|
#include <QJsonArray>
|
|
#include <QRegularExpression>
|
|
|
|
// Map a C type string to a LIDL TypeExpr
|
|
static TypeExpr cTypeToLidl(const QString& raw)
|
|
{
|
|
QString t = raw.trimmed();
|
|
// Remove trailing const (rare but possible)
|
|
// Normalize pointer types
|
|
t.replace(QRegularExpression("\\s+"), " ");
|
|
|
|
if (t == "const char*" || t == "const char *" || t == "char*" || t == "char *")
|
|
return {TypeExpr::Primitive, "tstr", {}};
|
|
if (t == "int64_t" || t == "long long" || t == "int32_t" || t == "int")
|
|
return {TypeExpr::Primitive, "int", {}};
|
|
if (t == "uint64_t" || t == "unsigned long long" || t == "uint32_t" || t == "unsigned int" || t == "size_t")
|
|
return {TypeExpr::Primitive, "uint", {}};
|
|
if (t == "double" || t == "float")
|
|
return {TypeExpr::Primitive, "float64", {}};
|
|
if (t == "bool" || t == "_Bool")
|
|
return {TypeExpr::Primitive, "bool", {}};
|
|
if (t == "void")
|
|
return {TypeExpr::Primitive, "void", {}};
|
|
|
|
return {TypeExpr::Primitive, "any", {}};
|
|
}
|
|
|
|
// Split a C parameter list respecting nested parens (should not appear but just in case)
|
|
static QStringList splitParams(const QString& params)
|
|
{
|
|
QStringList result;
|
|
int depth = 0;
|
|
int start = 0;
|
|
for (int i = 0; i < params.size(); ++i) {
|
|
QChar c = params[i];
|
|
if (c == '(') ++depth;
|
|
else if (c == ')') --depth;
|
|
else if (c == ',' && depth == 0) {
|
|
result.append(params.mid(start, i - start).trimmed());
|
|
start = i + 1;
|
|
}
|
|
}
|
|
QString last = params.mid(start).trimmed();
|
|
if (!last.isEmpty() && last != "void")
|
|
result.append(last);
|
|
return result;
|
|
}
|
|
|
|
// Parse a single parameter like "int64_t a" or "const char* name"
|
|
static bool parseCParam(const QString& raw, ParamDecl& out)
|
|
{
|
|
QString s = raw.trimmed();
|
|
if (s.isEmpty() || s == "void") return false;
|
|
|
|
// Handle pointer types: find the last word as the param name
|
|
// e.g. "const char* name" -> type="const char*", name="name"
|
|
// e.g. "int64_t a" -> type="int64_t", name="a"
|
|
|
|
// If there's a *, the name is after the last *
|
|
int lastStar = s.lastIndexOf('*');
|
|
if (lastStar >= 0) {
|
|
QString typeStr = s.left(lastStar + 1).trimmed();
|
|
QString nameStr = s.mid(lastStar + 1).trimmed();
|
|
if (nameStr.isEmpty()) {
|
|
// No param name — generate one
|
|
nameStr = "arg";
|
|
}
|
|
out.name = nameStr;
|
|
out.type = cTypeToLidl(typeStr);
|
|
return true;
|
|
}
|
|
|
|
// No pointer: last word is name, rest is type
|
|
int lastSpace = s.lastIndexOf(' ');
|
|
if (lastSpace < 0) {
|
|
// Single word — treat as type with generated name
|
|
out.name = "arg";
|
|
out.type = cTypeToLidl(s);
|
|
return true;
|
|
}
|
|
|
|
out.type = cTypeToLidl(s.left(lastSpace).trimmed());
|
|
out.name = s.mid(lastSpace + 1).trimmed();
|
|
return true;
|
|
}
|
|
|
|
// Extract return type from a function declaration line.
|
|
// Returns the return type string, and sets funcRest to "funcname(params)"
|
|
static QString extractReturnType(const QString& line, QString& funcRest)
|
|
{
|
|
// Find the opening paren
|
|
int parenIdx = line.indexOf('(');
|
|
if (parenIdx < 0) return QString();
|
|
|
|
// Everything before '(' is "returnType funcName"
|
|
QString beforeParen = line.left(parenIdx).trimmed();
|
|
|
|
// Last word before '(' is the function name
|
|
int lastSpace = beforeParen.lastIndexOf(' ');
|
|
int lastStar = beforeParen.lastIndexOf('*');
|
|
int splitPos = qMax(lastSpace, lastStar);
|
|
|
|
if (splitPos < 0) return QString(); // Can't separate return type from name
|
|
|
|
QString retType = beforeParen.left(splitPos + 1).trimmed();
|
|
QString funcName = beforeParen.mid(splitPos + 1).trimmed();
|
|
funcRest = funcName + line.mid(parenIdx);
|
|
|
|
return retType;
|
|
}
|
|
|
|
CHeaderParseResult parseCHeader(const QString& headerPath,
|
|
const QString& prefixArg,
|
|
const QString& metadataPath,
|
|
QTextStream& err)
|
|
{
|
|
CHeaderParseResult result;
|
|
|
|
// Read metadata.json
|
|
QFile metaFile(metadataPath);
|
|
if (!metaFile.open(QIODevice::ReadOnly)) {
|
|
result.error = "Cannot open metadata file: " + metadataPath;
|
|
return result;
|
|
}
|
|
QJsonParseError jsonErr;
|
|
QJsonDocument doc = QJsonDocument::fromJson(metaFile.readAll(), &jsonErr);
|
|
if (doc.isNull()) {
|
|
result.error = "Invalid metadata JSON in " + metadataPath + ": " + jsonErr.errorString();
|
|
return result;
|
|
}
|
|
QJsonObject meta = doc.object();
|
|
result.module.name = meta["name"].toString();
|
|
result.module.version = meta["version"].toString("1.0.0");
|
|
result.module.description = meta["description"].toString();
|
|
result.module.category = meta["category"].toString();
|
|
QJsonArray depsArr = meta["dependencies"].toArray();
|
|
for (const QJsonValue& v : depsArr)
|
|
result.module.depends.push_back(v.toString());
|
|
|
|
// Determine prefix
|
|
QString prefix = prefixArg;
|
|
if (prefix.isEmpty()) {
|
|
// Check for c_prefix in metadata.json nix section
|
|
QJsonObject nix = meta["nix"].toObject();
|
|
prefix = nix["c_prefix"].toString();
|
|
}
|
|
if (prefix.isEmpty()) {
|
|
// Auto-derive from module name: "rust_calc_module" -> "rust_calc_"
|
|
// Heuristic: strip "_module" suffix, then add "_"
|
|
QString name = result.module.name;
|
|
if (name.endsWith("_module"))
|
|
name = name.left(name.length() - 7); // remove "_module"
|
|
prefix = name + "_";
|
|
}
|
|
result.prefix = prefix;
|
|
|
|
// Read header file
|
|
QFile headerFile(headerPath);
|
|
if (!headerFile.open(QIODevice::ReadOnly | QIODevice::Text)) {
|
|
result.error = "Cannot open header file: " + headerPath;
|
|
return result;
|
|
}
|
|
QString content = QString::fromUtf8(headerFile.readAll());
|
|
QStringList lines = content.split('\n');
|
|
|
|
// Reserved method names that conflict with PluginInterface
|
|
QSet<QString> reservedNames = {"name", "version", "initLogos"};
|
|
|
|
for (const QString& rawLine : lines) {
|
|
QString line = rawLine.trimmed();
|
|
|
|
// Skip empty lines, preprocessor, comments, extern "C", braces
|
|
if (line.isEmpty()) continue;
|
|
if (line.startsWith('#')) continue;
|
|
if (line.startsWith("//")) continue;
|
|
if (line.startsWith("/*")) continue;
|
|
if (line.startsWith("extern")) continue;
|
|
if (line == "{" || line == "}") continue;
|
|
if (line.startsWith("typedef")) continue;
|
|
|
|
// Must end with ';' (function declaration)
|
|
if (!line.endsWith(';')) continue;
|
|
line.chop(1); // remove ';'
|
|
|
|
// Extract return type and function signature
|
|
QString funcRest;
|
|
QString retTypeStr = extractReturnType(line, funcRest);
|
|
if (retTypeStr.isEmpty()) continue;
|
|
|
|
// Extract function name and params
|
|
int parenOpen = funcRest.indexOf('(');
|
|
int parenClose = funcRest.lastIndexOf(')');
|
|
if (parenOpen < 0 || parenClose < 0) continue;
|
|
|
|
QString funcName = funcRest.left(parenOpen).trimmed();
|
|
QString paramsStr = funcRest.mid(parenOpen + 1, parenClose - parenOpen - 1).trimmed();
|
|
|
|
// Must start with prefix
|
|
if (!funcName.startsWith(prefix)) continue;
|
|
|
|
// Strip prefix to get method name
|
|
QString methodName = funcName.mid(prefix.length());
|
|
if (methodName.isEmpty()) continue;
|
|
|
|
// Skip reserved names — rename with "lib" prefix
|
|
if (reservedNames.contains(methodName)) {
|
|
methodName = "lib" + methodName.at(0).toUpper() + methodName.mid(1);
|
|
}
|
|
|
|
// Parse return type
|
|
TypeExpr retType = cTypeToLidl(retTypeStr);
|
|
|
|
// Parse parameters
|
|
QVector<ParamDecl> params;
|
|
QStringList paramParts = splitParams(paramsStr);
|
|
int argIdx = 0;
|
|
for (const QString& p : paramParts) {
|
|
ParamDecl pd;
|
|
if (parseCParam(p, pd)) {
|
|
// If param name is "arg", make it unique
|
|
if (pd.name == "arg") {
|
|
pd.name = QString("arg%1").arg(argIdx);
|
|
}
|
|
params.push_back(pd);
|
|
++argIdx;
|
|
}
|
|
}
|
|
|
|
MethodDecl method;
|
|
method.name = methodName;
|
|
method.returnType = retType;
|
|
method.params = params;
|
|
result.module.methods.push_back(method);
|
|
}
|
|
|
|
if (result.module.methods.empty()) {
|
|
err << "Warning: No functions with prefix '" << prefix
|
|
<< "' found in " << headerPath << "\n";
|
|
}
|
|
|
|
return result;
|
|
}
|