code-the-jewels 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 (60) hide show
  1. package/.claude/settings.local.json +7 -0
  2. package/PROMPTS/01-build-v0.1.md +10 -0
  3. package/README.md +186 -0
  4. package/dist/ast.d.ts +143 -0
  5. package/dist/ast.js +2 -0
  6. package/dist/cli.d.ts +2 -0
  7. package/dist/cli.js +145 -0
  8. package/dist/diagnostics.d.ts +7 -0
  9. package/dist/diagnostics.js +16 -0
  10. package/dist/generator.d.ts +11 -0
  11. package/dist/generator.js +126 -0
  12. package/dist/index.d.ts +7 -0
  13. package/dist/index.js +15 -0
  14. package/dist/lexer.d.ts +18 -0
  15. package/dist/lexer.js +210 -0
  16. package/dist/parser.d.ts +40 -0
  17. package/dist/parser.js +394 -0
  18. package/dist/repl.d.ts +1 -0
  19. package/dist/repl.js +132 -0
  20. package/dist/runtime/atl-data.d.ts +4 -0
  21. package/dist/runtime/atl-data.js +18 -0
  22. package/dist/runtime/atl-flow.d.ts +1 -0
  23. package/dist/runtime/atl-flow.js +5 -0
  24. package/dist/runtime/bk-parse.d.ts +3 -0
  25. package/dist/runtime/bk-parse.js +9 -0
  26. package/dist/runtime/bk-text.d.ts +5 -0
  27. package/dist/runtime/bk-text.js +13 -0
  28. package/dist/runtime/rtj-core.d.ts +1 -0
  29. package/dist/runtime/rtj-core.js +51 -0
  30. package/dist/semantic.d.ts +11 -0
  31. package/dist/semantic.js +153 -0
  32. package/dist/tests/basic.test.d.ts +1 -0
  33. package/dist/tests/basic.test.js +69 -0
  34. package/dist/token.d.ts +56 -0
  35. package/dist/token.js +77 -0
  36. package/examples/cities.rtj +11 -0
  37. package/examples/count-words.rtj +12 -0
  38. package/examples/duo.rtj +12 -0
  39. package/examples/hello.rtj +1 -0
  40. package/examples/pipes.rtj +6 -0
  41. package/package.json +22 -0
  42. package/public/_redirects +1 -0
  43. package/public/index.html +559 -0
  44. package/src/ast.ts +189 -0
  45. package/src/cli.ts +120 -0
  46. package/src/diagnostics.ts +15 -0
  47. package/src/generator.ts +129 -0
  48. package/src/index.ts +7 -0
  49. package/src/lexer.ts +208 -0
  50. package/src/parser.ts +461 -0
  51. package/src/repl.ts +105 -0
  52. package/src/runtime/atl-data.ts +11 -0
  53. package/src/runtime/atl-flow.ts +1 -0
  54. package/src/runtime/bk-parse.ts +3 -0
  55. package/src/runtime/bk-text.ts +5 -0
  56. package/src/runtime/rtj-core.ts +21 -0
  57. package/src/semantic.ts +144 -0
  58. package/src/tests/basic.test.ts +74 -0
  59. package/src/token.ts +85 -0
  60. package/tsconfig.json +15 -0
