value-formula.js 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281
  1. const {
  2. formatFixedValue
  3. } = require('../../utils/number-format.js')
  4. const TOKEN_NUMBER = 'number'
  5. const TOKEN_IDENTIFIER = 'identifier'
  6. const TOKEN_OPERATOR = 'operator'
  7. const TOKEN_LEFT_PAREN = 'leftParen'
  8. const TOKEN_RIGHT_PAREN = 'rightParen'
  9. const ALLOWED_IDENTIFIERS = [
  10. 'x',
  11. 'value',
  12. 'rawValue',
  13. 'caveFreq',
  14. 'refVolt',
  15. 'ampGain',
  16. 'rsShunt',
  17. 'busDiv',
  18. 'alongDiv'
  19. ]
  20. function tokenizeFormula(formulaText) {
  21. const source = String(formulaText || '').trim()
  22. const tokens = []
  23. let index = 0
  24. while (index < source.length) {
  25. const char = source[index]
  26. if (/\s/.test(char)) {
  27. index += 1
  28. continue
  29. }
  30. if (/[0-9.]/.test(char)) {
  31. const rest = source.slice(index)
  32. const match = rest.match(/^(?:\d+\.?\d*|\.\d+)(?:e[+-]?\d+)?/i)
  33. if (!match) throw new Error('公式数字无效')
  34. tokens.push({
  35. type: TOKEN_NUMBER,
  36. value: Number(match[0])
  37. })
  38. index += match[0].length
  39. continue
  40. }
  41. if (/[A-Za-z_]/.test(char)) {
  42. const rest = source.slice(index)
  43. const match = rest.match(/^[A-Za-z_][A-Za-z0-9_]*/)
  44. const name = match ? match[0] : ''
  45. if (ALLOWED_IDENTIFIERS.indexOf(name) < 0) {
  46. throw new Error(`公式变量 ${name || char} 不支持`)
  47. }
  48. tokens.push({
  49. type: TOKEN_IDENTIFIER,
  50. value: name
  51. })
  52. index += name.length
  53. continue
  54. }
  55. if ('+-*/'.indexOf(char) >= 0) {
  56. tokens.push({
  57. type: TOKEN_OPERATOR,
  58. value: char
  59. })
  60. index += 1
  61. continue
  62. }
  63. if (char === '(') {
  64. tokens.push({
  65. type: TOKEN_LEFT_PAREN,
  66. value: char
  67. })
  68. index += 1
  69. continue
  70. }
  71. if (char === ')') {
  72. tokens.push({
  73. type: TOKEN_RIGHT_PAREN,
  74. value: char
  75. })
  76. index += 1
  77. continue
  78. }
  79. throw new Error(`公式包含不支持字符 ${char}`)
  80. }
  81. return tokens
  82. }
  83. function getOperatorPrecedence(operator) {
  84. if (operator === 'u+' || operator === 'u-') return 3
  85. if (operator === '*' || operator === '/') return 2
  86. if (operator === '+' || operator === '-') return 1
  87. return 0
  88. }
  89. function operatorIsRightAssociative(operator) {
  90. return operator === 'u+' || operator === 'u-'
  91. }
  92. function toRpn(tokens) {
  93. const output = []
  94. const operators = []
  95. let previousToken = null
  96. tokens.forEach((token) => {
  97. if (token.type === TOKEN_NUMBER || token.type === TOKEN_IDENTIFIER) {
  98. output.push(token)
  99. previousToken = token
  100. return
  101. }
  102. if (token.type === TOKEN_OPERATOR) {
  103. const unary = !previousToken
  104. || previousToken.type === TOKEN_OPERATOR
  105. || previousToken.type === TOKEN_LEFT_PAREN
  106. const operator = unary && (token.value === '+' || token.value === '-')
  107. ? `u${token.value}`
  108. : token.value
  109. const precedence = getOperatorPrecedence(operator)
  110. while (operators.length) {
  111. const top = operators[operators.length - 1]
  112. if (top.type !== TOKEN_OPERATOR) break
  113. const topPrecedence = getOperatorPrecedence(top.value)
  114. if (
  115. topPrecedence > precedence
  116. || (topPrecedence === precedence && !operatorIsRightAssociative(operator))
  117. ) {
  118. output.push(operators.pop())
  119. continue
  120. }
  121. break
  122. }
  123. operators.push({
  124. type: TOKEN_OPERATOR,
  125. value: operator
  126. })
  127. previousToken = token
  128. return
  129. }
  130. if (token.type === TOKEN_LEFT_PAREN) {
  131. operators.push(token)
  132. previousToken = token
  133. return
  134. }
  135. if (token.type === TOKEN_RIGHT_PAREN) {
  136. let matched = false
  137. while (operators.length) {
  138. const top = operators.pop()
  139. if (top.type === TOKEN_LEFT_PAREN) {
  140. matched = true
  141. break
  142. }
  143. output.push(top)
  144. }
  145. if (!matched) throw new Error('公式括号不匹配')
  146. previousToken = token
  147. }
  148. })
  149. while (operators.length) {
  150. const operator = operators.pop()
  151. if (operator.type === TOKEN_LEFT_PAREN) throw new Error('公式括号不匹配')
  152. output.push(operator)
  153. }
  154. return output
  155. }
  156. function getContextValue(name, context) {
  157. if (name === 'x' || name === 'value' || name === 'rawValue') return context.x
  158. return context[name]
  159. }
  160. function evaluateRpn(rpn, context) {
  161. const stack = []
  162. rpn.forEach((token) => {
  163. if (token.type === TOKEN_NUMBER) {
  164. stack.push(token.value)
  165. return
  166. }
  167. if (token.type === TOKEN_IDENTIFIER) {
  168. const value = Number(getContextValue(token.value, context))
  169. if (!Number.isFinite(value)) throw new Error(`公式变量 ${token.value} 无效`)
  170. stack.push(value)
  171. return
  172. }
  173. if (token.type !== TOKEN_OPERATOR) return
  174. if (token.value === 'u+' || token.value === 'u-') {
  175. if (!stack.length) throw new Error('公式运算无效')
  176. const value = stack.pop()
  177. stack.push(token.value === 'u-' ? -value : value)
  178. return
  179. }
  180. if (stack.length < 2) throw new Error('公式运算无效')
  181. const right = stack.pop()
  182. const left = stack.pop()
  183. if (token.value === '+') stack.push(left + right)
  184. if (token.value === '-') stack.push(left - right)
  185. if (token.value === '*') stack.push(left * right)
  186. if (token.value === '/') stack.push(left / right)
  187. })
  188. if (stack.length !== 1 || !Number.isFinite(stack[0])) {
  189. throw new Error('公式计算结果无效')
  190. }
  191. return stack[0]
  192. }
  193. function formatFormulaNumber(value) {
  194. return formatFixedValue(value, 6).replace(/(\.\d*?)0+$/, '$1').replace(/\.$/, '')
  195. }
  196. function evaluateValueFormula(formulaText, rawValue, codeInfoContext = {}) {
  197. const formula = String(formulaText || '').trim()
  198. const numberValue = Number(rawValue)
  199. if (!formula || !Number.isFinite(numberValue)) {
  200. return {
  201. ok: false,
  202. value: numberValue,
  203. text: Number.isFinite(numberValue) ? formatFormulaNumber(numberValue) : '--'
  204. }
  205. }
  206. try {
  207. const context = {
  208. ...codeInfoContext,
  209. x: numberValue
  210. }
  211. const value = evaluateRpn(toRpn(tokenizeFormula(formula)), context)
  212. return {
  213. ok: true,
  214. value,
  215. text: formatFormulaNumber(value)
  216. }
  217. } catch (error) {
  218. return {
  219. errorText: error && error.message ? error.message : '公式无效',
  220. ok: false,
  221. value: numberValue,
  222. text: formatFormulaNumber(numberValue)
  223. }
  224. }
  225. }
  226. function validateValueFormula(formulaText) {
  227. const formula = String(formulaText || '').trim()
  228. if (!formula) return true
  229. toRpn(tokenizeFormula(formula))
  230. return true
  231. }
  232. module.exports = {
  233. ALLOWED_IDENTIFIERS,
  234. evaluateValueFormula,
  235. validateValueFormula
  236. }