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.
- package/.claude/settings.local.json +7 -0
- package/PROMPTS/01-build-v0.1.md +10 -0
- package/README.md +186 -0
- package/dist/ast.d.ts +143 -0
- package/dist/ast.js +2 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +145 -0
- package/dist/diagnostics.d.ts +7 -0
- package/dist/diagnostics.js +16 -0
- package/dist/generator.d.ts +11 -0
- package/dist/generator.js +126 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +15 -0
- package/dist/lexer.d.ts +18 -0
- package/dist/lexer.js +210 -0
- package/dist/parser.d.ts +40 -0
- package/dist/parser.js +394 -0
- package/dist/repl.d.ts +1 -0
- package/dist/repl.js +132 -0
- package/dist/runtime/atl-data.d.ts +4 -0
- package/dist/runtime/atl-data.js +18 -0
- package/dist/runtime/atl-flow.d.ts +1 -0
- package/dist/runtime/atl-flow.js +5 -0
- package/dist/runtime/bk-parse.d.ts +3 -0
- package/dist/runtime/bk-parse.js +9 -0
- package/dist/runtime/bk-text.d.ts +5 -0
- package/dist/runtime/bk-text.js +13 -0
- package/dist/runtime/rtj-core.d.ts +1 -0
- package/dist/runtime/rtj-core.js +51 -0
- package/dist/semantic.d.ts +11 -0
- package/dist/semantic.js +153 -0
- package/dist/tests/basic.test.d.ts +1 -0
- package/dist/tests/basic.test.js +69 -0
- package/dist/token.d.ts +56 -0
- package/dist/token.js +77 -0
- package/examples/cities.rtj +11 -0
- package/examples/count-words.rtj +12 -0
- package/examples/duo.rtj +12 -0
- package/examples/hello.rtj +1 -0
- package/examples/pipes.rtj +6 -0
- package/package.json +22 -0
- package/public/_redirects +1 -0
- package/public/index.html +559 -0
- package/src/ast.ts +189 -0
- package/src/cli.ts +120 -0
- package/src/diagnostics.ts +15 -0
- package/src/generator.ts +129 -0
- package/src/index.ts +7 -0
- package/src/lexer.ts +208 -0
- package/src/parser.ts +461 -0
- package/src/repl.ts +105 -0
- package/src/runtime/atl-data.ts +11 -0
- package/src/runtime/atl-flow.ts +1 -0
- package/src/runtime/bk-parse.ts +3 -0
- package/src/runtime/bk-text.ts +5 -0
- package/src/runtime/rtj-core.ts +21 -0
- package/src/semantic.ts +144 -0
- package/src/tests/basic.test.ts +74 -0
- package/src/token.ts +85 -0
- package/tsconfig.json +15 -0
package/src/parser.ts
ADDED
|
@@ -0,0 +1,461 @@
|
|
|
1
|
+
import { Token, TokenType } from './token';
|
|
2
|
+
import { RTJError } from './diagnostics';
|
|
3
|
+
import {
|
|
4
|
+
Program, Statement, Expression, BlockStmt, VarDecl, FunctionDecl,
|
|
5
|
+
ReturnStmt, TalkStmt, IfStmt, LoopStmt, ImportStmt, ThrowStmt,
|
|
6
|
+
ExpressionStmt, Identifier, StringLiteral, NumberLiteral,
|
|
7
|
+
BooleanLiteral, NullLiteral, ArrayLiteral, ObjectLiteral,
|
|
8
|
+
BinaryExpr, UnaryExpr, CallExpr, MemberExpr, PipeExpr, DuoExpr,
|
|
9
|
+
} from './ast';
|
|
10
|
+
|
|
11
|
+
export class Parser {
|
|
12
|
+
private tokens: Token[];
|
|
13
|
+
private pos: number = 0;
|
|
14
|
+
|
|
15
|
+
constructor(tokens: Token[]) {
|
|
16
|
+
this.tokens = tokens;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
parse(): Program {
|
|
20
|
+
const body: Statement[] = [];
|
|
21
|
+
while (!this.isAtEnd()) {
|
|
22
|
+
this.skipSemicolons();
|
|
23
|
+
if (!this.isAtEnd()) {
|
|
24
|
+
body.push(this.parseStatement());
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
return { type: 'Program', body };
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
private parseStatement(): Statement {
|
|
31
|
+
const tok = this.current();
|
|
32
|
+
|
|
33
|
+
switch (tok.type) {
|
|
34
|
+
case TokenType.JEWEL: return this.parseVarDecl();
|
|
35
|
+
case TokenType.VERSE: return this.parseFunctionDecl();
|
|
36
|
+
case TokenType.SEND: return this.parseReturnStmt();
|
|
37
|
+
case TokenType.TALK: return this.parseTalkStmt();
|
|
38
|
+
case TokenType.IFWILD: return this.parseIfStmt();
|
|
39
|
+
case TokenType.RUN: return this.parseLoopStmt();
|
|
40
|
+
case TokenType.FEATURE: return this.parseImportStmt();
|
|
41
|
+
case TokenType.YANK: return this.parseThrowStmt();
|
|
42
|
+
case TokenType.LBRACE: return this.parseBlock();
|
|
43
|
+
default: return this.parseExpressionStmt();
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
private parseVarDecl(): VarDecl {
|
|
48
|
+
const loc = { line: this.current().line, column: this.current().column };
|
|
49
|
+
this.expect(TokenType.JEWEL);
|
|
50
|
+
const name = this.expectIdentifierOrKeyword().value;
|
|
51
|
+
this.expect(TokenType.ASSIGN);
|
|
52
|
+
const init = this.parseExpression();
|
|
53
|
+
this.skipSemicolons();
|
|
54
|
+
return { type: 'VarDecl', name, init, loc };
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
private parseFunctionDecl(): FunctionDecl {
|
|
58
|
+
const loc = { line: this.current().line, column: this.current().column };
|
|
59
|
+
this.expect(TokenType.VERSE);
|
|
60
|
+
const name = this.expect(TokenType.IDENTIFIER).value;
|
|
61
|
+
this.expect(TokenType.LPAREN);
|
|
62
|
+
const params: string[] = [];
|
|
63
|
+
if (this.current().type !== TokenType.RPAREN) {
|
|
64
|
+
params.push(this.expect(TokenType.IDENTIFIER).value);
|
|
65
|
+
while (this.current().type === TokenType.COMMA) {
|
|
66
|
+
this.advance();
|
|
67
|
+
params.push(this.expect(TokenType.IDENTIFIER).value);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
this.expect(TokenType.RPAREN);
|
|
71
|
+
const body = this.parseBlock();
|
|
72
|
+
return { type: 'FunctionDecl', name, params, body, loc };
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
private parseReturnStmt(): ReturnStmt {
|
|
76
|
+
const loc = { line: this.current().line, column: this.current().column };
|
|
77
|
+
this.expect(TokenType.SEND);
|
|
78
|
+
let value: Expression | undefined;
|
|
79
|
+
if (!this.isAtEnd() && this.current().type !== TokenType.RBRACE && this.current().type !== TokenType.SEMICOLON) {
|
|
80
|
+
value = this.parseExpression();
|
|
81
|
+
}
|
|
82
|
+
this.skipSemicolons();
|
|
83
|
+
return { type: 'ReturnStmt', value, loc };
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
private parseTalkStmt(): TalkStmt {
|
|
87
|
+
const loc = { line: this.current().line, column: this.current().column };
|
|
88
|
+
this.expect(TokenType.TALK);
|
|
89
|
+
const value = this.parseExpression();
|
|
90
|
+
this.skipSemicolons();
|
|
91
|
+
return { type: 'TalkStmt', value, loc };
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
private parseIfStmt(): IfStmt {
|
|
95
|
+
const loc = { line: this.current().line, column: this.current().column };
|
|
96
|
+
this.expect(TokenType.IFWILD);
|
|
97
|
+
const condition = this.parseExpression();
|
|
98
|
+
const consequent = this.parseBlock();
|
|
99
|
+
let alternate: BlockStmt | undefined;
|
|
100
|
+
if (this.current().type === TokenType.ELSEWILD) {
|
|
101
|
+
this.advance();
|
|
102
|
+
alternate = this.parseBlock();
|
|
103
|
+
}
|
|
104
|
+
return { type: 'IfStmt', condition, consequent, alternate, loc };
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
private parseLoopStmt(): LoopStmt {
|
|
108
|
+
const loc = { line: this.current().line, column: this.current().column };
|
|
109
|
+
this.expect(TokenType.RUN);
|
|
110
|
+
const variable = this.expect(TokenType.IDENTIFIER).value;
|
|
111
|
+
this.expect(TokenType.IN);
|
|
112
|
+
const iterable = this.parseExpression();
|
|
113
|
+
const body = this.parseBlock();
|
|
114
|
+
return { type: 'LoopStmt', variable, iterable, body, loc };
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
private parseImportStmt(): ImportStmt {
|
|
118
|
+
const loc = { line: this.current().line, column: this.current().column };
|
|
119
|
+
this.expect(TokenType.FEATURE);
|
|
120
|
+
const names: string[] = [];
|
|
121
|
+
names.push(this.expect(TokenType.IDENTIFIER).value);
|
|
122
|
+
while (this.current().type === TokenType.COMMA) {
|
|
123
|
+
this.advance();
|
|
124
|
+
names.push(this.expect(TokenType.IDENTIFIER).value);
|
|
125
|
+
}
|
|
126
|
+
this.expect(TokenType.FROM);
|
|
127
|
+
const source = this.expect(TokenType.STRING).value;
|
|
128
|
+
this.skipSemicolons();
|
|
129
|
+
return { type: 'ImportStmt', names, source, loc };
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
private parseThrowStmt(): ThrowStmt {
|
|
133
|
+
const loc = { line: this.current().line, column: this.current().column };
|
|
134
|
+
this.expect(TokenType.YANK);
|
|
135
|
+
const value = this.parseExpression();
|
|
136
|
+
this.skipSemicolons();
|
|
137
|
+
return { type: 'ThrowStmt', value, loc };
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
private parseBlock(): BlockStmt {
|
|
141
|
+
const loc = { line: this.current().line, column: this.current().column };
|
|
142
|
+
this.expect(TokenType.LBRACE);
|
|
143
|
+
const body: Statement[] = [];
|
|
144
|
+
while (this.current().type !== TokenType.RBRACE && !this.isAtEnd()) {
|
|
145
|
+
this.skipSemicolons();
|
|
146
|
+
if (this.current().type !== TokenType.RBRACE) {
|
|
147
|
+
body.push(this.parseStatement());
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
this.expect(TokenType.RBRACE);
|
|
151
|
+
return { type: 'BlockStmt', body, loc };
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
private parseExpressionStmt(): ExpressionStmt {
|
|
155
|
+
const loc = { line: this.current().line, column: this.current().column };
|
|
156
|
+
const expr = this.parseExpression();
|
|
157
|
+
this.skipSemicolons();
|
|
158
|
+
return { type: 'ExpressionStmt', expr, loc };
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
private parseExpression(): Expression {
|
|
162
|
+
return this.parsePipeExpr();
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
private parsePipeExpr(): Expression {
|
|
166
|
+
let left = this.parseOr();
|
|
167
|
+
if (this.current().type === TokenType.PIPE) {
|
|
168
|
+
const loc = { line: this.current().line, column: this.current().column };
|
|
169
|
+
const steps: Expression[] = [left];
|
|
170
|
+
while (this.current().type === TokenType.PIPE) {
|
|
171
|
+
this.advance();
|
|
172
|
+
steps.push(this.parseOr());
|
|
173
|
+
}
|
|
174
|
+
return { type: 'PipeExpr', steps, loc } as PipeExpr;
|
|
175
|
+
}
|
|
176
|
+
return left;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
private parseOr(): Expression {
|
|
180
|
+
let left = this.parseAnd();
|
|
181
|
+
while (this.current().type === TokenType.OR_OR) {
|
|
182
|
+
const op = this.advance().value;
|
|
183
|
+
const right = this.parseAnd();
|
|
184
|
+
left = { type: 'BinaryExpr', op, left, right, loc: (left as any).loc } as BinaryExpr;
|
|
185
|
+
}
|
|
186
|
+
return left;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
private parseAnd(): Expression {
|
|
190
|
+
let left = this.parseEquality();
|
|
191
|
+
while (this.current().type === TokenType.AND_AND) {
|
|
192
|
+
const op = this.advance().value;
|
|
193
|
+
const right = this.parseEquality();
|
|
194
|
+
left = { type: 'BinaryExpr', op, left, right, loc: (left as any).loc } as BinaryExpr;
|
|
195
|
+
}
|
|
196
|
+
return left;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
private parseEquality(): Expression {
|
|
200
|
+
let left = this.parseComparison();
|
|
201
|
+
while (this.current().type === TokenType.EQ_EQ || this.current().type === TokenType.BANG_EQ) {
|
|
202
|
+
const op = this.advance().value;
|
|
203
|
+
const right = this.parseComparison();
|
|
204
|
+
left = { type: 'BinaryExpr', op, left, right, loc: (left as any).loc } as BinaryExpr;
|
|
205
|
+
}
|
|
206
|
+
return left;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
private parseComparison(): Expression {
|
|
210
|
+
let left = this.parseAddition();
|
|
211
|
+
while (
|
|
212
|
+
this.current().type === TokenType.GT ||
|
|
213
|
+
this.current().type === TokenType.LT ||
|
|
214
|
+
this.current().type === TokenType.GT_EQ ||
|
|
215
|
+
this.current().type === TokenType.LT_EQ
|
|
216
|
+
) {
|
|
217
|
+
const op = this.advance().value;
|
|
218
|
+
const right = this.parseAddition();
|
|
219
|
+
left = { type: 'BinaryExpr', op, left, right, loc: (left as any).loc } as BinaryExpr;
|
|
220
|
+
}
|
|
221
|
+
return left;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
private parseAddition(): Expression {
|
|
225
|
+
let left = this.parseMultiplication();
|
|
226
|
+
while (this.current().type === TokenType.PLUS || this.current().type === TokenType.MINUS) {
|
|
227
|
+
const op = this.advance().value;
|
|
228
|
+
const right = this.parseMultiplication();
|
|
229
|
+
left = { type: 'BinaryExpr', op, left, right, loc: (left as any).loc } as BinaryExpr;
|
|
230
|
+
}
|
|
231
|
+
return left;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
private parseMultiplication(): Expression {
|
|
235
|
+
let left = this.parseUnary();
|
|
236
|
+
while (
|
|
237
|
+
this.current().type === TokenType.STAR ||
|
|
238
|
+
this.current().type === TokenType.SLASH ||
|
|
239
|
+
this.current().type === TokenType.PERCENT
|
|
240
|
+
) {
|
|
241
|
+
const op = this.advance().value;
|
|
242
|
+
const right = this.parseUnary();
|
|
243
|
+
left = { type: 'BinaryExpr', op, left, right, loc: (left as any).loc } as BinaryExpr;
|
|
244
|
+
}
|
|
245
|
+
return left;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
private parseUnary(): Expression {
|
|
249
|
+
if (this.current().type === TokenType.BANG || this.current().type === TokenType.MINUS) {
|
|
250
|
+
const loc = { line: this.current().line, column: this.current().column };
|
|
251
|
+
const op = this.advance().value;
|
|
252
|
+
const operand = this.parseUnary();
|
|
253
|
+
return { type: 'UnaryExpr', op, operand, loc } as UnaryExpr;
|
|
254
|
+
}
|
|
255
|
+
return this.parsePostfix();
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
private parsePostfix(): Expression {
|
|
259
|
+
let expr = this.parsePrimary();
|
|
260
|
+
|
|
261
|
+
while (true) {
|
|
262
|
+
if (this.current().type === TokenType.LPAREN) {
|
|
263
|
+
this.advance();
|
|
264
|
+
const args: Expression[] = [];
|
|
265
|
+
if (this.current().type !== TokenType.RPAREN) {
|
|
266
|
+
args.push(this.parseExpression());
|
|
267
|
+
while (this.current().type === TokenType.COMMA) {
|
|
268
|
+
this.advance();
|
|
269
|
+
args.push(this.parseExpression());
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
this.expect(TokenType.RPAREN);
|
|
273
|
+
expr = { type: 'CallExpr', callee: expr, args, loc: (expr as any).loc } as CallExpr;
|
|
274
|
+
} else if (this.current().type === TokenType.DOT) {
|
|
275
|
+
this.advance();
|
|
276
|
+
const prop = this.expect(TokenType.IDENTIFIER);
|
|
277
|
+
expr = {
|
|
278
|
+
type: 'MemberExpr',
|
|
279
|
+
object: expr,
|
|
280
|
+
property: { type: 'Identifier', name: prop.value, loc: { line: prop.line, column: prop.column } } as Identifier,
|
|
281
|
+
computed: false,
|
|
282
|
+
loc: (expr as any).loc,
|
|
283
|
+
} as MemberExpr;
|
|
284
|
+
} else if (this.current().type === TokenType.LBRACKET) {
|
|
285
|
+
this.advance();
|
|
286
|
+
const index = this.parseExpression();
|
|
287
|
+
this.expect(TokenType.RBRACKET);
|
|
288
|
+
expr = {
|
|
289
|
+
type: 'MemberExpr',
|
|
290
|
+
object: expr,
|
|
291
|
+
property: index,
|
|
292
|
+
computed: true,
|
|
293
|
+
loc: (expr as any).loc,
|
|
294
|
+
} as MemberExpr;
|
|
295
|
+
} else {
|
|
296
|
+
break;
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
return expr;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
private parsePrimary(): Expression {
|
|
304
|
+
const tok = this.current();
|
|
305
|
+
|
|
306
|
+
// duo expression
|
|
307
|
+
if (tok.type === TokenType.DUO) {
|
|
308
|
+
return this.parseDuoExpr();
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
// Grouped expression
|
|
312
|
+
if (tok.type === TokenType.LPAREN) {
|
|
313
|
+
this.advance();
|
|
314
|
+
const expr = this.parseExpression();
|
|
315
|
+
this.expect(TokenType.RPAREN);
|
|
316
|
+
return expr;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
// Array literal
|
|
320
|
+
if (tok.type === TokenType.LBRACKET) {
|
|
321
|
+
return this.parseArrayLiteral();
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
// Object literal (only when not ambiguous with block)
|
|
325
|
+
// Objects are parsed in specific contexts (e.g., after =)
|
|
326
|
+
|
|
327
|
+
// String
|
|
328
|
+
if (tok.type === TokenType.STRING) {
|
|
329
|
+
this.advance();
|
|
330
|
+
return { type: 'StringLiteral', value: tok.value, loc: { line: tok.line, column: tok.column } } as StringLiteral;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
// Number
|
|
334
|
+
if (tok.type === TokenType.NUMBER) {
|
|
335
|
+
this.advance();
|
|
336
|
+
return { type: 'NumberLiteral', value: Number(tok.value), loc: { line: tok.line, column: tok.column } } as NumberLiteral;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
// Boolean
|
|
340
|
+
if (tok.type === TokenType.BOOLEAN) {
|
|
341
|
+
this.advance();
|
|
342
|
+
return { type: 'BooleanLiteral', value: tok.value === 'true', loc: { line: tok.line, column: tok.column } } as BooleanLiteral;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
// Null
|
|
346
|
+
if (tok.type === TokenType.NULL) {
|
|
347
|
+
this.advance();
|
|
348
|
+
return { type: 'NullLiteral', loc: { line: tok.line, column: tok.column } } as NullLiteral;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
// Identifier (including keywords used as variable names in expression context)
|
|
352
|
+
if (tok.type === TokenType.IDENTIFIER || this.isKeywordUsableAsIdentifier(tok.type)) {
|
|
353
|
+
this.advance();
|
|
354
|
+
return { type: 'Identifier', name: tok.value, loc: { line: tok.line, column: tok.column } } as Identifier;
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
throw new RTJError('SyntaxError', `unexpected token '${tok.value}'`, tok.line, tok.column);
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
private parseDuoExpr(): DuoExpr {
|
|
361
|
+
const loc = { line: this.current().line, column: this.current().column };
|
|
362
|
+
this.expect(TokenType.DUO);
|
|
363
|
+
this.expect(TokenType.LPAREN);
|
|
364
|
+
const input = this.parseExpression();
|
|
365
|
+
this.expect(TokenType.RPAREN);
|
|
366
|
+
this.expect(TokenType.LBRACE);
|
|
367
|
+
|
|
368
|
+
this.expect(TokenType.MIKE);
|
|
369
|
+
this.expect(TokenType.COLON);
|
|
370
|
+
const mikePipeline = this.parsePipelineList();
|
|
371
|
+
|
|
372
|
+
this.expect(TokenType.EL);
|
|
373
|
+
this.expect(TokenType.COLON);
|
|
374
|
+
const elPipeline = this.parsePipelineList();
|
|
375
|
+
|
|
376
|
+
this.expect(TokenType.RBRACE);
|
|
377
|
+
|
|
378
|
+
return { type: 'DuoExpr', input, mikePipeline, elPipeline, loc };
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
private parsePipelineList(): Expression[] {
|
|
382
|
+
const steps: Expression[] = [];
|
|
383
|
+
steps.push(this.parsePostfix());
|
|
384
|
+
while (this.current().type === TokenType.PIPE) {
|
|
385
|
+
this.advance();
|
|
386
|
+
steps.push(this.parsePostfix());
|
|
387
|
+
}
|
|
388
|
+
return steps;
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
private parseArrayLiteral(): ArrayLiteral {
|
|
392
|
+
const loc = { line: this.current().line, column: this.current().column };
|
|
393
|
+
this.expect(TokenType.LBRACKET);
|
|
394
|
+
const elements: Expression[] = [];
|
|
395
|
+
if (this.current().type !== TokenType.RBRACKET) {
|
|
396
|
+
elements.push(this.parseExpression());
|
|
397
|
+
while (this.current().type === TokenType.COMMA) {
|
|
398
|
+
this.advance();
|
|
399
|
+
elements.push(this.parseExpression());
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
this.expect(TokenType.RBRACKET);
|
|
403
|
+
return { type: 'ArrayLiteral', elements, loc };
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
private current(): Token {
|
|
407
|
+
return this.tokens[this.pos] || { type: TokenType.EOF, value: '', line: 0, column: 0 };
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
private advance(): Token {
|
|
411
|
+
const tok = this.current();
|
|
412
|
+
this.pos++;
|
|
413
|
+
return tok;
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
private expect(type: TokenType): Token {
|
|
417
|
+
const tok = this.current();
|
|
418
|
+
if (tok.type !== type) {
|
|
419
|
+
throw new RTJError(
|
|
420
|
+
'SyntaxError',
|
|
421
|
+
`beat slipped near line ${tok.line}, expected '${type}' but got '${tok.type}'`,
|
|
422
|
+
tok.line,
|
|
423
|
+
tok.column
|
|
424
|
+
);
|
|
425
|
+
}
|
|
426
|
+
return this.advance();
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
private isKeywordUsableAsIdentifier(type: TokenType): boolean {
|
|
430
|
+
// Keywords that can appear as variable names in expression contexts
|
|
431
|
+
return type === TokenType.VERSE || type === TokenType.JEWEL ||
|
|
432
|
+
type === TokenType.SEND || type === TokenType.TALK ||
|
|
433
|
+
type === TokenType.RUN || type === TokenType.IN ||
|
|
434
|
+
type === TokenType.FEATURE || type === TokenType.FROM ||
|
|
435
|
+
type === TokenType.YANK || type === TokenType.MIKE ||
|
|
436
|
+
type === TokenType.EL;
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
private expectIdentifierOrKeyword(): Token {
|
|
440
|
+
const tok = this.current();
|
|
441
|
+
if (tok.type === TokenType.IDENTIFIER || this.isKeywordUsableAsIdentifier(tok.type)) {
|
|
442
|
+
return this.advance();
|
|
443
|
+
}
|
|
444
|
+
throw new RTJError(
|
|
445
|
+
'SyntaxError',
|
|
446
|
+
`beat slipped near line ${tok.line}, expected identifier but got '${tok.type}'`,
|
|
447
|
+
tok.line,
|
|
448
|
+
tok.column
|
|
449
|
+
);
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
private isAtEnd(): boolean {
|
|
453
|
+
return this.current().type === TokenType.EOF;
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
private skipSemicolons(): void {
|
|
457
|
+
while (this.current().type === TokenType.SEMICOLON) {
|
|
458
|
+
this.advance();
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
}
|
package/src/repl.ts
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import * as readline from 'readline';
|
|
2
|
+
import * as path from 'path';
|
|
3
|
+
import * as vm from 'vm';
|
|
4
|
+
import { Lexer } from './lexer';
|
|
5
|
+
import { Parser } from './parser';
|
|
6
|
+
import { Semantic } from './semantic';
|
|
7
|
+
import { Generator } from './generator';
|
|
8
|
+
import { RTJError } from './diagnostics';
|
|
9
|
+
|
|
10
|
+
export function startRepl(): void {
|
|
11
|
+
const rl = readline.createInterface({
|
|
12
|
+
input: process.stdin,
|
|
13
|
+
output: process.stdout,
|
|
14
|
+
prompt: 'rtj> ',
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
// Load the runtime once
|
|
18
|
+
const runtimePath = path.join(__dirname, 'runtime', 'rtj-core');
|
|
19
|
+
const rtjCore = require(runtimePath);
|
|
20
|
+
|
|
21
|
+
// Persistent context for the REPL
|
|
22
|
+
const context = vm.createContext({
|
|
23
|
+
__rtj: rtjCore,
|
|
24
|
+
__modules: rtjCore.modules,
|
|
25
|
+
console: console,
|
|
26
|
+
require: require,
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
const history: string[] = [];
|
|
30
|
+
|
|
31
|
+
console.log('');
|
|
32
|
+
console.log(' \uD83D\uDC49{\uD83D\uDC8E}.rtj');
|
|
33
|
+
console.log(' Code The Jewels v0.1 \u2014 RTJ0 "The Self-Titled Era"');
|
|
34
|
+
console.log(' El-P (Brooklyn) & Killer Mike (Atlanta) \u2014 MIT Forever');
|
|
35
|
+
console.log('');
|
|
36
|
+
console.log(' .help for commands \u00B7 .exit to quit');
|
|
37
|
+
console.log('');
|
|
38
|
+
|
|
39
|
+
rl.prompt();
|
|
40
|
+
|
|
41
|
+
rl.on('line', (line) => {
|
|
42
|
+
const input = line.trim();
|
|
43
|
+
if (input === '.exit') {
|
|
44
|
+
process.exit(0);
|
|
45
|
+
}
|
|
46
|
+
if (input === '') {
|
|
47
|
+
rl.prompt();
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
if (input === '.help') {
|
|
51
|
+
console.log('.exit quit the REPL');
|
|
52
|
+
console.log('.legend print version info');
|
|
53
|
+
console.log('.duo print duo() spec');
|
|
54
|
+
rl.prompt();
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
if (input === '.legend') {
|
|
58
|
+
console.log('Code The Jewels v0.1 \u2014 RTJ0 "The Self-Titled Era"');
|
|
59
|
+
console.log('El-P (Brooklyn) & Killer Mike (Atlanta) \u2014 MIT Forever');
|
|
60
|
+
rl.prompt();
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
if (input === '.duo') {
|
|
64
|
+
console.log('duo(input) { mike: fn el: fn }');
|
|
65
|
+
console.log('mike runs first (Atlanta). el responds (Brooklyn).');
|
|
66
|
+
console.log('Both branches required. Compiles to: el(mike(input))');
|
|
67
|
+
rl.prompt();
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
try {
|
|
72
|
+
const lexer = new Lexer(input);
|
|
73
|
+
const tokens = lexer.tokenize();
|
|
74
|
+
const parser = new Parser(tokens);
|
|
75
|
+
const ast = parser.parse();
|
|
76
|
+
const semantic = new Semantic();
|
|
77
|
+
semantic.analyze(ast);
|
|
78
|
+
const gen = new Generator();
|
|
79
|
+
let js = gen.generate(ast);
|
|
80
|
+
|
|
81
|
+
// Strip the prelude since we already have the runtime in context
|
|
82
|
+
js = js.replace('"use strict";\n', '');
|
|
83
|
+
js = js.replace('const __rtj = require("./runtime/rtj-core");\n', '');
|
|
84
|
+
js = js.replace('const __modules = __rtj.modules;\n', '');
|
|
85
|
+
|
|
86
|
+
history.push(js);
|
|
87
|
+
const result = vm.runInContext(js, context);
|
|
88
|
+
if (result !== undefined) {
|
|
89
|
+
console.log(result);
|
|
90
|
+
}
|
|
91
|
+
} catch (err) {
|
|
92
|
+
if (err instanceof RTJError) {
|
|
93
|
+
console.error(err.format());
|
|
94
|
+
} else if (err instanceof Error) {
|
|
95
|
+
console.error(err.message);
|
|
96
|
+
} else {
|
|
97
|
+
console.error(err);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
rl.prompt();
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
rl.on('close', () => process.exit(0));
|
|
105
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export const count = (list: unknown[]) => list.length;
|
|
2
|
+
export const countBy = (list: unknown[]) => {
|
|
3
|
+
const map: Record<string, number> = {};
|
|
4
|
+
for (const item of list) {
|
|
5
|
+
const key = String(item);
|
|
6
|
+
map[key] = (map[key] || 0) + 1;
|
|
7
|
+
}
|
|
8
|
+
return map;
|
|
9
|
+
};
|
|
10
|
+
export const first = (list: unknown[]) => list[0];
|
|
11
|
+
export const last = (list: unknown[]) => list[list.length - 1];
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const identity = (x: unknown) => x;
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export const trim = (text: string) => text.trim();
|
|
2
|
+
export const upper = (text: string) => text.toUpperCase();
|
|
3
|
+
export const lower = (text: string) => text.toLowerCase();
|
|
4
|
+
export const split = (text: string, sep?: string) => sep ? text.split(sep) : text.split(' ');
|
|
5
|
+
export const join = (list: string[], sep: string = '') => list.join(sep);
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
const modules: Record<string, Record<string, Function>> = {};
|
|
2
|
+
|
|
3
|
+
function registerModule(name: string, fns: Record<string, Function>) {
|
|
4
|
+
modules[name] = fns;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
function talk(value: unknown): void {
|
|
8
|
+
console.log(value);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
import * as bkText from './bk-text';
|
|
12
|
+
import * as bkParse from './bk-parse';
|
|
13
|
+
import * as atlData from './atl-data';
|
|
14
|
+
import * as atlFlow from './atl-flow';
|
|
15
|
+
|
|
16
|
+
registerModule('bk:text', bkText);
|
|
17
|
+
registerModule('bk:parse', bkParse);
|
|
18
|
+
registerModule('atl:data', atlData);
|
|
19
|
+
registerModule('atl:flow', atlFlow);
|
|
20
|
+
|
|
21
|
+
module.exports = { talk, modules };
|