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.
Files changed (110) hide show
  1. package/.claude/commands/opsx/apply.md +152 -0
  2. package/.claude/commands/opsx/archive.md +157 -0
  3. package/.claude/commands/opsx/bulk-archive.md +242 -0
  4. package/.claude/commands/opsx/continue.md +114 -0
  5. package/.claude/commands/opsx/explore.md +174 -0
  6. package/.claude/commands/opsx/ff.md +94 -0
  7. package/.claude/commands/opsx/new.md +69 -0
  8. package/.claude/commands/opsx/onboard.md +534 -0
  9. package/.claude/commands/opsx/sync.md +134 -0
  10. package/.claude/commands/opsx/verify.md +164 -0
  11. package/.claude/settings.local.json +8 -0
  12. package/.claude/skills/npm-publish/SKILL.md +164 -0
  13. package/.claude/skills/openspec-apply-change/SKILL.md +156 -0
  14. package/.claude/skills/openspec-archive-change/SKILL.md +161 -0
  15. package/.claude/skills/openspec-bulk-archive-change/SKILL.md +246 -0
  16. package/.claude/skills/openspec-continue-change/SKILL.md +118 -0
  17. package/.claude/skills/openspec-explore/SKILL.md +289 -0
  18. package/.claude/skills/openspec-ff-change/SKILL.md +101 -0
  19. package/.claude/skills/openspec-new-change/SKILL.md +74 -0
  20. package/.claude/skills/openspec-onboard/SKILL.md +538 -0
  21. package/.claude/skills/openspec-sync-specs/SKILL.md +138 -0
  22. package/.claude/skills/openspec-verify-change/SKILL.md +168 -0
  23. package/CLAUDE.md +92 -0
  24. package/README.md +319 -0
  25. package/build/engines/decimal.d.ts +10 -0
  26. package/build/engines/decimal.d.ts.map +1 -0
  27. package/build/engines/decimal.js +61 -0
  28. package/build/engines/decimal.js.map +1 -0
  29. package/build/engines/programmer.d.ts +18 -0
  30. package/build/engines/programmer.d.ts.map +1 -0
  31. package/build/engines/programmer.js +103 -0
  32. package/build/engines/programmer.js.map +1 -0
  33. package/build/errors/handler.d.ts +10 -0
  34. package/build/errors/handler.d.ts.map +1 -0
  35. package/build/errors/handler.js +37 -0
  36. package/build/errors/handler.js.map +1 -0
  37. package/build/errors/types.d.ts +25 -0
  38. package/build/errors/types.d.ts.map +1 -0
  39. package/build/errors/types.js +2 -0
  40. package/build/errors/types.js.map +1 -0
  41. package/build/index.d.ts +3 -0
  42. package/build/index.d.ts.map +1 -0
  43. package/build/index.js +16 -0
  44. package/build/index.js.map +1 -0
  45. package/build/mcp/server.d.ts +3 -0
  46. package/build/mcp/server.d.ts.map +1 -0
  47. package/build/mcp/server.js +270 -0
  48. package/build/mcp/server.js.map +1 -0
  49. package/build/mcp/tools/ascii.d.ts +11 -0
  50. package/build/mcp/tools/ascii.d.ts.map +1 -0
  51. package/build/mcp/tools/ascii.js +93 -0
  52. package/build/mcp/tools/ascii.js.map +1 -0
  53. package/build/mcp/tools/basic.d.ts +6 -0
  54. package/build/mcp/tools/basic.d.ts.map +1 -0
  55. package/build/mcp/tools/basic.js +34 -0
  56. package/build/mcp/tools/basic.js.map +1 -0
  57. package/build/mcp/tools/conversion.d.ts +8 -0
  58. package/build/mcp/tools/conversion.d.ts.map +1 -0
  59. package/build/mcp/tools/conversion.js +81 -0
  60. package/build/mcp/tools/conversion.js.map +1 -0
  61. package/build/mcp/tools/programmer.d.ts +6 -0
  62. package/build/mcp/tools/programmer.d.ts.map +1 -0
  63. package/build/mcp/tools/programmer.js +29 -0
  64. package/build/mcp/tools/programmer.js.map +1 -0
  65. package/build/parser/ast.d.ts +47 -0
  66. package/build/parser/ast.d.ts.map +1 -0
  67. package/build/parser/ast.js +2 -0
  68. package/build/parser/ast.js.map +1 -0
  69. package/build/parser/lexer.d.ts +24 -0
  70. package/build/parser/lexer.d.ts.map +1 -0
  71. package/build/parser/lexer.js +168 -0
  72. package/build/parser/lexer.js.map +1 -0
  73. package/build/parser/parser.d.ts +14 -0
  74. package/build/parser/parser.d.ts.map +1 -0
  75. package/build/parser/parser.js +115 -0
  76. package/build/parser/parser.js.map +1 -0
  77. package/docs/plans/2025-02-24-mcp-calculator-design.md +344 -0
  78. package/docs/plans/2025-02-24-mcp-calculator-implementation.md +2626 -0
  79. package/openspec/changes/archive/2026-02-24-simplify-ascii-tools/.openspec.yaml +2 -0
  80. package/openspec/changes/archive/2026-02-24-simplify-ascii-tools/design.md +46 -0
  81. package/openspec/changes/archive/2026-02-24-simplify-ascii-tools/proposal.md +21 -0
  82. package/openspec/changes/archive/2026-02-24-simplify-ascii-tools/specs/ascii-conversion/spec.md +22 -0
  83. package/openspec/changes/archive/2026-02-24-simplify-ascii-tools/tasks.md +24 -0
  84. package/openspec/config.yaml +20 -0
  85. package/openspec/specs/ascii-conversion/spec.md +43 -0
  86. package/package.json +40 -0
  87. package/src/engines/decimal.ts +69 -0
  88. package/src/engines/programmer.ts +112 -0
  89. package/src/errors/handler.ts +55 -0
  90. package/src/errors/types.ts +37 -0
  91. package/src/index.ts +20 -0
  92. package/src/mcp/server.ts +287 -0
  93. package/src/mcp/tools/ascii.ts +116 -0
  94. package/src/mcp/tools/basic.ts +44 -0
  95. package/src/mcp/tools/conversion.ts +95 -0
  96. package/src/mcp/tools/programmer.ts +36 -0
  97. package/src/parser/ast.ts +51 -0
  98. package/src/parser/lexer.ts +216 -0
  99. package/src/parser/parser.ts +154 -0
  100. package/test/integration/ascii.test.ts +450 -0
  101. package/test/integration/basic-calculate.test.ts +272 -0
  102. package/test/integration/conversion.test.ts +357 -0
  103. package/test/integration/programmer-calculate.test.ts +363 -0
  104. package/test/unit/decimal-engine.test.ts +134 -0
  105. package/test/unit/error-handler.test.ts +173 -0
  106. package/test/unit/lexer.test.ts +176 -0
  107. package/test/unit/parser.test.ts +197 -0
  108. package/test/unit/programmer-engine.test.ts +234 -0
  109. package/tsconfig.json +20 -0
  110. package/vitest.config.ts +13 -0
