Download src/utils/calc.js from shutajb/agent: direct link, hf CLI and curl.
- Browser
- Download file 2.63 kB
-
https://huggingface.co/spaces/shutajb/agent/resolve/main/src/utils/calc.js
- Command line
-
hf download hf://spaces/shutajb/agent/src/utils/calc.js
-
curl -L -o calc.js https://huggingface.co/spaces/shutajb/agent/resolve/main/src/utils/calc.js
2.63 kB
| /* ------------------------------------------------------------------ * | |
| * 安全四則運算求值。 | |
| * | |
| * 模型輸出的 expression 屬於不可信輸入,因此: | |
| * - 先用正則白色清單擋掉任何運算子以外的字元 | |
| * - 再用遞迴下降解析器求值 | |
| * 全程不用 Function() / eval() / new Function,沒有代碼執行面。 | |
| * ------------------------------------------------------------------ */ | |
| const CALC_RE = /^[\d\s+\-*/().%]+$/ | |
| /** 把表達式轉成計算結果字串;任何異常都轉成錯誤訊息回傳,不丟例外。 */ | |
| export function safeCalc(expression) { | |
| if (typeof expression !== 'string' || !expression.trim()) { | |
| return '計算錯誤:表達式為空' | |
| } | |
| if (!CALC_RE.test(expression)) { | |
| return `計算錯誤:只支援數字與 + - * / ( ) . %(收到「${expression}」)` | |
| } | |
| try { | |
| const value = evaluate(expression.replace(/\s+/g, '')) | |
| if (!Number.isFinite(value)) return `計算錯誤:結果無效(${expression})` | |
| // 消掉浮點誤差:0.1 + 0.2 應顯示 0.3 | |
| return `計算結果:${Math.round(value * 1e10) / 1e10}` | |
| } catch (e) { | |
| return `計算錯誤:${expression}(${e.message})` | |
| } | |
| } | |
| /** 遞迴下降解析器:+ - 最外層,* / % 次之,數字/括號最內層。 */ | |
| function evaluate(expr) { | |
| let pos = 0 | |
| const peek = () => expr[pos] | |
| const take = () => expr[pos++] | |
| function parseExpr() { | |
| let left = parseTerm() | |
| while (peek() === '+' || peek() === '-') { | |
| const op = take() | |
| const right = parseTerm() | |
| left = op === '+' ? left + right : left - right | |
| } | |
| return left | |
| } | |
| function parseTerm() { | |
| let left = parseFactor() | |
| while (peek() === '*' || peek() === '/' || peek() === '%') { | |
| const op = take() | |
| const right = parseFactor() | |
| left = op === '*' ? left * right : op === '/' ? left / right : left % right | |
| } | |
| return left | |
| } | |
| function parseFactor() { | |
| if (peek() === '-') { take(); return -parseFactor() } | |
| if (peek() === '+') { take(); return parseFactor() } | |
| if (peek() === '(') { | |
| take() | |
| const value = parseExpr() | |
| if (peek() !== ')') throw new Error('括號不配對') | |
| take() | |
| return value | |
| } | |
| const start = pos | |
| while (pos < expr.length && /[\d.]/.test(expr[pos])) pos++ | |
| if (start === pos) throw new Error('預期數字') | |
| const value = Number(expr.slice(start, pos)) | |
| if (Number.isNaN(value)) throw new Error('數字格式錯誤') | |
| return value | |
| } | |
| const result = parseExpr() | |
| if (pos !== expr.length) throw new Error('表達式有多餘字元') | |
| return result | |
| } | |