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,287 @@
1
+ import { Server } from '@modelcontextprotocol/sdk/server/index.js';
2
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
3
+ import {
4
+ CallToolRequestSchema,
5
+ ListToolsRequestSchema,
6
+ Tool,
7
+ } from '@modelcontextprotocol/sdk/types.js';
8
+ import { basicCalculate } from './tools/basic.js';
9
+ import { programmerCalculate } from './tools/programmer.js';
10
+ import { convertBase } from './tools/conversion.js';
11
+ import { asciiToNumber, numberToAscii } from './tools/ascii.js';
12
+
13
+ // Define available tools
14
+ const TOOLS: Tool[] = [
15
+ {
16
+ name: 'basic_calculate',
17
+ description: 'Evaluate basic arithmetic expressions with decimal precision. Supports operators: +, -, *, /, %, and parentheses. Examples: "2 + 3 * 4", "(10 - 4) / 2", "0.1 + 0.2"',
18
+ inputSchema: {
19
+ type: 'object',
20
+ properties: {
21
+ expression: {
22
+ type: 'string',
23
+ description: 'The arithmetic expression to evaluate',
24
+ },
25
+ },
26
+ required: ['expression'],
27
+ },
28
+ },
29
+ {
30
+ name: 'programmer_calculate',
31
+ description: 'Evaluate programmer calculator expressions with integer arithmetic and bitwise operations. Supports decimal (10), hex (0x), binary (0b), octal (0o), and character literals (\'A\'). Operators: +, -, *, /, %, &, |, ^, ~, <<, >>, >>>. Examples: "0xA + 0x5", "0b1010 & 0b1100", "\'A\' + 1"',
32
+ inputSchema: {
33
+ type: 'object',
34
+ properties: {
35
+ expression: {
36
+ type: 'string',
37
+ description: 'The programmer expression to evaluate',
38
+ },
39
+ },
40
+ required: ['expression'],
41
+ },
42
+ },
43
+ {
44
+ name: 'convert_base',
45
+ description: 'Convert numbers between different bases (2, 8, 10, 16). Supports binary, octal, decimal, and hexadecimal conversions. Examples: Convert "1010" from base 2 to base 10, convert "FF" from base 16 to base 2',
46
+ inputSchema: {
47
+ type: 'object',
48
+ properties: {
49
+ value: {
50
+ type: 'string',
51
+ description: 'The numeric value to convert (without prefix)',
52
+ },
53
+ fromBase: {
54
+ type: 'number',
55
+ description: 'The source base (2, 8, 10, or 16)',
56
+ enum: [2, 8, 10, 16],
57
+ },
58
+ toBase: {
59
+ type: 'number',
60
+ description: 'The target base (2, 8, 10, or 16)',
61
+ enum: [2, 8, 10, 16],
62
+ },
63
+ },
64
+ required: ['value', 'fromBase', 'toBase'],
65
+ },
66
+ },
67
+ {
68
+ name: 'ascii_to_number',
69
+ description: 'Convert text to ASCII/Unicode codes. Returns comma-separated decimal codes for each character. Example: "ABC" becomes "65, 66, 67"',
70
+ inputSchema: {
71
+ type: 'object',
72
+ properties: {
73
+ text: {
74
+ type: 'string',
75
+ description: 'The text to convert to ASCII/Unicode codes',
76
+ },
77
+ },
78
+ required: ['text'],
79
+ },
80
+ },
81
+ {
82
+ name: 'number_to_ascii',
83
+ description: 'Convert an ASCII/Unicode code to its character. Supports single code or array of codes. Examples: 65 becomes "A", [72, 101, 108, 108, 111] becomes "Hello"',
84
+ inputSchema: {
85
+ type: 'object',
86
+ properties: {
87
+ code: {
88
+ type: 'number',
89
+ description: 'The ASCII/Unicode code to convert (0-65535)',
90
+ },
91
+ codes: {
92
+ type: 'array',
93
+ items: {
94
+ type: 'number',
95
+ },
96
+ description: 'Array of ASCII/Unicode codes to convert (each 0-65535)',
97
+ },
98
+ },
99
+ },
100
+ },
101
+ ];
102
+
103
+ export function createMCPServer(): Server {
104
+ const server = new Server(
105
+ {
106
+ name: 'mcp-calculator',
107
+ version: '0.1.0',
108
+ },
109
+ {
110
+ capabilities: {
111
+ tools: {},
112
+ },
113
+ }
114
+ );
115
+
116
+ // Handle list tools request
117
+ server.setRequestHandler(ListToolsRequestSchema, async () => {
118
+ return {
119
+ tools: TOOLS,
120
+ };
121
+ });
122
+
123
+ // Handle tool execution
124
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
125
+ const { name, arguments: args } = request.params;
126
+
127
+ try {
128
+ switch (name) {
129
+ case 'basic_calculate': {
130
+ const { expression } = args as { expression: string };
131
+ const result = basicCalculate({ expression });
132
+
133
+ if (result.success) {
134
+ return {
135
+ content: [
136
+ {
137
+ type: 'text',
138
+ text: `Result: ${result.result}${result.details?.expression ? `\nExpression: ${result.details.expression}` : ''}`,
139
+ },
140
+ ],
141
+ };
142
+ } else {
143
+ return {
144
+ content: [
145
+ {
146
+ type: 'text',
147
+ text: `Error: ${result.error.type}: ${result.error.message}${result.error.suggestion ? `\n\nSuggestion: ${result.error.suggestion}` : ''}`,
148
+ },
149
+ ],
150
+ isError: true,
151
+ };
152
+ }
153
+ }
154
+
155
+ case 'programmer_calculate': {
156
+ const { expression } = args as { expression: string };
157
+ const result = programmerCalculate({ expression });
158
+
159
+ if (result.success) {
160
+ return {
161
+ content: [
162
+ {
163
+ type: 'text',
164
+ text: `Result: ${result.result}${result.details?.expression ? `\nExpression: ${result.details.expression}` : ''}`,
165
+ },
166
+ ],
167
+ };
168
+ } else {
169
+ return {
170
+ content: [
171
+ {
172
+ type: 'text',
173
+ text: `Error: ${result.error.type}: ${result.error.message}${result.error.suggestion ? `\n\nSuggestion: ${result.error.suggestion}` : ''}`,
174
+ },
175
+ ],
176
+ isError: true,
177
+ };
178
+ }
179
+ }
180
+
181
+ case 'convert_base': {
182
+ const { value, fromBase, toBase } = args as { value: string; fromBase: number; toBase: number };
183
+ const result = convertBase({ value, fromBase, toBase });
184
+
185
+ if (result.success) {
186
+ const steps = result.details?.steps || [];
187
+ return {
188
+ content: [
189
+ {
190
+ type: 'text',
191
+ text: `Result: ${result.result}\n\nConversion steps:\n${steps.map((s: string, i: number) => `${i + 1}. ${s}`).join('\n')}`,
192
+ },
193
+ ],
194
+ };
195
+ } else {
196
+ return {
197
+ content: [
198
+ {
199
+ type: 'text',
200
+ text: `Error: ${result.error.type}: ${result.error.message}${result.error.suggestion ? `\n\nSuggestion: ${result.error.suggestion}` : ''}`,
201
+ },
202
+ ],
203
+ isError: true,
204
+ };
205
+ }
206
+ }
207
+
208
+ case 'ascii_to_number': {
209
+ const { text } = args as { text: string };
210
+ const result = asciiToNumber({ text });
211
+
212
+ if (result.success) {
213
+ const steps = result.details?.steps || [];
214
+ return {
215
+ content: [
216
+ {
217
+ type: 'text',
218
+ text: `ASCII codes: ${result.result}\n\nDetails:\n${steps.map((s: string) => ` ${s}`).join('\n')}`,
219
+ },
220
+ ],
221
+ };
222
+ } else {
223
+ return {
224
+ content: [
225
+ {
226
+ type: 'text',
227
+ text: `Error: ${result.error.type}: ${result.error.message}${result.error.suggestion ? `\n\nSuggestion: ${result.error.suggestion}` : ''}`,
228
+ },
229
+ ],
230
+ isError: true,
231
+ };
232
+ }
233
+ }
234
+
235
+ case 'number_to_ascii': {
236
+ const { code, codes } = args as { code?: number; codes?: number[] };
237
+ const result = numberToAscii({ code, codes });
238
+
239
+ if (result.success) {
240
+ const steps = result.details?.steps || [];
241
+ return {
242
+ content: [
243
+ {
244
+ type: 'text',
245
+ text: `Character: ${result.result}\n\nDetails:\n${steps.map((s: string) => ` ${s}`).join('\n')}`,
246
+ },
247
+ ],
248
+ };
249
+ } else {
250
+ return {
251
+ content: [
252
+ {
253
+ type: 'text',
254
+ text: `Error: ${result.error.type}: ${result.error.message}${result.error.suggestion ? `\n\nSuggestion: ${result.error.suggestion}` : ''}`,
255
+ },
256
+ ],
257
+ isError: true,
258
+ };
259
+ }
260
+ }
261
+
262
+ default:
263
+ return {
264
+ content: [
265
+ {
266
+ type: 'text',
267
+ text: `Error: Unknown tool "${name}"`,
268
+ },
269
+ ],
270
+ isError: true,
271
+ };
272
+ }
273
+ } catch (error) {
274
+ return {
275
+ content: [
276
+ {
277
+ type: 'text',
278
+ text: `Error: ${error instanceof Error ? error.message : 'Unknown error'}`,
279
+ },
280
+ ],
281
+ isError: true,
282
+ };
283
+ }
284
+ });
285
+
286
+ return server;
287
+ }
@@ -0,0 +1,116 @@
1
+ import { ErrorHandler, CalculationResponse } from '../../errors/handler.js';
2
+
3
+ export interface AsciiToNumberInput {
4
+ text: string;
5
+ }
6
+
7
+ export interface NumberToAsciiInput {
8
+ code?: number;
9
+ codes?: number[];
10
+ }
11
+
12
+ export function asciiToNumber(input: AsciiToNumberInput): CalculationResponse {
13
+ const errorHandler = new ErrorHandler();
14
+ const { text } = input;
15
+
16
+ if (!text || text.length === 0) {
17
+ return errorHandler.runtimeError('EmptyInput', 'Text cannot be empty');
18
+ }
19
+
20
+ try {
21
+ const codes: number[] = [];
22
+
23
+ for (let i = 0; i < text.length; i++) {
24
+ const code = text.charCodeAt(i);
25
+ codes.push(code);
26
+ }
27
+
28
+ return errorHandler.success(codes.join(', '), {
29
+ expression: `"${text}" → ASCII codes`,
30
+ steps: [
31
+ `Input text: "${text}"`,
32
+ `Character count: ${text.length}`,
33
+ `ASCII codes: ${codes.map((c, i) => `'${text[i]}' = ${c}`).join(', ')}`,
34
+ ],
35
+ });
36
+ } catch (error) {
37
+ return errorHandler.runtimeError('InvalidNumber', error instanceof Error ? error.message : 'Unknown error during ASCII conversion');
38
+ }
39
+ }
40
+
41
+ export function numberToAscii(input: NumberToAsciiInput): CalculationResponse {
42
+ const errorHandler = new ErrorHandler();
43
+ const { code, codes } = input;
44
+
45
+ // 参数验证:必须恰好提供一个参数
46
+ const hasCode = code !== undefined;
47
+ const hasCodes = codes !== undefined && codes.length > 0;
48
+
49
+ if (!hasCode && !hasCodes) {
50
+ return errorHandler.runtimeError('EmptyInput', 'Either code or codes parameter must be provided');
51
+ }
52
+
53
+ if (hasCode && hasCodes) {
54
+ return errorHandler.runtimeError('InvalidNumber', 'Only one of code or codes parameter should be provided, not both');
55
+ }
56
+
57
+ // 处理单个代码
58
+ if (hasCode) {
59
+ if (code < 0 || code > 0x10FFFF) {
60
+ return errorHandler.runtimeError('InvalidNumber', `Invalid Unicode code point: ${code}. Valid range is 0 to 1114111 (0x10FFFF).`);
61
+ }
62
+
63
+ try {
64
+ if (code > 0xFFFF) {
65
+ return errorHandler.runtimeError('InvalidNumber', `Code point ${code} requires surrogate pair handling. Currently only BMP characters (0-65535) are supported.`);
66
+ }
67
+
68
+ const text = String.fromCharCode(code);
69
+
70
+ return errorHandler.success(`"${text}"`, {
71
+ expression: `${code} → ASCII/Unicode character`,
72
+ steps: [
73
+ `Input code: ${code}`,
74
+ `Hex: 0x${code.toString(16).toUpperCase()}`,
75
+ `Binary: ${code.toString(2).padStart(16, '0')}`,
76
+ `Character: "${text}"`,
77
+ ],
78
+ });
79
+ } catch (error) {
80
+ return errorHandler.runtimeError('InvalidNumber', error instanceof Error ? error.message : 'Unknown error during ASCII conversion');
81
+ }
82
+ }
83
+
84
+ // 处理代码数组
85
+ if (hasCodes) {
86
+ try {
87
+ let text = '';
88
+ const steps: string[] = [];
89
+
90
+ for (const c of codes!) {
91
+ if (c < 0 || c > 0xFFFF) {
92
+ return errorHandler.runtimeError('InvalidNumber', `Invalid code point: ${c}. Valid range is 0 to 65535.`);
93
+ }
94
+
95
+ const char = String.fromCharCode(c);
96
+ text += char;
97
+ steps.push(`${c} → '${char}'`);
98
+ }
99
+
100
+ return errorHandler.success(`"${text}"`, {
101
+ expression: `[${codes.join(', ')}] → ASCII/Unicode text`,
102
+ steps: [
103
+ `Input codes: [${codes.join(', ')}]`,
104
+ `Character count: ${codes.length}`,
105
+ ...steps,
106
+ `Result: "${text}"`,
107
+ ],
108
+ });
109
+ } catch (error) {
110
+ return errorHandler.runtimeError('InvalidNumber', error instanceof Error ? error.message : 'Unknown error during ASCII conversion');
111
+ }
112
+ }
113
+
114
+ // 不应该到达这里
115
+ return errorHandler.runtimeError('InvalidNumber', 'Unexpected error in numberToAscii');
116
+ }
@@ -0,0 +1,44 @@
1
+ import { Lexer } from '../../parser/lexer.js';
2
+ import { Parser } from '../../parser/parser.js';
3
+ import { DecimalEngine } from '../../engines/decimal.js';
4
+ import { ErrorHandler, CalculationResponse } from '../../errors/handler.js';
5
+
6
+ export interface BasicCalculateInput {
7
+ expression: string;
8
+ }
9
+
10
+ export function basicCalculate(input: BasicCalculateInput): CalculationResponse {
11
+ const errorHandler = new ErrorHandler();
12
+ const lexer = new Lexer();
13
+ const parser = new Parser();
14
+ const engine = new DecimalEngine();
15
+
16
+ const { expression } = input;
17
+
18
+ if (!expression || expression.trim().length === 0) {
19
+ return errorHandler.runtimeError('EmptyInput', 'Expression cannot be empty');
20
+ }
21
+
22
+ try {
23
+ const tokens = lexer.tokenize(expression);
24
+ const ast = parser.parse(tokens);
25
+ const result = engine.evaluate(ast);
26
+
27
+ return errorHandler.success(result.toString(), {
28
+ expression,
29
+ });
30
+ } catch (error) {
31
+ if (typeof error === 'object' && error !== null && 'success' in error) {
32
+ return error as CalculationResponse;
33
+ }
34
+
35
+ const errorMessage = error instanceof Error ? error.message : String(error);
36
+
37
+ // Map error messages to error types
38
+ if (errorMessage === 'DivisionByZero') {
39
+ return errorHandler.runtimeError('DivisionByZero', 'Cannot divide by zero');
40
+ }
41
+
42
+ return errorHandler.runtimeError('InvalidNumber', errorMessage);
43
+ }
44
+ }
@@ -0,0 +1,95 @@
1
+ import { ErrorHandler, CalculationResponse } from '../../errors/handler.js';
2
+
3
+ export interface ConvertBaseInput {
4
+ value: string;
5
+ fromBase: number;
6
+ toBase: number;
7
+ }
8
+
9
+ export function convertBase(input: ConvertBaseInput): CalculationResponse {
10
+ const errorHandler = new ErrorHandler();
11
+ const { value, fromBase, toBase } = input;
12
+
13
+ if (!value || value.trim().length === 0) {
14
+ return errorHandler.runtimeError('EmptyInput', 'Value cannot be empty');
15
+ }
16
+
17
+ // Validate bases
18
+ const validBases = [2, 8, 10, 16];
19
+ if (!validBases.includes(fromBase)) {
20
+ return errorHandler.runtimeError('InvalidBase', `Invalid source base: ${fromBase}. Supported bases are 2, 8, 10, and 16.`);
21
+ }
22
+
23
+ if (!validBases.includes(toBase)) {
24
+ return errorHandler.runtimeError('InvalidBase', `Invalid target base: ${toBase}. Supported bases are 2, 8, 10, and 16.`);
25
+ }
26
+
27
+ try {
28
+ // Parse the value from the source base to decimal
29
+ let decimalValue: bigint;
30
+
31
+ switch (fromBase) {
32
+ case 2:
33
+ if (!/^[01]+$/.test(value)) {
34
+ return errorHandler.runtimeError('InvalidNumber', `Invalid binary number: ${value}`);
35
+ }
36
+ decimalValue = BigInt('0b' + value);
37
+ break;
38
+ case 8:
39
+ if (!/^[0-7]+$/.test(value)) {
40
+ return errorHandler.runtimeError('InvalidNumber', `Invalid octal number: ${value}`);
41
+ }
42
+ decimalValue = BigInt('0o' + value);
43
+ break;
44
+ case 10:
45
+ if (!/^-?\d+$/.test(value)) {
46
+ return errorHandler.runtimeError('InvalidNumber', `Invalid decimal number: ${value}`);
47
+ }
48
+ decimalValue = BigInt(value);
49
+ break;
50
+ case 16:
51
+ if (!/^[0-9a-fA-F]+$/.test(value)) {
52
+ return errorHandler.runtimeError('InvalidNumber', `Invalid hexadecimal number: ${value}`);
53
+ }
54
+ decimalValue = BigInt('0x' + value);
55
+ break;
56
+ default:
57
+ return errorHandler.runtimeError('InvalidBase', `Unsupported base: ${fromBase}`);
58
+ }
59
+
60
+ // Handle negative numbers for base 10
61
+ const isNegative = decimalValue < 0n;
62
+ const absoluteValue = isNegative ? -decimalValue : decimalValue;
63
+
64
+ // Convert from decimal to target base
65
+ let result: string;
66
+
67
+ switch (toBase) {
68
+ case 2:
69
+ result = absoluteValue.toString(2);
70
+ break;
71
+ case 8:
72
+ result = absoluteValue.toString(8);
73
+ break;
74
+ case 10:
75
+ result = decimalValue.toString();
76
+ break;
77
+ case 16:
78
+ result = absoluteValue.toString(16).toUpperCase();
79
+ break;
80
+ default:
81
+ return errorHandler.runtimeError('InvalidBase', `Unsupported base: ${toBase}`);
82
+ }
83
+
84
+ return errorHandler.success(result, {
85
+ expression: `${value} (base ${fromBase}) → (base ${toBase})`,
86
+ steps: [
87
+ `Input: ${value} in base ${fromBase}`,
88
+ `Decimal value: ${decimalValue}`,
89
+ `Output: ${result} in base ${toBase}`,
90
+ ],
91
+ });
92
+ } catch (error) {
93
+ return errorHandler.runtimeError('InvalidNumber', error instanceof Error ? error.message : 'Unknown error during base conversion');
94
+ }
95
+ }
@@ -0,0 +1,36 @@
1
+ import { Lexer } from '../../parser/lexer.js';
2
+ import { Parser } from '../../parser/parser.js';
3
+ import { ProgrammerEngine } from '../../engines/programmer.js';
4
+ import { ErrorHandler, CalculationResponse } from '../../errors/handler.js';
5
+
6
+ export interface ProgrammerCalculateInput {
7
+ expression: string;
8
+ }
9
+
10
+ export function programmerCalculate(input: ProgrammerCalculateInput): CalculationResponse {
11
+ const errorHandler = new ErrorHandler();
12
+ const lexer = new Lexer();
13
+ const parser = new Parser();
14
+ const engine = new ProgrammerEngine();
15
+
16
+ const { expression } = input;
17
+
18
+ if (!expression || expression.trim().length === 0) {
19
+ return errorHandler.runtimeError('EmptyInput', 'Expression cannot be empty');
20
+ }
21
+
22
+ try {
23
+ const tokens = lexer.tokenize(expression);
24
+ const ast = parser.parse(tokens);
25
+ const result = engine.evaluate(ast);
26
+
27
+ return errorHandler.success(result.toString(), {
28
+ expression,
29
+ });
30
+ } catch (error) {
31
+ if (typeof error === 'object' && error !== null && 'success' in error) {
32
+ return error as CalculationResponse;
33
+ }
34
+ return errorHandler.runtimeError('InvalidNumber', error instanceof Error ? error.message : 'Unknown error');
35
+ }
36
+ }
@@ -0,0 +1,51 @@
1
+ export type TokenType =
2
+ | 'NUMBER'
3
+ | 'HEX'
4
+ | 'BINARY'
5
+ | 'OCTAL'
6
+ | 'OPERATOR'
7
+ | 'LPAREN'
8
+ | 'RPAREN'
9
+ | 'CHAR_LITERAL'
10
+ | 'WHITESPACE'
11
+ | 'EOF';
12
+
13
+ export interface Token {
14
+ type: TokenType;
15
+ value: string;
16
+ position: { start: number; end: number };
17
+ }
18
+
19
+ export type ASTNode =
20
+ | BinaryOpNode
21
+ | UnaryOpNode
22
+ | LiteralNode
23
+ | GroupNode;
24
+
25
+ export interface BinaryOpNode {
26
+ type: 'BinaryOp';
27
+ operator: string;
28
+ left: ASTNode;
29
+ right: ASTNode;
30
+ position?: { start: number; end: number };
31
+ }
32
+
33
+ export interface UnaryOpNode {
34
+ type: 'UnaryOp';
35
+ operator: string;
36
+ operand: ASTNode;
37
+ position?: { start: number; end: number };
38
+ }
39
+
40
+ export interface LiteralNode {
41
+ type: 'Literal';
42
+ value: string;
43
+ tokenType: TokenType;
44
+ position?: { start: number; end: number };
45
+ }
46
+
47
+ export interface GroupNode {
48
+ type: 'Group';
49
+ expression: ASTNode;
50
+ position?: { start: number; end: number };
51
+ }