/** * 表达式与条件求值:算术表达式、条件表达式(不依赖 eval) */ const { parseValue, resolveValue } = require('./actions/set-parser.js') /** 在 str 中查找 needle 的起始下标,忽略单/双引号字符串内的匹配(支持 \\ 转义) */ function findOutsideQuotes(str, needle, fromIndex = 0) { let i = fromIndex let inSingle = false let inDouble = false while (i < str.length) { const c = str[i] if (inSingle) { if (c === '\\' && i + 1 < str.length) { i += 2 continue } if (c === "'") inSingle = false i++ continue } if (inDouble) { if (c === '\\' && i + 1 < str.length) { i += 2 continue } if (c === '"') inDouble = false i++ continue } if (c === "'") { inSingle = true i++ continue } if (c === '"') { inDouble = true i++ continue } if (str.startsWith(needle, i)) return i i++ } return -1 } function splitTopLevelFirst(expr, delimiter) { const idx = findOutsideQuotes(expr, delimiter) if (idx === -1) return null return [expr.slice(0, idx).trim(), expr.slice(idx + delimiter.length).trim()] } /** * 在引号外查找第一个比较运算符(长运算符优先,避免 >= 被拆成 >) */ function findBinaryComparisonOp(expr, operators) { const sorted = [...operators].sort((a, b) => b.op.length - a.op.length) let i = 0 let inSingle = false let inDouble = false while (i < expr.length) { const c = expr[i] if (inSingle) { if (c === '\\' && i + 1 < expr.length) { i += 2 continue } if (c === "'") inSingle = false i++ continue } if (inDouble) { if (c === '\\' && i + 1 < expr.length) { i += 2 continue } if (c === '"') inDouble = false i++ continue } if (c === "'") { inSingle = true i++ continue } if (c === '"') { inDouble = true i++ continue } for (const row of sorted) { if (expr.startsWith(row.op, i)) return { index: i, op: row.op, fn: row.fn } } i++ } return null } function parseArithmeticExpression(expr) { let index = 0 const skipWhitespace = () => { while (index < expr.length && /\s/.test(expr[index])) index++ } const parseNumber = () => { skipWhitespace() let numStr = '' let hasDot = false while (index < expr.length) { const c = expr[index] if (c >= '0' && c <= '9') { numStr += c; index++ } else if (c === '.' && !hasDot) { numStr += c; hasDot = true; index++ } else break } if (!numStr) throw new Error('期望数字') const n = parseFloat(numStr) if (isNaN(n)) throw new Error(`无效的数字: ${numStr}`) return n } const parseFactor = () => { skipWhitespace() if (index >= expr.length) throw new Error('表达式不完整') let neg = false if (expr[index] === '-') { neg = true; index++; skipWhitespace() } else if (expr[index] === '+') { index++; skipWhitespace() } let r if (expr[index] === '(') { index++ r = parseExpression() skipWhitespace() if (index >= expr.length || expr[index] !== ')') throw new Error('缺少右括号') index++ } else { r = parseNumber() } return neg ? -r : r } const parseTerm = () => { let r = parseFactor() skipWhitespace() while (index < expr.length) { const op = expr[index] if (op === '*') { index++; r *= parseFactor() } else if (op === '/') { index++; const d = parseFactor(); if (d === 0) throw new Error('除以零'); r /= d } else break skipWhitespace() } return r } const parseExpression = () => { let r = parseTerm() skipWhitespace() while (index < expr.length) { const op = expr[index] if (op === '+') { index++; r += parseTerm() } else if (op === '-') { index++; r -= parseTerm() } else break skipWhitespace() } return r } const r = parseExpression() skipWhitespace() if (index < expr.length) throw new Error(`表达式解析不完整: ${expr.substring(index)}`) return r } function evaluateExpression(expression, context) { if (typeof expression !== 'string') return expression try { let expr = expression.trim() const varPattern = /\{(\w+)\}(?!\})/g const originalExpr = expr let hasVars = false expr = expr.replace(varPattern, (match, varName) => { hasVars = true const v = context[varName] if (v === undefined || v === null) return '0' if (typeof v === 'number') return String(v) if (typeof v === 'boolean') return v ? '1' : '0' if (typeof v === 'string') { const n = Number(v) if (!isNaN(n) && v.trim() !== '') return String(n) return '0' } const n = Number(v) return !isNaN(n) ? String(n) : '0' }) if (!hasVars && !/[+\-*/]/.test(expr)) { const n = Number(expr) if (!isNaN(n) && expr.trim() !== '') return n return expr } if (!/[+\-*/]/.test(expr)) { const n = Number(expr) if (!isNaN(n) && expr.trim() !== '') return n return expr } expr = expr.replace(/\s+/g, '') if (!/^[0-9+\-*/().]+$/.test(expr)) return resolveValue(originalExpr, context) if (!/^[0-9(]/.test(expr) || !/[0-9)]$/.test(expr)) return resolveValue(originalExpr, context) const result = parseArithmeticExpression(expr) if (typeof result === 'number' && !isNaN(result) && isFinite(result)) return result return resolveValue(originalExpr, context) } catch (e) { return resolveValue(expression, context) } } function parseConditionExpression(expr) { expr = expr.trim() const orPart = splitTopLevelFirst(expr, '||') if (orPart) { return parseConditionExpression(orPart[0]) || parseConditionExpression(orPart[1]) } const andPart = splitTopLevelFirst(expr, '&&') if (andPart) { return parseConditionExpression(andPart[0]) && parseConditionExpression(andPart[1]) } const operators = [ { op: '!=', fn: (a, b) => a != b }, { op: '==', fn: (a, b) => { if (typeof a === 'string' && typeof b !== 'string') b = String(b) else if (typeof b === 'string' && typeof a !== 'string') a = String(a) if (a === '' && b === '') return true if ((a === '' && typeof b === 'string') || (b === '' && typeof a === 'string')) { try { const p = JSON.parse(a === '' ? b : a) if (Array.isArray(p)) return p.length === 0 } catch (e) {} } return a == b }}, { op: '>=', fn: (a, b) => Number(a) >= Number(b) }, { op: '<=', fn: (a, b) => Number(a) <= Number(b) }, { op: '>', fn: (a, b) => Number(a) > Number(b) }, { op: '<', fn: (a, b) => Number(a) < Number(b) }, ] const binary = findBinaryComparisonOp(expr, operators) if (binary) { const left = expr.slice(0, binary.index).trim() const right = expr.slice(binary.index + binary.op.length).trim() return binary.fn(parseValue(left), parseValue(right)) } const value = parseValue(expr) if (typeof value === 'boolean') return value if (typeof value === 'string') { if (value === '' || value === 'undefined') return false try { const p = JSON.parse(value) if (Array.isArray(p)) return p.length > 0 if (typeof p === 'object' && Object.keys(p).length === 0) return false } catch (e) {} return true } if (typeof value === 'number') return value !== 0 return Boolean(value) } function evaluateCondition(condition, context) { if (!condition) return true try { let expr = condition const varPattern = /\{([\w-]+)\}/g expr = expr.replace(varPattern, (match, varName) => { const v = context[varName] if (v === undefined || v === null || v === '' || v === 'undefined' || v === 'null') return '""' if (typeof v === 'string') { const trimmed = v.trim() if (trimmed === '' || trimmed === 'undefined' || trimmed === 'null') return '""' try { const p = JSON.parse(trimmed) if (Array.isArray(p)) return `"${v.replace(/"/g, '\\"')}"` } catch (e) {} return `"${v.replace(/"/g, '\\"')}"` } if (Array.isArray(v)) { try { return `"${JSON.stringify(v).replace(/"/g, '\\"')}"` } catch (e) { return '"[]"' } } if (typeof v === 'number' || typeof v === 'boolean') return v return `"${String(v)}"` }) return parseConditionExpression(expr) } catch (e) { return false } } module.exports = { parseArithmeticExpression, evaluateExpression, evaluateCondition, parseConditionExpression, }