Update DSL parser and interpreter
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
ca98c059ba
commit
39230139b9
@ -54,6 +54,8 @@ enum class TokenKind {
|
|||||||
KwWhen,
|
KwWhen,
|
||||||
KwAt,
|
KwAt,
|
||||||
KwDefault,
|
KwDefault,
|
||||||
|
KwUi, // ui
|
||||||
|
KwEdit, // edit
|
||||||
|
|
||||||
// punctuation additions
|
// punctuation additions
|
||||||
Arrow, // =>
|
Arrow, // =>
|
||||||
|
|||||||
@ -162,55 +162,12 @@ inline void collectFieldsFromStmt(const Stmt& s, QMap<QString, UiField>& out, co
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle CallStmt for parse_here, ui(), ui_edit(), hide() function calls
|
// Handle CallStmt for parse_here - merge UI fields from referenced type
|
||||||
if (std::holds_alternative<Stmt::CallStmt>(s.node)) {
|
if (std::holds_alternative<Stmt::CallStmt>(s.node)) {
|
||||||
const auto& cs = std::get<Stmt::CallStmt>(s.node);
|
const auto& cs = std::get<Stmt::CallStmt>(s.node);
|
||||||
if (cs.call && std::holds_alternative<Expr::Call>(cs.call->node)) {
|
if (cs.call && std::holds_alternative<Expr::Call>(cs.call->node)) {
|
||||||
const auto& call = std::get<Expr::Call>(cs.call->node);
|
const auto& call = std::get<Expr::Call>(cs.call->node);
|
||||||
|
|
||||||
// Handle ui(varName, displayName) - marks field for readonly display
|
|
||||||
// Handle ui_edit(varName, displayName) - marks field for editable display
|
|
||||||
if ((call.fn == "ui" || call.fn == "ui_edit") && !call.args.isEmpty()) {
|
|
||||||
QString varName;
|
|
||||||
QString displayName;
|
|
||||||
|
|
||||||
// First arg is variable name (as string or variable reference)
|
|
||||||
if (std::holds_alternative<Expr::String>(call.args[0]->node)) {
|
|
||||||
varName = std::get<Expr::String>(call.args[0]->node).v;
|
|
||||||
} else if (std::holds_alternative<Expr::Var>(call.args[0]->node)) {
|
|
||||||
varName = std::get<Expr::Var>(call.args[0]->node).name;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Second arg (optional) is display name
|
|
||||||
if (call.args.size() >= 2 && std::holds_alternative<Expr::String>(call.args[1]->node)) {
|
|
||||||
displayName = std::get<Expr::String>(call.args[1]->node).v;
|
|
||||||
} else {
|
|
||||||
displayName = varName;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool isReadOnly = (call.fn == "ui");
|
|
||||||
|
|
||||||
if (!varName.isEmpty()) {
|
|
||||||
add(varName, UiFieldKind::GenericValue, true, isReadOnly, displayName, nullptr);
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle hide(varName) - marks field as hidden
|
|
||||||
if (call.fn == "hide" && !call.args.isEmpty()) {
|
|
||||||
QString varName;
|
|
||||||
if (std::holds_alternative<Expr::String>(call.args[0]->node)) {
|
|
||||||
varName = std::get<Expr::String>(call.args[0]->node).v;
|
|
||||||
} else if (std::holds_alternative<Expr::Var>(call.args[0]->node)) {
|
|
||||||
varName = std::get<Expr::Var>(call.args[0]->node).name;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!varName.isEmpty() && out.contains(varName)) {
|
|
||||||
out[varName].visible = false;
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle parse_here - merge UI fields from referenced type
|
// Handle parse_here - merge UI fields from referenced type
|
||||||
if (call.fn == "parse_here" && !call.args.isEmpty() && mod) {
|
if (call.fn == "parse_here" && !call.args.isEmpty() && mod) {
|
||||||
if (std::holds_alternative<Expr::String>(call.args[0]->node)) {
|
if (std::holds_alternative<Expr::String>(call.args[0]->node)) {
|
||||||
|
|||||||
@ -865,46 +865,6 @@ QVariant Interpreter::evalCall(Runtime& rt, const Expr::Call& c) const {
|
|||||||
return lst;
|
return lst;
|
||||||
}
|
}
|
||||||
|
|
||||||
// UI metadata functions - store metadata for UI rendering
|
|
||||||
// ui(varName, displayName) - marks variable for readonly UI display
|
|
||||||
if (c.fn == "ui") {
|
|
||||||
if (c.args.size() < 1 || c.args.size() > 2) throw std::runtime_error("ui(varName[, displayName]) takes 1-2 args");
|
|
||||||
const QString varName = evalExpr(rt, *c.args[0]).toString();
|
|
||||||
QString displayName = varName;
|
|
||||||
if (c.args.size() == 2) {
|
|
||||||
displayName = evalExpr(rt, *c.args[1]).toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Store UI metadata
|
|
||||||
QVariantMap uiMeta = rt.vars.value("_ui_meta").toMap();
|
|
||||||
QVariantMap fieldMeta;
|
|
||||||
fieldMeta["visible"] = true;
|
|
||||||
fieldMeta["readonly"] = true;
|
|
||||||
fieldMeta["display"] = displayName;
|
|
||||||
uiMeta[varName] = fieldMeta;
|
|
||||||
rt.vars["_ui_meta"] = uiMeta;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ui_edit(varName, displayName) - marks variable for editable UI display
|
|
||||||
if (c.fn == "ui_edit") {
|
|
||||||
if (c.args.size() < 1 || c.args.size() > 2) throw std::runtime_error("ui_edit(varName[, displayName]) takes 1-2 args");
|
|
||||||
const QString varName = evalExpr(rt, *c.args[0]).toString();
|
|
||||||
QString displayName = varName;
|
|
||||||
if (c.args.size() == 2) {
|
|
||||||
displayName = evalExpr(rt, *c.args[1]).toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
QVariantMap uiMeta = rt.vars.value("_ui_meta").toMap();
|
|
||||||
QVariantMap fieldMeta;
|
|
||||||
fieldMeta["visible"] = true;
|
|
||||||
fieldMeta["readonly"] = false;
|
|
||||||
fieldMeta["display"] = displayName;
|
|
||||||
uiMeta[varName] = fieldMeta;
|
|
||||||
rt.vars["_ui_meta"] = uiMeta;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ui_table(varName, tableName, columns) - marks variable as table display
|
// ui_table(varName, tableName, columns) - marks variable as table display
|
||||||
if (c.fn == "ui_table") {
|
if (c.fn == "ui_table") {
|
||||||
if (c.args.size() != 3) throw std::runtime_error("ui_table(varName, tableName, columns) takes 3 args");
|
if (c.args.size() != 3) throw std::runtime_error("ui_table(varName, tableName, columns) takes 3 args");
|
||||||
@ -923,20 +883,6 @@ QVariant Interpreter::evalCall(Runtime& rt, const Expr::Call& c) const {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// hide(varName) - marks variable as hidden from UI
|
|
||||||
if (c.fn == "hide") {
|
|
||||||
if (c.args.size() != 1) throw std::runtime_error("hide(varName) takes 1 arg");
|
|
||||||
const QString varName = evalExpr(rt, *c.args[0]).toString();
|
|
||||||
|
|
||||||
QVariantMap uiMeta = rt.vars.value("_ui_meta").toMap();
|
|
||||||
QVariantMap fieldMeta = uiMeta.value(varName).toMap();
|
|
||||||
fieldMeta["visible"] = false;
|
|
||||||
fieldMeta["hidden"] = true;
|
|
||||||
uiMeta[varName] = fieldMeta;
|
|
||||||
rt.vars["_ui_meta"] = uiMeta;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// set_viewer(viewerType) - sets the viewer type for this object
|
// set_viewer(viewerType) - sets the viewer type for this object
|
||||||
// Valid types: "hex", "text", "image", "audio", "list", "table"
|
// Valid types: "hex", "text", "image", "audio", "list", "table"
|
||||||
if (c.fn == "set_viewer") {
|
if (c.fn == "set_viewer") {
|
||||||
@ -1699,6 +1645,17 @@ void Interpreter::execStmt(Runtime& rt, const Stmt& s) const {
|
|||||||
const auto& rs = std::get<Stmt::ReadScalar>(s.node);
|
const auto& rs = std::get<Stmt::ReadScalar>(s.node);
|
||||||
QVariant v = readScalar(rt, rs.type);
|
QVariant v = readScalar(rt, rs.type);
|
||||||
rt.vars[rs.name] = v;
|
rt.vars[rs.name] = v;
|
||||||
|
|
||||||
|
// Apply UI flags from AST if present
|
||||||
|
if (rs.ui.ui) {
|
||||||
|
QVariantMap uiMeta = rt.vars.value("_ui_meta").toMap();
|
||||||
|
QVariantMap fieldMeta;
|
||||||
|
fieldMeta["visible"] = !rs.ui.hidden;
|
||||||
|
fieldMeta["readonly"] = rs.ui.readOnly;
|
||||||
|
fieldMeta["display"] = rs.ui.display.isEmpty() ? rs.name : rs.ui.display;
|
||||||
|
uiMeta[rs.name] = fieldMeta;
|
||||||
|
rt.vars["_ui_meta"] = uiMeta;
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1753,6 +1710,16 @@ void Interpreter::execStmt(Runtime& rt, const Stmt& s) const {
|
|||||||
QVariant v = evalExpr(rt, *as.value);
|
QVariant v = evalExpr(rt, *as.value);
|
||||||
rt.vars[as.name] = v;
|
rt.vars[as.name] = v;
|
||||||
|
|
||||||
|
// Apply UI flags from AST if present
|
||||||
|
if (as.ui.ui) {
|
||||||
|
QVariantMap uiMeta = rt.vars.value("_ui_meta").toMap();
|
||||||
|
QVariantMap fieldMeta;
|
||||||
|
fieldMeta["visible"] = !as.ui.hidden;
|
||||||
|
fieldMeta["readonly"] = as.ui.readOnly;
|
||||||
|
fieldMeta["display"] = as.ui.display.isEmpty() ? as.name : as.ui.display;
|
||||||
|
uiMeta[as.name] = fieldMeta;
|
||||||
|
rt.vars["_ui_meta"] = uiMeta;
|
||||||
|
}
|
||||||
|
|
||||||
// Store hidden flag if set
|
// Store hidden flag if set
|
||||||
if (as.ui.hidden) {
|
if (as.ui.hidden) {
|
||||||
|
|||||||
@ -32,6 +32,8 @@ static QHash<QString, TokenKind> buildKeywords() {
|
|||||||
k["const"] = TokenKind::KwConst;
|
k["const"] = TokenKind::KwConst;
|
||||||
k["when"] = TokenKind::KwWhen;
|
k["when"] = TokenKind::KwWhen;
|
||||||
k["default"] = TokenKind::KwDefault;
|
k["default"] = TokenKind::KwDefault;
|
||||||
|
k["ui"] = TokenKind::KwUi;
|
||||||
|
k["edit"] = TokenKind::KwEdit;
|
||||||
|
|
||||||
k["u8"] = TokenKind::KwU8;
|
k["u8"] = TokenKind::KwU8;
|
||||||
k["u16"] = TokenKind::KwU16;
|
k["u16"] = TokenKind::KwU16;
|
||||||
|
|||||||
@ -66,10 +66,56 @@ UiFlags Parser::parseUiFlags(bool defaultUi)
|
|||||||
return f;
|
return f;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
UiFlags Parser::parseInlineUiSuffix()
|
||||||
|
{
|
||||||
|
UiFlags flags;
|
||||||
|
flags.ui = false;
|
||||||
|
|
||||||
|
if (peek().kind != TokenKind::KwUi)
|
||||||
|
return flags;
|
||||||
|
|
||||||
|
m_lex.next(); // consume 'ui'
|
||||||
|
expect(TokenKind::LParen, "Expected '(' after ui");
|
||||||
|
Token displayTok = expect(TokenKind::String, "Expected display name string in ui()");
|
||||||
|
flags.ui = true;
|
||||||
|
flags.display = displayTok.text;
|
||||||
|
flags.readOnly = true; // default to readonly
|
||||||
|
|
||||||
|
// Check for optional 'edit' modifier: ui("Display", edit)
|
||||||
|
if (match(TokenKind::Comma)) {
|
||||||
|
expect(TokenKind::KwEdit, "Expected 'edit' after comma in ui()");
|
||||||
|
flags.readOnly = false;
|
||||||
|
}
|
||||||
|
expect(TokenKind::RParen, "Expected ')' after ui arguments");
|
||||||
|
|
||||||
|
return flags;
|
||||||
|
}
|
||||||
|
|
||||||
TypeAttrs Parser::parseTypeAttrs()
|
TypeAttrs Parser::parseTypeAttrs()
|
||||||
{
|
{
|
||||||
TypeAttrs a;
|
TypeAttrs a;
|
||||||
|
|
||||||
|
// New syntax: ui("Display Name") or ui("Display Name", root)
|
||||||
|
if (peek().kind == TokenKind::KwUi) {
|
||||||
|
m_lex.next(); // consume 'ui'
|
||||||
|
expect(TokenKind::LParen, "Expected '(' after ui");
|
||||||
|
Token displayTok = expect(TokenKind::String, "Expected display name in ui()");
|
||||||
|
a.display = displayTok.text;
|
||||||
|
|
||||||
|
// Check for optional modifiers (root)
|
||||||
|
while (match(TokenKind::Comma)) {
|
||||||
|
Token mod = expect(TokenKind::Identifier, "Expected modifier after comma");
|
||||||
|
if (mod.text.toLower() == "root") {
|
||||||
|
a.root = true;
|
||||||
|
} else {
|
||||||
|
throw std::runtime_error(("Unknown type modifier: " + mod.text + " (expected 'root')").toStdString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
expect(TokenKind::RParen, "Expected ')' after ui arguments");
|
||||||
|
return a;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Legacy bracket syntax: [root, display="Name"]
|
||||||
if (!match(TokenKind::LBracket))
|
if (!match(TokenKind::LBracket))
|
||||||
return a;
|
return a;
|
||||||
|
|
||||||
@ -208,13 +254,13 @@ StmtPtr Parser::parseScalarStmt() {
|
|||||||
Token tTok = m_lex.next();
|
Token tTok = m_lex.next();
|
||||||
Token nameTok = expect(TokenKind::Identifier, "Expected variable name after scalar");
|
Token nameTok = expect(TokenKind::Identifier, "Expected variable name after scalar");
|
||||||
|
|
||||||
// Bracket attributes on scalars are no longer supported - use ui() function instead
|
// Bracket attributes on scalars are no longer supported - use inline ui() instead
|
||||||
if (peek().kind == TokenKind::LBracket) {
|
if (peek().kind == TokenKind::LBracket) {
|
||||||
throw std::runtime_error("Bracket attributes [ui, ...] are no longer supported. Use ui(\"" + nameTok.text.toStdString() + "\", \"Display Name\"); instead");
|
throw std::runtime_error("Bracket attributes [ui, ...] are no longer supported. Use inline syntax: u32 " + nameTok.text.toStdString() + " ui(\"Display Name\");");
|
||||||
}
|
}
|
||||||
|
|
||||||
UiFlags flags;
|
// Parse optional inline ui suffix: u32 name ui("Display");
|
||||||
flags.ui = false;
|
UiFlags flags = parseInlineUiSuffix();
|
||||||
|
|
||||||
expect(TokenKind::Semicolon, "Expected ';' after scalar statement");
|
expect(TokenKind::Semicolon, "Expected ';' after scalar statement");
|
||||||
|
|
||||||
@ -292,13 +338,13 @@ StmtPtr Parser::parseAssignStmt() {
|
|||||||
Token eq = expect(TokenKind::Assign, "Expected '='");
|
Token eq = expect(TokenKind::Assign, "Expected '='");
|
||||||
ExprPtr e = parseExpr();
|
ExprPtr e = parseExpr();
|
||||||
|
|
||||||
// Bracket attributes on assignments are no longer supported - use ui() function instead
|
// Bracket attributes on assignments are no longer supported - use inline ui() instead
|
||||||
if (peek().kind == TokenKind::LBracket) {
|
if (peek().kind == TokenKind::LBracket) {
|
||||||
throw std::runtime_error("Bracket attributes [ui, ...] are no longer supported. Use ui(\"" + nameTok.text.toStdString() + "\", \"Display Name\"); instead");
|
throw std::runtime_error("Bracket attributes [ui, ...] are no longer supported. Use inline syntax: " + nameTok.text.toStdString() + " = expr ui(\"Display Name\");");
|
||||||
}
|
}
|
||||||
|
|
||||||
UiFlags flags;
|
// Parse optional inline ui suffix: name = expr ui("Display");
|
||||||
flags.ui = false;
|
UiFlags flags = parseInlineUiSuffix();
|
||||||
|
|
||||||
expect(TokenKind::Semicolon, "Expected ';' after assignment");
|
expect(TokenKind::Semicolon, "Expected ';' after assignment");
|
||||||
|
|
||||||
@ -641,7 +687,8 @@ StmtPtr Parser::parseBreakStmt() {
|
|||||||
return s;
|
return s;
|
||||||
}
|
}
|
||||||
|
|
||||||
// inline typename varName @ ptrField [ui="Display", set_name];
|
// inline typename varName when ptrField ui("Display");
|
||||||
|
// inline typename varName when ptrField ui("Display", set_name);
|
||||||
StmtPtr Parser::parseInlineStmt() {
|
StmtPtr Parser::parseInlineStmt() {
|
||||||
Token kw = expect(TokenKind::KwInline, "Expected inline");
|
Token kw = expect(TokenKind::KwInline, "Expected inline");
|
||||||
Token typeTok = expect(TokenKind::Identifier, "Expected type name after inline");
|
Token typeTok = expect(TokenKind::Identifier, "Expected type name after inline");
|
||||||
@ -665,32 +712,26 @@ StmtPtr Parser::parseInlineStmt() {
|
|||||||
throw std::runtime_error("Expected 'when' or '@' after inline variable name");
|
throw std::runtime_error("Expected 'when' or '@' after inline variable name");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse optional attributes
|
// Parse optional ui() suffix: ui("Display") or ui("Display", set_name)
|
||||||
UiFlags flags;
|
UiFlags flags;
|
||||||
bool setName = false;
|
bool setName = false;
|
||||||
if (peek().kind == TokenKind::LBracket) {
|
if (peek().kind == TokenKind::KwUi) {
|
||||||
m_lex.next(); // consume [
|
m_lex.next(); // consume 'ui'
|
||||||
while (true) {
|
expect(TokenKind::LParen, "Expected '(' after ui");
|
||||||
Token attr = expect(TokenKind::Identifier, "Expected attribute");
|
Token displayTok = expect(TokenKind::String, "Expected display name in ui()");
|
||||||
QString attrName = attr.text.toLower();
|
flags.ui = true;
|
||||||
|
flags.display = displayTok.text;
|
||||||
|
|
||||||
if (attrName == "ui") {
|
// Check for optional set_name modifier
|
||||||
flags.ui = true;
|
if (match(TokenKind::Comma)) {
|
||||||
if (match(TokenKind::Assign)) {
|
Token mod = expect(TokenKind::Identifier, "Expected 'set_name' after comma");
|
||||||
Token v = expect(TokenKind::String, "Expected string after ui=");
|
if (mod.text.toLower() == "set_name") {
|
||||||
flags.display = v.text;
|
|
||||||
}
|
|
||||||
} else if (attrName == "set_name") {
|
|
||||||
setName = true;
|
setName = true;
|
||||||
} else if (attrName == "hidden") {
|
|
||||||
flags.hidden = true;
|
|
||||||
} else {
|
} else {
|
||||||
throw std::runtime_error(("Unknown inline attribute: " + attr.text).toStdString());
|
throw std::runtime_error(("Unknown inline modifier: " + mod.text + " (expected 'set_name')").toStdString());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!match(TokenKind::Comma)) break;
|
|
||||||
}
|
}
|
||||||
expect(TokenKind::RBracket, "Expected ']'");
|
expect(TokenKind::RParen, "Expected ')' after ui arguments");
|
||||||
}
|
}
|
||||||
|
|
||||||
expect(TokenKind::Semicolon, "Expected ';' after inline statement");
|
expect(TokenKind::Semicolon, "Expected ';' after inline statement");
|
||||||
|
|||||||
@ -32,6 +32,7 @@ private:
|
|||||||
|
|
||||||
TypeAttrs parseTypeAttrs();
|
TypeAttrs parseTypeAttrs();
|
||||||
UiFlags parseUiFlags(bool defaultUi);
|
UiFlags parseUiFlags(bool defaultUi);
|
||||||
|
UiFlags parseInlineUiSuffix();
|
||||||
|
|
||||||
QVector<StmtPtr> parseCriteriaBlock();
|
QVector<StmtPtr> parseCriteriaBlock();
|
||||||
StmtPtr parseRequireStmt();
|
StmtPtr parseRequireStmt();
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user