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 }