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,216 @@
1
+ import { Token, TokenType } from './ast.js';
2
+
3
+ export class Lexer {
4
+ private pos = 0;
5
+ private input = '';
6
+ private length = 0;
7
+
8
+ tokenize(input: string): Token[] {
9
+ this.input = input;
10
+ this.pos = 0;
11
+ this.length = input.length;
12
+ const tokens: Token[] = [];
13
+
14
+ while (this.pos < this.length) {
15
+ const char = this.input[this.pos];
16
+
17
+ if (this.isWhitespace(char)) {
18
+ this.skipWhitespace();
19
+ continue;
20
+ }
21
+
22
+ const token = this.readToken();
23
+ if (token && token.type !== 'WHITESPACE') {
24
+ tokens.push(token);
25
+ }
26
+ }
27
+
28
+ tokens.push({
29
+ type: 'EOF',
30
+ value: '',
31
+ position: { start: this.pos, end: this.pos },
32
+ });
33
+
34
+ return tokens;
35
+ }
36
+
37
+ private readToken(): Token | null {
38
+ const char = this.input[this.pos];
39
+ const start = this.pos;
40
+
41
+ // Hex literal: 0x or 0X
42
+ if (char === '0' && this.peek() === 'x') {
43
+ return this.readHexLiteral(start);
44
+ }
45
+
46
+ // Binary literal: 0b or 0B
47
+ if (char === '0' && this.peek() === 'b') {
48
+ return this.readBinaryLiteral(start);
49
+ }
50
+
51
+ // Octal literal: 0o or 0O
52
+ if (char === '0' && this.peek() === 'o') {
53
+ return this.readOctalLiteral(start);
54
+ }
55
+
56
+ // Number (decimal or float)
57
+ if (this.isDigit(char) || (char === '.' && this.isDigit(this.peek()))) {
58
+ return this.readNumber(start);
59
+ }
60
+
61
+ // Operators
62
+ if (this.isOperatorChar(char)) {
63
+ return this.readOperator(start);
64
+ }
65
+
66
+ // Parentheses
67
+ if (char === '(') {
68
+ this.advance();
69
+ return { type: 'LPAREN', value: '(', position: { start, end: this.pos } };
70
+ }
71
+
72
+ if (char === ')') {
73
+ this.advance();
74
+ return { type: 'RPAREN', value: ')', position: { start, end: this.pos } };
75
+ }
76
+
77
+ // Char literal
78
+ if (char === "'") {
79
+ return this.readCharLiteral(start);
80
+ }
81
+
82
+ // Unknown character
83
+ this.advance();
84
+ return null;
85
+ }
86
+
87
+ private readNumber(start: number): Token {
88
+ let value = '';
89
+
90
+ while (this.pos < this.length && (this.isDigit(this.input[this.pos]) || this.input[this.pos] === '.')) {
91
+ value += this.input[this.pos];
92
+ this.advance();
93
+ }
94
+
95
+ return { type: 'NUMBER', value, position: { start, end: this.pos } };
96
+ }
97
+
98
+ private readHexLiteral(start: number): Token {
99
+ this.advance(); // '0'
100
+ this.advance(); // 'x'
101
+
102
+ let value = '0x';
103
+ while (this.pos < this.length && this.isHexDigit(this.input[this.pos])) {
104
+ value += this.input[this.pos];
105
+ this.advance();
106
+ }
107
+
108
+ return { type: 'HEX', value, position: { start, end: this.pos } };
109
+ }
110
+
111
+ private readBinaryLiteral(start: number): Token {
112
+ this.advance(); // '0'
113
+ this.advance(); // 'b'
114
+
115
+ let value = '0b';
116
+ while (this.pos < this.length && (this.input[this.pos] === '0' || this.input[this.pos] === '1')) {
117
+ value += this.input[this.pos];
118
+ this.advance();
119
+ }
120
+
121
+ return { type: 'BINARY', value, position: { start, end: this.pos } };
122
+ }
123
+
124
+ private readOctalLiteral(start: number): Token {
125
+ this.advance(); // '0'
126
+ this.advance(); // 'o'
127
+
128
+ let value = '0o';
129
+ while (this.pos < this.length && this.isOctalDigit(this.input[this.pos])) {
130
+ value += this.input[this.pos];
131
+ this.advance();
132
+ }
133
+
134
+ return { type: 'OCTAL', value, position: { start, end: this.pos } };
135
+ }
136
+
137
+ private readOperator(start: number): Token {
138
+ // Check for multi-char operators: <<, >>, >>>
139
+ const twoChar = this.input.substring(this.pos, this.pos + 2);
140
+
141
+ if (twoChar === '<<' || twoChar === '>>') {
142
+ // Check for >>>
143
+ if (twoChar === '>>' && this.peek(2) === '>') {
144
+ this.pos += 3;
145
+ return { type: 'OPERATOR', value: '>>>', position: { start, end: this.pos } };
146
+ }
147
+ this.pos += 2;
148
+ return { type: 'OPERATOR', value: twoChar, position: { start, end: this.pos } };
149
+ }
150
+
151
+ // Single char operators
152
+ const op = this.input[this.pos];
153
+ this.advance();
154
+ return { type: 'OPERATOR', value: op, position: { start, end: this.pos } };
155
+ }
156
+
157
+ private readCharLiteral(start: number): Token {
158
+ this.advance(); // opening '
159
+
160
+ let value = "'";
161
+
162
+ // Handle escape sequences
163
+ if (this.input[this.pos] === '\\') {
164
+ value += this.input[this.pos];
165
+ this.advance();
166
+ }
167
+
168
+ if (this.pos < this.length) {
169
+ value += this.input[this.pos];
170
+ this.advance();
171
+ }
172
+
173
+ if (this.pos < this.length && this.input[this.pos] === "'") {
174
+ value += this.input[this.pos];
175
+ this.advance();
176
+ }
177
+
178
+ return { type: 'CHAR_LITERAL', value, position: { start, end: this.pos } };
179
+ }
180
+
181
+ private skipWhitespace(): void {
182
+ while (this.pos < this.length && this.isWhitespace(this.input[this.pos])) {
183
+ this.pos++;
184
+ }
185
+ }
186
+
187
+ private advance(): void {
188
+ this.pos++;
189
+ }
190
+
191
+ private peek(offset = 1): string {
192
+ return this.pos + offset < this.length ? this.input[this.pos + offset] : '';
193
+ }
194
+
195
+ private isWhitespace(char: string): boolean {
196
+ return /\s/.test(char);
197
+ }
198
+
199
+ private isDigit(char: string): boolean {
200
+ return /[0-9]/.test(char);
201
+ }
202
+
203
+ private isHexDigit(char: string): boolean {
204
+ return /[0-9a-fA-F]/.test(char);
205
+ }
206
+
207
+ private isOctalDigit(char: string): boolean {
208
+ return /[0-7]/.test(char);
209
+ }
210
+
211
+ private isOperatorChar(char: string): boolean {
212
+ return ['+', '-', '*', '/', '%', '&', '|', '^', '~', '!', '<', '>'].includes(char);
213
+ }
214
+ }
215
+
216
+ export type { Token, TokenType };
@@ -0,0 +1,154 @@
1
+ import { Token, ASTNode, BinaryOpNode, UnaryOpNode, LiteralNode, GroupNode, TokenType } from './ast.js';
2
+ import { ErrorHandler } from '../errors/handler.js';
3
+
4
+ // Operator precedence (higher number = higher precedence)
5
+ const PRECEDENCE: Record<string, number> = {
6
+ '(': 10,
7
+ ')': 10,
8
+ '~': 9,
9
+ '*': 8,
10
+ '/': 8,
11
+ '%': 8,
12
+ '+': 7,
13
+ '-': 7,
14
+ '<<': 6,
15
+ '>>': 6,
16
+ '>>>': 6,
17
+ '&': 5,
18
+ '^': 4,
19
+ '|': 3,
20
+ };
21
+
22
+ export class Parser {
23
+ private pos = 0;
24
+ private tokens: Token[] = [];
25
+ private errorHandler = new ErrorHandler();
26
+
27
+ parse(tokens: Token[]): ASTNode {
28
+ this.tokens = tokens;
29
+ this.pos = 0;
30
+
31
+ if (this.tokens.length === 0 || (this.tokens.length === 1 && this.tokens[0].type === 'EOF')) {
32
+ throw new Error('Expression cannot be empty');
33
+ }
34
+
35
+ const ast = this.parseExpression();
36
+
37
+ if (this.current()?.type !== 'EOF') {
38
+ const token = this.current()!;
39
+ throw this.errorHandler.syntaxError(
40
+ `Unexpected token: ${token.value}`,
41
+ token.position.start,
42
+ token.position.end
43
+ );
44
+ }
45
+
46
+ return ast;
47
+ }
48
+
49
+ private parseExpression(minPrecedence = 0): ASTNode {
50
+ let left = this.parseUnary();
51
+
52
+ while (true) {
53
+ const op = this.current();
54
+ if (!op || op.type !== 'OPERATOR') {
55
+ break;
56
+ }
57
+
58
+ const precedence = PRECEDENCE[op.value] ?? 0;
59
+ if (precedence < minPrecedence) {
60
+ break;
61
+ }
62
+
63
+ this.advance();
64
+ const right = this.parseExpression(precedence + 1);
65
+
66
+ left = {
67
+ type: 'BinaryOp',
68
+ operator: op.value,
69
+ left,
70
+ right,
71
+ position: { start: left.position?.start ?? 0, end: right.position?.end ?? 0 },
72
+ } as BinaryOpNode;
73
+ }
74
+
75
+ return left;
76
+ }
77
+
78
+ private parseUnary(): ASTNode {
79
+ const token = this.current();
80
+
81
+ if (token && token.type === 'OPERATOR' && (token.value === '~' || token.value === '-')) {
82
+ this.advance();
83
+ const operand = this.parseUnary();
84
+
85
+ return {
86
+ type: 'UnaryOp',
87
+ operator: token.value,
88
+ operand,
89
+ position: { start: token.position.start, end: operand.position?.end ?? token.position.end },
90
+ } as UnaryOpNode;
91
+ }
92
+
93
+ return this.parsePrimary();
94
+ }
95
+
96
+ private parsePrimary(): ASTNode {
97
+ const token = this.current();
98
+
99
+ if (!token || token.type === 'EOF') {
100
+ throw new Error('Unexpected end of input');
101
+ }
102
+
103
+ // Parenthesized expression
104
+ if (token.type === 'LPAREN') {
105
+ this.advance();
106
+ const expr = this.parseExpression();
107
+
108
+ const closing = this.current();
109
+ if (!closing || closing.type !== 'RPAREN') {
110
+ throw this.errorHandler.syntaxError(
111
+ 'Unmatched parenthesis: expected )',
112
+ token.position.start,
113
+ token.position.end
114
+ );
115
+ }
116
+ this.advance();
117
+
118
+ return {
119
+ type: 'Group',
120
+ expression: expr,
121
+ position: { start: token.position.start, end: closing.position.end },
122
+ } as GroupNode;
123
+ }
124
+
125
+ // Literal
126
+ if (this.isLiteral(token)) {
127
+ this.advance();
128
+ return {
129
+ type: 'Literal',
130
+ value: token.value,
131
+ tokenType: token.type,
132
+ position: token.position,
133
+ } as LiteralNode;
134
+ }
135
+
136
+ throw this.errorHandler.syntaxError(
137
+ `Unexpected token: ${token.value}`,
138
+ token.position.start,
139
+ token.position.end
140
+ );
141
+ }
142
+
143
+ private isLiteral(token: Token): boolean {
144
+ return ['NUMBER', 'HEX', 'BINARY', 'OCTAL', 'CHAR_LITERAL'].includes(token.type);
145
+ }
146
+
147
+ private current(): Token | undefined {
148
+ return this.tokens[this.pos];
149
+ }
150
+
151
+ private advance(): void {
152
+ this.pos++;
153
+ }
154
+ }