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,
|
||||
KwAt,
|
||||
KwDefault,
|
||||
KwUi, // ui
|
||||
KwEdit, // edit
|
||||
|
||||
// punctuation additions
|
||||
Arrow, // =>
|
||||
|
||||
@ -162,55 +162,12 @@ inline void collectFieldsFromStmt(const Stmt& s, QMap<QString, UiField>& out, co
|
||||
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)) {
|
||||
const auto& cs = std::get<Stmt::CallStmt>(s.node);
|
||||
if (cs.call && std::holds_alternative<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
|
||||
if (call.fn == "parse_here" && !call.args.isEmpty() && mod) {
|
||||
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;
|
||||
}
|
||||
|
||||
// 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
|
||||
if (c.fn == "ui_table") {
|
||||
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;
|
||||
}
|
||||
|
||||
// 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
|
||||
// Valid types: "hex", "text", "image", "audio", "list", "table"
|
||||
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);
|
||||
QVariant v = readScalar(rt, rs.type);
|
||||
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;
|
||||
}
|
||||
|
||||
@ -1753,6 +1710,16 @@ void Interpreter::execStmt(Runtime& rt, const Stmt& s) const {
|
||||
QVariant v = evalExpr(rt, *as.value);
|
||||
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
|
||||
if (as.ui.hidden) {
|
||||
|
||||
@ -32,6 +32,8 @@ static QHash<QString, TokenKind> buildKeywords() {
|
||||
k["const"] = TokenKind::KwConst;
|
||||
k["when"] = TokenKind::KwWhen;
|
||||
k["default"] = TokenKind::KwDefault;
|
||||
k["ui"] = TokenKind::KwUi;
|
||||
k["edit"] = TokenKind::KwEdit;
|
||||
|
||||
k["u8"] = TokenKind::KwU8;
|
||||
k["u16"] = TokenKind::KwU16;
|
||||
|
||||
@ -66,10 +66,56 @@ UiFlags Parser::parseUiFlags(bool defaultUi)
|
||||
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 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))
|
||||
return a;
|
||||
|
||||
@ -208,13 +254,13 @@ StmtPtr Parser::parseScalarStmt() {
|
||||
Token tTok = m_lex.next();
|
||||
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) {
|
||||
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;
|
||||
flags.ui = false;
|
||||
// Parse optional inline ui suffix: u32 name ui("Display");
|
||||
UiFlags flags = parseInlineUiSuffix();
|
||||
|
||||
expect(TokenKind::Semicolon, "Expected ';' after scalar statement");
|
||||
|
||||
@ -292,13 +338,13 @@ StmtPtr Parser::parseAssignStmt() {
|
||||
Token eq = expect(TokenKind::Assign, "Expected '='");
|
||||
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) {
|
||||
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;
|
||||
flags.ui = false;
|
||||
// Parse optional inline ui suffix: name = expr ui("Display");
|
||||
UiFlags flags = parseInlineUiSuffix();
|
||||
|
||||
expect(TokenKind::Semicolon, "Expected ';' after assignment");
|
||||
|
||||
@ -641,7 +687,8 @@ StmtPtr Parser::parseBreakStmt() {
|
||||
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() {
|
||||
Token kw = expect(TokenKind::KwInline, "Expected 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");
|
||||
}
|
||||
|
||||
// Parse optional attributes
|
||||
// Parse optional ui() suffix: ui("Display") or ui("Display", set_name)
|
||||
UiFlags flags;
|
||||
bool setName = false;
|
||||
if (peek().kind == TokenKind::LBracket) {
|
||||
m_lex.next(); // consume [
|
||||
while (true) {
|
||||
Token attr = expect(TokenKind::Identifier, "Expected attribute");
|
||||
QString attrName = attr.text.toLower();
|
||||
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()");
|
||||
flags.ui = true;
|
||||
flags.display = displayTok.text;
|
||||
|
||||
if (attrName == "ui") {
|
||||
flags.ui = true;
|
||||
if (match(TokenKind::Assign)) {
|
||||
Token v = expect(TokenKind::String, "Expected string after ui=");
|
||||
flags.display = v.text;
|
||||
}
|
||||
} else if (attrName == "set_name") {
|
||||
// Check for optional set_name modifier
|
||||
if (match(TokenKind::Comma)) {
|
||||
Token mod = expect(TokenKind::Identifier, "Expected 'set_name' after comma");
|
||||
if (mod.text.toLower() == "set_name") {
|
||||
setName = true;
|
||||
} else if (attrName == "hidden") {
|
||||
flags.hidden = true;
|
||||
} 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");
|
||||
|
||||
@ -32,6 +32,7 @@ private:
|
||||
|
||||
TypeAttrs parseTypeAttrs();
|
||||
UiFlags parseUiFlags(bool defaultUi);
|
||||
UiFlags parseInlineUiSuffix();
|
||||
|
||||
QVector<StmtPtr> parseCriteriaBlock();
|
||||
StmtPtr parseRequireStmt();
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user