Files
wehub-resource-sync 2114b14ee0
Sync main into demo / sync (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:35:26 +08:00

126 lines
3.6 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 表达式构建器 — 输入校验规则
* 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));
}