@@ -0,0 +1,176 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { Lexer } from '../../src/parser/lexer.js';
3
+ import { TokenType } from '../../src/parser/ast.js';
4
+
5
+ describe('Lexer', () => {
6
+ describe('Number literals', () => {
7
+ it('should tokenize decimal integers', () => {
8
+ const lexer = new Lexer();
9
+ const tokens = lexer.tokenize('42');
10
+
11
+ expect(tokens).toHaveLength(2); // NUMBER + EOF
12
+ expect(tokens[0].type).toBe('NUMBER');
13
+ expect(tokens[0].value).toBe('42');
14
+ });
15
+
16
+ it('should tokenize decimal floats', () => {
17
+ const lexer = new Lexer();
18
+ const tokens = lexer.tokenize('3.14');
19
+
20
+ expect(tokens[0].type).toBe('NUMBER');
21
+ expect(tokens[0].value).toBe('3.14');
22
+ });
23
+
24
+ it('should tokenize hex literals', () => {
25
+ const lexer = new Lexer();
26
+ const tokens = lexer.tokenize('0xFF');
27
+
28
+ expect(tokens[0].type).toBe('HEX');
29
+ expect(tokens[0].value).toBe('0xFF');
30
+ });
31
+
32
+ it('should tokenize binary literals', () => {
33
+ const lexer = new Lexer();
34
+ const tokens = lexer.tokenize('0b1011');
35
+
36
+ expect(tokens[0].type).toBe('BINARY');
37
+ expect(tokens[0].value).toBe('0b1011');
38
+ });
39
+
40
+ it('should tokenize octal literals', () => {
41
+ const lexer = new Lexer();
42
+ const tokens = lexer.tokenize('0o77');
43
+
44
+ expect(tokens[0].type).toBe('OCTAL');
45
+ expect(tokens[0].value).toBe('0o77');
46
+ });
47
+ });
48
+
49
+ describe('Operators', () => {
50
+ it('should tokenize single character operators', () => {
51
+ const lexer = new Lexer();
52
+ const tokens = lexer.tokenize('1+2-3*4/5%6&7|8^9~0');
53
+
54
+ expect(tokens[0].type).toBe('NUMBER');
55
+ expect(tokens[1].type).toBe('OPERATOR');
56
+ expect(tokens[1].value).toBe('+');
57
+ expect(tokens[3].type).toBe('OPERATOR');
58
+ expect(tokens[3].value).toBe('-');
59
+ expect(tokens[5].type).toBe('OPERATOR');
60
+ expect(tokens[5].value).toBe('*');
61
+ });
62
+
63
+ it('should tokenize shift operators', () => {
64
+ const lexer = new Lexer();
65
+ const tokens = lexer.tokenize('1 << 2 >> 3');
66
+
67
+ expect(tokens[1].type).toBe('OPERATOR');
68
+ expect(tokens[1].value).toBe('<<');
69
+ expect(tokens[3].type).toBe('OPERATOR');
70
+ expect(tokens[3].value).toBe('>>');
71
+ });
72
+
73
+ it('should tokenize unsigned right shift operator', () => {
74
+ const lexer = new Lexer();
75
+ const tokens = lexer.tokenize('1 >>> 2');
76
+
77
+ expect(tokens[1].type).toBe('OPERATOR');
78
+ expect(tokens[1].value).toBe('>>>');
79
+ });
80
+ });
81
+
82
+ describe('Parentheses', () => {
83
+ it('should tokenize parentheses', () => {
84
+ const lexer = new Lexer();
85
+ const tokens = lexer.tokenize('()');
86
+
87
+ expect(tokens[0].type).toBe('LPAREN');
88
+ expect(tokens[0].value).toBe('(');
89
+ expect(tokens[1].type).toBe('RPAREN');
90
+ expect(tokens[1].value).toBe(')');
91
+ });
92
+
93
+ it('should tokenize nested expressions', () => {
94
+ const lexer = new Lexer();
95
+ const tokens = lexer.tokenize('((1+2)*3)');
96
+
97
+ expect(tokens[0].type).toBe('LPAREN');
98
+ expect(tokens[1].type).toBe('LPAREN');
99
+ expect(tokens[2].type).toBe('NUMBER');
100
+ expect(tokens[3].type).toBe('OPERATOR');
101
+ expect(tokens[4].type).toBe('NUMBER');
102
+ expect(tokens[5].type).toBe('RPAREN');
103
+ expect(tokens[6].type).toBe('OPERATOR');
104
+ expect(tokens[7].type).toBe('NUMBER');
105
+ expect(tokens[8].type).toBe('RPAREN');
106
+ });
107
+ });
108
+
109
+ describe('Character literals', () => {
110
+ it('should tokenize simple char literals', () => {
111
+ const lexer = new Lexer();
112
+ const tokens = lexer.tokenize("'A'");
113
+
114
+ expect(tokens[0].type).toBe('CHAR_LITERAL');
115
+ expect(tokens[0].value).toBe("'A'");
116
+ });
117
+
118
+ it('should tokenize escaped char literals', () => {
119
+ const lexer = new Lexer();
120
+ const tokens = lexer.tokenize("'\\n'");
121
+
122
+ expect(tokens[0].type).toBe('CHAR_LITERAL');
123
+ expect(tokens[0].value).toBe("'\\n'");
124
+ });
125
+ });
126
+
127
+ describe('Position tracking', () => {
128
+ it('should track token positions correctly', () => {
129
+ const lexer = new Lexer();
130
+ const tokens = lexer.tokenize('42 + 10');
131
+
132
+ expect(tokens[0].position).toEqual({ start: 0, end: 2 });
133
+ expect(tokens[1].position).toEqual({ start: 3, end: 4 });
134
+ expect(tokens[2].position).toEqual({ start: 5, end: 7 });
135
+ });
136
+
137
+ it('should track EOF position', () => {
138
+ const lexer = new Lexer();
139
+ const tokens = lexer.tokenize('42');
140
+
141
+ const eofToken = tokens[tokens.length - 1];
142
+ expect(eofToken.type).toBe('EOF');
143
+ expect(eofToken.position).toEqual({ start: 2, end: 2 });
144
+ });
145
+ });
146
+
147
+ describe('Whitespace handling', () => {
148
+ it('should ignore whitespace', () => {
149
+ const lexer = new Lexer();
150
+ const tokens = lexer.tokenize('1 +\t\n2');
151
+
152
+ expect(tokens).toHaveLength(4); // NUMBER, OPERATOR, NUMBER, EOF
153
+ expect(tokens[0].type).toBe('NUMBER');
154
+ expect(tokens[1].type).toBe('OPERATOR');
155
+ expect(tokens[2].type).toBe('NUMBER');
156
+ });
157
+ });
158
+
159
+ describe('Complex expressions', () => {
160
+ it('should tokenize complex arithmetic expressions', () => {
161
+ const lexer = new Lexer();
162
+ const tokens = lexer.tokenize('(1 + 2) * 3 >> 0b10');
163
+
164
+ expect(tokens[0].type).toBe('LPAREN');
165
+ expect(tokens[1].type).toBe('NUMBER');
166
+ expect(tokens[2].type).toBe('OPERATOR');
167
+ expect(tokens[3].type).toBe('NUMBER');
168
+ expect(tokens[4].type).toBe('RPAREN');
169
+ expect(tokens[5].type).toBe('OPERATOR');
170
+ expect(tokens[5].value).toBe('*');
171
+ expect(tokens[7].type).toBe('OPERATOR');
172
+ expect(tokens[7].value).toBe('>>');
173
+ expect(tokens[8].type).toBe('BINARY');
174
+ });
175
+ });
176
+ });
@@ -0,0 +1,197 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { Parser } from '../../src/parser/parser.js';
3
+ import { Lexer } from '../../src/parser/lexer.js';
4
+
5
+ describe('Parser', () => {
6
+ describe('Operator precedence', () => {
7
+ it('should respect multiplication over addition', () => {
8
+ const lexer = new Lexer();
9
+ const parser = new Parser();
10
+ const tokens = lexer.tokenize('2 + 3 * 4');
11
+ const ast = parser.parse(tokens);
12
+
13
+ expect(ast.type).toBe('BinaryOp');
14
+ expect(ast.operator).toBe('+');
15
+ expect((ast as any).left.type).toBe('Literal');
16
+ expect((ast as any).left.value).toBe('2');
17
+ expect((ast as any).right.type).toBe('BinaryOp');
18
+ expect((ast as any).right.operator).toBe('*');
19
+ });
20
+
21
+ it('should respect parentheses', () => {
22
+ const lexer = new Lexer();
23
+ const parser = new Parser();
24
+ const tokens = lexer.tokenize('(2 + 3) * 4');
25
+ const ast = parser.parse(tokens);
26
+
27
+ expect(ast.type).toBe('BinaryOp');
28
+ expect(ast.operator).toBe('*');
29
+ expect((ast as any).left.type).toBe('Group');
30
+ expect((ast as any).right.type).toBe('Literal');
31
+ });
32
+ });
33
+
34
+ describe('Unary operators', () => {
35
+ it('should parse bitwise NOT', () => {
36
+ const lexer = new Lexer();
37
+ const parser = new Parser();
38
+ const tokens = lexer.tokenize('~0xFF');
39
+ const ast = parser.parse(tokens);
40
+
41
+ expect(ast.type).toBe('UnaryOp');
42
+ expect((ast as any).operator).toBe('~');
43
+ expect((ast as any).operand.type).toBe('Literal');
44
+ });
45
+
46
+ it('should parse negation', () => {
47
+ const lexer = new Lexer();
48
+ const parser = new Parser();
49
+ const tokens = lexer.tokenize('-42');
50
+ const ast = parser.parse(tokens);
51
+
52
+ expect(ast.type).toBe('UnaryOp');
53
+ expect((ast as any).operator).toBe('-');
54
+ });
55
+ });
56
+
57
+ describe('Bitwise operators', () => {
58
+ it('should parse AND', () => {
59
+ const lexer = new Lexer();
60
+ const parser = new Parser();
61
+ const tokens = lexer.tokenize('0xFF & 0x0F');
62
+ const ast = parser.parse(tokens);
63
+
64
+ expect(ast.type).toBe('BinaryOp');
65
+ expect((ast as any).operator).toBe('&');
66
+ });
67
+
68
+ it('should parse OR', () => {
69
+ const lexer = new Lexer();
70
+ const parser = new Parser();
71
+ const tokens = lexer.tokenize('0xFF | 0x0F');
72
+ const ast = parser.parse(tokens);
73
+
74
+ expect(ast.type).toBe('BinaryOp');
75
+ expect((ast as any).operator).toBe('|');
76
+ });
77
+
78
+ it('should parse XOR', () => {
79
+ const lexer = new Lexer();
80
+ const parser = new Parser();
81
+ const tokens = lexer.tokenize('0xFF ^ 0x0F');
82
+ const ast = parser.parse(tokens);
83
+
84
+ expect(ast.type).toBe('BinaryOp');
85
+ expect((ast as any).operator).toBe('^');
86
+ });
87
+ });
88
+
89
+ describe('Shift operators', () => {
90
+ it('should parse left shift', () => {
91
+ const lexer = new Lexer();
92
+ const parser = new Parser();
93
+ const tokens = lexer.tokenize('1 << 4');
94
+ const ast = parser.parse(tokens);
95
+
96
+ expect(ast.type).toBe('BinaryOp');
97
+ expect((ast as any).operator).toBe('<<');
98
+ });
99
+
100
+ it('should parse right shift', () => {
101
+ const lexer = new Lexer();
102
+ const parser = new Parser();
103
+ const tokens = lexer.tokenize('16 >> 2');
104
+ const ast = parser.parse(tokens);
105
+
106
+ expect(ast.type).toBe('BinaryOp');
107
+ expect((ast as any).operator).toBe('>>');
108
+ });
109
+ });
110
+
111
+ describe('Error paths', () => {
112
+ it('should throw error for empty input', () => {
113
+ const lexer = new Lexer();
114
+ const parser = new Parser();
115
+ const tokens = lexer.tokenize('');
116
+
117
+ expect(() => parser.parse(tokens)).toThrow('Expression cannot be empty');
118
+ });
119
+
120
+ it('should throw error for unmatched opening parenthesis', () => {
121
+ const lexer = new Lexer();
122
+ const parser = new Parser();
123
+ const tokens = lexer.tokenize('(1 + 2');
124
+
125
+ expect(() => parser.parse(tokens)).toThrow();
126
+ });
127
+
128
+ it('should throw error for unmatched closing parenthesis', () => {
129
+ const lexer = new Lexer();
130
+ const parser = new Parser();
131
+ const tokens = lexer.tokenize('1 + 2)');
132
+
133
+ expect(() => parser.parse(tokens)).toThrow();
134
+ });
135
+
136
+ it('should throw error for unexpected character sequence', () => {
137
+ const lexer = new Lexer();
138
+ const parser = new Parser();
139
+ // '@' will be ignored by lexer, resulting in empty expression
140
+ const tokens = lexer.tokenize('@');
141
+
142
+ expect(() => parser.parse(tokens)).toThrow();
143
+ });
144
+ });
145
+
146
+ describe('Edge cases', () => {
147
+ it('should parse single literal', () => {
148
+ const lexer = new Lexer();
149
+ const parser = new Parser();
150
+ const tokens = lexer.tokenize('42');
151
+ const ast = parser.parse(tokens);
152
+
153
+ expect(ast.type).toBe('Literal');
154
+ expect((ast as any).value).toBe('42');
155
+ });
156
+
157
+ it('should parse single hex literal', () => {
158
+ const lexer = new Lexer();
159
+ const parser = new Parser();
160
+ const tokens = lexer.tokenize('0xFF');
161
+ const ast = parser.parse(tokens);
162
+
163
+ expect(ast.type).toBe('Literal');
164
+ expect((ast as any).value).toBe('0xFF');
165
+ });
166
+
167
+ it('should parse deeply nested parentheses', () => {
168
+ const lexer = new Lexer();
169
+ const parser = new Parser();
170
+ const tokens = lexer.tokenize('(((1 + 2) * 3) - 4)');
171
+ const ast = parser.parse(tokens);
172
+
173
+ expect(ast.type).toBe('Group');
174
+ });
175
+
176
+ it('should parse multiple unary operators', () => {
177
+ const lexer = new Lexer();
178
+ const parser = new Parser();
179
+ const tokens = lexer.tokenize('--42');
180
+ const ast = parser.parse(tokens);
181
+
182
+ expect(ast.type).toBe('UnaryOp');
183
+ expect((ast as any).operator).toBe('-');
184
+ expect((ast as any).operand.type).toBe('UnaryOp');
185
+ });
186
+
187
+ it('should parse mixed unary and binary operations', () => {
188
+ const lexer = new Lexer();
189
+ const parser = new Parser();
190
+ const tokens = lexer.tokenize('-2 + ~3');
191
+ const ast = parser.parse(tokens);
192
+
193
+ expect(ast.type).toBe('BinaryOp');
194
+ expect((ast as any).operator).toBe('+');
195
+ });
196
+ });
197
+ });
@@ -0,0 +1,234 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { ProgrammerEngine } from '../../src/engines/programmer.js';
3
+ import { Lexer } from '../../src/parser/lexer.js';
4
+ import { Parser } from '../../src/parser/parser.js';
5
+
6
+ describe('ProgrammerEngine', () => {
7
+ const lexer = new Lexer();
8
+ const parser = new Parser();
9
+ const engine = new ProgrammerEngine();
10
+
11
+ describe('Bitwise AND', () => {
12
+ it('should perform bitwise AND', () => {
13
+ const tokens = lexer.tokenize('0xFF & 0x0F');
14
+ const ast = parser.parse(tokens);
15
+ const result = engine.evaluate(ast);
16
+
17
+ expect(result.toString()).toBe('15');
18
+ });
19
+ });
20
+
21
+ describe('Bitwise OR', () => {
22
+ it('should perform bitwise OR', () => {
23
+ const tokens = lexer.tokenize('0xF0 | 0x0F');
24
+ const ast = parser.parse(tokens);
25
+ const result = engine.evaluate(ast);
26
+
27
+ expect(result.toString()).toBe('255');
28
+ });
29
+ });
30
+
31
+ describe('Bitwise XOR', () => {
32
+ it('should perform bitwise XOR', () => {
33
+ const tokens = lexer.tokenize('0xFF ^ 0xFF');
34
+ const ast = parser.parse(tokens);
35
+ const result = engine.evaluate(ast);
36
+
37
+ expect(result.toString()).toBe('0');
38
+ });
39
+ });
40
+
41
+ describe('Bitwise NOT', () => {
42
+ it('should perform bitwise NOT', () => {
43
+ const tokens = lexer.tokenize('~0x00');
44
+ const ast = parser.parse(tokens);
45
+ const result = engine.evaluate(ast);
46
+
47
+ // ~0 = -1 in two's complement
48
+ expect(result.toString()).toBe('-1');
49
+ });
50
+ });
51
+
52
+ describe('Left shift', () => {
53
+ it('should perform left shift', () => {
54
+ const tokens = lexer.tokenize('1 << 4');
55
+ const ast = parser.parse(tokens);
56
+ const result = engine.evaluate(ast);
57
+
58
+ expect(result.toString()).toBe('16');
59
+ });
60
+ });
61
+
62
+ describe('Right shift', () => {
63
+ it('should perform right shift', () => {
64
+ const tokens = lexer.tokenize('16 >> 2');
65
+ const ast = parser.parse(tokens);
66
+ const result = engine.evaluate(ast);
67
+
68
+ expect(result.toString()).toBe('4');
69
+ });
70
+ });
71
+
72
+ describe('BigInt support', () => {
73
+ it('should handle very large numbers', () => {
74
+ const tokens = lexer.tokenize('0xFFFFFFFFFFFFFFFF + 1');
75
+ const ast = parser.parse(tokens);
76
+ const result = engine.evaluate(ast);
77
+
78
+ expect(result.toString()).toBe('18446744073709551616');
79
+ });
80
+ });
81
+
82
+ describe('Complex expressions', () => {
83
+ it('should handle multiple bitwise operations', () => {
84
+ const tokens = lexer.tokenize('0xFF & 0x0F | 0x10');
85
+ const ast = parser.parse(tokens);
86
+ const result = engine.evaluate(ast);
87
+
88
+ // (0xFF & 0x0F) | 0x10 = 15 | 16 = 31
89
+ expect(result.toString()).toBe('31');
90
+ });
91
+
92
+ it('should handle shift with bitwise ops', () => {
93
+ const tokens = lexer.tokenize('(1 << 4) & 0xFF');
94
+ const ast = parser.parse(tokens);
95
+ const result = engine.evaluate(ast);
96
+
97
+ // (16) & 255 = 16
98
+ expect(result.toString()).toBe('16');
99
+ });
100
+ });
101
+
102
+ describe('Binary literals', () => {
103
+ it('should parse binary literals', () => {
104
+ const tokens = lexer.tokenize('0b1010');
105
+ const ast = parser.parse(tokens);
106
+ const result = engine.evaluate(ast);
107
+
108
+ expect(result.toString()).toBe('10');
109
+ });
110
+
111
+ it('should perform operations on binary literals', () => {
112
+ const tokens = lexer.tokenize('0b1010 << 2');
113
+ const ast = parser.parse(tokens);
114
+ const result = engine.evaluate(ast);
115
+
116
+ expect(result.toString()).toBe('40');
117
+ });
118
+ });
119
+
120
+ describe('Octal literals', () => {
121
+ it('should parse octal literals', () => {
122
+ const tokens = lexer.tokenize('0o77');
123
+ const ast = parser.parse(tokens);
124
+ const result = engine.evaluate(ast);
125
+
126
+ expect(result.toString()).toBe('63');
127
+ });
128
+ });
129
+
130
+ describe('Unsigned right shift', () => {
131
+ it('should perform unsigned right shift', () => {
132
+ const tokens = lexer.tokenize('-16 >>> 2');
133
+ const ast = parser.parse(tokens);
134
+ const result = engine.evaluate(ast);
135
+
136
+ // -16 >>> 2 = 1073741820 (in 32-bit arithmetic)
137
+ expect(result.toString()).toBe('1073741820');
138
+ });
139
+
140
+ it('should handle positive unsigned right shift', () => {
141
+ const tokens = lexer.tokenize('16 >>> 2');
142
+ const ast = parser.parse(tokens);
143
+ const result = engine.evaluate(ast);
144
+
145
+ expect(result.toString()).toBe('4');
146
+ });
147
+ });
148
+
149
+ describe('Char literals', () => {
150
+ it('should parse char literals', () => {
151
+ const tokens = lexer.tokenize("'A'");
152
+ const ast = parser.parse(tokens);
153
+ const result = engine.evaluate(ast);
154
+
155
+ expect(result.toString()).toBe('65'); // ASCII code for 'A'
156
+ });
157
+
158
+ it('should handle backslash in char literals', () => {
159
+ const tokens = lexer.tokenize("'\\\\'");
160
+ const ast = parser.parse(tokens);
161
+ const result = engine.evaluate(ast);
162
+
163
+ expect(result.toString()).toBe('92'); // ASCII code for backslash
164
+ });
165
+
166
+ it('should perform operations on char literals', () => {
167
+ const tokens = lexer.tokenize("'A' + 'B'");
168
+ const ast = parser.parse(tokens);
169
+ const result = engine.evaluate(ast);
170
+
171
+ expect(result.toString()).toBe('131'); // 65 + 66
172
+ });
173
+ });
174
+
175
+ describe('Division by zero error', () => {
176
+ it('should throw division by zero error for division', () => {
177
+ const tokens = lexer.tokenize('10 / 0');
178
+ const ast = parser.parse(tokens);
179
+
180
+ expect(() => engine.evaluate(ast)).toThrow();
181
+ try {
182
+ engine.evaluate(ast);
183
+ expect.fail('Should have thrown an error');
184
+ } catch (e) {
185
+ expect(e).toHaveProperty('error');
186
+ expect(e.error.type).toBe('DivisionByZero');
187
+ }
188
+ });
189
+
190
+ it('should throw division by zero error for modulo', () => {
191
+ const tokens = lexer.tokenize('10 % 0');
192
+ const ast = parser.parse(tokens);
193
+
194
+ expect(() => engine.evaluate(ast)).toThrow();
195
+ try {
196
+ engine.evaluate(ast);
197
+ expect.fail('Should have thrown an error');
198
+ } catch (e) {
199
+ expect(e).toHaveProperty('error');
200
+ expect(e.error.type).toBe('DivisionByZero');
201
+ }
202
+ });
203
+ });
204
+
205
+ describe('Floating point error', () => {
206
+ it('should throw error for floating point numbers', () => {
207
+ const tokens = lexer.tokenize('3.14');
208
+ const ast = parser.parse(tokens);
209
+
210
+ expect(() => engine.evaluate(ast)).toThrow();
211
+ try {
212
+ engine.evaluate(ast);
213
+ expect.fail('Should have thrown an error');
214
+ } catch (e) {
215
+ expect(e).toHaveProperty('error');
216
+ expect(e.error.type).toBe('InvalidNumber');
217
+ }
218
+ });
219
+
220
+ it('should throw error for floating point in expression', () => {
221
+ const tokens = lexer.tokenize('10 + 3.14');
222
+ const ast = parser.parse(tokens);
223
+
224
+ expect(() => engine.evaluate(ast)).toThrow();
225
+ try {
226
+ engine.evaluate(ast);
227
+ expect.fail('Should have thrown an error');
228
+ } catch (e) {
229
+ expect(e).toHaveProperty('error');
230
+ expect(e.error.type).toBe('InvalidNumber');
231
+ }
232
+ });
233
+ });
234
+ });
package/tsconfig.json ADDED
@@ -0,0 +1,20 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "Node16",
5
+ "moduleResolution": "Node16",
6
+ "lib": ["ES2022"],
7
+ "outDir": "./build",
8
+ "rootDir": "./src",
9
+ "strict": true,
10
+ "esModuleInterop": true,
11
+ "skipLibCheck": true,
12
+ "forceConsistentCasingInFileNames": true,
13
+ "resolveJsonModule": true,
14
+ "declaration": true,
15
+ "declarationMap": true,
16
+ "sourceMap": true
17
+ },
18
+ "include": ["src/**/*"],
19
+ "exclude": ["node_modules", "build", "test"]
20
+ }
@@ -0,0 +1,13 @@
1
+ import { defineConfig } from 'vitest/config';
2
+
3
+ export default defineConfig({
4
+ test: {
5
+ globals: true,
6
+ environment: 'node',
7
+ coverage: {
8
+ provider: 'v8',
9
+ reporter: ['text', 'json', 'html'],
10
+ exclude: ['**/node_modules/**', '**/dist/**', '**/test/**'],
11
+ },
12
+ },
13
+ });