expression-evaluator.js 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198
  1. /**
  2. * 表达式与条件求值:算术表达式、条件表达式(不依赖 eval)
  3. */
  4. const { parseValue, resolveValue } = require('./value-resolver.js')
  5. function parseArithmeticExpression(expr) {
  6. let index = 0
  7. const skipWhitespace = () => {
  8. while (index < expr.length && /\s/.test(expr[index])) index++
  9. }
  10. const parseNumber = () => {
  11. skipWhitespace()
  12. let numStr = ''
  13. let hasDot = false
  14. while (index < expr.length) {
  15. const c = expr[index]
  16. if (c >= '0' && c <= '9') { numStr += c; index++ }
  17. else if (c === '.' && !hasDot) { numStr += c; hasDot = true; index++ }
  18. else break
  19. }
  20. if (!numStr) throw new Error('期望数字')
  21. const n = parseFloat(numStr)
  22. if (isNaN(n)) throw new Error(`无效的数字: ${numStr}`)
  23. return n
  24. }
  25. const parseFactor = () => {
  26. skipWhitespace()
  27. if (index >= expr.length) throw new Error('表达式不完整')
  28. let neg = false
  29. if (expr[index] === '-') { neg = true; index++; skipWhitespace() }
  30. else if (expr[index] === '+') { index++; skipWhitespace() }
  31. let r
  32. if (expr[index] === '(') {
  33. index++
  34. r = parseExpression()
  35. skipWhitespace()
  36. if (index >= expr.length || expr[index] !== ')') throw new Error('缺少右括号')
  37. index++
  38. } else {
  39. r = parseNumber()
  40. }
  41. return neg ? -r : r
  42. }
  43. const parseTerm = () => {
  44. let r = parseFactor()
  45. skipWhitespace()
  46. while (index < expr.length) {
  47. const op = expr[index]
  48. if (op === '*') { index++; r *= parseFactor() }
  49. else if (op === '/') { index++; const d = parseFactor(); if (d === 0) throw new Error('除以零'); r /= d }
  50. else break
  51. skipWhitespace()
  52. }
  53. return r
  54. }
  55. const parseExpression = () => {
  56. let r = parseTerm()
  57. skipWhitespace()
  58. while (index < expr.length) {
  59. const op = expr[index]
  60. if (op === '+') { index++; r += parseTerm() }
  61. else if (op === '-') { index++; r -= parseTerm() }
  62. else break
  63. skipWhitespace()
  64. }
  65. return r
  66. }
  67. const r = parseExpression()
  68. skipWhitespace()
  69. if (index < expr.length) throw new Error(`表达式解析不完整: ${expr.substring(index)}`)
  70. return r
  71. }
  72. function evaluateExpression(expression, context) {
  73. if (typeof expression !== 'string') return expression
  74. try {
  75. let expr = expression.trim()
  76. const varPattern = /\{(\w+)\}(?!\})/g
  77. const originalExpr = expr
  78. let hasVars = false
  79. expr = expr.replace(varPattern, (match, varName) => {
  80. hasVars = true
  81. const v = context[varName]
  82. if (v === undefined || v === null) return '0'
  83. if (typeof v === 'number') return String(v)
  84. if (typeof v === 'boolean') return v ? '1' : '0'
  85. if (typeof v === 'string') {
  86. const n = Number(v)
  87. if (!isNaN(n) && v.trim() !== '') return String(n)
  88. return '0'
  89. }
  90. const n = Number(v)
  91. return !isNaN(n) ? String(n) : '0'
  92. })
  93. if (!hasVars && !/[+\-*/]/.test(expr)) {
  94. const n = Number(expr)
  95. if (!isNaN(n) && expr.trim() !== '') return n
  96. return expr
  97. }
  98. if (!/[+\-*/]/.test(expr)) {
  99. const n = Number(expr)
  100. if (!isNaN(n) && expr.trim() !== '') return n
  101. return expr
  102. }
  103. expr = expr.replace(/\s+/g, '')
  104. if (!/^[0-9+\-*/().]+$/.test(expr)) return resolveValue(originalExpr, context)
  105. if (!/^[0-9(]/.test(expr) || !/[0-9)]$/.test(expr)) return resolveValue(originalExpr, context)
  106. const result = parseArithmeticExpression(expr)
  107. if (typeof result === 'number' && !isNaN(result) && isFinite(result)) return result
  108. return resolveValue(originalExpr, context)
  109. } catch (e) {
  110. return resolveValue(expression, context)
  111. }
  112. }
  113. function parseConditionExpression(expr) {
  114. expr = expr.trim()
  115. if (expr.includes('||')) {
  116. return expr.split('||').map(p => p.trim()).some(part => parseConditionExpression(part))
  117. }
  118. if (expr.includes('&&')) {
  119. return expr.split('&&').map(p => p.trim()).every(part => parseConditionExpression(part))
  120. }
  121. const operators = [
  122. { op: '!=', fn: (a, b) => a != b },
  123. { op: '==', fn: (a, b) => {
  124. if (typeof a === 'string' && typeof b !== 'string') b = String(b)
  125. else if (typeof b === 'string' && typeof a !== 'string') a = String(a)
  126. if (a === '' && b === '') return true
  127. if ((a === '' && typeof b === 'string') || (b === '' && typeof a === 'string')) {
  128. try {
  129. const p = JSON.parse(a === '' ? b : a)
  130. if (Array.isArray(p)) return p.length === 0
  131. } catch (e) {}
  132. }
  133. return a == b
  134. }},
  135. { op: '>=', fn: (a, b) => Number(a) >= Number(b) },
  136. { op: '<=', fn: (a, b) => Number(a) <= Number(b) },
  137. { op: '>', fn: (a, b) => Number(a) > Number(b) },
  138. { op: '<', fn: (a, b) => Number(a) < Number(b) },
  139. ]
  140. for (const { op, fn } of operators) {
  141. if (expr.includes(op)) {
  142. const parts = expr.split(op).map(p => p.trim())
  143. if (parts.length === 2) return fn(parseValue(parts[0]), parseValue(parts[1]))
  144. }
  145. }
  146. const value = parseValue(expr)
  147. if (typeof value === 'boolean') return value
  148. if (typeof value === 'string') {
  149. if (value === '' || value === 'undefined') return false
  150. try {
  151. const p = JSON.parse(value)
  152. if (Array.isArray(p)) return p.length > 0
  153. if (typeof p === 'object' && Object.keys(p).length === 0) return false
  154. } catch (e) {}
  155. return true
  156. }
  157. if (typeof value === 'number') return value !== 0
  158. return Boolean(value)
  159. }
  160. function evaluateCondition(condition, context) {
  161. if (!condition) return true
  162. try {
  163. let expr = condition
  164. const varPattern = /\{([\w-]+)\}/g
  165. expr = expr.replace(varPattern, (match, varName) => {
  166. const v = context[varName]
  167. if (v === undefined || v === null || v === '' || v === 'undefined' || v === 'null') return '""'
  168. if (typeof v === 'string') {
  169. try {
  170. const p = JSON.parse(v)
  171. if (Array.isArray(p)) return `"${v.replace(/"/g, '\\"')}"`
  172. } catch (e) {}
  173. return `"${v.replace(/"/g, '\\"')}"`
  174. }
  175. if (Array.isArray(v)) {
  176. try {
  177. return `"${JSON.stringify(v).replace(/"/g, '\\"')}"`
  178. } catch (e) { return '"[]"' }
  179. }
  180. if (typeof v === 'number' || typeof v === 'boolean') return v
  181. return `"${String(v)}"`
  182. })
  183. return parseConditionExpression(expr)
  184. } catch (e) {
  185. return false
  186. }
  187. }
  188. module.exports = {
  189. parseArithmeticExpression,
  190. evaluateExpression,
  191. evaluateCondition,
  192. parseConditionExpression,
  193. }