/** * visible_when 表达式解析与求值 * * 支持语法: * field == 'value' 等值判断 * field != 'value' 不等判断 * expr1 AND expr2 逻辑与 * expr1 OR expr2 逻辑或 * NOT expr 逻辑非 * (expr) 括号分组 */ interface ExprNode { type: 'eq' | 'and' | 'or' | 'not'; field?: string; value?: string; left?: ExprNode; right?: ExprNode; operand?: ExprNode; } function tokenize(input: string): string[] { const tokens: string[] = []; let i = 0; while (i < input.length) { if (input[i] === ' ') { i++; continue; } if (input[i] === '(' || input[i] === ')') { tokens.push(input[i]); i++; continue; } if (input[i] === "'") { let j = i + 1; while (j < input.length && input[j] !== "'") j++; tokens.push(input.substring(i, j + 1)); i = j + 1; continue; } if (input[i] === '=' && input[i + 1] === '=') { tokens.push('=='); i += 2; continue; } if (input[i] === '!' && input[i + 1] === '=') { tokens.push('!='); i += 2; continue; } let j = i; while ( j < input.length && !' ()\''.includes(input[j]) && !(input[j] === '=' && input[j + 1] === '=') && !(input[j] === '!' && input[j + 1] === '=') ) { j++; } tokens.push(input.substring(i, j)); i = j; } return tokens; } function parseAtom(tokens: string[]): ExprNode | null { const token = tokens.shift(); if (!token) return null; if (token === '(') { const expr = parseOr(tokens); if (tokens[0] === ')') tokens.shift(); return expr; } if (token === 'NOT') { const operand = parseAtom(tokens); return { type: 'not', operand: operand || undefined }; } const field = token; const op = tokens.shift(); if (op !== '==' && op !== '!=') return null; const rawValue = tokens.shift() || ''; const value = rawValue.replace(/^'(.*)'$/, '$1'); return { type: 'eq', field, value }; } function parseAnd(tokens: string[]): ExprNode | null { let left = parseAtom(tokens); while (tokens[0] === 'AND') { tokens.shift(); const right = parseAtom(tokens); if (left && right) { left = { type: 'and', left, right }; } } return left; } function parseOr(tokens: string[]): ExprNode | null { let left = parseAnd(tokens); while (tokens[0] === 'OR') { tokens.shift(); const right = parseAnd(tokens); if (left && right) { left = { type: 'or', left, right }; } } return left; } export function parseExpr(input: string): ExprNode | null { const tokens = tokenize(input); return parseOr(tokens); } export function evaluateExpr(node: ExprNode, values: Record): boolean { switch (node.type) { case 'eq': return String(values[node.field!] ?? '') === node.value; case 'and': return evaluateExpr(node.left!, values) && evaluateExpr(node.right!, values); case 'or': return evaluateExpr(node.left!, values) || evaluateExpr(node.right!, values); case 'not': return !evaluateExpr(node.operand!, values); default: return false; } } export function evaluateVisibleWhen( expr: string | undefined, values: Record, ): boolean { if (!expr) return true; const ast = parseExpr(expr); return ast ? evaluateExpr(ast, values) : true; }