/** * 表达式与条件求值:算术表达式、条件表达式(不依赖 eval) */ const { parseValue, resolveValue } = require('./value-resolver.js') 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() if (expr.includes('||')) { return expr.split('||').map(p => p.trim()).some(part => parseConditionExpression(part)) } if (expr.includes('&&')) { return expr.split('&&').map(p => p.trim()).every(part => parseConditionExpression(part)) } 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) }, ] for (const { op, fn } of operators) { if (expr.includes(op)) { const parts = expr.split(op).map(p => p.trim()) if (parts.length === 2) return fn(parseValue(parts[0]), parseValue(parts[1])) } } 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') { try { const p = JSON.parse(v) 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, }