-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathsafeExpression.ts
More file actions
82 lines (75 loc) · 1.89 KB
/
safeExpression.ts
File metadata and controls
82 lines (75 loc) · 1.89 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
/**
* Evaluates a numeric expression with + - * / parentheses only.
* No eval/Function — safe for untrusted-looking editor input.
*/
export function evaluateSafeNumericExpression(raw: string): number {
const s = raw.trim().replace(/;$/, "").trim();
if (!s) throw new Error("Empty expression");
let i = 0;
const peek = () => s[i];
const eat = (c?: string) => {
while (peek() === " ") i++;
if (c !== undefined && peek() !== c) {
throw new Error(`Expected '${c}' at position ${i}`);
}
if (c !== undefined) i++;
};
const readNumber = (): number => {
eat();
const start = i;
while (/\d/.test(peek() ?? "")) i++;
if (peek() === ".") {
i++;
while (/\d/.test(peek() ?? "")) i++;
}
const slice = s.slice(start, i).trim();
if (!slice || slice === "-") throw new Error("Invalid number");
const n = Number(slice);
if (!Number.isFinite(n)) throw new Error("Invalid number");
return n;
};
const parseFactor = (): number => {
eat();
if (peek() === "(") {
eat("(");
const v = parseExpr();
eat(")");
return v;
}
if (peek() === "-") {
i++;
return -parseFactor();
}
return readNumber();
};
const parseTerm = (): number => {
let v = parseFactor();
eat();
while (peek() === "*" || peek() === "/") {
const op = peek()!;
i++;
const rhs = parseFactor();
v = op === "*" ? v * rhs : v / rhs;
eat();
}
return v;
};
const parseExpr = (): number => {
let v = parseTerm();
eat();
while (peek() === "+" || peek() === "-") {
const op = peek()!;
i++;
const rhs = parseTerm();
v = op === "+" ? v + rhs : v - rhs;
eat();
}
return v;
};
const result = parseExpr();
eat();
if (i < s.length) {
throw new Error(`Unexpected character '${peek()}' at position ${i}`);
}
return result;
}