188 lines
6.0 KiB
C++
188 lines
6.0 KiB
C++
#ifndef DSLUISCHEMA_H
|
||
#define DSLUISCHEMA_H
|
||
|
||
#include "ast.h"
|
||
|
||
#include <QMap>
|
||
|
||
enum class UiFieldKind {
|
||
ScalarU8, ScalarI8, ScalarU16, ScalarI16, ScalarU32, ScalarI32, ScalarU64, ScalarI64, GenericValue, Bool
|
||
};
|
||
|
||
struct UiField {
|
||
QString name;
|
||
QString displayName;
|
||
UiFieldKind kind = UiFieldKind::GenericValue;
|
||
bool visible = true;
|
||
bool readOnly = false;
|
||
|
||
int firstLine = 1;
|
||
int firstCol = 1;
|
||
|
||
bool isTable = false;
|
||
QString tableTitle;
|
||
QStringList columns;
|
||
QHash<QString, QString> formats;
|
||
};
|
||
|
||
inline UiFieldKind toUiKind(ScalarType t) {
|
||
switch (t) {
|
||
case ScalarType::U8: return UiFieldKind::ScalarU8;
|
||
case ScalarType::I8: return UiFieldKind::ScalarI8;
|
||
case ScalarType::U16: return UiFieldKind::ScalarU16;
|
||
case ScalarType::I16: return UiFieldKind::ScalarI16;
|
||
case ScalarType::U32: return UiFieldKind::ScalarU32;
|
||
case ScalarType::I32: return UiFieldKind::ScalarI32;
|
||
case ScalarType::U64: return UiFieldKind::ScalarU64;
|
||
case ScalarType::I64: return UiFieldKind::ScalarI64;
|
||
case ScalarType::Bool: return UiFieldKind::Bool;
|
||
}
|
||
return UiFieldKind::GenericValue;
|
||
}
|
||
|
||
inline bool isBoolExpr(const Expr& e) {
|
||
if (std::holds_alternative<Expr::Unary>(e.node)) {
|
||
const auto& u = std::get<Expr::Unary>(e.node);
|
||
if (u.op == "!") return true;
|
||
return isBoolExpr(*u.rhs);
|
||
}
|
||
if (std::holds_alternative<Expr::Binary>(e.node)) {
|
||
const auto& b = std::get<Expr::Binary>(e.node);
|
||
const QString& op = b.op;
|
||
if (op == "==" || op == "!=" || op == "<" || op == "<=" || op == ">" || op == ">=" ||
|
||
op == "&&" || op == "||") {
|
||
return true;
|
||
}
|
||
// allow nested boolean expressions too
|
||
return isBoolExpr(*b.lhs) || isBoolExpr(*b.rhs);
|
||
}
|
||
return false;
|
||
}
|
||
|
||
inline UiFieldKind inferKindFromAssignExpr(const ExprPtr& value) {
|
||
if (!value) return UiFieldKind::GenericValue;
|
||
if (isBoolExpr(*value)) return UiFieldKind::Bool;
|
||
return UiFieldKind::GenericValue;
|
||
}
|
||
|
||
inline QStringList splitCsvCols(const QString& s) {
|
||
QStringList out;
|
||
for (const QString& part : s.split(',', Qt::SkipEmptyParts)) {
|
||
const QString trimmed = part.trimmed();
|
||
if (!trimmed.isEmpty()) out.push_back(trimmed);
|
||
}
|
||
return out;
|
||
}
|
||
|
||
inline void collectFieldsFromBlock(const QVector<StmtPtr>& blk, QMap<QString, UiField>& out);
|
||
|
||
inline void collectFieldsFromStmt(const Stmt& s, QMap<QString, UiField>& out) {
|
||
auto add = [&](const QString& name,
|
||
UiFieldKind kind,
|
||
bool visible,
|
||
bool readOnly,
|
||
const QString& display,
|
||
const UiFlags* flags)
|
||
{
|
||
if (name.isEmpty()) return;
|
||
if (!visible) return;
|
||
|
||
if (!out.contains(name)) {
|
||
UiField f;
|
||
f.name = name;
|
||
f.displayName = display.isEmpty() ? name : display;
|
||
f.kind = kind;
|
||
f.visible = visible;
|
||
f.readOnly = readOnly;
|
||
f.firstLine = s.line;
|
||
f.firstCol = s.col;
|
||
|
||
if (flags && flags->isTable()) {
|
||
f.isTable = true;
|
||
f.tableTitle = flags->tableTitle;
|
||
f.columns = splitCsvCols(flags->columnsCsv);
|
||
f.formats = flags->formats;
|
||
}
|
||
|
||
out.insert(name, f);
|
||
} else {
|
||
if (!display.isEmpty())
|
||
out[name].displayName = display;
|
||
|
||
if (out[name].kind == UiFieldKind::GenericValue && kind != UiFieldKind::GenericValue)
|
||
out[name].kind = kind;
|
||
|
||
out[name].readOnly = out[name].readOnly || readOnly;
|
||
|
||
// allow upgrading to table if later statement provides it
|
||
if (flags && flags->isTable() && !out[name].isTable) {
|
||
out[name].isTable = true;
|
||
out[name].tableTitle = flags->tableTitle;
|
||
out[name].columns = splitCsvCols(flags->columnsCsv);
|
||
out[name].formats = flags->formats;
|
||
}
|
||
}
|
||
};
|
||
|
||
if (std::holds_alternative<Stmt::ReadScalar>(s.node)) {
|
||
const auto& rs = std::get<Stmt::ReadScalar>(s.node);
|
||
add(rs.name, toUiKind(rs.type), rs.ui.visible(), rs.ui.readOnly, rs.ui.display, &rs.ui);
|
||
return;
|
||
}
|
||
|
||
if (std::holds_alternative<Stmt::Assign>(s.node)) {
|
||
const auto& as = std::get<Stmt::Assign>(s.node);
|
||
const bool vis = as.ui.visible();
|
||
const bool ro = as.ui.readOnly || vis;
|
||
add(as.name, inferKindFromAssignExpr(as.value), vis, ro, as.ui.display, &as.ui);
|
||
return;
|
||
}
|
||
|
||
if (std::holds_alternative<Stmt::If>(s.node)) {
|
||
const auto& iff = std::get<Stmt::If>(s.node);
|
||
collectFieldsFromBlock(iff.thenBody, out);
|
||
collectFieldsFromBlock(iff.elseBody, out);
|
||
return;
|
||
}
|
||
|
||
if (std::holds_alternative<Stmt::While>(s.node)) {
|
||
const auto& wh = std::get<Stmt::While>(s.node);
|
||
collectFieldsFromBlock(wh.body, out);
|
||
return;
|
||
}
|
||
|
||
if (std::holds_alternative<Stmt::Repeat>(s.node)) {
|
||
const auto& rp = std::get<Stmt::Repeat>(s.node);
|
||
collectFieldsFromBlock(rp.body, out);
|
||
return;
|
||
}
|
||
|
||
if (std::holds_alternative<Stmt::ForRange>(s.node)) {
|
||
const auto& fr = std::get<Stmt::ForRange>(s.node);
|
||
// loop variable itself is user-visible sometimes
|
||
add(fr.var, UiFieldKind::GenericValue, false, true, "", {});
|
||
collectFieldsFromBlock(fr.body, out);
|
||
return;
|
||
}
|
||
}
|
||
|
||
inline void collectFieldsFromBlock(const QVector<StmtPtr>& blk, QMap<QString, UiField>& out) {
|
||
for (const auto& sp : blk) {
|
||
if (!sp) continue;
|
||
collectFieldsFromStmt(*sp, out);
|
||
}
|
||
}
|
||
|
||
inline QMap<QString, UiField> buildUiSchemaForType(const TypeDef& td) {
|
||
QMap<QString, UiField> fields;
|
||
collectFieldsFromBlock(td.criteria, fields);
|
||
collectFieldsFromBlock(td.body, fields);
|
||
|
||
// If you don’t want criteria-only vars exposed, remove those by policy here.
|
||
// For now: keep them all (you can filter by prefix later if you want).
|
||
|
||
return fields;
|
||
}
|
||
|
||
#endif // DSLUISCHEMA_H
|