| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198 |
- /**
- * 表达式与条件求值:算术表达式、条件表达式(不依赖 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,
- }
|