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/dist/lexer.d.ts
ADDED
|
@@ -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;
|
package/dist/parser.d.ts
ADDED
|
@@ -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;
|