value-formula.js 6.6 KB

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