@vierratale/ai 0.1.0-beta.14 → 0.1.0-beta.16

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/utils/math.js DELETED
@@ -1,142 +0,0 @@
1
- // utils/math.js - Deterministic, safe math evaluator.
2
- // Parses arithmetic ( + - * / ^ % parentheses, decimals, scientific notation )
3
- // WITHOUT using eval(), so it can never execute arbitrary code. Returns null
4
- // when the input is not a clean math expression, so callers fall back to the
5
- // model.
6
-
7
- // Extract a bare arithmetic expression from a line of chat text.
8
- export function extractMathExpression(text) {
9
- const s = String(text || '').trim();
10
- // Strip a leading question/math verb phrase if present.
11
- const cleaned = s.replace(
12
- /^(?:what(?:'s| is| are)|what's|compute|calculate|solve|evaluate|equals?)\b\s*/i,
13
- ''
14
- ).replace(/\??$/, '').trim();
15
- const expr = cleaned.replace(/[?,!.]$/, '').trim();
16
- if (!expr) return null;
17
- // Guard: must be purely numeric + arithmetic operators + parens + spaces.
18
- if (!/^[0-9+\-*\/^%().\s]+$/.test(expr)) return null;
19
- if (!/\d/.test(expr)) return null;
20
- if (/[a-zA-Z]/.test(expr)) return null;
21
- // Must contain at least one operator (otherwise "2" alone isn't a math ask).
22
- if (!/[\+\-\*\/\^%]/.test(expr)) return null;
23
- return expr;
24
- }
25
-
26
- // Tokenize an expression into numbers and operators.
27
- function tokenize(expr) {
28
- const tokens = [];
29
- let i = 0;
30
- const n = expr.length;
31
- while (i < n) {
32
- const c = expr[i];
33
- if (/\s/.test(c)) { i++; continue; }
34
- if ('+-*/^%()'.includes(c)) {
35
- tokens.push(c);
36
- i++;
37
- continue;
38
- }
39
- // read a number (int, decimal, scientific)
40
- const num = expr.slice(i).match(/^(\d+(?:\.\d+)?|\.\d+)([eE][+-]?\d+)?/);
41
- if (!num) return null;
42
- tokens.push(parseFloat(num[0]));
43
- i += num[0].length;
44
- }
45
- return tokens;
46
- }
47
-
48
- // Recursive-descent parser with correct precedence:
49
- // expr := term (('+'|'-') term)*
50
- // term := power (('*'|'/'|'%') power)*
51
- // power := unary ('^' power)? right-associative
52
- // unary := ('+'|'-') unary | factor
53
- // factor := '(' expr ')' | number
54
- class Parser {
55
- constructor(tokens) {
56
- this.tokens = tokens;
57
- this.pos = 0;
58
- }
59
- peek() { return this.tokens[this.pos]; }
60
- next() { return this.tokens[this.pos++]; }
61
- expect(val) {
62
- const t = this.next();
63
- if (t !== val) throw new Error('syntax error');
64
- }
65
- parseExpr() { return this.parseTerm(); }
66
- parseTerm() {
67
- let left = this.parseMul();
68
- while (['+', '-'].includes(this.peek())) {
69
- const op = this.next();
70
- const right = this.parseMul();
71
- left = op === '+' ? left + right : left - right;
72
- }
73
- return left;
74
- }
75
- parseMul() {
76
- let left = this.parsePower();
77
- while (['*', '/', '%'].includes(this.peek())) {
78
- const op = this.next();
79
- const right = this.parsePower();
80
- if (op === '*') left = left * right;
81
- else if (op === '/') left = left / right;
82
- else left = left % right;
83
- }
84
- return left;
85
- }
86
- parsePower() {
87
- let left = this.parseUnary();
88
- if (this.peek() === '^') {
89
- this.next();
90
- const right = this.parsePower();
91
- left = Math.pow(left, right);
92
- }
93
- return left;
94
- }
95
- parseUnary() {
96
- if (this.peek() === '+') { this.next(); return this.parseUnary(); }
97
- if (this.peek() === '-') {
98
- this.next();
99
- return -this.parseUnary();
100
- }
101
- return this.parseFactor();
102
- }
103
- parseFactor() {
104
- const t = this.peek();
105
- if (t === '(') {
106
- this.next();
107
- const v = this.parseExpr();
108
- this.expect(')');
109
- return v;
110
- }
111
- if (typeof t === 'number') {
112
- this.next();
113
- return t;
114
- }
115
- throw new Error('syntax error');
116
- }
117
- }
118
-
119
- export function evaluateMath(expr) {
120
- try {
121
- const tokens = tokenize(expr);
122
- if (!tokens || !tokens.length) return null;
123
- const p = new Parser(tokens);
124
- const result = p.parseExpr();
125
- if (p.pos !== tokens.length) return null; // leftover tokens = not a clean expr
126
- if (!Number.isFinite(result)) return null;
127
- // Round to avoid floating point noise like 0.30000000000000004
128
- const rounded = Math.round((result + Number.EPSILON) * 1e10) / 1e10;
129
- return Number(rounded.toPrecision(12));
130
- } catch {
131
- return null;
132
- }
133
- }
134
-
135
- // Front-door: given a chat line, return a clean math answer or null.
136
- export function trySolveMath(text) {
137
- const expr = extractMathExpression(text);
138
- if (!expr) return null;
139
- const value = evaluateMath(expr);
140
- if (value === null) return null;
141
- return `${expr} = ${value}`;
142
- }