| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281 |
- const {
- formatFixedValue
- } = require('../../utils/number-format.js')
- const TOKEN_NUMBER = 'number'
- const TOKEN_IDENTIFIER = 'identifier'
- const TOKEN_OPERATOR = 'operator'
- const TOKEN_LEFT_PAREN = 'leftParen'
- const TOKEN_RIGHT_PAREN = 'rightParen'
- const ALLOWED_IDENTIFIERS = [
- 'x',
- 'value',
- 'rawValue',
- 'caveFreq',
- 'refVolt',
- 'ampGain',
- 'rsShunt',
- 'busDiv',
- 'alongDiv'
- ]
- function tokenizeFormula(formulaText) {
- const source = String(formulaText || '').trim()
- const tokens = []
- let index = 0
- while (index < source.length) {
- const char = source[index]
- if (/\s/.test(char)) {
- index += 1
- continue
- }
- if (/[0-9.]/.test(char)) {
- const rest = source.slice(index)
- const match = rest.match(/^(?:\d+\.?\d*|\.\d+)(?:e[+-]?\d+)?/i)
- if (!match) throw new Error('公式数字无效')
- tokens.push({
- type: TOKEN_NUMBER,
- value: Number(match[0])
- })
- index += match[0].length
- continue
- }
- if (/[A-Za-z_]/.test(char)) {
- const rest = source.slice(index)
- const match = rest.match(/^[A-Za-z_][A-Za-z0-9_]*/)
- const name = match ? match[0] : ''
- if (ALLOWED_IDENTIFIERS.indexOf(name) < 0) {
- throw new Error(`公式变量 ${name || char} 不支持`)
- }
- tokens.push({
- type: TOKEN_IDENTIFIER,
- value: name
- })
- index += name.length
- continue
- }
- if ('+-*/'.indexOf(char) >= 0) {
- tokens.push({
- type: TOKEN_OPERATOR,
- value: char
- })
- index += 1
- continue
- }
- if (char === '(') {
- tokens.push({
- type: TOKEN_LEFT_PAREN,
- value: char
- })
- index += 1
- continue
- }
- if (char === ')') {
- tokens.push({
- type: TOKEN_RIGHT_PAREN,
- value: char
- })
- index += 1
- continue
- }
- throw new Error(`公式包含不支持字符 ${char}`)
- }
- return tokens
- }
- function getOperatorPrecedence(operator) {
- if (operator === 'u+' || operator === 'u-') return 3
- if (operator === '*' || operator === '/') return 2
- if (operator === '+' || operator === '-') return 1
- return 0
- }
- function operatorIsRightAssociative(operator) {
- return operator === 'u+' || operator === 'u-'
- }
- function toRpn(tokens) {
- const output = []
- const operators = []
- let previousToken = null
- tokens.forEach((token) => {
- if (token.type === TOKEN_NUMBER || token.type === TOKEN_IDENTIFIER) {
- output.push(token)
- previousToken = token
- return
- }
- if (token.type === TOKEN_OPERATOR) {
- const unary = !previousToken
- || previousToken.type === TOKEN_OPERATOR
- || previousToken.type === TOKEN_LEFT_PAREN
- const operator = unary && (token.value === '+' || token.value === '-')
- ? `u${token.value}`
- : token.value
- const precedence = getOperatorPrecedence(operator)
- while (operators.length) {
- const top = operators[operators.length - 1]
- if (top.type !== TOKEN_OPERATOR) break
- const topPrecedence = getOperatorPrecedence(top.value)
- if (
- topPrecedence > precedence
- || (topPrecedence === precedence && !operatorIsRightAssociative(operator))
- ) {
- output.push(operators.pop())
- continue
- }
- break
- }
- operators.push({
- type: TOKEN_OPERATOR,
- value: operator
- })
- previousToken = token
- return
- }
- if (token.type === TOKEN_LEFT_PAREN) {
- operators.push(token)
- previousToken = token
- return
- }
- if (token.type === TOKEN_RIGHT_PAREN) {
- let matched = false
- while (operators.length) {
- const top = operators.pop()
- if (top.type === TOKEN_LEFT_PAREN) {
- matched = true
- break
- }
- output.push(top)
- }
- if (!matched) throw new Error('公式括号不匹配')
- previousToken = token
- }
- })
- while (operators.length) {
- const operator = operators.pop()
- if (operator.type === TOKEN_LEFT_PAREN) throw new Error('公式括号不匹配')
- output.push(operator)
- }
- return output
- }
- function getContextValue(name, context) {
- if (name === 'x' || name === 'value' || name === 'rawValue') return context.x
- return context[name]
- }
- function evaluateRpn(rpn, context) {
- const stack = []
- rpn.forEach((token) => {
- if (token.type === TOKEN_NUMBER) {
- stack.push(token.value)
- return
- }
- if (token.type === TOKEN_IDENTIFIER) {
- const value = Number(getContextValue(token.value, context))
- if (!Number.isFinite(value)) throw new Error(`公式变量 ${token.value} 无效`)
- stack.push(value)
- return
- }
- if (token.type !== TOKEN_OPERATOR) return
- if (token.value === 'u+' || token.value === 'u-') {
- if (!stack.length) throw new Error('公式运算无效')
- const value = stack.pop()
- stack.push(token.value === 'u-' ? -value : value)
- return
- }
- if (stack.length < 2) throw new Error('公式运算无效')
- const right = stack.pop()
- const left = stack.pop()
- if (token.value === '+') stack.push(left + right)
- if (token.value === '-') stack.push(left - right)
- if (token.value === '*') stack.push(left * right)
- if (token.value === '/') stack.push(left / right)
- })
- if (stack.length !== 1 || !Number.isFinite(stack[0])) {
- throw new Error('公式计算结果无效')
- }
- return stack[0]
- }
- function formatFormulaNumber(value) {
- return formatFixedValue(value, 6).replace(/(\.\d*?)0+$/, '$1').replace(/\.$/, '')
- }
- function evaluateValueFormula(formulaText, rawValue, codeInfoContext = {}) {
- const formula = String(formulaText || '').trim()
- const numberValue = Number(rawValue)
- if (!formula || !Number.isFinite(numberValue)) {
- return {
- ok: false,
- value: numberValue,
- text: Number.isFinite(numberValue) ? formatFormulaNumber(numberValue) : '--'
- }
- }
- try {
- const context = {
- ...codeInfoContext,
- x: numberValue
- }
- const value = evaluateRpn(toRpn(tokenizeFormula(formula)), context)
- return {
- ok: true,
- value,
- text: formatFormulaNumber(value)
- }
- } catch (error) {
- return {
- errorText: error && error.message ? error.message : '公式无效',
- ok: false,
- value: numberValue,
- text: formatFormulaNumber(numberValue)
- }
- }
- }
- function validateValueFormula(formulaText) {
- const formula = String(formulaText || '').trim()
- if (!formula) return true
- toRpn(tokenizeFormula(formula))
- return true
- }
- module.exports = {
- ALLOWED_IDENTIFIERS,
- evaluateValueFormula,
- validateValueFormula
- }
|