Enhance DSL interpreter with new built-in functions
Add support for: - basename() function for extracting filenames from paths - cstring() function for reading null-terminated strings - ascii() function for reading fixed-length ASCII strings - Enhanced type registry with additional primitive types - Improved parser with better error handling These additions enable more flexible XScript definitions for parsing binary file formats. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
14d507dc48
commit
d7285b5bbe
@ -43,10 +43,12 @@ enum class TokenKind {
|
||||
KwCriteria,
|
||||
KwRequire,
|
||||
KwBool,
|
||||
KwBreak,
|
||||
|
||||
// scalar keywords
|
||||
KwU8, KwU16, KwU32, KwU64,
|
||||
KwI8, KwI16, KwI32, KwI64,
|
||||
KwF32, KwF64,
|
||||
};
|
||||
|
||||
struct Token {
|
||||
|
||||
@ -9,7 +9,7 @@
|
||||
|
||||
enum class ByteOrder { LE, BE };
|
||||
|
||||
enum class ScalarType { U8, I8, U16, I16, U32, I32, U64, I64, Bool};
|
||||
enum class ScalarType { U8, I8, U16, I16, U32, I32, U64, I64, F32, F64, Bool};
|
||||
|
||||
struct Expr;
|
||||
using ExprPtr = QSharedPointer<Expr>;
|
||||
@ -101,8 +101,9 @@ struct Stmt {
|
||||
struct Require { ExprPtr cond; };
|
||||
struct SetByteOrder { ByteOrder order; };
|
||||
struct CallStmt { ExprPtr call; }; // Function call as statement (result discarded)
|
||||
struct Break {}; // break from while loop
|
||||
|
||||
using Node = std::variant<ReadScalar, Skip, Align, Seek, Assign, If, While, Repeat, ForRange, Require, SetByteOrder, CallStmt>;
|
||||
using Node = std::variant<ReadScalar, Skip, Align, Seek, Assign, If, While, Repeat, ForRange, Require, SetByteOrder, CallStmt, Break>;
|
||||
Node node;
|
||||
|
||||
int line = 1;
|
||||
|
||||
@ -1,13 +1,37 @@
|
||||
// #define DSL_DEBUG_LOGGING // Uncomment to enable debug logging
|
||||
#ifdef DSL_DEBUG_LOGGING
|
||||
#define DSL_LOG(msg) LogManager::instance().addEntry(msg)
|
||||
#else
|
||||
#define DSL_LOG(msg) ((void)0)
|
||||
#endif
|
||||
|
||||
#include "interpreter.h"
|
||||
#include <cstring>
|
||||
#include "compression.h"
|
||||
#include "utils.h"
|
||||
#include "logmanager.h"
|
||||
|
||||
#include <stdexcept>
|
||||
// Custom exception for break statement
|
||||
struct BreakException : std::exception {};
|
||||
#include <QFileInfo>
|
||||
|
||||
Interpreter::Interpreter(Module mod) : m_mod(std::move(mod)) {}
|
||||
|
||||
void Interpreter::setProgressCallback(ProgressCallback cb) {
|
||||
m_progressCallback = std::move(cb);
|
||||
}
|
||||
|
||||
void Interpreter::setFileSize(qint64 size) {
|
||||
m_fileSize = size;
|
||||
}
|
||||
|
||||
void Interpreter::reportProgress(Runtime& rt) const {
|
||||
if (m_progressCallback && rt.in && rt.in->device()) {
|
||||
m_progressCallback(rt.in->device()->pos(), m_fileSize);
|
||||
}
|
||||
}
|
||||
|
||||
static QDataStream::ByteOrder toQtOrder(ByteOrder b) {
|
||||
return (b == ByteOrder::LE) ? QDataStream::LittleEndian : QDataStream::BigEndian;
|
||||
}
|
||||
@ -23,13 +47,14 @@ QVariantMap Interpreter::runType(const QString& typeName, QDataStream& stream, c
|
||||
}
|
||||
|
||||
const qint64 startPos = stream.device() ? stream.device()->pos() : -1;
|
||||
LogManager::instance().addEntry(QString("[PARSE] Starting type '%1' at pos 0x%2")
|
||||
DSL_LOG(QString("[PARSE] Starting type '%1' at pos 0x%2")
|
||||
.arg(typeName)
|
||||
.arg(startPos, 0, 16));
|
||||
|
||||
Runtime rt;
|
||||
rt.in = &stream;
|
||||
rt.module = &m_mod;
|
||||
rt.pushType(typeName); // Track root type
|
||||
|
||||
const TypeDef& td = m_mod.types[typeName];
|
||||
rt.order = td.order;
|
||||
@ -45,7 +70,7 @@ QVariantMap Interpreter::runType(const QString& typeName, QDataStream& stream, c
|
||||
rt.vars["_type"] = typeName;
|
||||
|
||||
const qint64 endPos = stream.device() ? stream.device()->pos() : -1;
|
||||
LogManager::instance().addEntry(QString("[PARSE] Finished type '%1' at pos 0x%2 (consumed %3 bytes)")
|
||||
DSL_LOG(QString("[PARSE] Finished type '%1' at pos 0x%2 (consumed %3 bytes)")
|
||||
.arg(typeName)
|
||||
.arg(endPos, 0, 16)
|
||||
.arg(endPos - startPos));
|
||||
@ -67,18 +92,18 @@ bool Interpreter::checkCriteria(const QString &typeName, QIODevice *dev, const Q
|
||||
{
|
||||
if (!m_mod.types.contains(typeName))
|
||||
{
|
||||
LogManager::instance().addEntry(QString("[CRITERIA] Type '%1' not found").arg(typeName));
|
||||
DSL_LOG(QString("[CRITERIA] Type '%1' not found").arg(typeName));
|
||||
return false;
|
||||
}
|
||||
|
||||
const TypeDef& td = m_mod.types[typeName];
|
||||
if (td.criteria.isEmpty())
|
||||
{
|
||||
LogManager::instance().addEntry(QString("[CRITERIA] Type '%1' has no criteria, returning true").arg(typeName));
|
||||
DSL_LOG(QString("[CRITERIA] Type '%1' has no criteria, returning true").arg(typeName));
|
||||
return true;
|
||||
}
|
||||
|
||||
LogManager::instance().addEntry(QString("[CRITERIA] Checking type '%1' for file '%2'").arg(typeName).arg(filePath));
|
||||
DSL_LOG(QString("[CRITERIA] Checking type '%1' for file '%2'").arg(typeName).arg(filePath));
|
||||
|
||||
// Save/restore device position
|
||||
const qint64 savedPos = dev->pos();
|
||||
@ -102,17 +127,17 @@ bool Interpreter::checkCriteria(const QString &typeName, QIODevice *dev, const Q
|
||||
|
||||
restore();
|
||||
|
||||
LogManager::instance().addEntry(QString("[CRITERIA] Type '%1' PASSED criteria").arg(typeName));
|
||||
DSL_LOG(QString("[CRITERIA] Type '%1' PASSED criteria").arg(typeName));
|
||||
return true;
|
||||
} catch (const std::exception& e) {
|
||||
restore();
|
||||
|
||||
LogManager::instance().addEntry(QString("[CRITERIA] Type '%1' FAILED: %2").arg(typeName).arg(e.what()));
|
||||
DSL_LOG(QString("[CRITERIA] Type '%1' FAILED: %2").arg(typeName).arg(e.what()));
|
||||
return false;
|
||||
} catch (...) {
|
||||
restore();
|
||||
|
||||
LogManager::instance().addEntry(QString("[CRITERIA] Type '%1' FAILED (unknown exception)").arg(typeName));
|
||||
DSL_LOG(QString("[CRITERIA] Type '%1' FAILED (unknown exception)").arg(typeName));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@ -126,6 +151,7 @@ QVariantMap Interpreter::runTypeInternal(const QString &typeName, QDataStream &s
|
||||
Runtime rt;
|
||||
rt.in = &stream;
|
||||
rt.module = &m_mod;
|
||||
rt.pushType(typeName); // Track type for error context
|
||||
|
||||
const TypeDef& td = m_mod.types[typeName];
|
||||
|
||||
@ -164,16 +190,32 @@ bool Interpreter::toBool(const QVariant& v) const {
|
||||
|
||||
QVariant Interpreter::readScalar(Runtime& rt, ScalarType t) const {
|
||||
QDataStream& in = *rt.in;
|
||||
QIODevice* dev = in.device();
|
||||
const qint64 pos = dev ? dev->pos() : -1;
|
||||
|
||||
auto checkStatus = [&](const char* typeName, int expectedSize) {
|
||||
if (in.status() != QDataStream::Ok) {
|
||||
throw std::runtime_error(
|
||||
QString("Failed to read %1 at pos 0x%2. Parsing: %3")
|
||||
.arg(typeName)
|
||||
.arg(pos, 0, 16)
|
||||
.arg(rt.typeStackString().isEmpty() ? "root" : rt.typeStackString())
|
||||
.toStdString());
|
||||
}
|
||||
};
|
||||
|
||||
switch (t) {
|
||||
case ScalarType::U8: { quint8 v; in >> v; return v; }
|
||||
case ScalarType::I8: { qint8 v; in >> v; return v; }
|
||||
case ScalarType::U16: { quint16 v; in >> v; return v; }
|
||||
case ScalarType::I16: { qint16 v; in >> v; return v; }
|
||||
case ScalarType::U32: { quint32 v; in >> v; return v; }
|
||||
case ScalarType::I32: { qint32 v; in >> v; return v; }
|
||||
case ScalarType::U64: { quint64 v; in >> v; return v; }
|
||||
case ScalarType::I64: { qint64 v; in >> v; return v; }
|
||||
case ScalarType::Bool: { quint8 v; in >> v; return (v != 0); }
|
||||
case ScalarType::U8: { quint8 v; in >> v; checkStatus("u8", 1); return v; }
|
||||
case ScalarType::I8: { qint8 v; in >> v; checkStatus("i8", 1); return v; }
|
||||
case ScalarType::U16: { quint16 v; in >> v; checkStatus("u16", 2); return v; }
|
||||
case ScalarType::I16: { qint16 v; in >> v; checkStatus("i16", 2); return v; }
|
||||
case ScalarType::U32: { quint32 v; in >> v; checkStatus("u32", 4); return v; }
|
||||
case ScalarType::I32: { qint32 v; in >> v; checkStatus("i32", 4); return v; }
|
||||
case ScalarType::U64: { quint64 v; in >> v; checkStatus("u64", 8); return v; }
|
||||
case ScalarType::I64: { qint64 v; in >> v; checkStatus("i64", 8); return v; }
|
||||
case ScalarType::F32: { quint32 bits; in >> bits; checkStatus("f32", 4); float v; memcpy(&v, &bits, 4); return v; }
|
||||
case ScalarType::F64: { quint64 bits; in >> bits; checkStatus("f64", 8); double v; memcpy(&v, &bits, 8); return v; }
|
||||
case ScalarType::Bool: { quint8 v; in >> v; checkStatus("bool", 1); return (v != 0); }
|
||||
}
|
||||
return {};
|
||||
}
|
||||
@ -183,12 +225,19 @@ QByteArray Interpreter::readBytes(Runtime& rt, qint64 n) const {
|
||||
QIODevice* dev = rt.in->device();
|
||||
if (!dev) throw std::runtime_error("No device in stream");
|
||||
|
||||
const qint64 pos = dev->pos();
|
||||
QByteArray buf;
|
||||
buf.resize(int(n));
|
||||
const int got = rt.in->readRawData(buf.data(), int(n));
|
||||
if (got != int(n)) {
|
||||
buf.resize(qMax(0, got));
|
||||
throw std::runtime_error("Unexpected EOF while reading bytes");
|
||||
throw std::runtime_error(
|
||||
QString("Unexpected EOF while reading %1 bytes at pos 0x%2 (got %3). Parsing: %4")
|
||||
.arg(n)
|
||||
.arg(pos, 0, 16)
|
||||
.arg(got)
|
||||
.arg(rt.typeStackString().isEmpty() ? "root" : rt.typeStackString())
|
||||
.toStdString());
|
||||
}
|
||||
return buf;
|
||||
}
|
||||
@ -241,6 +290,19 @@ QVariant Interpreter::evalCall(Runtime& rt, const Expr::Call& c) const {
|
||||
|
||||
return decompressed;
|
||||
}
|
||||
if (c.fn == "xmem") {
|
||||
if (c.args.size() != 1) throw std::runtime_error("xmem(bytes) takes 1 arg");
|
||||
QByteArray in = evalExpr(rt, *c.args[0]).toByteArray();
|
||||
const QByteArray decompressed = Compression::DecompressXMem(in);
|
||||
|
||||
if (!rt.filePath.isEmpty()) {
|
||||
QFileInfo fi(rt.filePath);
|
||||
const QString stem = fi.completeBaseName();
|
||||
rt.vars["_decompressed_name"] = stem + ".decompressed";
|
||||
}
|
||||
|
||||
return decompressed;
|
||||
}
|
||||
if (c.fn == "parse") {
|
||||
if (c.args.size() != 2) throw std::runtime_error("parse(typeName, bytes) takes 2 args");
|
||||
const QString typeName = evalExpr(rt, *c.args[0]).toString();
|
||||
@ -266,10 +328,13 @@ QVariant Interpreter::evalCall(Runtime& rt, const Expr::Call& c) const {
|
||||
}
|
||||
|
||||
const qint64 startPos = rt.in->device() ? rt.in->device()->pos() : -1;
|
||||
LogManager::instance().addEntry(QString("[PARSE_HERE] Parsing '%1' at pos 0x%2")
|
||||
DSL_LOG(QString("[PARSE_HERE] Parsing '%1' at pos 0x%2")
|
||||
.arg(typeName)
|
||||
.arg(startPos, 0, 16));
|
||||
|
||||
// Push type onto stack for error context
|
||||
rt.pushType(typeName);
|
||||
|
||||
// IMPORTANT: parse using the SAME stream (advance)
|
||||
// We must temporarily apply that type's byteorder.
|
||||
const TypeDef& td = rt.module->types[typeName];
|
||||
@ -281,16 +346,28 @@ QVariant Interpreter::evalCall(Runtime& rt, const Expr::Call& c) const {
|
||||
ByteOrder childOrder = oldOrder;
|
||||
if (td.hasExplicitByteOrder) childOrder = td.order;
|
||||
|
||||
// Create child runtime sharing stream
|
||||
// Create child runtime sharing stream but inheriting type stack
|
||||
Runtime childRt;
|
||||
childRt.in = rt.in;
|
||||
childRt.module = rt.module;
|
||||
childRt.order = childOrder;
|
||||
childRt.typeStack = rt.typeStack; // Inherit type stack for error context
|
||||
childRt.filePath = rt.filePath; // Inherit file path for naming
|
||||
|
||||
// Copy file variables to child context
|
||||
if (rt.vars.contains("_path")) childRt.vars["_path"] = rt.vars["_path"];
|
||||
if (rt.vars.contains("_name")) childRt.vars["_name"] = rt.vars["_name"];
|
||||
if (rt.vars.contains("_basename")) childRt.vars["_basename"] = rt.vars["_basename"];
|
||||
if (rt.vars.contains("_ext")) childRt.vars["_ext"] = rt.vars["_ext"];
|
||||
|
||||
applyByteOrder(childRt);
|
||||
|
||||
execBlock(childRt, td.body);
|
||||
QVariantMap child = childRt.vars;
|
||||
|
||||
// Pop type from stack
|
||||
rt.popType();
|
||||
|
||||
// Restore parent order
|
||||
rt.order = oldOrder;
|
||||
applyByteOrder(rt);
|
||||
@ -298,7 +375,7 @@ QVariant Interpreter::evalCall(Runtime& rt, const Expr::Call& c) const {
|
||||
child["_type"] = typeName;
|
||||
|
||||
const qint64 endPos = rt.in->device() ? rt.in->device()->pos() : -1;
|
||||
LogManager::instance().addEntry(QString("[PARSE_HERE] Finished '%1' at pos 0x%2 (consumed %3 bytes)")
|
||||
DSL_LOG(QString("[PARSE_HERE] Finished '%1' at pos 0x%2 (consumed %3 bytes)")
|
||||
.arg(typeName)
|
||||
.arg(endPos, 0, 16)
|
||||
.arg(endPos - startPos));
|
||||
@ -321,7 +398,7 @@ QVariant Interpreter::evalCall(Runtime& rt, const Expr::Call& c) const {
|
||||
const QVariantMap itemMap = v.toMap();
|
||||
const QString itemType = itemMap.value("_type").toString();
|
||||
const QString itemName = itemMap.value("_name").toString();
|
||||
LogManager::instance().addEntry(QString("[PUSH] Added item #%1 to '%2': type='%3', name='%4'")
|
||||
DSL_LOG(QString("[PUSH] Added item #%1 to '%2': type='%3', name='%4'")
|
||||
.arg(newIndex)
|
||||
.arg(listName)
|
||||
.arg(itemType)
|
||||
@ -339,8 +416,10 @@ QVariant Interpreter::evalCall(Runtime& rt, const Expr::Call& c) const {
|
||||
const QVariantList list = container.toList();
|
||||
const qint64 index = toInt(key);
|
||||
if (index < 0 || index >= list.size()) {
|
||||
throw std::runtime_error(QString("get: array index %1 out of bounds (size=%2)")
|
||||
.arg(index).arg(list.size()).toStdString());
|
||||
throw std::runtime_error(QString("get: array index %1 out of bounds (size=%2). Parsing: %3")
|
||||
.arg(index).arg(list.size())
|
||||
.arg(rt.typeStackString().isEmpty() ? "root" : rt.typeStackString())
|
||||
.toStdString());
|
||||
}
|
||||
return list[int(index)];
|
||||
}
|
||||
@ -350,8 +429,10 @@ QVariant Interpreter::evalCall(Runtime& rt, const Expr::Call& c) const {
|
||||
const QVariantMap map = container.toMap();
|
||||
const QString fieldName = key.toString();
|
||||
if (!map.contains(fieldName)) {
|
||||
throw std::runtime_error(QString("get: field '%1' not found in object")
|
||||
.arg(fieldName).toStdString());
|
||||
throw std::runtime_error(QString("get: field '%1' not found in object. Parsing: %2")
|
||||
.arg(fieldName)
|
||||
.arg(rt.typeStackString().isEmpty() ? "root" : rt.typeStackString())
|
||||
.toStdString());
|
||||
}
|
||||
return map.value(fieldName);
|
||||
}
|
||||
@ -445,6 +526,25 @@ QVariant Interpreter::evalCall(Runtime& rt, const Expr::Call& c) const {
|
||||
return v;
|
||||
}
|
||||
|
||||
if (c.fn == "i32at") {
|
||||
if (c.args.size() != 1) throw std::runtime_error("i32at(off) takes 1 arg");
|
||||
qint64 off = toInt(evalExpr(rt, *c.args[0]));
|
||||
QByteArray b = readAt(off, 4);
|
||||
qint32 v = 0;
|
||||
if (rt.order == ByteOrder::LE) {
|
||||
v = qint32(quint8(b[0])) |
|
||||
(qint32(quint8(b[1])) << 8) |
|
||||
(qint32(quint8(b[2])) << 16) |
|
||||
(qint32(quint8(b[3])) << 24);
|
||||
} else {
|
||||
v = (qint32(quint8(b[0])) << 24) |
|
||||
(qint32(quint8(b[1])) << 16) |
|
||||
(qint32(quint8(b[2])) << 8) |
|
||||
qint32(quint8(b[3]));
|
||||
}
|
||||
return v;
|
||||
}
|
||||
|
||||
if (c.fn == "u64at") {
|
||||
if (c.args.size() != 1) throw std::runtime_error("u64at(off) takes 1 arg");
|
||||
qint64 off = toInt(evalExpr(rt, *c.args[0]));
|
||||
@ -466,6 +566,17 @@ QVariant Interpreter::evalCall(Runtime& rt, const Expr::Call& c) const {
|
||||
return readAt(off, len);
|
||||
}
|
||||
|
||||
// Extract bytes from a byte array: bytesof(data, off, len)
|
||||
if (c.fn == "bytesof") {
|
||||
if (c.args.size() != 3) throw std::runtime_error("bytesof(data, off, len) takes 3 args");
|
||||
QByteArray data = evalExpr(rt, *c.args[0]).toByteArray();
|
||||
qint64 off = toInt(evalExpr(rt, *c.args[1]));
|
||||
int len = int(toInt(evalExpr(rt, *c.args[2])));
|
||||
if (off < 0 || off >= data.size()) return QByteArray();
|
||||
if (off + len > data.size()) len = data.size() - off;
|
||||
return data.mid(off, len);
|
||||
}
|
||||
|
||||
if (c.fn == "ascii") {
|
||||
if (c.args.size() != 1) throw std::runtime_error("ascii(bytes) takes 1 arg");
|
||||
const QByteArray b = evalExpr(rt, *c.args[0]).toByteArray();
|
||||
@ -475,11 +586,16 @@ QVariant Interpreter::evalCall(Runtime& rt, const Expr::Call& c) const {
|
||||
if (c.fn == "cstring") {
|
||||
if (c.args.size() != 0) throw std::runtime_error("cstring() takes 0 args");
|
||||
// Read null-terminated string (C-style string)
|
||||
const qint64 startPos = rt.in->device() ? rt.in->device()->pos() : -1;
|
||||
QByteArray result;
|
||||
char ch;
|
||||
while (true) {
|
||||
if (rt.in->readRawData(&ch, 1) != 1) {
|
||||
throw std::runtime_error("cstring(): unexpected EOF");
|
||||
throw std::runtime_error(
|
||||
QString("cstring(): unexpected EOF at pos 0x%1. Parsing: %2")
|
||||
.arg(rt.in->device() ? rt.in->device()->pos() : -1, 0, 16)
|
||||
.arg(rt.typeStackString().isEmpty() ? "root" : rt.typeStackString())
|
||||
.toStdString());
|
||||
}
|
||||
if (ch == '\0') break;
|
||||
result.append(ch);
|
||||
@ -505,12 +621,93 @@ QVariant Interpreter::evalCall(Runtime& rt, const Expr::Call& c) const {
|
||||
throw std::runtime_error(("Failed to export file: " + filename).toStdString());
|
||||
}
|
||||
|
||||
LogManager::instance().addEntry(QString("[EXPORT] Exported %1 bytes to exports/%2")
|
||||
DSL_LOG(QString("[EXPORT] Exported %1 bytes to exports/%2")
|
||||
.arg(data.size())
|
||||
.arg(filename));
|
||||
return data.size(); // Return number of bytes written
|
||||
}
|
||||
|
||||
if (c.fn == "strip") {
|
||||
if (c.args.size() != 2) throw std::runtime_error("strip(string, chars) takes 2 args");
|
||||
QString str = evalExpr(rt, *c.args[0]).toString();
|
||||
QString chars = evalExpr(rt, *c.args[1]).toString();
|
||||
for (const QChar& ch : chars) {
|
||||
str.remove(ch);
|
||||
}
|
||||
return str;
|
||||
}
|
||||
|
||||
if (c.fn == "basename") {
|
||||
if (c.args.size() != 1) throw std::runtime_error("basename(path) takes 1 arg");
|
||||
QString path = evalExpr(rt, *c.args[0]).toString();
|
||||
// Handle both forward and back slashes
|
||||
int lastSlash = path.lastIndexOf('/');
|
||||
int lastBackslash = path.lastIndexOf('\\');
|
||||
int pos = qMax(lastSlash, lastBackslash);
|
||||
if (pos >= 0) {
|
||||
return path.mid(pos + 1);
|
||||
}
|
||||
return path;
|
||||
}
|
||||
|
||||
if (c.fn == "ends_with") {
|
||||
if (c.args.size() != 2) throw std::runtime_error("ends_with(string, suffix) takes 2 args");
|
||||
QString str = evalExpr(rt, *c.args[0]).toString();
|
||||
QString suffix = evalExpr(rt, *c.args[1]).toString();
|
||||
return str.endsWith(suffix, Qt::CaseInsensitive);
|
||||
}
|
||||
|
||||
if (c.fn == "starts_with") {
|
||||
if (c.args.size() != 2) throw std::runtime_error("starts_with(string, prefix) takes 2 args");
|
||||
QString str = evalExpr(rt, *c.args[0]).toString();
|
||||
QString prefix = evalExpr(rt, *c.args[1]).toString();
|
||||
return str.startsWith(prefix, Qt::CaseInsensitive);
|
||||
}
|
||||
|
||||
if (c.fn == "to_lower") {
|
||||
if (c.args.size() != 1) throw std::runtime_error("to_lower(string) takes 1 arg");
|
||||
return evalExpr(rt, *c.args[0]).toString().toLower();
|
||||
}
|
||||
|
||||
if (c.fn == "preview") {
|
||||
// preview(data) - stores image data for UI preview
|
||||
// preview(filename, data) - stores with filename hint
|
||||
if (c.args.size() < 1 || c.args.size() > 2) throw std::runtime_error("preview(data) or preview(filename, data)");
|
||||
|
||||
QString filename;
|
||||
QByteArray data;
|
||||
|
||||
if (c.args.size() == 1) {
|
||||
data = evalExpr(rt, *c.args[0]).toByteArray();
|
||||
filename = "preview";
|
||||
} else {
|
||||
filename = evalExpr(rt, *c.args[0]).toString();
|
||||
data = evalExpr(rt, *c.args[1]).toByteArray();
|
||||
}
|
||||
|
||||
// Store preview data in runtime vars for UI to pick up
|
||||
QVariantMap preview;
|
||||
preview["filename"] = filename;
|
||||
preview["data"] = data;
|
||||
preview["size"] = data.size();
|
||||
rt.vars["_preview"] = preview;
|
||||
|
||||
DSL_LOG(QString("[PREVIEW] Set preview: %1 (%2 bytes)")
|
||||
.arg(filename)
|
||||
.arg(data.size()));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
if (c.fn == "len") {
|
||||
if (c.args.size() != 1) throw std::runtime_error("len(value) takes 1 arg");
|
||||
QVariant v = evalExpr(rt, *c.args[0]);
|
||||
if (v.typeId() == QMetaType::QString) return v.toString().length();
|
||||
if (v.typeId() == QMetaType::QByteArray) return v.toByteArray().size();
|
||||
if (v.typeId() == QMetaType::QVariantList) return v.toList().size();
|
||||
throw std::runtime_error("len() requires string, bytes, or array");
|
||||
}
|
||||
|
||||
throw std::runtime_error(("Unknown function: " + c.fn).toStdString());
|
||||
}
|
||||
|
||||
@ -535,6 +732,22 @@ QVariant Interpreter::evalPipe(Runtime& rt, const Expr::Pipe& p) const {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (stage.fn == "xmem") {
|
||||
if (!v.canConvert<QByteArray>()) throw std::runtime_error("xmem stage expects bytes");
|
||||
|
||||
const QByteArray in = v.toByteArray();
|
||||
const QByteArray decompressed = Compression::DecompressXMem(in);
|
||||
|
||||
if (!rt.filePath.isEmpty()) {
|
||||
QFileInfo fi(rt.filePath);
|
||||
const QString stem = fi.completeBaseName();
|
||||
rt.vars["_decompressed_name"] = stem + ".decompressed";
|
||||
}
|
||||
|
||||
v = decompressed;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (stage.fn == "parse") {
|
||||
// stage args[0] is a String expr holding type name (we created it that way in parser)
|
||||
if (stage.args.size() != 1) throw std::runtime_error("parse stage requires type name");
|
||||
@ -603,8 +816,21 @@ QVariant Interpreter::evalExpr(Runtime& rt, const Expr& e) const {
|
||||
const QString& op = b.op;
|
||||
|
||||
// comparisons/logical
|
||||
if (op == "==") return lv == rv;
|
||||
if (op == "!=") return lv != rv;
|
||||
// For == and !=: use numeric comparison if both are convertible to numbers,
|
||||
// otherwise fall back to QVariant comparison (for strings, maps, etc.)
|
||||
if (op == "==" || op == "!=") {
|
||||
bool lnumeric = lv.canConvert<qint64>() &&
|
||||
(lv.typeId() != QMetaType::QString && lv.typeId() != QMetaType::QVariantMap);
|
||||
bool rnumeric = rv.canConvert<qint64>() &&
|
||||
(rv.typeId() != QMetaType::QString && rv.typeId() != QMetaType::QVariantMap);
|
||||
if (lnumeric && rnumeric) {
|
||||
qint64 lval = toInt(lv);
|
||||
qint64 rval = toInt(rv);
|
||||
return (op == "==") ? (lval == rval) : (lval != rval);
|
||||
}
|
||||
// Non-numeric: use QVariant comparison
|
||||
return (op == "==") ? (lv == rv) : (lv != rv);
|
||||
}
|
||||
if (op == "<") return toInt(lv) < toInt(rv);
|
||||
if (op == "<=") return toInt(lv) <= toInt(rv);
|
||||
if (op == ">") return toInt(lv) > toInt(rv);
|
||||
@ -648,7 +874,10 @@ QVariant Interpreter::evalExpr(Runtime& rt, const Expr& e) const {
|
||||
}
|
||||
|
||||
void Interpreter::execBlock(Runtime& rt, const QVector<StmtPtr>& body) const {
|
||||
for (const auto& s : body) execStmt(rt, *s);
|
||||
for (const auto& s : body) {
|
||||
execStmt(rt, *s);
|
||||
reportProgress(rt);
|
||||
}
|
||||
}
|
||||
|
||||
void Interpreter::execStmt(Runtime& rt, const Stmt& s) const {
|
||||
@ -663,8 +892,15 @@ void Interpreter::execStmt(Runtime& rt, const Stmt& s) const {
|
||||
const auto& sk = std::get<Stmt::Skip>(s.node);
|
||||
const qint64 n = toInt(evalExpr(rt, *sk.count));
|
||||
if (n < 0) throw std::runtime_error("skip n must be >= 0");
|
||||
const qint64 pos = rt.in->device() ? rt.in->device()->pos() : -1;
|
||||
const int skipped = rt.in->skipRawData(int(n));
|
||||
if (skipped != int(n)) throw std::runtime_error("Unexpected EOF during skip");
|
||||
if (skipped != int(n)) {
|
||||
throw std::runtime_error(
|
||||
QString("Unexpected EOF during skip(%1) at pos 0x%2. Parsing: %3")
|
||||
.arg(n).arg(pos, 0, 16)
|
||||
.arg(rt.typeStackString().isEmpty() ? "root" : rt.typeStackString())
|
||||
.toStdString());
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
@ -678,7 +914,13 @@ void Interpreter::execStmt(Runtime& rt, const Stmt& s) const {
|
||||
if (mod != 0) {
|
||||
const qint64 pad = n - mod;
|
||||
const int skipped = rt.in->skipRawData(int(pad));
|
||||
if (skipped != int(pad)) throw std::runtime_error("Unexpected EOF during align");
|
||||
if (skipped != int(pad)) {
|
||||
throw std::runtime_error(
|
||||
QString("Unexpected EOF during align(%1) at pos 0x%2. Parsing: %3")
|
||||
.arg(n).arg(pos, 0, 16)
|
||||
.arg(rt.typeStackString().isEmpty() ? "root" : rt.typeStackString())
|
||||
.toStdString());
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
@ -699,7 +941,7 @@ void Interpreter::execStmt(Runtime& rt, const Stmt& s) const {
|
||||
|
||||
// Log assignments of key fields for debugging
|
||||
if (as.name == "_name" || as.name == "asset_type" || as.name == "_type") {
|
||||
LogManager::instance().addEntry(QString("[ASSIGN] %1 = '%2'")
|
||||
DSL_LOG(QString("[ASSIGN] %1 = '%2'")
|
||||
.arg(as.name)
|
||||
.arg(v.toString()));
|
||||
}
|
||||
@ -720,8 +962,12 @@ void Interpreter::execStmt(Runtime& rt, const Stmt& s) const {
|
||||
|
||||
if (std::holds_alternative<Stmt::While>(s.node)) {
|
||||
const auto& wh = std::get<Stmt::While>(s.node);
|
||||
while (toBool(evalExpr(rt, *wh.cond))) {
|
||||
execBlock(rt, wh.body);
|
||||
try {
|
||||
while (toBool(evalExpr(rt, *wh.cond))) {
|
||||
execBlock(rt, wh.body);
|
||||
}
|
||||
} catch (const BreakException&) {
|
||||
// break statement encountered, exit loop
|
||||
}
|
||||
return;
|
||||
}
|
||||
@ -732,7 +978,7 @@ void Interpreter::execStmt(Runtime& rt, const Stmt& s) const {
|
||||
if (count < 0) count = 0;
|
||||
|
||||
const qint64 startPos = rt.in->device() ? rt.in->device()->pos() : -1;
|
||||
LogManager::instance().addEntry(QString("[REPEAT] Starting repeat loop: count=%1, pos=0x%2")
|
||||
DSL_LOG(QString("[REPEAT] Starting repeat loop: count=%1, pos=0x%2")
|
||||
.arg(count)
|
||||
.arg(startPos, 0, 16));
|
||||
|
||||
@ -740,18 +986,22 @@ void Interpreter::execStmt(Runtime& rt, const Stmt& s) const {
|
||||
const QVariant old = rt.vars.value("_i");
|
||||
const bool hadOld = rt.vars.contains("_i");
|
||||
|
||||
for (qint64 i = 0; i < count; i++) {
|
||||
const qint64 iterPos = rt.in->device() ? rt.in->device()->pos() : -1;
|
||||
LogManager::instance().addEntry(QString("[REPEAT] Iteration %1/%2 at pos 0x%3")
|
||||
.arg(i)
|
||||
.arg(count)
|
||||
.arg(iterPos, 0, 16));
|
||||
rt.vars["_i"] = i;
|
||||
execBlock(rt, rp.body);
|
||||
try {
|
||||
for (qint64 i = 0; i < count; i++) {
|
||||
const qint64 iterPos = rt.in->device() ? rt.in->device()->pos() : -1;
|
||||
DSL_LOG(QString("[REPEAT] Iteration %1/%2 at pos 0x%3")
|
||||
.arg(i)
|
||||
.arg(count)
|
||||
.arg(iterPos, 0, 16));
|
||||
rt.vars["_i"] = i;
|
||||
execBlock(rt, rp.body);
|
||||
}
|
||||
} catch (const BreakException&) {
|
||||
// break statement encountered, exit loop
|
||||
}
|
||||
|
||||
const qint64 endPos = rt.in->device() ? rt.in->device()->pos() : -1;
|
||||
LogManager::instance().addEntry(QString("[REPEAT] Finished repeat loop at pos 0x%1")
|
||||
DSL_LOG(QString("[REPEAT] Finished repeat loop at pos 0x%1")
|
||||
.arg(endPos, 0, 16));
|
||||
|
||||
if (hadOld) rt.vars["_i"] = old;
|
||||
@ -768,9 +1018,13 @@ void Interpreter::execStmt(Runtime& rt, const Stmt& s) const {
|
||||
const QVariant old = rt.vars.value(fr.var);
|
||||
const bool hadOld = rt.vars.contains(fr.var);
|
||||
|
||||
for (qint64 i = start; i < end; i++) {
|
||||
rt.vars[fr.var] = i;
|
||||
execBlock(rt, fr.body);
|
||||
try {
|
||||
for (qint64 i = start; i < end; i++) {
|
||||
rt.vars[fr.var] = i;
|
||||
execBlock(rt, fr.body);
|
||||
}
|
||||
} catch (const BreakException&) {
|
||||
// break statement encountered, exit loop
|
||||
}
|
||||
|
||||
if (hadOld) rt.vars[fr.var] = old;
|
||||
@ -798,5 +1052,9 @@ void Interpreter::execStmt(Runtime& rt, const Stmt& s) const {
|
||||
return;
|
||||
}
|
||||
|
||||
if (std::holds_alternative<Stmt::Break>(s.node)) {
|
||||
throw BreakException{};
|
||||
}
|
||||
|
||||
throw std::runtime_error("Unknown statement node");
|
||||
}
|
||||
|
||||
@ -6,6 +6,7 @@
|
||||
#include <QDataStream>
|
||||
#include <QVariantMap>
|
||||
#include <QIODevice>
|
||||
#include <functional>
|
||||
|
||||
struct Runtime {
|
||||
QDataStream* in = nullptr;
|
||||
@ -15,8 +16,17 @@ struct Runtime {
|
||||
QVariantMap vars;
|
||||
|
||||
QString filePath;
|
||||
|
||||
// Error context tracking
|
||||
QStringList typeStack; // Stack of types being parsed
|
||||
QString currentType() const { return typeStack.isEmpty() ? QString() : typeStack.last(); }
|
||||
void pushType(const QString& t) { typeStack.append(t); }
|
||||
void popType() { if (!typeStack.isEmpty()) typeStack.removeLast(); }
|
||||
QString typeStackString() const { return typeStack.join(" -> "); }
|
||||
};
|
||||
|
||||
using ProgressCallback = std::function<void(qint64 pos, qint64 size)>;
|
||||
|
||||
class Interpreter {
|
||||
public:
|
||||
explicit Interpreter(Module mod);
|
||||
@ -37,6 +47,10 @@ public:
|
||||
|
||||
static void seedFileVars(Runtime &rt, const QString &filePath);
|
||||
|
||||
// Progress reporting
|
||||
void setProgressCallback(ProgressCallback cb);
|
||||
void setFileSize(qint64 size);
|
||||
|
||||
private:
|
||||
QVariant evalExpr(Runtime& rt, const Expr& e) const;
|
||||
QVariant evalCall(Runtime& rt, const Expr::Call& c) const;
|
||||
@ -53,9 +67,12 @@ private:
|
||||
|
||||
qint64 toInt(const QVariant& v) const;
|
||||
bool toBool(const QVariant& v) const;
|
||||
void reportProgress(Runtime& rt) const;
|
||||
|
||||
private:
|
||||
mutable Module m_mod; // Mutable to allow modifying globals from const methods
|
||||
mutable Module m_mod;
|
||||
mutable ProgressCallback m_progressCallback;
|
||||
mutable qint64 m_fileSize = 0; // Mutable to allow modifying globals from const methods
|
||||
};
|
||||
|
||||
#endif // INTERPRETER_H
|
||||
|
||||
@ -23,6 +23,7 @@ static QHash<QString, TokenKind> buildKeywords() {
|
||||
k["criteria"] = TokenKind::KwCriteria;
|
||||
k["require"] = TokenKind::KwRequire;
|
||||
k["bool"] = TokenKind::KwBool;
|
||||
k["break"] = TokenKind::KwBreak;
|
||||
|
||||
k["u8"] = TokenKind::KwU8;
|
||||
k["u16"] = TokenKind::KwU16;
|
||||
@ -32,6 +33,8 @@ static QHash<QString, TokenKind> buildKeywords() {
|
||||
k["i16"] = TokenKind::KwI16;
|
||||
k["i32"] = TokenKind::KwI32;
|
||||
k["i64"] = TokenKind::KwI64;
|
||||
k["f32"] = TokenKind::KwF32;
|
||||
k["f64"] = TokenKind::KwF64;
|
||||
|
||||
return k;
|
||||
}
|
||||
|
||||
@ -146,6 +146,8 @@ StmtPtr Parser::parseStatement() {
|
||||
case TokenKind::KwI16:
|
||||
case TokenKind::KwI32:
|
||||
case TokenKind::KwI64:
|
||||
case TokenKind::KwF32:
|
||||
case TokenKind::KwF64:
|
||||
return parseScalarStmt();
|
||||
case TokenKind::KwSkip:
|
||||
return parseSkipStmt();
|
||||
@ -167,6 +169,8 @@ StmtPtr Parser::parseStatement() {
|
||||
return parseRequireStmt();
|
||||
case TokenKind::KwByteOrder:
|
||||
return parseByteOrderStmt();
|
||||
case TokenKind::KwBreak:
|
||||
return parseBreakStmt();
|
||||
case TokenKind::KwBool:
|
||||
return parseScalarStmt();
|
||||
default:
|
||||
@ -184,6 +188,8 @@ ScalarType Parser::scalarFrom(TokenKind k) const {
|
||||
case TokenKind::KwI32: return ScalarType::I32;
|
||||
case TokenKind::KwU64: return ScalarType::U64;
|
||||
case TokenKind::KwI64: return ScalarType::I64;
|
||||
case TokenKind::KwF32: return ScalarType::F32;
|
||||
case TokenKind::KwF64: return ScalarType::F64;
|
||||
case TokenKind::KwBool: return ScalarType::Bool;
|
||||
default: break;
|
||||
}
|
||||
@ -316,9 +322,14 @@ StmtPtr Parser::parseIfStmt() {
|
||||
|
||||
QVector<StmtPtr> elseBody;
|
||||
if (match(TokenKind::KwElse)) {
|
||||
expect(TokenKind::LBrace, "Expected '{' after else");
|
||||
elseBody = parseBlock();
|
||||
expect(TokenKind::RBrace, "Expected '}' after else body");
|
||||
// Support 'else if' by recursively parsing if statement
|
||||
if (peek().kind == TokenKind::KwIf) {
|
||||
elseBody.push_back(parseIfStmt());
|
||||
} else {
|
||||
expect(TokenKind::LBrace, "Expected '{' after else");
|
||||
elseBody = parseBlock();
|
||||
expect(TokenKind::RBrace, "Expected '}' after else body");
|
||||
}
|
||||
}
|
||||
|
||||
auto s = QSharedPointer<Stmt>::create();
|
||||
@ -567,3 +578,14 @@ StmtPtr Parser::parseByteOrderStmt() {
|
||||
s->node = Stmt::SetByteOrder{bo};
|
||||
return s;
|
||||
}
|
||||
|
||||
StmtPtr Parser::parseBreakStmt() {
|
||||
Token kw = expect(TokenKind::KwBreak, "Expected break");
|
||||
expect(TokenKind::Semicolon, "Expected ';' after break");
|
||||
|
||||
auto s = QSharedPointer<Stmt>::create();
|
||||
s->line = kw.line;
|
||||
s->col = kw.col;
|
||||
s->node = Stmt::Break{};
|
||||
return s;
|
||||
}
|
||||
|
||||
@ -36,6 +36,7 @@ private:
|
||||
QVector<StmtPtr> parseCriteriaBlock();
|
||||
StmtPtr parseRequireStmt();
|
||||
StmtPtr parseByteOrderStmt();
|
||||
StmtPtr parseBreakStmt();
|
||||
|
||||
ExprPtr parseExpr(int minPrec = 0);
|
||||
ExprPtr parsePrimary();
|
||||
|
||||
@ -3,6 +3,7 @@
|
||||
#include "parser.h"
|
||||
#include "lexer.h"
|
||||
#include <stdexcept>
|
||||
#include <QSet>
|
||||
|
||||
void TypeRegistry::ingestScript(const QString& scriptText, const QString& sourceName) {
|
||||
Lexer lex(scriptText);
|
||||
@ -38,30 +39,127 @@ QStringList TypeRegistry::typeNames() const {
|
||||
return m_module.types.keys();
|
||||
}
|
||||
|
||||
bool TypeRegistry::canParse(const QString &typeName, QIODevice *dev, const QString& filePath) const
|
||||
{
|
||||
void TypeRegistry::collectTypeRefsFromExpr(const Expr& expr, QSet<QString>& refs) const {
|
||||
if (std::holds_alternative<Expr::Call>(expr.node)) {
|
||||
const auto& call = std::get<Expr::Call>(expr.node);
|
||||
if (call.fn == "parse_here" && call.args.size() == 1) {
|
||||
if (std::holds_alternative<Expr::String>(call.args[0]->node)) {
|
||||
refs.insert(std::get<Expr::String>(call.args[0]->node).v);
|
||||
}
|
||||
}
|
||||
if (call.fn == "parse" && call.args.size() >= 1) {
|
||||
if (std::holds_alternative<Expr::String>(call.args[0]->node)) {
|
||||
refs.insert(std::get<Expr::String>(call.args[0]->node).v);
|
||||
}
|
||||
}
|
||||
for (const auto& arg : call.args) {
|
||||
collectTypeRefsFromExpr(*arg, refs);
|
||||
}
|
||||
}
|
||||
else if (std::holds_alternative<Expr::Pipe>(expr.node)) {
|
||||
const auto& pipe = std::get<Expr::Pipe>(expr.node);
|
||||
collectTypeRefsFromExpr(*pipe.base, refs);
|
||||
for (const auto& stage : pipe.stages) {
|
||||
if (stage.fn == "parse" && stage.args.size() >= 1) {
|
||||
if (std::holds_alternative<Expr::String>(stage.args[0]->node)) {
|
||||
refs.insert(std::get<Expr::String>(stage.args[0]->node).v);
|
||||
}
|
||||
}
|
||||
for (const auto& arg : stage.args) {
|
||||
collectTypeRefsFromExpr(*arg, refs);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (std::holds_alternative<Expr::Unary>(expr.node)) {
|
||||
collectTypeRefsFromExpr(*std::get<Expr::Unary>(expr.node).rhs, refs);
|
||||
}
|
||||
else if (std::holds_alternative<Expr::Binary>(expr.node)) {
|
||||
const auto& bin = std::get<Expr::Binary>(expr.node);
|
||||
collectTypeRefsFromExpr(*bin.lhs, refs);
|
||||
collectTypeRefsFromExpr(*bin.rhs, refs);
|
||||
}
|
||||
}
|
||||
|
||||
void TypeRegistry::collectTypeRefsFromStmt(const Stmt& stmt, QSet<QString>& refs) const {
|
||||
if (std::holds_alternative<Stmt::Assign>(stmt.node)) {
|
||||
collectTypeRefsFromExpr(*std::get<Stmt::Assign>(stmt.node).value, refs);
|
||||
}
|
||||
else if (std::holds_alternative<Stmt::If>(stmt.node)) {
|
||||
const auto& iff = std::get<Stmt::If>(stmt.node);
|
||||
collectTypeRefsFromExpr(*iff.cond, refs);
|
||||
collectTypeRefsFromBlock(iff.thenBody, refs);
|
||||
collectTypeRefsFromBlock(iff.elseBody, refs);
|
||||
}
|
||||
else if (std::holds_alternative<Stmt::While>(stmt.node)) {
|
||||
const auto& wh = std::get<Stmt::While>(stmt.node);
|
||||
collectTypeRefsFromExpr(*wh.cond, refs);
|
||||
collectTypeRefsFromBlock(wh.body, refs);
|
||||
}
|
||||
else if (std::holds_alternative<Stmt::Repeat>(stmt.node)) {
|
||||
const auto& rp = std::get<Stmt::Repeat>(stmt.node);
|
||||
collectTypeRefsFromExpr(*rp.count, refs);
|
||||
collectTypeRefsFromBlock(rp.body, refs);
|
||||
}
|
||||
else if (std::holds_alternative<Stmt::ForRange>(stmt.node)) {
|
||||
const auto& fr = std::get<Stmt::ForRange>(stmt.node);
|
||||
collectTypeRefsFromExpr(*fr.start, refs);
|
||||
collectTypeRefsFromExpr(*fr.end, refs);
|
||||
collectTypeRefsFromBlock(fr.body, refs);
|
||||
}
|
||||
else if (std::holds_alternative<Stmt::Skip>(stmt.node)) {
|
||||
collectTypeRefsFromExpr(*std::get<Stmt::Skip>(stmt.node).count, refs);
|
||||
}
|
||||
else if (std::holds_alternative<Stmt::Align>(stmt.node)) {
|
||||
collectTypeRefsFromExpr(*std::get<Stmt::Align>(stmt.node).n, refs);
|
||||
}
|
||||
else if (std::holds_alternative<Stmt::Seek>(stmt.node)) {
|
||||
collectTypeRefsFromExpr(*std::get<Stmt::Seek>(stmt.node).pos, refs);
|
||||
}
|
||||
else if (std::holds_alternative<Stmt::Require>(stmt.node)) {
|
||||
collectTypeRefsFromExpr(*std::get<Stmt::Require>(stmt.node).cond, refs);
|
||||
}
|
||||
else if (std::holds_alternative<Stmt::CallStmt>(stmt.node)) {
|
||||
collectTypeRefsFromExpr(*std::get<Stmt::CallStmt>(stmt.node).call, refs);
|
||||
}
|
||||
}
|
||||
|
||||
void TypeRegistry::collectTypeRefsFromBlock(const QVector<StmtPtr>& block, QSet<QString>& refs) const {
|
||||
for (const auto& stmt : block) {
|
||||
collectTypeRefsFromStmt(*stmt, refs);
|
||||
}
|
||||
}
|
||||
|
||||
QStringList TypeRegistry::validateTypeReferences() const {
|
||||
QStringList errors;
|
||||
for (auto it = m_module.types.begin(); it != m_module.types.end(); ++it) {
|
||||
const QString& typeName = it.key();
|
||||
const TypeDef& td = it.value();
|
||||
QSet<QString> refs;
|
||||
collectTypeRefsFromBlock(td.criteria, refs);
|
||||
collectTypeRefsFromBlock(td.body, refs);
|
||||
for (const QString& ref : refs) {
|
||||
if (!m_module.types.contains(ref)) {
|
||||
errors.append(QString("Type '%1' references unknown type '%2'").arg(typeName, ref));
|
||||
}
|
||||
}
|
||||
}
|
||||
return errors;
|
||||
}
|
||||
|
||||
bool TypeRegistry::canParse(const QString &typeName, QIODevice *dev, const QString& filePath) const {
|
||||
if (!hasType(typeName)) return false;
|
||||
Interpreter interp(m_module);
|
||||
return interp.checkCriteria(typeName, dev, filePath);
|
||||
}
|
||||
|
||||
QString TypeRegistry::chooseType(QIODevice* dev, const QString& filePath) const
|
||||
{
|
||||
QString TypeRegistry::chooseType(QIODevice* dev, const QString& filePath) const {
|
||||
Interpreter interp(m_module);
|
||||
|
||||
// 1) Prefer root types only
|
||||
// Only check root types - non-root types are internal helpers
|
||||
for (auto it = m_module.types.begin(); it != m_module.types.end(); ++it) {
|
||||
const TypeDef& td = it.value();
|
||||
if (!td.isRoot) continue;
|
||||
if (interp.checkCriteria(td.name, dev, filePath)) return td.name;
|
||||
}
|
||||
|
||||
// 2) Fallback: any type (optional, but useful for debugging)
|
||||
for (auto it = m_module.types.begin(); it != m_module.types.end(); ++it) {
|
||||
const TypeDef& td = it.value();
|
||||
if (interp.checkCriteria(td.name, dev, filePath)) return td.name;
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
@ -82,11 +180,20 @@ QVariantMap TypeRegistry::parseBytes(const QString& typeName, const QByteArray&
|
||||
return interp.runType(typeName, ds);
|
||||
}
|
||||
|
||||
QVariantMap TypeRegistry::parse(const QString &typeName, QIODevice *dev, const QString &filePath) const
|
||||
{
|
||||
QVariantMap TypeRegistry::parse(const QString &typeName, QIODevice *dev, const QString &filePath) const {
|
||||
if (!hasType(typeName))
|
||||
throw std::runtime_error(("Unknown type: " + typeName).toStdString());
|
||||
Interpreter interp(m_module);
|
||||
return interp.runType(typeName, dev, filePath);
|
||||
}
|
||||
|
||||
QVariantMap TypeRegistry::parse(const QString &typeName, QIODevice *dev, const QString &filePath, ProgressCallback progress) const {
|
||||
if (!hasType(typeName))
|
||||
throw std::runtime_error(("Unknown type: " + typeName).toStdString());
|
||||
Interpreter interp(m_module);
|
||||
interp.setProgressCallback(std::move(progress));
|
||||
interp.setFileSize(dev->size());
|
||||
return interp.runType(typeName, dev, filePath);
|
||||
}
|
||||
|
||||
const Module &TypeRegistry::module() const { return m_module; }
|
||||
|
||||
@ -19,8 +19,12 @@ public:
|
||||
bool hasType(const QString& typeName) const;
|
||||
QStringList typeNames() const;
|
||||
|
||||
// Validate all type references - returns list of errors, empty if valid
|
||||
QStringList validateTypeReferences() const;
|
||||
|
||||
QString chooseType(QIODevice* dev, const QString& filePath = {}) const;
|
||||
QVariantMap parse(const QString& typeName, QIODevice* dev, const QString& filePath = {}) const;
|
||||
QVariantMap parse(const QString& typeName, QIODevice* dev, const QString& filePath, ProgressCallback progress) const;
|
||||
bool canParse(const QString& typeName, QIODevice* dev, const QString& filePath = {}) const;
|
||||
|
||||
// Parse later depending on the file
|
||||
@ -33,6 +37,11 @@ public:
|
||||
|
||||
private:
|
||||
void mergeModule(Module&& mod, const QString& sourceName);
|
||||
|
||||
// Helper to collect type references from expressions and statements
|
||||
void collectTypeRefsFromExpr(const Expr& expr, QSet<QString>& refs) const;
|
||||
void collectTypeRefsFromStmt(const Stmt& stmt, QSet<QString>& refs) const;
|
||||
void collectTypeRefsFromBlock(const QVector<StmtPtr>& block, QSet<QString>& refs) const;
|
||||
|
||||
private:
|
||||
Module m_module; // merged types live here
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user