@@ -0,0 +1,18 @@
1
+ import { Token } from './token';
2
+ export declare class Lexer {
3
+ private source;
4
+ private pos;
5
+ private line;
6
+ private col;
7
+ constructor(source: string);
8
+ tokenize(): Token[];
9
+ private advance;
10
+ private peek;
11
+ private makeToken;
12
+ private isDigit;
13
+ private isAlpha;
14
+ private isAlphaNumeric;
15
+ private readString;
16
+ private readNumber;
17
+ private readIdentifier;
18
+ }
package/dist/lexer.js ADDED
@@ -0,0 +1,210 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.Lexer = void 0;
4
+ const token_1 = require("./token");
5
+ const diagnostics_1 = require("./diagnostics");
6
+ class Lexer {
7
+ constructor(source) {
8
+ this.pos = 0;
9
+ this.line = 1;
10
+ this.col = 1;
11
+ this.source = source;
12
+ }
13
+ tokenize() {
14
+ const tokens = [];
15
+ while (this.pos < this.source.length) {
16
+ const ch = this.source[this.pos];
17
+ // Skip whitespace (not newlines)
18
+ if (ch === ' ' || ch === '\t' || ch === '\r') {
19
+ this.advance();
20
+ continue;
21
+ }
22
+ // Newlines
23
+ if (ch === '\n') {
24
+ this.advance();
25
+ this.line++;
26
+ this.col = 1;
27
+ continue;
28
+ }
29
+ // Line comments
30
+ if (ch === '/' && this.peek(1) === '/') {
31
+ while (this.pos < this.source.length && this.source[this.pos] !== '\n') {
32
+ this.advance();
33
+ }
34
+ continue;
35
+ }
36
+ // Block comments
37
+ if (ch === '/' && this.peek(1) === '*') {
38
+ this.advance();
39
+ this.advance();
40
+ while (this.pos < this.source.length) {
41
+ if (this.source[this.pos] === '*' && this.peek(1) === '/') {
42
+ this.advance();
43
+ this.advance();
44
+ break;
45
+ }
46
+ if (this.source[this.pos] === '\n') {
47
+ this.line++;
48
+ this.col = 0;
49
+ }
50
+ this.advance();
51
+ }
52
+ continue;
53
+ }
54
+ // Strings
55
+ if (ch === '"' || ch === "'") {
56
+ tokens.push(this.readString(ch));
57
+ continue;
58
+ }
59
+ // Numbers
60
+ if (this.isDigit(ch)) {
61
+ tokens.push(this.readNumber());
62
+ continue;
63
+ }
64
+ // Identifiers / keywords
65
+ if (this.isAlpha(ch)) {
66
+ tokens.push(this.readIdentifier());
67
+ continue;
68
+ }
69
+ // Pipe operator |>
70
+ if (ch === '|' && this.peek(1) === '>') {
71
+ tokens.push(this.makeToken(token_1.TokenType.PIPE, '|>', 2));
72
+ continue;
73
+ }
74
+ // Two-char operators
75
+ if (ch === '=' && this.peek(1) === '=') {
76
+ tokens.push(this.makeToken(token_1.TokenType.EQ_EQ, '==', 2));
77
+ continue;
78
+ }
79
+ if (ch === '!' && this.peek(1) === '=') {
80
+ tokens.push(this.makeToken(token_1.TokenType.BANG_EQ, '!=', 2));
81
+ continue;
82
+ }
83
+ if (ch === '>' && this.peek(1) === '=') {
84
+ tokens.push(this.makeToken(token_1.TokenType.GT_EQ, '>=', 2));
85
+ continue;
86
+ }
87
+ if (ch === '<' && this.peek(1) === '=') {
88
+ tokens.push(this.makeToken(token_1.TokenType.LT_EQ, '<=', 2));
89
+ continue;
90
+ }
91
+ if (ch === '&' && this.peek(1) === '&') {
92
+ tokens.push(this.makeToken(token_1.TokenType.AND_AND, '&&', 2));
93
+ continue;
94
+ }
95
+ if (ch === '|' && this.peek(1) === '|') {
96
+ tokens.push(this.makeToken(token_1.TokenType.OR_OR, '||', 2));
97
+ continue;
98
+ }
99
+ // Single-char tokens
100
+ const singles = {
101
+ '+': token_1.TokenType.PLUS,
102
+ '-': token_1.TokenType.MINUS,
103
+ '*': token_1.TokenType.STAR,
104
+ '/': token_1.TokenType.SLASH,
105
+ '%': token_1.TokenType.PERCENT,
106
+ '>': token_1.TokenType.GT,
107
+ '<': token_1.TokenType.LT,
108
+ '!': token_1.TokenType.BANG,
109
+ '=': token_1.TokenType.ASSIGN,
110
+ '{': token_1.TokenType.LBRACE,
111
+ '}': token_1.TokenType.RBRACE,
112
+ '(': token_1.TokenType.LPAREN,
113
+ ')': token_1.TokenType.RPAREN,
114
+ '[': token_1.TokenType.LBRACKET,
115
+ ']': token_1.TokenType.RBRACKET,
116
+ ',': token_1.TokenType.COMMA,
117
+ ':': token_1.TokenType.COLON,
118
+ '.': token_1.TokenType.DOT,
119
+ ';': token_1.TokenType.SEMICOLON,
120
+ };
121
+ if (singles[ch]) {
122
+ tokens.push(this.makeToken(singles[ch], ch, 1));
123
+ continue;
124
+ }
125
+ throw new diagnostics_1.RTJError('SyntaxError', `unexpected character '${ch}'`, this.line, this.col);
126
+ }
127
+ tokens.push({ type: token_1.TokenType.EOF, value: '', line: this.line, column: this.col });
128
+ return tokens;
129
+ }
130
+ advance() {
131
+ this.pos++;
132
+ this.col++;
133
+ }
134
+ peek(offset) {
135
+ return this.source[this.pos + offset];
136
+ }
137
+ makeToken(type, value, length) {
138
+ const token = { type, value, line: this.line, column: this.col };
139
+ for (let i = 0; i < length; i++)
140
+ this.advance();
141
+ return token;
142
+ }
143
+ isDigit(ch) {
144
+ return ch >= '0' && ch <= '9';
145
+ }
146
+ isAlpha(ch) {
147
+ return (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') || ch === '_';
148
+ }
149
+ isAlphaNumeric(ch) {
150
+ return this.isAlpha(ch) || this.isDigit(ch);
151
+ }
152
+ readString(quote) {
153
+ const startCol = this.col;
154
+ this.advance(); // skip opening quote
155
+ let value = '';
156
+ while (this.pos < this.source.length && this.source[this.pos] !== quote) {
157
+ if (this.source[this.pos] === '\\') {
158
+ this.advance();
159
+ const esc = this.source[this.pos];
160
+ if (esc === 'n')
161
+ value += '\n';
162
+ else if (esc === 't')
163
+ value += '\t';
164
+ else if (esc === '\\')
165
+ value += '\\';
166
+ else if (esc === quote)
167
+ value += quote;
168
+ else
169
+ value += '\\' + esc;
170
+ }
171
+ else {
172
+ value += this.source[this.pos];
173
+ }
174
+ this.advance();
175
+ }
176
+ if (this.pos >= this.source.length) {
177
+ throw new diagnostics_1.RTJError('SyntaxError', 'unterminated string', this.line, startCol);
178
+ }
179
+ this.advance(); // skip closing quote
180
+ return { type: token_1.TokenType.STRING, value, line: this.line, column: startCol };
181
+ }
182
+ readNumber() {
183
+ const startCol = this.col;
184
+ let value = '';
185
+ while (this.pos < this.source.length && this.isDigit(this.source[this.pos])) {
186
+ value += this.source[this.pos];
187
+ this.advance();
188
+ }
189
+ if (this.pos < this.source.length && this.source[this.pos] === '.' && this.peek(1) && this.isDigit(this.peek(1))) {
190
+ value += '.';
191
+ this.advance();
192
+ while (this.pos < this.source.length && this.isDigit(this.source[this.pos])) {
193
+ value += this.source[this.pos];
194
+ this.advance();
195
+ }
196
+ }
197
+ return { type: token_1.TokenType.NUMBER, value, line: this.line, column: startCol };
198
+ }
199
+ readIdentifier() {
200
+ const startCol = this.col;
201
+ let value = '';
202
+ while (this.pos < this.source.length && this.isAlphaNumeric(this.source[this.pos])) {
203
+ value += this.source[this.pos];
204
+ this.advance();
205
+ }
206
+ const type = token_1.KEYWORDS[value] || token_1.TokenType.IDENTIFIER;
207
+ return { type, value, line: this.line, column: startCol };
208
+ }
209
+ }
210
+ exports.Lexer = Lexer;
@@ -0,0 +1,40 @@
1
+ import { Token } from './token';
2
+ import { Program } from './ast';
3
+ export declare class Parser {
4
+ private tokens;
5
+ private pos;
6
+ constructor(tokens: Token[]);
7
+ parse(): Program;
8
+ private parseStatement;
9
+ private parseVarDecl;
10
+ private parseFunctionDecl;
11
+ private parseReturnStmt;
12
+ private parseTalkStmt;
13
+ private parseIfStmt;
14
+ private parseLoopStmt;
15
+ private parseImportStmt;
16
+ private parseThrowStmt;
17
+ private parseBlock;
18
+ private parseExpressionStmt;
19
+ private parseExpression;
20
+ private parsePipeExpr;
21
+ private parseOr;
22
+ private parseAnd;
23
+ private parseEquality;
24
+ private parseComparison;
25
+ private parseAddition;
26
+ private parseMultiplication;
27
+ private parseUnary;
28
+ private parsePostfix;
29
+ private parsePrimary;
30
+ private parseDuoExpr;
31
+ private parsePipelineList;
32
+ private parseArrayLiteral;
33
+ private current;
34
+ private advance;
35
+ private expect;
36
+ private isKeywordUsableAsIdentifier;
37
+ private expectIdentifierOrKeyword;
38
+ private isAtEnd;
39
+ private skipSemicolons;
40
+ }
package/dist/parser.js ADDED
@@ -0,0 +1,394 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.Parser = void 0;
4
+ const token_1 = require("./token");
5
+ const diagnostics_1 = require("./diagnostics");
6
+ class Parser {
7
+ constructor(tokens) {
8
+ this.pos = 0;
9
+ this.tokens = tokens;
10
+ }
11
+ parse() {
12
+ const body = [];
13
+ while (!this.isAtEnd()) {
14
+ this.skipSemicolons();
15
+ if (!this.isAtEnd()) {
16
+ body.push(this.parseStatement());
17
+ }
18
+ }
19
+ return { type: 'Program', body };
20
+ }
21
+ parseStatement() {
22
+ const tok = this.current();
23
+ switch (tok.type) {
24
+ case token_1.TokenType.JEWEL: return this.parseVarDecl();
25
+ case token_1.TokenType.VERSE: return this.parseFunctionDecl();
26
+ case token_1.TokenType.SEND: return this.parseReturnStmt();
27
+ case token_1.TokenType.TALK: return this.parseTalkStmt();
28
+ case token_1.TokenType.IFWILD: return this.parseIfStmt();
29
+ case token_1.TokenType.RUN: return this.parseLoopStmt();
30
+ case token_1.TokenType.FEATURE: return this.parseImportStmt();
31
+ case token_1.TokenType.YANK: return this.parseThrowStmt();
32
+ case token_1.TokenType.LBRACE: return this.parseBlock();
33
+ default: return this.parseExpressionStmt();
34
+ }
35
+ }
36
+ parseVarDecl() {
37
+ const loc = { line: this.current().line, column: this.current().column };
38
+ this.expect(token_1.TokenType.JEWEL);
39
+ const name = this.expectIdentifierOrKeyword().value;
40
+ this.expect(token_1.TokenType.ASSIGN);
41
+ const init = this.parseExpression();
42
+ this.skipSemicolons();
43
+ return { type: 'VarDecl', name, init, loc };
44
+ }
45
+ parseFunctionDecl() {
46
+ const loc = { line: this.current().line, column: this.current().column };
47
+ this.expect(token_1.TokenType.VERSE);
48
+ const name = this.expect(token_1.TokenType.IDENTIFIER).value;
49
+ this.expect(token_1.TokenType.LPAREN);
50
+ const params = [];
51
+ if (this.current().type !== token_1.TokenType.RPAREN) {
52
+ params.push(this.expect(token_1.TokenType.IDENTIFIER).value);
53
+ while (this.current().type === token_1.TokenType.COMMA) {
54
+ this.advance();
55
+ params.push(this.expect(token_1.TokenType.IDENTIFIER).value);
56
+ }
57
+ }
58
+ this.expect(token_1.TokenType.RPAREN);
59
+ const body = this.parseBlock();
60
+ return { type: 'FunctionDecl', name, params, body, loc };
61
+ }
62
+ parseReturnStmt() {
63
+ const loc = { line: this.current().line, column: this.current().column };
64
+ this.expect(token_1.TokenType.SEND);
65
+ let value;
66
+ if (!this.isAtEnd() && this.current().type !== token_1.TokenType.RBRACE && this.current().type !== token_1.TokenType.SEMICOLON) {
67
+ value = this.parseExpression();
68
+ }
69
+ this.skipSemicolons();
70
+ return { type: 'ReturnStmt', value, loc };
71
+ }
72
+ parseTalkStmt() {
73
+ const loc = { line: this.current().line, column: this.current().column };
74
+ this.expect(token_1.TokenType.TALK);
75
+ const value = this.parseExpression();
76
+ this.skipSemicolons();
77
+ return { type: 'TalkStmt', value, loc };
78
+ }
79
+ parseIfStmt() {
80
+ const loc = { line: this.current().line, column: this.current().column };
81
+ this.expect(token_1.TokenType.IFWILD);
82
+ const condition = this.parseExpression();
83
+ const consequent = this.parseBlock();
84
+ let alternate;
85
+ if (this.current().type === token_1.TokenType.ELSEWILD) {
86
+ this.advance();
87
+ alternate = this.parseBlock();
88
+ }
89
+ return { type: 'IfStmt', condition, consequent, alternate, loc };
90
+ }
91
+ parseLoopStmt() {
92
+ const loc = { line: this.current().line, column: this.current().column };
93
+ this.expect(token_1.TokenType.RUN);
94
+ const variable = this.expect(token_1.TokenType.IDENTIFIER).value;
95
+ this.expect(token_1.TokenType.IN);
96
+ const iterable = this.parseExpression();
97
+ const body = this.parseBlock();
98
+ return { type: 'LoopStmt', variable, iterable, body, loc };
99
+ }
100
+ parseImportStmt() {
101
+ const loc = { line: this.current().line, column: this.current().column };
102
+ this.expect(token_1.TokenType.FEATURE);
103
+ const names = [];
104
+ names.push(this.expect(token_1.TokenType.IDENTIFIER).value);
105
+ while (this.current().type === token_1.TokenType.COMMA) {
106
+ this.advance();
107
+ names.push(this.expect(token_1.TokenType.IDENTIFIER).value);
108
+ }
109
+ this.expect(token_1.TokenType.FROM);
110
+ const source = this.expect(token_1.TokenType.STRING).value;
111
+ this.skipSemicolons();
112
+ return { type: 'ImportStmt', names, source, loc };
113
+ }
114
+ parseThrowStmt() {
115
+ const loc = { line: this.current().line, column: this.current().column };
116
+ this.expect(token_1.TokenType.YANK);
117
+ const value = this.parseExpression();
118
+ this.skipSemicolons();
119
+ return { type: 'ThrowStmt', value, loc };
120
+ }
121
+ parseBlock() {
122
+ const loc = { line: this.current().line, column: this.current().column };
123
+ this.expect(token_1.TokenType.LBRACE);
124
+ const body = [];
125
+ while (this.current().type !== token_1.TokenType.RBRACE && !this.isAtEnd()) {
126
+ this.skipSemicolons();
127
+ if (this.current().type !== token_1.TokenType.RBRACE) {
128
+ body.push(this.parseStatement());
129
+ }
130
+ }
131
+ this.expect(token_1.TokenType.RBRACE);
132
+ return { type: 'BlockStmt', body, loc };
133
+ }
134
+ parseExpressionStmt() {
135
+ const loc = { line: this.current().line, column: this.current().column };
136
+ const expr = this.parseExpression();
137
+ this.skipSemicolons();
138
+ return { type: 'ExpressionStmt', expr, loc };
139
+ }
140
+ parseExpression() {
141
+ return this.parsePipeExpr();
142
+ }
143
+ parsePipeExpr() {
144
+ let left = this.parseOr();
145
+ if (this.current().type === token_1.TokenType.PIPE) {
146
+ const loc = { line: this.current().line, column: this.current().column };
147
+ const steps = [left];
148
+ while (this.current().type === token_1.TokenType.PIPE) {
149
+ this.advance();
150
+ steps.push(this.parseOr());
151
+ }
152
+ return { type: 'PipeExpr', steps, loc };
153
+ }
154
+ return left;
155
+ }
156
+ parseOr() {
157
+ let left = this.parseAnd();
158
+ while (this.current().type === token_1.TokenType.OR_OR) {
159
+ const op = this.advance().value;
160
+ const right = this.parseAnd();
161
+ left = { type: 'BinaryExpr', op, left, right, loc: left.loc };
162
+ }
163
+ return left;
164
+ }
165
+ parseAnd() {
166
+ let left = this.parseEquality();
167
+ while (this.current().type === token_1.TokenType.AND_AND) {
168
+ const op = this.advance().value;
169
+ const right = this.parseEquality();
170
+ left = { type: 'BinaryExpr', op, left, right, loc: left.loc };
171
+ }
172
+ return left;
173
+ }
174
+ parseEquality() {
175
+ let left = this.parseComparison();
176
+ while (this.current().type === token_1.TokenType.EQ_EQ || this.current().type === token_1.TokenType.BANG_EQ) {
177
+ const op = this.advance().value;
178
+ const right = this.parseComparison();
179
+ left = { type: 'BinaryExpr', op, left, right, loc: left.loc };
180
+ }
181
+ return left;
182
+ }
183
+ parseComparison() {
184
+ let left = this.parseAddition();
185
+ while (this.current().type === token_1.TokenType.GT ||
186
+ this.current().type === token_1.TokenType.LT ||
187
+ this.current().type === token_1.TokenType.GT_EQ ||
188
+ this.current().type === token_1.TokenType.LT_EQ) {
189
+ const op = this.advance().value;
190
+ const right = this.parseAddition();
191
+ left = { type: 'BinaryExpr', op, left, right, loc: left.loc };
192
+ }
193
+ return left;
194
+ }
195
+ parseAddition() {
196
+ let left = this.parseMultiplication();
197
+ while (this.current().type === token_1.TokenType.PLUS || this.current().type === token_1.TokenType.MINUS) {
198
+ const op = this.advance().value;
199
+ const right = this.parseMultiplication();
200
+ left = { type: 'BinaryExpr', op, left, right, loc: left.loc };
201
+ }
202
+ return left;
203
+ }
204
+ parseMultiplication() {
205
+ let left = this.parseUnary();
206
+ while (this.current().type === token_1.TokenType.STAR ||
207
+ this.current().type === token_1.TokenType.SLASH ||
208
+ this.current().type === token_1.TokenType.PERCENT) {
209
+ const op = this.advance().value;
210
+ const right = this.parseUnary();
211
+ left = { type: 'BinaryExpr', op, left, right, loc: left.loc };
212
+ }
213
+ return left;
214
+ }
215
+ parseUnary() {
216
+ if (this.current().type === token_1.TokenType.BANG || this.current().type === token_1.TokenType.MINUS) {
217
+ const loc = { line: this.current().line, column: this.current().column };
218
+ const op = this.advance().value;
219
+ const operand = this.parseUnary();
220
+ return { type: 'UnaryExpr', op, operand, loc };
221
+ }
222
+ return this.parsePostfix();
223
+ }
224
+ parsePostfix() {
225
+ let expr = this.parsePrimary();
226
+ while (true) {
227
+ if (this.current().type === token_1.TokenType.LPAREN) {
228
+ this.advance();
229
+ const args = [];
230
+ if (this.current().type !== token_1.TokenType.RPAREN) {
231
+ args.push(this.parseExpression());
232
+ while (this.current().type === token_1.TokenType.COMMA) {
233
+ this.advance();
234
+ args.push(this.parseExpression());
235
+ }
236
+ }
237
+ this.expect(token_1.TokenType.RPAREN);
238
+ expr = { type: 'CallExpr', callee: expr, args, loc: expr.loc };
239
+ }
240
+ else if (this.current().type === token_1.TokenType.DOT) {
241
+ this.advance();
242
+ const prop = this.expect(token_1.TokenType.IDENTIFIER);
243
+ expr = {
244
+ type: 'MemberExpr',
245
+ object: expr,
246
+ property: { type: 'Identifier', name: prop.value, loc: { line: prop.line, column: prop.column } },
247
+ computed: false,
248
+ loc: expr.loc,
249
+ };
250
+ }
251
+ else if (this.current().type === token_1.TokenType.LBRACKET) {
252
+ this.advance();
253
+ const index = this.parseExpression();
254
+ this.expect(token_1.TokenType.RBRACKET);
255
+ expr = {
256
+ type: 'MemberExpr',
257
+ object: expr,
258
+ property: index,
259
+ computed: true,
260
+ loc: expr.loc,
261
+ };
262
+ }
263
+ else {
264
+ break;
265
+ }
266
+ }
267
+ return expr;
268
+ }
269
+ parsePrimary() {
270
+ const tok = this.current();
271
+ // duo expression
272
+ if (tok.type === token_1.TokenType.DUO) {
273
+ return this.parseDuoExpr();
274
+ }
275
+ // Grouped expression
276
+ if (tok.type === token_1.TokenType.LPAREN) {
277
+ this.advance();
278
+ const expr = this.parseExpression();
279
+ this.expect(token_1.TokenType.RPAREN);
280
+ return expr;
281
+ }
282
+ // Array literal
283
+ if (tok.type === token_1.TokenType.LBRACKET) {
284
+ return this.parseArrayLiteral();
285
+ }
286
+ // Object literal (only when not ambiguous with block)
287
+ // Objects are parsed in specific contexts (e.g., after =)
288
+ // String
289
+ if (tok.type === token_1.TokenType.STRING) {
290
+ this.advance();
291
+ return { type: 'StringLiteral', value: tok.value, loc: { line: tok.line, column: tok.column } };
292
+ }
293
+ // Number
294
+ if (tok.type === token_1.TokenType.NUMBER) {
295
+ this.advance();
296
+ return { type: 'NumberLiteral', value: Number(tok.value), loc: { line: tok.line, column: tok.column } };
297
+ }
298
+ // Boolean
299
+ if (tok.type === token_1.TokenType.BOOLEAN) {
300
+ this.advance();
301
+ return { type: 'BooleanLiteral', value: tok.value === 'true', loc: { line: tok.line, column: tok.column } };
302
+ }
303
+ // Null
304
+ if (tok.type === token_1.TokenType.NULL) {
305
+ this.advance();
306
+ return { type: 'NullLiteral', loc: { line: tok.line, column: tok.column } };
307
+ }
308
+ // Identifier (including keywords used as variable names in expression context)
309
+ if (tok.type === token_1.TokenType.IDENTIFIER || this.isKeywordUsableAsIdentifier(tok.type)) {
310
+ this.advance();
311
+ return { type: 'Identifier', name: tok.value, loc: { line: tok.line, column: tok.column } };
312
+ }
313
+ throw new diagnostics_1.RTJError('SyntaxError', `unexpected token '${tok.value}'`, tok.line, tok.column);
314
+ }
315
+ parseDuoExpr() {
316
+ const loc = { line: this.current().line, column: this.current().column };
317
+ this.expect(token_1.TokenType.DUO);
318
+ this.expect(token_1.TokenType.LPAREN);
319
+ const input = this.parseExpression();
320
+ this.expect(token_1.TokenType.RPAREN);
321
+ this.expect(token_1.TokenType.LBRACE);
322
+ this.expect(token_1.TokenType.MIKE);
323
+ this.expect(token_1.TokenType.COLON);
324
+ const mikePipeline = this.parsePipelineList();
325
+ this.expect(token_1.TokenType.EL);
326
+ this.expect(token_1.TokenType.COLON);
327
+ const elPipeline = this.parsePipelineList();
328
+ this.expect(token_1.TokenType.RBRACE);
329
+ return { type: 'DuoExpr', input, mikePipeline, elPipeline, loc };
330
+ }
331
+ parsePipelineList() {
332
+ const steps = [];
333
+ steps.push(this.parsePostfix());
334
+ while (this.current().type === token_1.TokenType.PIPE) {
335
+ this.advance();
336
+ steps.push(this.parsePostfix());
337
+ }
338
+ return steps;
339
+ }
340
+ parseArrayLiteral() {
341
+ const loc = { line: this.current().line, column: this.current().column };
342
+ this.expect(token_1.TokenType.LBRACKET);
343
+ const elements = [];
344
+ if (this.current().type !== token_1.TokenType.RBRACKET) {
345
+ elements.push(this.parseExpression());
346
+ while (this.current().type === token_1.TokenType.COMMA) {
347
+ this.advance();
348
+ elements.push(this.parseExpression());
349
+ }
350
+ }
351
+ this.expect(token_1.TokenType.RBRACKET);
352
+ return { type: 'ArrayLiteral', elements, loc };
353
+ }
354
+ current() {
355
+ return this.tokens[this.pos] || { type: token_1.TokenType.EOF, value: '', line: 0, column: 0 };
356
+ }
357
+ advance() {
358
+ const tok = this.current();
359
+ this.pos++;
360
+ return tok;
361
+ }
362
+ expect(type) {
363
+ const tok = this.current();
364
+ if (tok.type !== type) {
365
+ throw new diagnostics_1.RTJError('SyntaxError', `beat slipped near line ${tok.line}, expected '${type}' but got '${tok.type}'`, tok.line, tok.column);
366
+ }
367
+ return this.advance();
368
+ }
369
+ isKeywordUsableAsIdentifier(type) {
370
+ // Keywords that can appear as variable names in expression contexts
371
+ return type === token_1.TokenType.VERSE || type === token_1.TokenType.JEWEL ||
372
+ type === token_1.TokenType.SEND || type === token_1.TokenType.TALK ||
373
+ type === token_1.TokenType.RUN || type === token_1.TokenType.IN ||
374
+ type === token_1.TokenType.FEATURE || type === token_1.TokenType.FROM ||
375
+ type === token_1.TokenType.YANK || type === token_1.TokenType.MIKE ||
376
+ type === token_1.TokenType.EL;
377
+ }
378
+ expectIdentifierOrKeyword() {
379
+ const tok = this.current();
380
+ if (tok.type === token_1.TokenType.IDENTIFIER || this.isKeywordUsableAsIdentifier(tok.type)) {
381
+ return this.advance();
382
+ }
383
+ throw new diagnostics_1.RTJError('SyntaxError', `beat slipped near line ${tok.line}, expected identifier but got '${tok.type}'`, tok.line, tok.column);
384
+ }
385
+ isAtEnd() {
386
+ return this.current().type === token_1.TokenType.EOF;
387
+ }
388
+ skipSemicolons() {
389
+ while (this.current().type === token_1.TokenType.SEMICOLON) {
390
+ this.advance();
391
+ }
392
+ }
393
+ }
394
+ exports.Parser = Parser;
package/dist/repl.d.ts ADDED
@@ -0,0 +1 @@
1
+ export declare function startRepl(): void;