Update DSL parser and interpreter

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
njohnson 2026-01-11 16:08:38 -05:00
parent ca98c059ba
commit 39230139b9
6 changed files with 96 additions and 126 deletions

View File

@ -54,6 +54,8 @@ enum class TokenKind {
KwWhen, KwWhen,
KwAt, KwAt,
KwDefault, KwDefault,
KwUi, // ui
KwEdit, // edit
// punctuation additions // punctuation additions
Arrow, // => Arrow, // =>

View File

@ -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)) {

View File

@ -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) {

View File

@ -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;

View File

@ -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");

View File

@ -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();