vladx 1.0.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/README.md +256 -0
- package/bin/cli.js +486 -0
- package/bin/vlad.js +539 -0
- package/bin/vladpm.js +710 -0
- package/bin/vladx.js +491 -0
- package/package.json +57 -0
- package/src/engine/jit-compiler.js +285 -0
- package/src/engine/vladx-engine.js +941 -0
- package/src/index.js +44 -0
- package/src/interpreter/interpreter.js +2114 -0
- package/src/lexer/lexer.js +658 -0
- package/src/lexer/optimized-lexer.js +106 -0
- package/src/lexer/regex-cache.js +83 -0
- package/src/parser/ast-nodes.js +472 -0
- package/src/parser/parser.js +1408 -0
- package/src/runtime/advanced-type-system.js +209 -0
- package/src/runtime/async-manager.js +252 -0
- package/src/runtime/builtins.js +143 -0
- package/src/runtime/bundler.js +422 -0
- package/src/runtime/cache-manager.js +126 -0
- package/src/runtime/data-structures.js +612 -0
- package/src/runtime/debugger.js +260 -0
- package/src/runtime/enhanced-module-system.js +196 -0
- package/src/runtime/environment-enhanced.js +272 -0
- package/src/runtime/environment.js +140 -0
- package/src/runtime/event-emitter.js +232 -0
- package/src/runtime/formatter.js +280 -0
- package/src/runtime/functional.js +359 -0
- package/src/runtime/io-operations.js +390 -0
- package/src/runtime/linter.js +374 -0
- package/src/runtime/logging.js +314 -0
- package/src/runtime/minifier.js +242 -0
- package/src/runtime/module-system.js +377 -0
- package/src/runtime/network-operations.js +373 -0
- package/src/runtime/profiler.js +295 -0
- package/src/runtime/repl.js +336 -0
- package/src/runtime/security-manager.js +244 -0
- package/src/runtime/source-map-generator.js +208 -0
- package/src/runtime/test-runner.js +394 -0
- package/src/runtime/transformer.js +277 -0
- package/src/runtime/type-system.js +244 -0
- package/src/runtime/vladx-object.js +250 -0
|
@@ -0,0 +1,1408 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* VladX Parser — Синтаксический анализатор
|
|
3
|
+
* Преобразует поток токенов в абстрактное синтаксическое дерево (AST)
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { ASTNodes } from './ast-nodes.js';
|
|
7
|
+
|
|
8
|
+
export class Parser {
|
|
9
|
+
constructor(tokens, options = {}) {
|
|
10
|
+
this.tokens = tokens;
|
|
11
|
+
this.pos = 0;
|
|
12
|
+
this.debug = options.debug || false;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Получить текущий токен
|
|
17
|
+
*/
|
|
18
|
+
peek(offset = 0) {
|
|
19
|
+
const index = this.pos + offset;
|
|
20
|
+
return index < this.tokens.length ? this.tokens[index] : this.tokens[this.tokens.length - 1];
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Получить и продвинуться
|
|
25
|
+
*/
|
|
26
|
+
advance() {
|
|
27
|
+
return this.tokens[this.pos++];
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Проверка типа текущего токена
|
|
32
|
+
*/
|
|
33
|
+
check(type, value = null) {
|
|
34
|
+
const token = this.peek();
|
|
35
|
+
if (value !== null) {
|
|
36
|
+
return token.type === type && token.value === value;
|
|
37
|
+
}
|
|
38
|
+
return token.type === type;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Проверка и потребление токена
|
|
43
|
+
*/
|
|
44
|
+
consume(type, value = null, message = `Expected ${type}`) {
|
|
45
|
+
const token = this.peek();
|
|
46
|
+
if (token.type !== type || (value !== null && token.value !== value)) {
|
|
47
|
+
throw this.error(message);
|
|
48
|
+
}
|
|
49
|
+
return this.advance();
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Синтаксическая ошибка
|
|
54
|
+
*/
|
|
55
|
+
error(message, token = null) {
|
|
56
|
+
const t = token || this.peek();
|
|
57
|
+
const filename = t.filename || '<unknown>';
|
|
58
|
+
const line = t.line || '?';
|
|
59
|
+
const column = t.column || '?';
|
|
60
|
+
const tokenType = t.type || 'unknown';
|
|
61
|
+
const tokenValue = t.value !== undefined ? t.value : 'unknown';
|
|
62
|
+
|
|
63
|
+
return new Error(`[${filename}:${line}:${column}] Синтаксическая ошибка: ${message}\n Токен: ${tokenType} = '${tokenValue}'`);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Основной метод парсинга
|
|
68
|
+
*/
|
|
69
|
+
parse() {
|
|
70
|
+
try {
|
|
71
|
+
const program = new ASTNodes.Program();
|
|
72
|
+
program.body = this.parseStatements();
|
|
73
|
+
if (this.debug) {
|
|
74
|
+
console.log('[Parser] Parsed program with', program.body.length, 'statements');
|
|
75
|
+
}
|
|
76
|
+
return program;
|
|
77
|
+
} catch (error) {
|
|
78
|
+
if (this.debug) {
|
|
79
|
+
console.error('[Parser] Error during parsing:', error);
|
|
80
|
+
console.error('[Parser] Current token:', this.peek());
|
|
81
|
+
}
|
|
82
|
+
throw error;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Парсинг блока инструкций
|
|
88
|
+
*/
|
|
89
|
+
parseStatements(endTokens = ['EOF', 'RBRACE']) {
|
|
90
|
+
const body = [];
|
|
91
|
+
|
|
92
|
+
while (!endTokens.includes(this.peek().type)) {
|
|
93
|
+
if (this.check('NEWLINE')) {
|
|
94
|
+
this.advance();
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const statement = this.parseStatement();
|
|
99
|
+
if (statement) {
|
|
100
|
+
body.push(statement);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return body;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Парсинг одной инструкции
|
|
109
|
+
*/
|
|
110
|
+
parseStatement() {
|
|
111
|
+
const token = this.peek();
|
|
112
|
+
|
|
113
|
+
if (this.check('NEWLINE')) {
|
|
114
|
+
this.advance();
|
|
115
|
+
return new ASTNodes.EmptyStatement();
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
switch (token.type) {
|
|
119
|
+
case 'LET':
|
|
120
|
+
case 'ПУСТЬ': // Русское ключевое слово
|
|
121
|
+
return this.parseVariableDeclaration();
|
|
122
|
+
|
|
123
|
+
case 'CONST':
|
|
124
|
+
case 'КОНСТ': // Русское ключевое слово
|
|
125
|
+
return this.parseVariableDeclaration();
|
|
126
|
+
|
|
127
|
+
case 'RETURN':
|
|
128
|
+
case 'ВЕРНУТЬ': // Русское ключевое слово
|
|
129
|
+
return this.parseReturnStatement();
|
|
130
|
+
|
|
131
|
+
case 'IF':
|
|
132
|
+
case 'ЕСЛИ': // Русское ключевое слово
|
|
133
|
+
return this.parseIfStatement();
|
|
134
|
+
|
|
135
|
+
case 'WHILE':
|
|
136
|
+
case 'ПОКА': // Русское ключевое слово
|
|
137
|
+
return this.parseWhileStatement();
|
|
138
|
+
|
|
139
|
+
case 'FOR':
|
|
140
|
+
case 'ДЛЯ': // Русское ключевое слово
|
|
141
|
+
return this.parseForStatement();
|
|
142
|
+
|
|
143
|
+
case 'BREAK':
|
|
144
|
+
case 'ПРЕРВАТЬ': // Русское ключевое слово
|
|
145
|
+
return this.parseBreakStatement();
|
|
146
|
+
|
|
147
|
+
case 'CONTINUE':
|
|
148
|
+
case 'ПРОДОЛЖИТЬ': // Русское ключевое слово
|
|
149
|
+
return this.parseContinueStatement();
|
|
150
|
+
|
|
151
|
+
case 'ASYNC':
|
|
152
|
+
return this.parseAsyncFunctionDeclaration();
|
|
153
|
+
case 'FUNCTION':
|
|
154
|
+
case 'ФУНКЦИЯ': // Русское ключевое слово
|
|
155
|
+
return this.parseFunctionDeclaration();
|
|
156
|
+
|
|
157
|
+
case 'IMPORT':
|
|
158
|
+
case 'ИМПОРТ': // Импорт модулей
|
|
159
|
+
return this.parseImportStatement();
|
|
160
|
+
|
|
161
|
+
case 'EXPORT':
|
|
162
|
+
case 'ЭКСПОРТ': // Экспорт имен
|
|
163
|
+
return this.parseExportStatement();
|
|
164
|
+
|
|
165
|
+
case 'CLASS':
|
|
166
|
+
case 'КЛАСС': // Русское ключевое слово
|
|
167
|
+
return this.parseClassDeclaration();
|
|
168
|
+
|
|
169
|
+
case 'SWITCH':
|
|
170
|
+
case 'ВЫБОР': // Русское ключевое слово
|
|
171
|
+
return this.parseSwitchStatement();
|
|
172
|
+
|
|
173
|
+
case 'TRY':
|
|
174
|
+
case 'ПОПЫТКА': // Русское ключевое слово
|
|
175
|
+
return this.parseTryStatement();
|
|
176
|
+
|
|
177
|
+
case 'THROW':
|
|
178
|
+
case 'БРОСИТЬ': // Русское ключевое слово
|
|
179
|
+
return this.parseThrowStatement();
|
|
180
|
+
|
|
181
|
+
case 'LBRACE':
|
|
182
|
+
return this.parseBlockStatement();
|
|
183
|
+
|
|
184
|
+
default:
|
|
185
|
+
return new ASTNodes.ExpressionStatement(this.parseExpression());
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Объявление переменной (пусть/const)
|
|
191
|
+
*/
|
|
192
|
+
parseVariableDeclaration() {
|
|
193
|
+
const token = this.advance();
|
|
194
|
+
const isConst = token.type === 'CONST' || token.type === 'КОНСТ';
|
|
195
|
+
|
|
196
|
+
const name = this.consume('IDENTIFIER', null, 'Ожидалось имя переменной').value;
|
|
197
|
+
|
|
198
|
+
this.consume('ASSIGN', null, 'Ожидалось =');
|
|
199
|
+
|
|
200
|
+
const initializer = this.parseExpression();
|
|
201
|
+
|
|
202
|
+
// Пропуск точки с запятой
|
|
203
|
+
if (this.check('SEMICOLON')) {
|
|
204
|
+
this.advance();
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
return isConst
|
|
208
|
+
? new ASTNodes.ConstStatement(name, initializer)
|
|
209
|
+
: new ASTNodes.LetStatement(name, initializer);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* Return statement
|
|
214
|
+
*/
|
|
215
|
+
parseReturnStatement() {
|
|
216
|
+
this.advance(); // consume RETURN
|
|
217
|
+
|
|
218
|
+
if (this.check('NEWLINE') || this.check('RBRACE') || this.check('EOF')) {
|
|
219
|
+
return new ASTNodes.ReturnStatement(null);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
const argument = this.parseExpression();
|
|
223
|
+
|
|
224
|
+
if (this.check('SEMICOLON')) {
|
|
225
|
+
this.advance();
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
return new ASTNodes.ReturnStatement(argument);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* If statement
|
|
233
|
+
*/
|
|
234
|
+
parseIfStatement() {
|
|
235
|
+
this.advance(); // consume IF
|
|
236
|
+
|
|
237
|
+
const condition = this.parseExpression();
|
|
238
|
+
|
|
239
|
+
const thenBranch = this.parseBlockStatement();
|
|
240
|
+
|
|
241
|
+
// Skip any newlines before checking for else/elseif
|
|
242
|
+
while (this.check('NEWLINE')) {
|
|
243
|
+
this.advance();
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
let elseBranch = null;
|
|
247
|
+
if (this.check('ELSE') || this.check('ИНАЧЕ') || this.check('ELSEIF') || this.check('ИЛИЕСЛИ')) {
|
|
248
|
+
elseBranch = this.parseElseClause();
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
return new ASTNodes.IfStatement(condition, thenBranch, elseBranch);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* Else clause
|
|
256
|
+
*/
|
|
257
|
+
parseElseClause() {
|
|
258
|
+
const token = this.peek();
|
|
259
|
+
|
|
260
|
+
// For ELSEIF/ИЛИЕСЛИ, we need to parse the if statement that follows
|
|
261
|
+
if (token.type === 'ELSEIF' || token.type === 'ИЛИЕСЛИ') {
|
|
262
|
+
// Consume the ELSEIF token
|
|
263
|
+
this.advance();
|
|
264
|
+
// Now parse the if statement structure: condition in parentheses and then the block
|
|
265
|
+
const condition = this.parseExpression();
|
|
266
|
+
const thenBranch = this.parseBlockStatement();
|
|
267
|
+
|
|
268
|
+
// Skip any newlines before checking for another else/elseif
|
|
269
|
+
while (this.check('NEWLINE')) {
|
|
270
|
+
this.advance();
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
// Check if there's another else/elseif after this one
|
|
274
|
+
let elseBranch = null;
|
|
275
|
+
if (this.check('ELSE') || this.check('ИНАЧЕ') || this.check('ELSEIF') || this.check('ИЛИЕСЛИ')) {
|
|
276
|
+
elseBranch = this.parseElseClause();
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
return new ASTNodes.IfStatement(condition, thenBranch, elseBranch);
|
|
280
|
+
} else {
|
|
281
|
+
// For regular ELSE, just consume the token and return the block
|
|
282
|
+
this.advance(); // consume ELSE or ИНАЧЕ
|
|
283
|
+
return this.parseBlockStatement();
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
/**
|
|
288
|
+
* While statement
|
|
289
|
+
*/
|
|
290
|
+
parseWhileStatement() {
|
|
291
|
+
this.advance(); // consume WHILE
|
|
292
|
+
|
|
293
|
+
const condition = this.parseExpression();
|
|
294
|
+
const body = this.parseBlockStatement();
|
|
295
|
+
|
|
296
|
+
return new ASTNodes.WhileStatement(condition, body);
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
/**
|
|
300
|
+
* For statement
|
|
301
|
+
*/
|
|
302
|
+
parseForStatement() {
|
|
303
|
+
this.advance(); // consume FOR
|
|
304
|
+
|
|
305
|
+
this.consume('LPAREN', null, 'Ожидалось (');
|
|
306
|
+
|
|
307
|
+
// Инициализация - parseStatement уже потребляет точку с запятой для переменных
|
|
308
|
+
let initializer = null;
|
|
309
|
+
initializer = this.parseStatement();
|
|
310
|
+
|
|
311
|
+
// Условие
|
|
312
|
+
let condition = null;
|
|
313
|
+
if (!this.check('RPAREN')) {
|
|
314
|
+
condition = this.parseExpression();
|
|
315
|
+
this.consume('SEMICOLON', null, 'Ожидалось ;');
|
|
316
|
+
} else {
|
|
317
|
+
// Если сразу ) - значит было пустое условие, нужно потребовать ;
|
|
318
|
+
this.consume('SEMICOLON', null, 'Ожидалось ;');
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
// Обновление
|
|
322
|
+
let update = null;
|
|
323
|
+
if (!this.check('RPAREN')) {
|
|
324
|
+
update = this.parseExpression();
|
|
325
|
+
}
|
|
326
|
+
this.consume('RPAREN', null, 'Ожидалось )');
|
|
327
|
+
|
|
328
|
+
const body = this.parseBlockStatement();
|
|
329
|
+
|
|
330
|
+
return new ASTNodes.ForStatement(initializer, condition, update, body);
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
/**
|
|
334
|
+
* Break statement
|
|
335
|
+
*/
|
|
336
|
+
parseBreakStatement() {
|
|
337
|
+
this.advance();
|
|
338
|
+
|
|
339
|
+
if (this.check('SEMICOLON')) {
|
|
340
|
+
this.advance();
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
return new ASTNodes.BreakStatement();
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
/**
|
|
347
|
+
* Continue statement
|
|
348
|
+
*/
|
|
349
|
+
parseContinueStatement() {
|
|
350
|
+
this.advance();
|
|
351
|
+
|
|
352
|
+
if (this.check('SEMICOLON')) {
|
|
353
|
+
this.advance();
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
return new ASTNodes.ContinueStatement();
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
/**
|
|
360
|
+
* Function declaration
|
|
361
|
+
*/
|
|
362
|
+
parseFunctionDeclaration() {
|
|
363
|
+
this.advance(); // consume FUNCTION
|
|
364
|
+
|
|
365
|
+
const name = this.consume('IDENTIFIER', null, 'Ожидалось имя функции').value;
|
|
366
|
+
|
|
367
|
+
this.consume('LPAREN', null, 'Ожидалось (');
|
|
368
|
+
|
|
369
|
+
const params = [];
|
|
370
|
+
if (!this.check('RPAREN')) {
|
|
371
|
+
while (true) {
|
|
372
|
+
// Проверяем, есть ли у параметра значение по умолчанию
|
|
373
|
+
let param;
|
|
374
|
+
if (this.check('IDENTIFIER')) {
|
|
375
|
+
const paramName = this.consume('IDENTIFIER', null, 'Ожидался параметр').value;
|
|
376
|
+
|
|
377
|
+
// Проверяем, есть ли оператор присваивания (значение по умолчанию)
|
|
378
|
+
if (this.check('ASSIGN')) {
|
|
379
|
+
this.advance(); // consume =
|
|
380
|
+
const defaultValue = this.parseExpression();
|
|
381
|
+
param = new ASTNodes.AssignmentPattern(
|
|
382
|
+
new ASTNodes.Identifier(paramName),
|
|
383
|
+
defaultValue
|
|
384
|
+
);
|
|
385
|
+
} else {
|
|
386
|
+
param = new ASTNodes.Identifier(paramName);
|
|
387
|
+
}
|
|
388
|
+
} else {
|
|
389
|
+
throw this.error('Ожидался параметр функции');
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
params.push(param);
|
|
393
|
+
|
|
394
|
+
if (this.check('COMMA')) {
|
|
395
|
+
this.advance();
|
|
396
|
+
} else {
|
|
397
|
+
break;
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
this.consume('RPAREN', null, 'Ожидалось )');
|
|
403
|
+
|
|
404
|
+
const body = this.parseBlockStatement();
|
|
405
|
+
|
|
406
|
+
return new ASTNodes.FunctionDeclaration(name, params, body);
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
/**
|
|
410
|
+
* Async function declaration
|
|
411
|
+
*/
|
|
412
|
+
parseAsyncFunctionDeclaration() {
|
|
413
|
+
this.advance(); // consume ASYNC
|
|
414
|
+
|
|
415
|
+
// Check if the next token is FUNCTION
|
|
416
|
+
if (!this.check('FUNCTION') && !this.check('ФУНКЦИЯ')) {
|
|
417
|
+
throw this.error('Ожидалось ключевое слово function после async');
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
this.advance(); // consume FUNCTION
|
|
421
|
+
|
|
422
|
+
const name = this.consume('IDENTIFIER', null, 'Ожидалось имя функции').value;
|
|
423
|
+
|
|
424
|
+
this.consume('LPAREN', null, 'Ожидалось (');
|
|
425
|
+
|
|
426
|
+
const params = [];
|
|
427
|
+
if (!this.check('RPAREN')) {
|
|
428
|
+
while (true) {
|
|
429
|
+
// Проверяем, есть ли у параметра значение по умолчанию
|
|
430
|
+
let param;
|
|
431
|
+
if (this.check('IDENTIFIER')) {
|
|
432
|
+
const paramName = this.consume('IDENTIFIER', null, 'Ожидался параметр').value;
|
|
433
|
+
|
|
434
|
+
// Проверяем, есть ли оператор присваивания (значение по умолчанию)
|
|
435
|
+
if (this.check('ASSIGN')) {
|
|
436
|
+
this.advance(); // consume =
|
|
437
|
+
const defaultValue = this.parseExpression();
|
|
438
|
+
param = new ASTNodes.AssignmentPattern(
|
|
439
|
+
new ASTNodes.Identifier(paramName),
|
|
440
|
+
defaultValue
|
|
441
|
+
);
|
|
442
|
+
} else {
|
|
443
|
+
param = new ASTNodes.Identifier(paramName);
|
|
444
|
+
}
|
|
445
|
+
} else {
|
|
446
|
+
throw this.error('Ожидался параметр функции');
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
params.push(param);
|
|
450
|
+
|
|
451
|
+
if (this.check('COMMA')) {
|
|
452
|
+
this.advance();
|
|
453
|
+
} else {
|
|
454
|
+
break;
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
this.consume('RPAREN', null, 'Ожидалось )');
|
|
460
|
+
|
|
461
|
+
const body = this.parseBlockStatement();
|
|
462
|
+
|
|
463
|
+
return new ASTNodes.FunctionDeclaration(name, params, body, true); // isAsync = true
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
/**
|
|
467
|
+
* Class declaration
|
|
468
|
+
*/
|
|
469
|
+
parseClassDeclaration() {
|
|
470
|
+
this.advance(); // consume CLASS
|
|
471
|
+
|
|
472
|
+
const name = this.consume('IDENTIFIER', null, 'Ожидалось имя класса').value;
|
|
473
|
+
|
|
474
|
+
// Наследование
|
|
475
|
+
let superClass = null;
|
|
476
|
+
if (this.check('EXTENDS')) {
|
|
477
|
+
this.advance();
|
|
478
|
+
superClass = this.consume('IDENTIFIER', null, 'Ожидалось имя родительского класса').value;
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
const methods = [];
|
|
482
|
+
|
|
483
|
+
this.consume('LBRACE', null, 'Ожидалось {');
|
|
484
|
+
|
|
485
|
+
while (!this.check('RBRACE') && !this.check('EOF')) {
|
|
486
|
+
if (this.check('NEWLINE')) {
|
|
487
|
+
this.advance();
|
|
488
|
+
continue;
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
let isStatic = false;
|
|
492
|
+
let isGetter = false;
|
|
493
|
+
let isSetter = false;
|
|
494
|
+
let isAsync = false;
|
|
495
|
+
|
|
496
|
+
if (this.check('STATIC') || this.check('СТАТИЧЕСКИЙ')) {
|
|
497
|
+
this.advance();
|
|
498
|
+
isStatic = true;
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
if (this.check('GET')) {
|
|
502
|
+
this.advance();
|
|
503
|
+
isGetter = true;
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
if (this.check('SET')) {
|
|
507
|
+
this.advance();
|
|
508
|
+
isSetter = true;
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
if (this.check('ASYNC') || this.check('АСИНХ')) {
|
|
512
|
+
this.advance();
|
|
513
|
+
isAsync = true;
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
const methodName = this.consume('IDENTIFIER', null, 'Ожидалось имя метода').value;
|
|
517
|
+
|
|
518
|
+
this.consume('LPAREN', null, 'Ожидалось (');
|
|
519
|
+
|
|
520
|
+
const params = [];
|
|
521
|
+
if (!this.check('RPAREN')) {
|
|
522
|
+
while (true) {
|
|
523
|
+
const paramName = this.consume('IDENTIFIER', null, 'Ожидался параметр').value;
|
|
524
|
+
params.push(new ASTNodes.Identifier(paramName));
|
|
525
|
+
|
|
526
|
+
if (this.check('COMMA')) {
|
|
527
|
+
this.advance();
|
|
528
|
+
} else {
|
|
529
|
+
break;
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
this.consume('RPAREN', null, 'Ожидалось )');
|
|
535
|
+
|
|
536
|
+
const body = this.parseBlockStatement();
|
|
537
|
+
|
|
538
|
+
methods.push(new ASTNodes.ClassMethod(methodName, params, body, isStatic, isGetter, isSetter, isAsync));
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
this.consume('RBRACE', null, 'Ожидалось }');
|
|
542
|
+
|
|
543
|
+
return new ASTNodes.ClassDeclaration(name, methods, superClass);
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
/**
|
|
547
|
+
* Try-catch statement
|
|
548
|
+
*/
|
|
549
|
+
parseTryStatement() {
|
|
550
|
+
this.advance(); // consume TRY
|
|
551
|
+
|
|
552
|
+
const block = this.parseBlockStatement();
|
|
553
|
+
|
|
554
|
+
// Catch
|
|
555
|
+
let handler = null;
|
|
556
|
+
if (this.check('CATCH') || this.check('ПЕРЕХВАТ')) {
|
|
557
|
+
this.advance();
|
|
558
|
+
|
|
559
|
+
let param = null;
|
|
560
|
+
if (this.check('LPAREN')) {
|
|
561
|
+
this.advance();
|
|
562
|
+
param = this.consume('IDENTIFIER', null, 'Ожидалось имя параметра').value;
|
|
563
|
+
this.consume('RPAREN', null, 'Ожидалось )');
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
const body = this.parseBlockStatement();
|
|
567
|
+
handler = { param, body };
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
// Finally
|
|
571
|
+
let finalizer = null;
|
|
572
|
+
if (this.check('FINALLY') || this.check('НАКОНЕЦ')) {
|
|
573
|
+
this.advance();
|
|
574
|
+
finalizer = this.parseBlockStatement();
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
return new ASTNodes.TryStatement(block, handler, finalizer);
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
/**
|
|
581
|
+
* Throw statement
|
|
582
|
+
*/
|
|
583
|
+
parseThrowStatement() {
|
|
584
|
+
this.advance();
|
|
585
|
+
|
|
586
|
+
const argument = this.parseExpression();
|
|
587
|
+
|
|
588
|
+
if (this.check('SEMICOLON')) {
|
|
589
|
+
this.advance();
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
return new ASTNodes.ThrowStatement(argument);
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
/**
|
|
596
|
+
* Switch statement
|
|
597
|
+
*/
|
|
598
|
+
parseSwitchStatement() {
|
|
599
|
+
this.advance(); // consume SWITCH or ВЫБОР
|
|
600
|
+
|
|
601
|
+
this.consume('LPAREN', null, 'Ожидалось ( после выбор');
|
|
602
|
+
const condition = this.parseExpression();
|
|
603
|
+
this.consume('RPAREN', null, 'Ожидалось ) после условия выбор');
|
|
604
|
+
|
|
605
|
+
this.consume('LBRACE', null, 'Ожидалось { после выбор');
|
|
606
|
+
|
|
607
|
+
const cases = [];
|
|
608
|
+
let defaultCase = null;
|
|
609
|
+
|
|
610
|
+
while (!this.check('RBRACE') && !this.check('EOF')) {
|
|
611
|
+
if (this.check('CASE') || this.check('КОГДА')) { // когда
|
|
612
|
+
this.advance(); // consume CASE or КОГДА
|
|
613
|
+
const caseValue = this.parseExpression();
|
|
614
|
+
|
|
615
|
+
// In JavaScript-style switch, case values are followed by :
|
|
616
|
+
this.consume('COLON', null, 'Expected : after case value');
|
|
617
|
+
|
|
618
|
+
const consequent = [];
|
|
619
|
+
|
|
620
|
+
// Parse statements until we hit the next case/default or end of switch
|
|
621
|
+
while (true) {
|
|
622
|
+
// Check if the current token is a case/default token before parsing
|
|
623
|
+
if (this.check('CASE') || this.check('КОГДА') ||
|
|
624
|
+
this.check('DEFAULT') || this.check('ПоУмолчанию') ||
|
|
625
|
+
this.check('RBRACE') || this.check('EOF')) {
|
|
626
|
+
break; // Stop parsing statements
|
|
627
|
+
}
|
|
628
|
+
consequent.push(this.parseStatement());
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
cases.push({
|
|
632
|
+
test: caseValue,
|
|
633
|
+
consequent: consequent
|
|
634
|
+
});
|
|
635
|
+
} else if (this.check('DEFAULT') || this.check('ПоУмолчанию')) { // поУмолчанию
|
|
636
|
+
this.advance(); // consume DEFAULT or ПоУмолчанию
|
|
637
|
+
this.consume('COLON', null, 'Ожидалось : после поумолчанию');
|
|
638
|
+
|
|
639
|
+
const consequent = [];
|
|
640
|
+
|
|
641
|
+
// Parse statements until we hit the end of switch
|
|
642
|
+
// (no more cases after default)
|
|
643
|
+
while (true) {
|
|
644
|
+
// Check if the current token is a case/default token before parsing
|
|
645
|
+
if (this.check('CASE') || this.check('КОГДА') ||
|
|
646
|
+
this.check('DEFAULT') || this.check('ПоУмолчанию') ||
|
|
647
|
+
this.check('RBRACE') || this.check('EOF')) {
|
|
648
|
+
break; // Stop parsing statements
|
|
649
|
+
}
|
|
650
|
+
consequent.push(this.parseStatement());
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
defaultCase = {
|
|
654
|
+
consequent: consequent
|
|
655
|
+
};
|
|
656
|
+
} else {
|
|
657
|
+
// Skip unexpected tokens
|
|
658
|
+
this.advance();
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
this.consume('RBRACE', null, 'Ожидалось } после тела выбор');
|
|
663
|
+
|
|
664
|
+
return new ASTNodes.SwitchStatement(condition, cases, defaultCase);
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
/**
|
|
668
|
+
* Block statement
|
|
669
|
+
*/
|
|
670
|
+
parseBlockStatement() {
|
|
671
|
+
const body = [];
|
|
672
|
+
|
|
673
|
+
if (this.check('LBRACE')) {
|
|
674
|
+
this.advance(); // consume {
|
|
675
|
+
|
|
676
|
+
while (!this.check('RBRACE') && !this.check('EOF')) {
|
|
677
|
+
if (this.check('NEWLINE')) {
|
|
678
|
+
this.advance();
|
|
679
|
+
continue;
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
const statement = this.parseStatement();
|
|
683
|
+
if (statement) {
|
|
684
|
+
body.push(statement);
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
this.consume('RBRACE', null, 'Ожидалось }');
|
|
689
|
+
} else {
|
|
690
|
+
// Одиночный оператор без блока
|
|
691
|
+
body.push(this.parseStatement());
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
return new ASTNodes.BlockStatement(body);
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
/**
|
|
698
|
+
* Парсинг выражения
|
|
699
|
+
*/
|
|
700
|
+
parseExpression() {
|
|
701
|
+
return this.parseAssignment();
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
/**
|
|
705
|
+
* Присваивание
|
|
706
|
+
*/
|
|
707
|
+
parseAssignment() {
|
|
708
|
+
const left = this.parseTernary();
|
|
709
|
+
|
|
710
|
+
if (this.check('ASSIGN')) {
|
|
711
|
+
this.advance();
|
|
712
|
+
const right = this.parseAssignment();
|
|
713
|
+
|
|
714
|
+
// Проверяем, является ли левая часть деструктуризацией
|
|
715
|
+
if (left.type === 'ArrayPattern' || left.type === 'ObjectPattern') {
|
|
716
|
+
return new ASTNodes.AssignmentExpression(left, '=', right);
|
|
717
|
+
} else if (left.type === 'Identifier') {
|
|
718
|
+
return new ASTNodes.Assignment(left.name, right);
|
|
719
|
+
} else if (left.type === 'MemberExpression') {
|
|
720
|
+
return new ASTNodes.MemberAssignment(left.object, left.property, right);
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
return right;
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
// Обработка составных присваиваний
|
|
727
|
+
if (this.check('PLUSEQ') || this.check('MINUSEQ') || this.check('MULTEQ') ||
|
|
728
|
+
this.check('DIVEQ') || this.check('MODEQ')) {
|
|
729
|
+
const operatorToken = this.advance();
|
|
730
|
+
const right = this.parseAssignment();
|
|
731
|
+
|
|
732
|
+
let operator;
|
|
733
|
+
switch (operatorToken.type) {
|
|
734
|
+
case 'PLUSEQ': operator = '+='; break;
|
|
735
|
+
case 'MINUSEQ': operator = '-='; break;
|
|
736
|
+
case 'MULTEQ': operator = '*='; break;
|
|
737
|
+
case 'DIVEQ': operator = '/='; break;
|
|
738
|
+
case 'MODEQ': operator = '%='; break;
|
|
739
|
+
default: operator = operatorToken.value;
|
|
740
|
+
}
|
|
741
|
+
|
|
742
|
+
if (left.type === 'Identifier') {
|
|
743
|
+
return new ASTNodes.CompoundAssignmentExpression(left, operator, right);
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
if (left.type === 'MemberExpression') {
|
|
747
|
+
return new ASTNodes.CompoundAssignmentExpression(left, operator, right);
|
|
748
|
+
}
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
return left;
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
/**
|
|
755
|
+
* Логические операторы
|
|
756
|
+
*/
|
|
757
|
+
parseLogical() {
|
|
758
|
+
let left = this.parseComparison();
|
|
759
|
+
|
|
760
|
+
while (this.check('AND') || this.check('OR')) {
|
|
761
|
+
const operator = this.advance().value;
|
|
762
|
+
const right = this.parseLogical(); // Используем тот же уровень приоритета для левой ассоциативности
|
|
763
|
+
left = new ASTNodes.BinaryExpression(operator, left, right);
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
return left;
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
|
|
770
|
+
|
|
771
|
+
|
|
772
|
+
/**
|
|
773
|
+
* Операторы сравнения
|
|
774
|
+
*/
|
|
775
|
+
parseComparison() {
|
|
776
|
+
let left = this.parseAdditive();
|
|
777
|
+
|
|
778
|
+
const comparators = ['EQEQ', 'NEQ', 'LT', 'LTE', 'GT', 'GTE'];
|
|
779
|
+
while (comparators.includes(this.peek().type)) {
|
|
780
|
+
const operator = this.advance().value;
|
|
781
|
+
const right = this.parseComparison(); // Используем тот же уровень приоритета для левой ассоциативности
|
|
782
|
+
left = new ASTNodes.BinaryExpression(operator, left, right);
|
|
783
|
+
}
|
|
784
|
+
|
|
785
|
+
return left;
|
|
786
|
+
}
|
|
787
|
+
|
|
788
|
+
/**
|
|
789
|
+
* Аддитивные операторы
|
|
790
|
+
*/
|
|
791
|
+
parseAdditive() {
|
|
792
|
+
let left = this.parseMultiplicative(); // Восстанавливаем правильный приоритет
|
|
793
|
+
|
|
794
|
+
while (this.check('PLUS') || this.check('MINUS')) {
|
|
795
|
+
const operator = this.advance().value;
|
|
796
|
+
const right = this.parseAdditive(); // Используем тот же уровень приоритета для левой ассоциативности
|
|
797
|
+
left = new ASTNodes.BinaryExpression(operator, left, right);
|
|
798
|
+
}
|
|
799
|
+
|
|
800
|
+
return left;
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
/**
|
|
804
|
+
* Экспоненциальные операторы
|
|
805
|
+
*/
|
|
806
|
+
parseExponential() {
|
|
807
|
+
let left = this.parseUnary();
|
|
808
|
+
|
|
809
|
+
while (this.check('EXP')) {
|
|
810
|
+
const operator = this.advance().value;
|
|
811
|
+
const right = this.parseExponential(); // Используем тот же уровень приоритета для правой ассоциативности (для экспоненты)
|
|
812
|
+
left = new ASTNodes.BinaryExpression(operator, left, right);
|
|
813
|
+
}
|
|
814
|
+
|
|
815
|
+
return left;
|
|
816
|
+
}
|
|
817
|
+
|
|
818
|
+
/**
|
|
819
|
+
* Битовые сдвиги
|
|
820
|
+
*/
|
|
821
|
+
parseShift() {
|
|
822
|
+
let left = this.parseMultiplicative(); // Битовые сдвиги имеют приоритет между мультипликативными и аддитивными
|
|
823
|
+
|
|
824
|
+
while (this.check('LEFTSHIFT') || this.check('RIGHTSHIFT')) {
|
|
825
|
+
const token = this.advance();
|
|
826
|
+
const operator = token.value;
|
|
827
|
+
const right = this.parseShift(); // Используем тот же уровень для левой ассоциативности
|
|
828
|
+
left = new ASTNodes.BitwiseExpression(operator, left, right);
|
|
829
|
+
}
|
|
830
|
+
|
|
831
|
+
return left;
|
|
832
|
+
}
|
|
833
|
+
|
|
834
|
+
/**
|
|
835
|
+
* Мультипликативные операторы
|
|
836
|
+
*/
|
|
837
|
+
parseMultiplicative() {
|
|
838
|
+
let left = this.parseExponential(); // Восстанавливаем правильный приоритет
|
|
839
|
+
|
|
840
|
+
while (this.check('MULT') || this.check('DIV') || this.check('MOD')) {
|
|
841
|
+
const operator = this.advance().value;
|
|
842
|
+
const right = this.parseMultiplicative(); // Используем тот же уровень приоритета для левой ассоциативности
|
|
843
|
+
left = new ASTNodes.BinaryExpression(operator, left, right);
|
|
844
|
+
}
|
|
845
|
+
|
|
846
|
+
return left;
|
|
847
|
+
}
|
|
848
|
+
|
|
849
|
+
/**
|
|
850
|
+
* Унарные операторы
|
|
851
|
+
*/
|
|
852
|
+
parseUnary() {
|
|
853
|
+
if (this.check('MINUS') || this.check('PLUS') || this.check('NOT')) {
|
|
854
|
+
const token = this.advance();
|
|
855
|
+
// Convert Russian operators to standard operators for internal use
|
|
856
|
+
let operator = token.value;
|
|
857
|
+
if (token.value === 'не') {
|
|
858
|
+
operator = '!';
|
|
859
|
+
}
|
|
860
|
+
const operand = this.parseUnary();
|
|
861
|
+
return new ASTNodes.UnaryExpression(operator, operand);
|
|
862
|
+
}
|
|
863
|
+
|
|
864
|
+
return this.parseCallMember();
|
|
865
|
+
}
|
|
866
|
+
|
|
867
|
+
/**
|
|
868
|
+
* Вызовы и доступ к членам
|
|
869
|
+
*/
|
|
870
|
+
parseCallMember() {
|
|
871
|
+
let object = this.parsePrimary();
|
|
872
|
+
|
|
873
|
+
while (true) {
|
|
874
|
+
if (this.check('LPAREN')) {
|
|
875
|
+
// Вызов функции
|
|
876
|
+
this.advance(); // consume (
|
|
877
|
+
|
|
878
|
+
const args = [];
|
|
879
|
+
if (!this.check('RPAREN')) {
|
|
880
|
+
while (true) {
|
|
881
|
+
// Проверяем, является ли аргумент spread оператором
|
|
882
|
+
if (this.check('SPREAD')) {
|
|
883
|
+
this.advance(); // consume '...'
|
|
884
|
+
const argument = this.parseExpression();
|
|
885
|
+
args.push(new ASTNodes.SpreadElement(argument));
|
|
886
|
+
} else {
|
|
887
|
+
args.push(this.parseExpression());
|
|
888
|
+
}
|
|
889
|
+
|
|
890
|
+
if (this.check('COMMA')) {
|
|
891
|
+
this.advance();
|
|
892
|
+
} else {
|
|
893
|
+
break;
|
|
894
|
+
}
|
|
895
|
+
}
|
|
896
|
+
}
|
|
897
|
+
|
|
898
|
+
this.consume('RPAREN', null, 'Ожидалось )');
|
|
899
|
+
object = new ASTNodes.CallExpression(object, args);
|
|
900
|
+
} else if (this.check('DOT')) {
|
|
901
|
+
// Доступ к члену
|
|
902
|
+
this.advance();
|
|
903
|
+
const property = this.consume('IDENTIFIER', null, 'Ожидалось свойство').value;
|
|
904
|
+
object = new ASTNodes.MemberExpression(object, new ASTNodes.Identifier(property));
|
|
905
|
+
} else if (this.check('LBRACKET')) {
|
|
906
|
+
// Доступ к элементу массива array[index]
|
|
907
|
+
this.advance(); // consume [
|
|
908
|
+
const index = this.parseExpression();
|
|
909
|
+
this.consume('RBRACKET', null, 'Ожидалось ]');
|
|
910
|
+
object = new ASTNodes.MemberExpression(object, index, true); // computed = true для array[index]
|
|
911
|
+
} else {
|
|
912
|
+
break;
|
|
913
|
+
}
|
|
914
|
+
}
|
|
915
|
+
|
|
916
|
+
return object;
|
|
917
|
+
}
|
|
918
|
+
|
|
919
|
+
/**
|
|
920
|
+
* Первичные выражения
|
|
921
|
+
*/
|
|
922
|
+
parsePrimary() {
|
|
923
|
+
const token = this.peek();
|
|
924
|
+
|
|
925
|
+
// Пропускаем переводы строк перед выражением
|
|
926
|
+
while (this.check('NEWLINE')) {
|
|
927
|
+
this.advance();
|
|
928
|
+
}
|
|
929
|
+
|
|
930
|
+
switch (token.type) {
|
|
931
|
+
case 'LBRACE':
|
|
932
|
+
return this.parseObjectLiteral();
|
|
933
|
+
|
|
934
|
+
case 'LBRACKET':
|
|
935
|
+
return this.parseArrayPatternOrLiteral();
|
|
936
|
+
|
|
937
|
+
case 'LPAREN':
|
|
938
|
+
return this.parseGroupedOrSequence();
|
|
939
|
+
|
|
940
|
+
case 'STRING':
|
|
941
|
+
return new ASTNodes.Literal(this.advance().value);
|
|
942
|
+
|
|
943
|
+
case 'INT':
|
|
944
|
+
return new ASTNodes.Literal(this.advance().value);
|
|
945
|
+
|
|
946
|
+
case 'TRUE':
|
|
947
|
+
this.advance();
|
|
948
|
+
return new ASTNodes.Literal(true);
|
|
949
|
+
|
|
950
|
+
case 'FALSE':
|
|
951
|
+
this.advance();
|
|
952
|
+
return new ASTNodes.Literal(false);
|
|
953
|
+
|
|
954
|
+
case 'NULL':
|
|
955
|
+
case 'NOTHING':
|
|
956
|
+
this.advance();
|
|
957
|
+
return new ASTNodes.Literal(null);
|
|
958
|
+
|
|
959
|
+
case 'IDENTIFIER':
|
|
960
|
+
return new ASTNodes.Identifier(this.advance().value);
|
|
961
|
+
|
|
962
|
+
case 'THIS':
|
|
963
|
+
this.advance();
|
|
964
|
+
return new ASTNodes.ThisExpression();
|
|
965
|
+
|
|
966
|
+
case 'SUPER':
|
|
967
|
+
this.advance();
|
|
968
|
+
return new ASTNodes.SuperExpression();
|
|
969
|
+
|
|
970
|
+
case 'TEMPLATE_LITERAL':
|
|
971
|
+
return this.parseTemplateLiteral();
|
|
972
|
+
|
|
973
|
+
case 'IMPORT':
|
|
974
|
+
case 'ИМПОРТ':
|
|
975
|
+
return this.parseImportExpression();
|
|
976
|
+
|
|
977
|
+
case 'AWAIT':
|
|
978
|
+
return this.parseAwaitExpression();
|
|
979
|
+
|
|
980
|
+
case 'AWAIT':
|
|
981
|
+
return this.parseAwaitExpression();
|
|
982
|
+
|
|
983
|
+
case 'FUNCTION':
|
|
984
|
+
return this.parseArrowFunction();
|
|
985
|
+
|
|
986
|
+
case 'NEW':
|
|
987
|
+
return this.parseNewExpression();
|
|
988
|
+
|
|
989
|
+
default:
|
|
990
|
+
throw this.error(`Ожидалось выражение, получено: ${token.type}`);
|
|
991
|
+
}
|
|
992
|
+
}
|
|
993
|
+
|
|
994
|
+
/**
|
|
995
|
+
* Объектный литерал или паттерн
|
|
996
|
+
*/
|
|
997
|
+
parseObjectPatternOrLiteral() {
|
|
998
|
+
this.consume('LBRACE', null, 'Ожидалась {');
|
|
999
|
+
|
|
1000
|
+
const properties = [];
|
|
1001
|
+
let isPattern = false;
|
|
1002
|
+
|
|
1003
|
+
// Пропускаем переводы строк в начале объекта
|
|
1004
|
+
while (this.check('NEWLINE')) {
|
|
1005
|
+
this.advance();
|
|
1006
|
+
}
|
|
1007
|
+
|
|
1008
|
+
while (!this.check('RBRACE')) {
|
|
1009
|
+
// Пропускаем переводы строк между свойствами
|
|
1010
|
+
while (this.check('NEWLINE')) {
|
|
1011
|
+
this.advance();
|
|
1012
|
+
}
|
|
1013
|
+
|
|
1014
|
+
if (this.check('COMMA')) {
|
|
1015
|
+
this.advance();
|
|
1016
|
+
continue;
|
|
1017
|
+
}
|
|
1018
|
+
|
|
1019
|
+
// Проверяем, является ли это паттерном деструктуризации
|
|
1020
|
+
// В паттерне ключ может быть идентификатором, и если нет двоеточия, это сокращенная запись
|
|
1021
|
+
let key;
|
|
1022
|
+
if (this.check('IDENTIFIER')) {
|
|
1023
|
+
key = new ASTNodes.Identifier(this.advance().value);
|
|
1024
|
+
isPattern = true;
|
|
1025
|
+
} else if (this.check('STRING')) {
|
|
1026
|
+
key = new ASTNodes.PropertyKey(this.advance().value);
|
|
1027
|
+
} else {
|
|
1028
|
+
throw this.error('Ожидался ключ свойства');
|
|
1029
|
+
}
|
|
1030
|
+
|
|
1031
|
+
let value = key; // По умолчанию значение такое же, как ключ (для сокращенной записи)
|
|
1032
|
+
|
|
1033
|
+
// Если есть двоеточие, значит это полная запись (ключ: значение)
|
|
1034
|
+
if (this.check('COLON')) {
|
|
1035
|
+
this.advance(); // пропускаем двоеточие
|
|
1036
|
+
value = this.parsePatternOrExpression(); // для паттернов может быть идентификатор
|
|
1037
|
+
}
|
|
1038
|
+
|
|
1039
|
+
properties.push(new ASTNodes.Property(key, value));
|
|
1040
|
+
|
|
1041
|
+
// Пропускаем переводы строк после свойства
|
|
1042
|
+
while (this.check('NEWLINE')) {
|
|
1043
|
+
this.advance();
|
|
1044
|
+
}
|
|
1045
|
+
|
|
1046
|
+
// Проверяем, есть ли еще свойства
|
|
1047
|
+
if (!this.check('RBRACE')) {
|
|
1048
|
+
this.consume('COMMA', null, 'Ожидалась ,');
|
|
1049
|
+
}
|
|
1050
|
+
}
|
|
1051
|
+
|
|
1052
|
+
this.consume('RBRACE', null, 'Ожидалась }');
|
|
1053
|
+
|
|
1054
|
+
// Возвращаем паттерн, если мы в контексте присваивания, иначе выражение
|
|
1055
|
+
if (isPattern) {
|
|
1056
|
+
return new ASTNodes.ObjectPattern(properties);
|
|
1057
|
+
} else {
|
|
1058
|
+
return new ASTNodes.ObjectExpression(properties);
|
|
1059
|
+
}
|
|
1060
|
+
}
|
|
1061
|
+
|
|
1062
|
+
/**
|
|
1063
|
+
* Объектный литерал
|
|
1064
|
+
*/
|
|
1065
|
+
parseObjectLiteral() {
|
|
1066
|
+
this.consume('LBRACE', null, 'Ожидалась {');
|
|
1067
|
+
|
|
1068
|
+
const properties = [];
|
|
1069
|
+
|
|
1070
|
+
// Пропускаем переводы строк в начале объекта
|
|
1071
|
+
while (this.check('NEWLINE')) {
|
|
1072
|
+
this.advance();
|
|
1073
|
+
}
|
|
1074
|
+
|
|
1075
|
+
while (!this.check('RBRACE')) {
|
|
1076
|
+
// Пропускаем переводы строк между свойствами
|
|
1077
|
+
while (this.check('NEWLINE')) {
|
|
1078
|
+
this.advance();
|
|
1079
|
+
}
|
|
1080
|
+
|
|
1081
|
+
if (this.check('COMMA')) {
|
|
1082
|
+
this.advance();
|
|
1083
|
+
continue;
|
|
1084
|
+
}
|
|
1085
|
+
|
|
1086
|
+
// Ключ свойства
|
|
1087
|
+
let key;
|
|
1088
|
+
if (this.check('IDENTIFIER')) {
|
|
1089
|
+
key = new ASTNodes.PropertyKey(this.advance().value);
|
|
1090
|
+
} else if (this.check('STRING')) {
|
|
1091
|
+
key = new ASTNodes.PropertyKey(this.advance().value);
|
|
1092
|
+
} else {
|
|
1093
|
+
throw this.error('Ожидался ключ свойства');
|
|
1094
|
+
}
|
|
1095
|
+
|
|
1096
|
+
// Двоеточие
|
|
1097
|
+
this.consume('COLON', null, 'Ожидалось :');
|
|
1098
|
+
|
|
1099
|
+
// Значение
|
|
1100
|
+
const value = this.parseExpression();
|
|
1101
|
+
|
|
1102
|
+
properties.push(new ASTNodes.PropertyDefinition(key, value));
|
|
1103
|
+
|
|
1104
|
+
// Пропускаем переводы строк после свойства
|
|
1105
|
+
while (this.check('NEWLINE')) {
|
|
1106
|
+
this.advance();
|
|
1107
|
+
}
|
|
1108
|
+
|
|
1109
|
+
// Проверяем, есть ли еще свойства
|
|
1110
|
+
if (!this.check('RBRACE')) {
|
|
1111
|
+
this.consume('COMMA', null, 'Ожидалась ,');
|
|
1112
|
+
}
|
|
1113
|
+
}
|
|
1114
|
+
|
|
1115
|
+
this.consume('RBRACE', null, 'Ожидалась }');
|
|
1116
|
+
|
|
1117
|
+
return new ASTNodes.ObjectExpression(properties);
|
|
1118
|
+
}
|
|
1119
|
+
|
|
1120
|
+
/**
|
|
1121
|
+
* Массив или паттерн массива
|
|
1122
|
+
*/
|
|
1123
|
+
parseArrayPatternOrLiteral() {
|
|
1124
|
+
this.consume('LBRACKET', null, 'Ожидалось [');
|
|
1125
|
+
|
|
1126
|
+
const elements = [];
|
|
1127
|
+
let isPattern = false;
|
|
1128
|
+
let hasSpread = false;
|
|
1129
|
+
|
|
1130
|
+
while (!this.check('RBRACKET')) {
|
|
1131
|
+
// Skip any newlines before checking for comma or elements
|
|
1132
|
+
while (this.check('NEWLINE')) {
|
|
1133
|
+
this.advance();
|
|
1134
|
+
}
|
|
1135
|
+
|
|
1136
|
+
// Check if next token after a comma is another comma (empty slot) or closing bracket
|
|
1137
|
+
if (this.check('COMMA')) {
|
|
1138
|
+
// Empty slot - push null
|
|
1139
|
+
elements.push(null);
|
|
1140
|
+
this.advance(); // consume comma
|
|
1141
|
+
continue;
|
|
1142
|
+
}
|
|
1143
|
+
|
|
1144
|
+
// Skip any newlines before parsing the element
|
|
1145
|
+
while (this.check('NEWLINE')) {
|
|
1146
|
+
this.advance();
|
|
1147
|
+
}
|
|
1148
|
+
|
|
1149
|
+
// Проверяем, является ли элемент spread оператором
|
|
1150
|
+
if (this.check('SPREAD')) {
|
|
1151
|
+
this.advance(); // consume '...'
|
|
1152
|
+
const argument = this.parsePatternOrExpression();
|
|
1153
|
+
elements.push(new ASTNodes.SpreadElement(argument));
|
|
1154
|
+
hasSpread = true;
|
|
1155
|
+
} else {
|
|
1156
|
+
// Проверяем, является ли элемент паттерном (идентификатором или другим паттерном)
|
|
1157
|
+
// до того, как его распарсим
|
|
1158
|
+
const isElementPattern = this.check('IDENTIFIER') || this.check('LBRACKET') || this.check('LBRACE');
|
|
1159
|
+
if (isElementPattern) {
|
|
1160
|
+
isPattern = true;
|
|
1161
|
+
}
|
|
1162
|
+
|
|
1163
|
+
elements.push(this.parsePatternOrExpression());
|
|
1164
|
+
}
|
|
1165
|
+
|
|
1166
|
+
if (this.check('COMMA')) {
|
|
1167
|
+
this.advance();
|
|
1168
|
+
} else {
|
|
1169
|
+
break;
|
|
1170
|
+
}
|
|
1171
|
+
}
|
|
1172
|
+
|
|
1173
|
+
// Skip any newlines before consuming the closing bracket
|
|
1174
|
+
while (this.check('NEWLINE')) {
|
|
1175
|
+
this.advance();
|
|
1176
|
+
}
|
|
1177
|
+
|
|
1178
|
+
this.consume('RBRACKET', null, 'Ожидалось ]');
|
|
1179
|
+
|
|
1180
|
+
// Возвращаем паттерн, если мы в контексте присваивания или содержит паттерны
|
|
1181
|
+
if (hasSpread) {
|
|
1182
|
+
return new ASTNodes.ArrayExpression(elements);
|
|
1183
|
+
} else if (isPattern) {
|
|
1184
|
+
return new ASTNodes.ArrayPattern(elements);
|
|
1185
|
+
} else {
|
|
1186
|
+
return new ASTNodes.ArrayExpression(elements);
|
|
1187
|
+
}
|
|
1188
|
+
}
|
|
1189
|
+
|
|
1190
|
+
/**
|
|
1191
|
+
* Паттерн или выражение
|
|
1192
|
+
*/
|
|
1193
|
+
parsePatternOrExpression() {
|
|
1194
|
+
// Проверяем, является ли это паттерном деструктуризации
|
|
1195
|
+
if (this.check('IDENTIFIER')) {
|
|
1196
|
+
return new ASTNodes.Identifier(this.advance().value);
|
|
1197
|
+
} else if (this.check('LBRACKET')) {
|
|
1198
|
+
return this.parseArrayPatternOrLiteral();
|
|
1199
|
+
} else if (this.check('LBRACE')) {
|
|
1200
|
+
return this.parseObjectPatternOrLiteral();
|
|
1201
|
+
} else {
|
|
1202
|
+
// Иначе это обычное выражение
|
|
1203
|
+
return this.parseExpression();
|
|
1204
|
+
}
|
|
1205
|
+
}
|
|
1206
|
+
|
|
1207
|
+
/**
|
|
1208
|
+
* Массив
|
|
1209
|
+
*/
|
|
1210
|
+
parseArrayLiteral() {
|
|
1211
|
+
this.consume('LBRACKET', null, 'Ожидалось [');
|
|
1212
|
+
|
|
1213
|
+
const elements = [];
|
|
1214
|
+
|
|
1215
|
+
while (!this.check('RBRACKET')) {
|
|
1216
|
+
if (this.check('COMMA')) {
|
|
1217
|
+
elements.push(null);
|
|
1218
|
+
this.advance();
|
|
1219
|
+
continue;
|
|
1220
|
+
}
|
|
1221
|
+
|
|
1222
|
+
elements.push(this.parseExpression());
|
|
1223
|
+
|
|
1224
|
+
if (this.check('COMMA')) {
|
|
1225
|
+
this.advance();
|
|
1226
|
+
} else {
|
|
1227
|
+
break;
|
|
1228
|
+
}
|
|
1229
|
+
}
|
|
1230
|
+
|
|
1231
|
+
this.consume('RBRACKET', null, 'Ожидалось ]');
|
|
1232
|
+
|
|
1233
|
+
return new ASTNodes.ArrayExpression(elements);
|
|
1234
|
+
}
|
|
1235
|
+
|
|
1236
|
+
/**
|
|
1237
|
+
* Группировка или последовательность
|
|
1238
|
+
*/
|
|
1239
|
+
parseGroupedOrSequence() {
|
|
1240
|
+
this.advance(); // consume (
|
|
1241
|
+
|
|
1242
|
+
// Проверяем, пустая ли группа или последовательность
|
|
1243
|
+
if (this.check('RPAREN')) {
|
|
1244
|
+
this.advance();
|
|
1245
|
+
return new ASTNodes.ArrayExpression([]);
|
|
1246
|
+
}
|
|
1247
|
+
|
|
1248
|
+
const expr = this.parseExpression();
|
|
1249
|
+
|
|
1250
|
+
if (this.check('COMMA')) {
|
|
1251
|
+
// Последовательность (a, b, c)
|
|
1252
|
+
const expressions = [expr];
|
|
1253
|
+
|
|
1254
|
+
while (this.check('COMMA')) {
|
|
1255
|
+
this.advance();
|
|
1256
|
+
expressions.push(this.parseExpression());
|
|
1257
|
+
}
|
|
1258
|
+
|
|
1259
|
+
this.consume('RPAREN', null, 'Ожидалось )');
|
|
1260
|
+
|
|
1261
|
+
// Возвращаем последовательность
|
|
1262
|
+
return new ASTNodes.SequenceExpression(expressions);
|
|
1263
|
+
}
|
|
1264
|
+
|
|
1265
|
+
this.consume('RPAREN', null, 'Ожидалось )');
|
|
1266
|
+
return expr;
|
|
1267
|
+
}
|
|
1268
|
+
|
|
1269
|
+
/**
|
|
1270
|
+
* Тернарный оператор (условие ? then : else)
|
|
1271
|
+
*/
|
|
1272
|
+
parseTernary() {
|
|
1273
|
+
let condition = this.parseLogical();
|
|
1274
|
+
|
|
1275
|
+
if (this.check('TERNARY')) {
|
|
1276
|
+
this.advance(); // consume '?'
|
|
1277
|
+
const thenBranch = this.parseAssignment(); // use same precedence level for branches
|
|
1278
|
+
this.consume('COLON', null, 'Expected : in ternary operator');
|
|
1279
|
+
const elseBranch = this.parseAssignment(); // use same precedence level for branches
|
|
1280
|
+
return new ASTNodes.TernaryExpression(condition, thenBranch, elseBranch);
|
|
1281
|
+
}
|
|
1282
|
+
|
|
1283
|
+
return condition;
|
|
1284
|
+
}
|
|
1285
|
+
|
|
1286
|
+
/**
|
|
1287
|
+
* Шаблонная строка
|
|
1288
|
+
*/
|
|
1289
|
+
parseTemplateLiteral() {
|
|
1290
|
+
const token = this.advance();
|
|
1291
|
+
const value = token.value;
|
|
1292
|
+
|
|
1293
|
+
// Простая реализация - возвращаем как строку
|
|
1294
|
+
// В реальности нужно разобрать интерполяции ${...}
|
|
1295
|
+
return new ASTNodes.Literal(value);
|
|
1296
|
+
}
|
|
1297
|
+
|
|
1298
|
+
/**
|
|
1299
|
+
* Await выражение
|
|
1300
|
+
*/
|
|
1301
|
+
parseAwaitExpression() {
|
|
1302
|
+
this.advance(); // consume AWAIT
|
|
1303
|
+
const argument = this.parseUnary(); // Await имеет тот же приоритет, что и унарные операторы
|
|
1304
|
+
return new ASTNodes.AwaitExpression(argument);
|
|
1305
|
+
}
|
|
1306
|
+
|
|
1307
|
+
/**
|
|
1308
|
+
* Стрелочная функция
|
|
1309
|
+
*/
|
|
1310
|
+
parseArrowFunction() {
|
|
1311
|
+
const func = this.parseFunctionDeclaration();
|
|
1312
|
+
|
|
1313
|
+
return new ASTNodes.ArrowFunctionExpression(func.params, func.body);
|
|
1314
|
+
}
|
|
1315
|
+
|
|
1316
|
+
/**
|
|
1317
|
+
* Import statement - импорт модуля
|
|
1318
|
+
*/
|
|
1319
|
+
parseImportStatement() {
|
|
1320
|
+
this.advance(); // consume IMPORT или ИМПОРТ
|
|
1321
|
+
|
|
1322
|
+
const pathToken = this.consume('STRING', null, 'Ожидался строковый путь к модулю');
|
|
1323
|
+
const path = pathToken.value;
|
|
1324
|
+
|
|
1325
|
+
let alias = null;
|
|
1326
|
+
|
|
1327
|
+
// Проверяем, идёт ли токен AS ("как" или "as")
|
|
1328
|
+
if (this.check('AS')) {
|
|
1329
|
+
this.advance(); // пропускаем "как" или "as"
|
|
1330
|
+
|
|
1331
|
+
const aliasToken = this.consume('IDENTIFIER', null, 'Ожидалось имя переменной после "как" или "as"');
|
|
1332
|
+
alias = aliasToken.value;
|
|
1333
|
+
}
|
|
1334
|
+
|
|
1335
|
+
// Точка с запятой опциональна
|
|
1336
|
+
if (this.check('SEMICOLON')) {
|
|
1337
|
+
this.advance();
|
|
1338
|
+
}
|
|
1339
|
+
|
|
1340
|
+
return new ASTNodes.ImportStatement(path, alias);
|
|
1341
|
+
}
|
|
1342
|
+
/**
|
|
1343
|
+
* Export statement - экспорт имен
|
|
1344
|
+
*/
|
|
1345
|
+
parseExportStatement() {
|
|
1346
|
+
this.advance(); // consume EXPORT
|
|
1347
|
+
|
|
1348
|
+
this.consume('LBRACE', null, 'Ожидалось {');
|
|
1349
|
+
|
|
1350
|
+
const identifiers = [];
|
|
1351
|
+
|
|
1352
|
+
if (!this.check('RBRACE')) {
|
|
1353
|
+
while (true) {
|
|
1354
|
+
const name = this.consume('IDENTIFIER', null, 'Ожидалось имя для экспорта').value;
|
|
1355
|
+
identifiers.push(name);
|
|
1356
|
+
|
|
1357
|
+
if (this.check('COMMA')) {
|
|
1358
|
+
this.advance();
|
|
1359
|
+
} else {
|
|
1360
|
+
break;
|
|
1361
|
+
}
|
|
1362
|
+
}
|
|
1363
|
+
}
|
|
1364
|
+
|
|
1365
|
+
this.consume('RBRACE', null, 'Ожидалось }');
|
|
1366
|
+
|
|
1367
|
+
// Пропуск точки с запятой
|
|
1368
|
+
if (this.check('SEMICOLON')) {
|
|
1369
|
+
this.advance();
|
|
1370
|
+
}
|
|
1371
|
+
|
|
1372
|
+
return new ASTNodes.ExportStatement(identifiers);
|
|
1373
|
+
}
|
|
1374
|
+
|
|
1375
|
+
/**
|
|
1376
|
+
* New expression - создание экземпляра класса
|
|
1377
|
+
*/
|
|
1378
|
+
parseNewExpression() {
|
|
1379
|
+
this.advance();
|
|
1380
|
+
|
|
1381
|
+
const callee = this.parsePrimary();
|
|
1382
|
+
|
|
1383
|
+
const args = [];
|
|
1384
|
+
if (this.check('LPAREN')) {
|
|
1385
|
+
this.advance();
|
|
1386
|
+
if (!this.check('RPAREN')) {
|
|
1387
|
+
while (true) {
|
|
1388
|
+
if (this.check('SPREAD')) {
|
|
1389
|
+
this.advance();
|
|
1390
|
+
const argument = this.parseExpression();
|
|
1391
|
+
args.push(new ASTNodes.SpreadElement(argument));
|
|
1392
|
+
} else {
|
|
1393
|
+
args.push(this.parseExpression());
|
|
1394
|
+
}
|
|
1395
|
+
|
|
1396
|
+
if (this.check('COMMA')) {
|
|
1397
|
+
this.advance();
|
|
1398
|
+
} else {
|
|
1399
|
+
break;
|
|
1400
|
+
}
|
|
1401
|
+
}
|
|
1402
|
+
}
|
|
1403
|
+
this.consume('RPAREN', null, 'Ожидалось )');
|
|
1404
|
+
}
|
|
1405
|
+
|
|
1406
|
+
return new ASTNodes.NewExpression(callee, args);
|
|
1407
|
+
}
|
|
1408
|
+
}
|