calc-mcp-server 0.1.0
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/.claude/commands/opsx/apply.md +152 -0
- package/.claude/commands/opsx/archive.md +157 -0
- package/.claude/commands/opsx/bulk-archive.md +242 -0
- package/.claude/commands/opsx/continue.md +114 -0
- package/.claude/commands/opsx/explore.md +174 -0
- package/.claude/commands/opsx/ff.md +94 -0
- package/.claude/commands/opsx/new.md +69 -0
- package/.claude/commands/opsx/onboard.md +534 -0
- package/.claude/commands/opsx/sync.md +134 -0
- package/.claude/commands/opsx/verify.md +164 -0
- package/.claude/settings.local.json +8 -0
- package/.claude/skills/npm-publish/SKILL.md +164 -0
- package/.claude/skills/openspec-apply-change/SKILL.md +156 -0
- package/.claude/skills/openspec-archive-change/SKILL.md +161 -0
- package/.claude/skills/openspec-bulk-archive-change/SKILL.md +246 -0
- package/.claude/skills/openspec-continue-change/SKILL.md +118 -0
- package/.claude/skills/openspec-explore/SKILL.md +289 -0
- package/.claude/skills/openspec-ff-change/SKILL.md +101 -0
- package/.claude/skills/openspec-new-change/SKILL.md +74 -0
- package/.claude/skills/openspec-onboard/SKILL.md +538 -0
- package/.claude/skills/openspec-sync-specs/SKILL.md +138 -0
- package/.claude/skills/openspec-verify-change/SKILL.md +168 -0
- package/CLAUDE.md +92 -0
- package/README.md +319 -0
- package/build/engines/decimal.d.ts +10 -0
- package/build/engines/decimal.d.ts.map +1 -0
- package/build/engines/decimal.js +61 -0
- package/build/engines/decimal.js.map +1 -0
- package/build/engines/programmer.d.ts +18 -0
- package/build/engines/programmer.d.ts.map +1 -0
- package/build/engines/programmer.js +103 -0
- package/build/engines/programmer.js.map +1 -0
- package/build/errors/handler.d.ts +10 -0
- package/build/errors/handler.d.ts.map +1 -0
- package/build/errors/handler.js +37 -0
- package/build/errors/handler.js.map +1 -0
- package/build/errors/types.d.ts +25 -0
- package/build/errors/types.d.ts.map +1 -0
- package/build/errors/types.js +2 -0
- package/build/errors/types.js.map +1 -0
- package/build/index.d.ts +3 -0
- package/build/index.d.ts.map +1 -0
- package/build/index.js +16 -0
- package/build/index.js.map +1 -0
- package/build/mcp/server.d.ts +3 -0
- package/build/mcp/server.d.ts.map +1 -0
- package/build/mcp/server.js +270 -0
- package/build/mcp/server.js.map +1 -0
- package/build/mcp/tools/ascii.d.ts +11 -0
- package/build/mcp/tools/ascii.d.ts.map +1 -0
- package/build/mcp/tools/ascii.js +93 -0
- package/build/mcp/tools/ascii.js.map +1 -0
- package/build/mcp/tools/basic.d.ts +6 -0
- package/build/mcp/tools/basic.d.ts.map +1 -0
- package/build/mcp/tools/basic.js +34 -0
- package/build/mcp/tools/basic.js.map +1 -0
- package/build/mcp/tools/conversion.d.ts +8 -0
- package/build/mcp/tools/conversion.d.ts.map +1 -0
- package/build/mcp/tools/conversion.js +81 -0
- package/build/mcp/tools/conversion.js.map +1 -0
- package/build/mcp/tools/programmer.d.ts +6 -0
- package/build/mcp/tools/programmer.d.ts.map +1 -0
- package/build/mcp/tools/programmer.js +29 -0
- package/build/mcp/tools/programmer.js.map +1 -0
- package/build/parser/ast.d.ts +47 -0
- package/build/parser/ast.d.ts.map +1 -0
- package/build/parser/ast.js +2 -0
- package/build/parser/ast.js.map +1 -0
- package/build/parser/lexer.d.ts +24 -0
- package/build/parser/lexer.d.ts.map +1 -0
- package/build/parser/lexer.js +168 -0
- package/build/parser/lexer.js.map +1 -0
- package/build/parser/parser.d.ts +14 -0
- package/build/parser/parser.d.ts.map +1 -0
- package/build/parser/parser.js +115 -0
- package/build/parser/parser.js.map +1 -0
- package/docs/plans/2025-02-24-mcp-calculator-design.md +344 -0
- package/docs/plans/2025-02-24-mcp-calculator-implementation.md +2626 -0
- package/openspec/changes/archive/2026-02-24-simplify-ascii-tools/.openspec.yaml +2 -0
- package/openspec/changes/archive/2026-02-24-simplify-ascii-tools/design.md +46 -0
- package/openspec/changes/archive/2026-02-24-simplify-ascii-tools/proposal.md +21 -0
- package/openspec/changes/archive/2026-02-24-simplify-ascii-tools/specs/ascii-conversion/spec.md +22 -0
- package/openspec/changes/archive/2026-02-24-simplify-ascii-tools/tasks.md +24 -0
- package/openspec/config.yaml +20 -0
- package/openspec/specs/ascii-conversion/spec.md +43 -0
- package/package.json +40 -0
- package/src/engines/decimal.ts +69 -0
- package/src/engines/programmer.ts +112 -0
- package/src/errors/handler.ts +55 -0
- package/src/errors/types.ts +37 -0
- package/src/index.ts +20 -0
- package/src/mcp/server.ts +287 -0
- package/src/mcp/tools/ascii.ts +116 -0
- package/src/mcp/tools/basic.ts +44 -0
- package/src/mcp/tools/conversion.ts +95 -0
- package/src/mcp/tools/programmer.ts +36 -0
- package/src/parser/ast.ts +51 -0
- package/src/parser/lexer.ts +216 -0
- package/src/parser/parser.ts +154 -0
- package/test/integration/ascii.test.ts +450 -0
- package/test/integration/basic-calculate.test.ts +272 -0
- package/test/integration/conversion.test.ts +357 -0
- package/test/integration/programmer-calculate.test.ts +363 -0
- package/test/unit/decimal-engine.test.ts +134 -0
- package/test/unit/error-handler.test.ts +173 -0
- package/test/unit/lexer.test.ts +176 -0
- package/test/unit/parser.test.ts +197 -0
- package/test/unit/programmer-engine.test.ts +234 -0
- package/tsconfig.json +20 -0
- package/vitest.config.ts +13 -0
|
@@ -0,0 +1,363 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { programmerCalculate } from '../../src/mcp/tools/programmer.js';
|
|
3
|
+
|
|
4
|
+
describe('programmerCalculate', () => {
|
|
5
|
+
describe('Basic arithmetic operations', () => {
|
|
6
|
+
it('should add two decimal numbers', () => {
|
|
7
|
+
const result = programmerCalculate({ expression: '10 + 20' });
|
|
8
|
+
|
|
9
|
+
expect(result.success).toBe(true);
|
|
10
|
+
if (result.success) {
|
|
11
|
+
expect(result.result).toBe('30');
|
|
12
|
+
}
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
it('should subtract two decimal numbers', () => {
|
|
16
|
+
const result = programmerCalculate({ expression: '50 - 15' });
|
|
17
|
+
|
|
18
|
+
expect(result.success).toBe(true);
|
|
19
|
+
if (result.success) {
|
|
20
|
+
expect(result.result).toBe('35');
|
|
21
|
+
}
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it('should multiply two numbers', () => {
|
|
25
|
+
const result = programmerCalculate({ expression: '6 * 7' });
|
|
26
|
+
|
|
27
|
+
expect(result.success).toBe(true);
|
|
28
|
+
if (result.success) {
|
|
29
|
+
expect(result.result).toBe('42');
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it('should divide two numbers', () => {
|
|
34
|
+
const result = programmerCalculate({ expression: '100 / 4' });
|
|
35
|
+
|
|
36
|
+
expect(result.success).toBe(true);
|
|
37
|
+
if (result.success) {
|
|
38
|
+
expect(result.result).toBe('25');
|
|
39
|
+
}
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it('should calculate modulo', () => {
|
|
43
|
+
const result = programmerCalculate({ expression: '17 % 5' });
|
|
44
|
+
|
|
45
|
+
expect(result.success).toBe(true);
|
|
46
|
+
if (result.success) {
|
|
47
|
+
expect(result.result).toBe('2');
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
describe('Hexadecimal operations', () => {
|
|
53
|
+
it('should add two hex numbers', () => {
|
|
54
|
+
const result = programmerCalculate({ expression: '0xA + 0x5' });
|
|
55
|
+
|
|
56
|
+
expect(result.success).toBe(true);
|
|
57
|
+
if (result.success) {
|
|
58
|
+
expect(result.result).toBe('15');
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it('should multiply hex numbers', () => {
|
|
63
|
+
const result = programmerCalculate({ expression: '0x10 * 0x10' });
|
|
64
|
+
|
|
65
|
+
expect(result.success).toBe(true);
|
|
66
|
+
if (result.success) {
|
|
67
|
+
expect(result.result).toBe('256');
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it('should handle mixed hex and decimal', () => {
|
|
72
|
+
const result = programmerCalculate({ expression: '0xFF + 10' });
|
|
73
|
+
|
|
74
|
+
expect(result.success).toBe(true);
|
|
75
|
+
if (result.success) {
|
|
76
|
+
expect(result.result).toBe('265');
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it('should subtract hex numbers', () => {
|
|
81
|
+
const result = programmerCalculate({ expression: '0x20 - 0x10' });
|
|
82
|
+
|
|
83
|
+
expect(result.success).toBe(true);
|
|
84
|
+
if (result.success) {
|
|
85
|
+
expect(result.result).toBe('16');
|
|
86
|
+
}
|
|
87
|
+
});
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
describe('Binary operations', () => {
|
|
91
|
+
it('should add two binary numbers', () => {
|
|
92
|
+
const result = programmerCalculate({ expression: '0b1010 + 0b0101' });
|
|
93
|
+
|
|
94
|
+
expect(result.success).toBe(true);
|
|
95
|
+
if (result.success) {
|
|
96
|
+
expect(result.result).toBe('15');
|
|
97
|
+
}
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
it('should multiply binary numbers', () => {
|
|
101
|
+
const result = programmerCalculate({ expression: '0b1100 * 0b10' });
|
|
102
|
+
|
|
103
|
+
expect(result.success).toBe(true);
|
|
104
|
+
if (result.success) {
|
|
105
|
+
expect(result.result).toBe('24');
|
|
106
|
+
}
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
it('should handle mixed binary and decimal', () => {
|
|
110
|
+
const result = programmerCalculate({ expression: '0b1111 + 5' });
|
|
111
|
+
|
|
112
|
+
expect(result.success).toBe(true);
|
|
113
|
+
if (result.success) {
|
|
114
|
+
expect(result.result).toBe('20');
|
|
115
|
+
}
|
|
116
|
+
});
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
describe('Octal operations', () => {
|
|
120
|
+
it('should add two octal numbers', () => {
|
|
121
|
+
const result = programmerCalculate({ expression: '0o10 + 0o7' });
|
|
122
|
+
|
|
123
|
+
expect(result.success).toBe(true);
|
|
124
|
+
if (result.success) {
|
|
125
|
+
expect(result.result).toBe('15');
|
|
126
|
+
}
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
it('should multiply octal numbers', () => {
|
|
130
|
+
const result = programmerCalculate({ expression: '0o10 * 0o10' });
|
|
131
|
+
|
|
132
|
+
expect(result.success).toBe(true);
|
|
133
|
+
if (result.success) {
|
|
134
|
+
expect(result.result).toBe('64');
|
|
135
|
+
}
|
|
136
|
+
});
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
describe('Bitwise operations', () => {
|
|
140
|
+
it('should perform bitwise AND', () => {
|
|
141
|
+
const result = programmerCalculate({ expression: '0b1100 & 0b1010' });
|
|
142
|
+
|
|
143
|
+
expect(result.success).toBe(true);
|
|
144
|
+
if (result.success) {
|
|
145
|
+
expect(result.result).toBe('8');
|
|
146
|
+
}
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
it('should perform bitwise OR', () => {
|
|
150
|
+
const result = programmerCalculate({ expression: '0b1100 | 0b1010' });
|
|
151
|
+
|
|
152
|
+
expect(result.success).toBe(true);
|
|
153
|
+
if (result.success) {
|
|
154
|
+
expect(result.result).toBe('14');
|
|
155
|
+
}
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
it('should perform bitwise XOR', () => {
|
|
159
|
+
const result = programmerCalculate({ expression: '0b1100 ^ 0b1010' });
|
|
160
|
+
|
|
161
|
+
expect(result.success).toBe(true);
|
|
162
|
+
if (result.success) {
|
|
163
|
+
expect(result.result).toBe('6');
|
|
164
|
+
}
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
it('should perform bitwise NOT', () => {
|
|
168
|
+
const result = programmerCalculate({ expression: '~0b1010' });
|
|
169
|
+
|
|
170
|
+
expect(result.success).toBe(true);
|
|
171
|
+
if (result.success) {
|
|
172
|
+
expect(result.result).toBe('-11');
|
|
173
|
+
}
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
it('should perform left shift', () => {
|
|
177
|
+
const result = programmerCalculate({ expression: '0b1010 << 2' });
|
|
178
|
+
|
|
179
|
+
expect(result.success).toBe(true);
|
|
180
|
+
if (result.success) {
|
|
181
|
+
expect(result.result).toBe('40');
|
|
182
|
+
}
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
it('should perform right shift', () => {
|
|
186
|
+
const result = programmerCalculate({ expression: '0b101000 >> 2' });
|
|
187
|
+
|
|
188
|
+
expect(result.success).toBe(true);
|
|
189
|
+
if (result.success) {
|
|
190
|
+
expect(result.result).toBe('10');
|
|
191
|
+
}
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
it('should perform unsigned right shift', () => {
|
|
195
|
+
const result = programmerCalculate({ expression: '-10 >>> 1' });
|
|
196
|
+
|
|
197
|
+
expect(result.success).toBe(true);
|
|
198
|
+
if (result.success) {
|
|
199
|
+
// -10 in binary is ...11110110, unsigned right shift by 1 gives a large positive number
|
|
200
|
+
expect(BigInt(result.result)).toBeGreaterThan(0n);
|
|
201
|
+
}
|
|
202
|
+
});
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
describe('Complex expressions', () => {
|
|
206
|
+
it('should handle mixed operations', () => {
|
|
207
|
+
const result = programmerCalculate({ expression: '(0x10 + 0b10) * 2' });
|
|
208
|
+
|
|
209
|
+
expect(result.success).toBe(true);
|
|
210
|
+
if (result.success) {
|
|
211
|
+
expect(result.result).toBe('36');
|
|
212
|
+
}
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
it('should handle bitwise with arithmetic', () => {
|
|
216
|
+
const result = programmerCalculate({ expression: '(0xFF & 0xF0) + 10' });
|
|
217
|
+
|
|
218
|
+
expect(result.success).toBe(true);
|
|
219
|
+
if (result.success) {
|
|
220
|
+
expect(result.result).toBe('250');
|
|
221
|
+
}
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
it('should respect operator precedence', () => {
|
|
225
|
+
const result = programmerCalculate({ expression: '0b1010 | 0b1100 & 0b1000' });
|
|
226
|
+
|
|
227
|
+
expect(result.success).toBe(true);
|
|
228
|
+
if (result.success) {
|
|
229
|
+
// & has higher precedence than |
|
|
230
|
+
// 0b1010 | (0b1100 & 0b1000) = 0b1010 | 0b1000 = 0b1010 = 10
|
|
231
|
+
expect(result.result).toBe('10');
|
|
232
|
+
}
|
|
233
|
+
});
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
describe('Character literals', () => {
|
|
237
|
+
it('should handle single character literal', () => {
|
|
238
|
+
const result = programmerCalculate({ expression: "'A'" });
|
|
239
|
+
|
|
240
|
+
expect(result.success).toBe(true);
|
|
241
|
+
if (result.success) {
|
|
242
|
+
expect(result.result).toBe('65');
|
|
243
|
+
}
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
it('should add character literals', () => {
|
|
247
|
+
const result = programmerCalculate({ expression: "'A' + 'B'" });
|
|
248
|
+
|
|
249
|
+
expect(result.success).toBe(true);
|
|
250
|
+
if (result.success) {
|
|
251
|
+
expect(result.result).toBe('131');
|
|
252
|
+
}
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
it('should handle character arithmetic', () => {
|
|
256
|
+
const result = programmerCalculate({ expression: "'a' - 'A'" });
|
|
257
|
+
|
|
258
|
+
expect(result.success).toBe(true);
|
|
259
|
+
if (result.success) {
|
|
260
|
+
expect(result.result).toBe('32');
|
|
261
|
+
}
|
|
262
|
+
});
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
describe('Negative numbers', () => {
|
|
266
|
+
it('should handle negative decimal numbers', () => {
|
|
267
|
+
const result = programmerCalculate({ expression: '-10 + 5' });
|
|
268
|
+
|
|
269
|
+
expect(result.success).toBe(true);
|
|
270
|
+
if (result.success) {
|
|
271
|
+
expect(result.result).toBe('-5');
|
|
272
|
+
}
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
it('should handle bitwise NOT of positive', () => {
|
|
276
|
+
const result = programmerCalculate({ expression: '~5' });
|
|
277
|
+
|
|
278
|
+
expect(result.success).toBe(true);
|
|
279
|
+
if (result.success) {
|
|
280
|
+
expect(result.result).toBe('-6');
|
|
281
|
+
}
|
|
282
|
+
});
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
describe('Error handling', () => {
|
|
286
|
+
it('should return error for empty expression', () => {
|
|
287
|
+
const result = programmerCalculate({ expression: '' });
|
|
288
|
+
|
|
289
|
+
expect(result.success).toBe(false);
|
|
290
|
+
if (!result.success) {
|
|
291
|
+
expect(result.error.type).toBe('EmptyInput');
|
|
292
|
+
}
|
|
293
|
+
});
|
|
294
|
+
|
|
295
|
+
it('should return error for division by zero', () => {
|
|
296
|
+
const result = programmerCalculate({ expression: '10 / 0' });
|
|
297
|
+
|
|
298
|
+
expect(result.success).toBe(false);
|
|
299
|
+
if (!result.success) {
|
|
300
|
+
expect(result.error.type).toBe('DivisionByZero');
|
|
301
|
+
}
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
it('should return error for floating point numbers', () => {
|
|
305
|
+
const result = programmerCalculate({ expression: '3.14 * 2' });
|
|
306
|
+
|
|
307
|
+
expect(result.success).toBe(false);
|
|
308
|
+
if (!result.success) {
|
|
309
|
+
expect(result.error.type).toBe('InvalidNumber');
|
|
310
|
+
expect(result.error.message).toContain('Floating point');
|
|
311
|
+
}
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
it('should return error for unmatched parenthesis', () => {
|
|
315
|
+
const result = programmerCalculate({ expression: '(0x10 + 0x20' });
|
|
316
|
+
|
|
317
|
+
expect(result.success).toBe(false);
|
|
318
|
+
if (!result.success) {
|
|
319
|
+
expect(result.error.type).toBe('SyntaxError');
|
|
320
|
+
}
|
|
321
|
+
});
|
|
322
|
+
});
|
|
323
|
+
|
|
324
|
+
describe('Large numbers', () => {
|
|
325
|
+
it('should handle very large hex numbers', () => {
|
|
326
|
+
const result = programmerCalculate({ expression: '0xFFFFFFFF + 1' });
|
|
327
|
+
|
|
328
|
+
expect(result.success).toBe(true);
|
|
329
|
+
if (result.success) {
|
|
330
|
+
expect(result.result).toBe('4294967296');
|
|
331
|
+
}
|
|
332
|
+
});
|
|
333
|
+
|
|
334
|
+
it('should handle bitwise operations on large numbers', () => {
|
|
335
|
+
const result = programmerCalculate({ expression: '0xFFFFFFFF & 0xFFFF' });
|
|
336
|
+
|
|
337
|
+
expect(result.success).toBe(true);
|
|
338
|
+
if (result.success) {
|
|
339
|
+
expect(result.result).toBe('65535');
|
|
340
|
+
}
|
|
341
|
+
});
|
|
342
|
+
});
|
|
343
|
+
|
|
344
|
+
describe('Precedence and grouping', () => {
|
|
345
|
+
it('should handle (5 + 3) * (10 - 2)', () => {
|
|
346
|
+
const result = programmerCalculate({ expression: '(5 + 3) * (10 - 2)' });
|
|
347
|
+
|
|
348
|
+
expect(result.success).toBe(true);
|
|
349
|
+
if (result.success) {
|
|
350
|
+
expect(result.result).toBe('64');
|
|
351
|
+
}
|
|
352
|
+
});
|
|
353
|
+
|
|
354
|
+
it('should handle nested parentheses', () => {
|
|
355
|
+
const result = programmerCalculate({ expression: '((0x10 + 0x10) * 2) - 10' });
|
|
356
|
+
|
|
357
|
+
expect(result.success).toBe(true);
|
|
358
|
+
if (result.success) {
|
|
359
|
+
expect(result.result).toBe('54');
|
|
360
|
+
}
|
|
361
|
+
});
|
|
362
|
+
});
|
|
363
|
+
});
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { DecimalEngine } from '../../src/engines/decimal.js';
|
|
3
|
+
import { Lexer } from '../../src/parser/lexer.js';
|
|
4
|
+
import { Parser } from '../../src/parser/parser.js';
|
|
5
|
+
|
|
6
|
+
describe('DecimalEngine', () => {
|
|
7
|
+
const lexer = new Lexer();
|
|
8
|
+
const parser = new Parser();
|
|
9
|
+
const engine = new DecimalEngine();
|
|
10
|
+
|
|
11
|
+
describe('Basic operations', () => {
|
|
12
|
+
it('should add two numbers', () => {
|
|
13
|
+
const tokens = lexer.tokenize('2 + 3');
|
|
14
|
+
const ast = parser.parse(tokens);
|
|
15
|
+
const result = engine.evaluate(ast);
|
|
16
|
+
|
|
17
|
+
expect(result.toString()).toBe('5');
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it('should subtract two numbers', () => {
|
|
21
|
+
const tokens = lexer.tokenize('5 - 3');
|
|
22
|
+
const ast = parser.parse(tokens);
|
|
23
|
+
const result = engine.evaluate(ast);
|
|
24
|
+
|
|
25
|
+
expect(result.toString()).toBe('2');
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it('should multiply two numbers', () => {
|
|
29
|
+
const tokens = lexer.tokenize('4 * 3');
|
|
30
|
+
const ast = parser.parse(tokens);
|
|
31
|
+
const result = engine.evaluate(ast);
|
|
32
|
+
|
|
33
|
+
expect(result.toString()).toBe('12');
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it('should divide two numbers', () => {
|
|
37
|
+
const tokens = lexer.tokenize('10 / 2');
|
|
38
|
+
const ast = parser.parse(tokens);
|
|
39
|
+
const result = engine.evaluate(ast);
|
|
40
|
+
|
|
41
|
+
expect(result.toString()).toBe('5');
|
|
42
|
+
});
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
describe('Decimal precision', () => {
|
|
46
|
+
it('should handle 0.1 + 0.2 correctly', () => {
|
|
47
|
+
const tokens = lexer.tokenize('0.1 + 0.2');
|
|
48
|
+
const ast = parser.parse(tokens);
|
|
49
|
+
const result = engine.evaluate(ast);
|
|
50
|
+
|
|
51
|
+
expect(result.toString()).toBe('0.3');
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it('should handle floating point division', () => {
|
|
55
|
+
const tokens = lexer.tokenize('1 / 3');
|
|
56
|
+
const ast = parser.parse(tokens);
|
|
57
|
+
const result = engine.evaluate(ast);
|
|
58
|
+
|
|
59
|
+
expect(parseFloat(result.toString())).toBeCloseTo(0.333, 2);
|
|
60
|
+
});
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
describe('Operator precedence', () => {
|
|
64
|
+
it('should respect precedence', () => {
|
|
65
|
+
const tokens = lexer.tokenize('2 + 3 * 4');
|
|
66
|
+
const ast = parser.parse(tokens);
|
|
67
|
+
const result = engine.evaluate(ast);
|
|
68
|
+
|
|
69
|
+
expect(result.toString()).toBe('14');
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it('should respect parentheses', () => {
|
|
73
|
+
const tokens = lexer.tokenize('(2 + 3) * 4');
|
|
74
|
+
const ast = parser.parse(tokens);
|
|
75
|
+
const result = engine.evaluate(ast);
|
|
76
|
+
|
|
77
|
+
expect(result.toString()).toBe('20');
|
|
78
|
+
});
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
describe('Error handling', () => {
|
|
82
|
+
it('should throw on division by zero', () => {
|
|
83
|
+
const tokens = lexer.tokenize('1 / 0');
|
|
84
|
+
const ast = parser.parse(tokens);
|
|
85
|
+
|
|
86
|
+
expect(() => engine.evaluate(ast)).toThrow('DivisionByZero');
|
|
87
|
+
});
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
describe('Complex expressions', () => {
|
|
91
|
+
it('should handle multiple operations', () => {
|
|
92
|
+
const tokens = lexer.tokenize('2.5 * 3 + (10 - 4) / 2');
|
|
93
|
+
const ast = parser.parse(tokens);
|
|
94
|
+
const result = engine.evaluate(ast);
|
|
95
|
+
|
|
96
|
+
expect(result.toString()).toBe('10.5');
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
it('should handle modulo', () => {
|
|
100
|
+
const tokens = lexer.tokenize('10 % 3');
|
|
101
|
+
const ast = parser.parse(tokens);
|
|
102
|
+
const result = engine.evaluate(ast);
|
|
103
|
+
|
|
104
|
+
expect(result.toString()).toBe('1');
|
|
105
|
+
});
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
describe('Negative number operations', () => {
|
|
109
|
+
it('should handle -5 + 3', () => {
|
|
110
|
+
const tokens = lexer.tokenize('-5 + 3');
|
|
111
|
+
const ast = parser.parse(tokens);
|
|
112
|
+
const result = engine.evaluate(ast);
|
|
113
|
+
|
|
114
|
+
expect(result.toString()).toBe('-2');
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
it('should handle -10 * -2', () => {
|
|
118
|
+
const tokens = lexer.tokenize('-10 * -2');
|
|
119
|
+
const ast = parser.parse(tokens);
|
|
120
|
+
const result = engine.evaluate(ast);
|
|
121
|
+
|
|
122
|
+
expect(result.toString()).toBe('20');
|
|
123
|
+
});
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
describe('Modulo edge cases', () => {
|
|
127
|
+
it('should throw on modulo by zero', () => {
|
|
128
|
+
const tokens = lexer.tokenize('5 % 0');
|
|
129
|
+
const ast = parser.parse(tokens);
|
|
130
|
+
|
|
131
|
+
expect(() => engine.evaluate(ast)).toThrow('DivisionByZero');
|
|
132
|
+
});
|
|
133
|
+
});
|
|
134
|
+
});
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { ErrorHandler, ErrorType } from '../../src/errors/handler.js';
|
|
3
|
+
|
|
4
|
+
describe('ErrorHandler', () => {
|
|
5
|
+
describe('syntaxError', () => {
|
|
6
|
+
it('should create syntax error with position', () => {
|
|
7
|
+
const handler = new ErrorHandler();
|
|
8
|
+
const error = handler.syntaxError('Unexpected token', 5, 8);
|
|
9
|
+
|
|
10
|
+
expect(error.success).toBe(false);
|
|
11
|
+
expect(error.error.type).toBe('SyntaxError');
|
|
12
|
+
expect(error.error.message).toBe('Unexpected token');
|
|
13
|
+
expect(error.error.position).toEqual({ start: 5, end: 8 });
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
it('should include suggestion for syntax errors', () => {
|
|
17
|
+
const handler = new ErrorHandler();
|
|
18
|
+
const error = handler.syntaxError('Unexpected token', 5, 8);
|
|
19
|
+
|
|
20
|
+
expect(error.error.suggestion).toBeDefined();
|
|
21
|
+
expect(typeof error.error.suggestion).toBe('string');
|
|
22
|
+
expect(error.error.suggestion).toContain('syntax');
|
|
23
|
+
});
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
describe('runtimeError', () => {
|
|
27
|
+
it('should create runtime error', () => {
|
|
28
|
+
const handler = new ErrorHandler();
|
|
29
|
+
const error = handler.runtimeError('DivisionByZero', 'Cannot divide by zero');
|
|
30
|
+
|
|
31
|
+
expect(error.success).toBe(false);
|
|
32
|
+
expect(error.error.type).toBe('DivisionByZero');
|
|
33
|
+
expect(error.error.message).toBe('Cannot divide by zero');
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it('should include suggestion for runtime errors', () => {
|
|
37
|
+
const handler = new ErrorHandler();
|
|
38
|
+
const error = handler.runtimeError('DivisionByZero', 'Cannot divide by zero');
|
|
39
|
+
|
|
40
|
+
expect(error.error.suggestion).toBeDefined();
|
|
41
|
+
expect(typeof error.error.suggestion).toBe('string');
|
|
42
|
+
expect(error.error.suggestion).toContain('denominators');
|
|
43
|
+
});
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
describe('createError', () => {
|
|
47
|
+
it('should create error with position', () => {
|
|
48
|
+
const handler = new ErrorHandler();
|
|
49
|
+
const error = handler.createError('InvalidOperator', 'Unsupported operator', { start: 10, end: 12 });
|
|
50
|
+
|
|
51
|
+
expect(error.success).toBe(false);
|
|
52
|
+
expect(error.error.type).toBe('InvalidOperator');
|
|
53
|
+
expect(error.error.message).toBe('Unsupported operator');
|
|
54
|
+
expect(error.error.position).toEqual({ start: 10, end: 12 });
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it('should create error without position', () => {
|
|
58
|
+
const handler = new ErrorHandler();
|
|
59
|
+
const error = handler.createError('EmptyInput', 'No expression provided');
|
|
60
|
+
|
|
61
|
+
expect(error.success).toBe(false);
|
|
62
|
+
expect(error.error.type).toBe('EmptyInput');
|
|
63
|
+
expect(error.error.message).toBe('No expression provided');
|
|
64
|
+
expect(error.error.position).toBeUndefined();
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it('should include suggestion for error type', () => {
|
|
68
|
+
const handler = new ErrorHandler();
|
|
69
|
+
const error = handler.createError('OverflowError', 'Number too large');
|
|
70
|
+
|
|
71
|
+
expect(error.error.suggestion).toBeDefined();
|
|
72
|
+
expect(error.error.suggestion).toContain('too large');
|
|
73
|
+
});
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
describe('success', () => {
|
|
77
|
+
it('should create success response with result only', () => {
|
|
78
|
+
const handler = new ErrorHandler();
|
|
79
|
+
const response = handler.success('42');
|
|
80
|
+
|
|
81
|
+
expect(response.success).toBe(true);
|
|
82
|
+
expect(response.result).toBe('42');
|
|
83
|
+
expect(response.details).toBeUndefined();
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it('should create success response with details', () => {
|
|
87
|
+
const handler = new ErrorHandler();
|
|
88
|
+
const response = handler.success('42', {
|
|
89
|
+
expression: '2 * 21',
|
|
90
|
+
steps: ['2 * 21 = 42'],
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
expect(response.success).toBe(true);
|
|
94
|
+
expect(response.result).toBe('42');
|
|
95
|
+
expect(response.details).toEqual({
|
|
96
|
+
expression: '2 * 21',
|
|
97
|
+
steps: ['2 * 21 = 42'],
|
|
98
|
+
});
|
|
99
|
+
});
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
describe('Error suggestions completeness', () => {
|
|
103
|
+
const errorTypes: ErrorType[] = [
|
|
104
|
+
'SyntaxError',
|
|
105
|
+
'DivisionByZero',
|
|
106
|
+
'InvalidNumber',
|
|
107
|
+
'OverflowError',
|
|
108
|
+
'InvalidBase',
|
|
109
|
+
'UnmatchedParen',
|
|
110
|
+
'InvalidOperator',
|
|
111
|
+
'EmptyInput',
|
|
112
|
+
];
|
|
113
|
+
|
|
114
|
+
it('should have suggestions for all 8 error types', () => {
|
|
115
|
+
const handler = new ErrorHandler();
|
|
116
|
+
|
|
117
|
+
errorTypes.forEach((errorType) => {
|
|
118
|
+
const error = handler.createError(errorType, 'Test error message');
|
|
119
|
+
expect(error.error.suggestion).toBeDefined();
|
|
120
|
+
expect(typeof error.error.suggestion).toBe('string');
|
|
121
|
+
expect(error.error.suggestion.length).toBeGreaterThan(0);
|
|
122
|
+
});
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
it('should provide specific suggestion for SyntaxError', () => {
|
|
126
|
+
const handler = new ErrorHandler();
|
|
127
|
+
const error = handler.createError('SyntaxError', 'Test');
|
|
128
|
+
expect(error.error.suggestion).toContain('syntax');
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
it('should provide specific suggestion for DivisionByZero', () => {
|
|
132
|
+
const handler = new ErrorHandler();
|
|
133
|
+
const error = handler.createError('DivisionByZero', 'Test');
|
|
134
|
+
expect(error.error.suggestion).toContain('denominators');
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
it('should provide specific suggestion for InvalidNumber', () => {
|
|
138
|
+
const handler = new ErrorHandler();
|
|
139
|
+
const error = handler.createError('InvalidNumber', 'Test');
|
|
140
|
+
expect(error.error.suggestion).toContain('format');
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
it('should provide specific suggestion for OverflowError', () => {
|
|
144
|
+
const handler = new ErrorHandler();
|
|
145
|
+
const error = handler.createError('OverflowError', 'Test');
|
|
146
|
+
expect(error.error.suggestion).toContain('too large');
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
it('should provide specific suggestion for InvalidBase', () => {
|
|
150
|
+
const handler = new ErrorHandler();
|
|
151
|
+
const error = handler.createError('InvalidBase', 'Test');
|
|
152
|
+
expect(error.error.suggestion).toContain('2, 8, 10, and 16');
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
it('should provide specific suggestion for UnmatchedParen', () => {
|
|
156
|
+
const handler = new ErrorHandler();
|
|
157
|
+
const error = handler.createError('UnmatchedParen', 'Test');
|
|
158
|
+
expect(error.error.suggestion).toContain('parenthesis');
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
it('should provide specific suggestion for InvalidOperator', () => {
|
|
162
|
+
const handler = new ErrorHandler();
|
|
163
|
+
const error = handler.createError('InvalidOperator', 'Test');
|
|
164
|
+
expect(error.error.suggestion).toContain('operator');
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
it('should provide specific suggestion for EmptyInput', () => {
|
|
168
|
+
const handler = new ErrorHandler();
|
|
169
|
+
const error = handler.createError('EmptyInput', 'Test');
|
|
170
|
+
expect(error.error.suggestion).toContain('empty');
|
|
171
|
+
});
|
|
172
|
+
});
|
|
173
|
+
});
|