126 lines
3.6 KiB
TypeScript
126 lines
3.6 KiB
TypeScript
/**
|
||
* 表达式构建器 — 输入校验规则
|
||
* 1:1 翻译自 CalculatorExpressionBuilder.java
|
||
*
|
||
* 所有输入都在 normalized (ASCII) 空间处理
|
||
*/
|
||
|
||
import { normalize, localize, isDigit } from './tokenizer';
|
||
|
||
export type CalcState = 'INPUT' | 'EVALUATE' | 'RESULT' | 'ERROR';
|
||
|
||
/**
|
||
* 在表达式末尾追加一个字符(或函数名),返回新的 normalized 表达式
|
||
*
|
||
* @param formula 当前显示表达式
|
||
* @param token 要追加的 ASCII 字符 (e.g. '1', '+', '-', '*', '/', '.')
|
||
* @param state 当前计算器状态
|
||
* @param isEdited 是否已编辑过(用于 RESULT/ERROR 首次编辑替换)
|
||
* @returns [newFormula (localized), newIsEdited]
|
||
*/
|
||
export function appendToken(
|
||
formula: string,
|
||
token: string,
|
||
state: CalcState,
|
||
isEdited: boolean,
|
||
): [string, boolean] {
|
||
const norm = normalize(formula);
|
||
const start = norm.length;
|
||
let editStart = start;
|
||
let edited = isEdited;
|
||
|
||
// RESULT/ERROR 状态下首次编辑替换全文
|
||
if ((state === 'RESULT' || state === 'ERROR') && !edited) {
|
||
editStart = 0;
|
||
edited = true;
|
||
}
|
||
|
||
if (token === '.') {
|
||
// 查找当前数字段中是否已有小数点
|
||
const lastDotIdx = norm.lastIndexOf('.');
|
||
if (lastDotIdx >= 0) {
|
||
// 检查 lastDotIdx 之后是否全是数字(同一数字段)
|
||
const afterDot = norm.substring(lastDotIdx + 1, start);
|
||
const allDigits = [...afterDot].every(ch => isDigit(ch));
|
||
if (allDigits) {
|
||
return [formula, edited]; // 阻止重复小数点
|
||
}
|
||
}
|
||
} else if (token === '+' || token === '*' || token === '/') {
|
||
if (start === 0 && editStart === 0) {
|
||
return [formula, edited]; // 不允许前导运算符
|
||
}
|
||
// 运算符总是续算:去掉尾部已有的运算符序列后追加新运算符
|
||
// RESULT 状态下也保留公式(链式计算: 5 → 5×),不像数字那样替换
|
||
let trimEnd = start;
|
||
while (trimEnd > 0 && '+-*/'.includes(norm[trimEnd - 1])) {
|
||
trimEnd--;
|
||
}
|
||
if (trimEnd === 0) {
|
||
return [formula, edited]; // 全是运算符,不追加
|
||
}
|
||
const base = norm.substring(0, trimEnd);
|
||
return [localize(base + token), true];
|
||
}
|
||
|
||
if (token === '-') {
|
||
// 减号允许作为负号使用,也允许续算
|
||
const checkStart = start; // 总是检查末尾字符(包括 RESULT 状态)
|
||
if (checkStart > 0 && '+-'.includes(norm[checkStart - 1])) {
|
||
// 替换前一个 +/- 为 -
|
||
const base = norm.substring(0, checkStart - 1);
|
||
return [localize(base + '-'), true];
|
||
}
|
||
}
|
||
|
||
// 默认追加
|
||
if (editStart === 0 && !edited) {
|
||
// 替换整个表达式
|
||
return [localize(token), true];
|
||
}
|
||
return [localize(norm + token), true];
|
||
}
|
||
|
||
/**
|
||
* 追加函数名(自动补左括号)
|
||
* AOSP: fun_sin onClick → append "sin("
|
||
*/
|
||
export function appendFunction(
|
||
formula: string,
|
||
funcName: string,
|
||
state: CalcState,
|
||
isEdited: boolean,
|
||
): [string, boolean] {
|
||
const norm = normalize(formula);
|
||
let edited = isEdited;
|
||
|
||
if ((state === 'RESULT' || state === 'ERROR') && !edited) {
|
||
return [localize(funcName + '('), true];
|
||
}
|
||
|
||
return [localize(norm + funcName + '('), true];
|
||
}
|
||
|
||
/**
|
||
* 删除末尾一个字符
|
||
*/
|
||
export function deleteLastChar(formula: string): string {
|
||
const norm = normalize(formula);
|
||
if (norm.length === 0) return '';
|
||
|
||
// 检查是否以函数名 + ( 结尾(如 "sin(")
|
||
const funcs = ['sin(', 'cos(', 'tan(', 'ln(', 'log('];
|
||
for (const fn of funcs) {
|
||
if (norm.endsWith(fn)) {
|
||
return localize(norm.slice(0, -fn.length));
|
||
}
|
||
}
|
||
|
||
// 检查是否以 √( 结尾
|
||
if (norm.endsWith('sqrt(')) {
|
||
return localize(norm.slice(0, -5));
|
||
}
|
||
|
||
return localize(norm.slice(0, -1));
|
||
}
|