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.
Files changed (42) hide show
  1. package/README.md +256 -0
  2. package/bin/cli.js +486 -0
  3. package/bin/vlad.js +539 -0
  4. package/bin/vladpm.js +710 -0
  5. package/bin/vladx.js +491 -0
  6. package/package.json +57 -0
  7. package/src/engine/jit-compiler.js +285 -0
  8. package/src/engine/vladx-engine.js +941 -0
  9. package/src/index.js +44 -0
  10. package/src/interpreter/interpreter.js +2114 -0
  11. package/src/lexer/lexer.js +658 -0
  12. package/src/lexer/optimized-lexer.js +106 -0
  13. package/src/lexer/regex-cache.js +83 -0
  14. package/src/parser/ast-nodes.js +472 -0
  15. package/src/parser/parser.js +1408 -0
  16. package/src/runtime/advanced-type-system.js +209 -0
  17. package/src/runtime/async-manager.js +252 -0
  18. package/src/runtime/builtins.js +143 -0
  19. package/src/runtime/bundler.js +422 -0
  20. package/src/runtime/cache-manager.js +126 -0
  21. package/src/runtime/data-structures.js +612 -0
  22. package/src/runtime/debugger.js +260 -0
  23. package/src/runtime/enhanced-module-system.js +196 -0
  24. package/src/runtime/environment-enhanced.js +272 -0
  25. package/src/runtime/environment.js +140 -0
  26. package/src/runtime/event-emitter.js +232 -0
  27. package/src/runtime/formatter.js +280 -0
  28. package/src/runtime/functional.js +359 -0
  29. package/src/runtime/io-operations.js +390 -0
  30. package/src/runtime/linter.js +374 -0
  31. package/src/runtime/logging.js +314 -0
  32. package/src/runtime/minifier.js +242 -0
  33. package/src/runtime/module-system.js +377 -0
  34. package/src/runtime/network-operations.js +373 -0
  35. package/src/runtime/profiler.js +295 -0
  36. package/src/runtime/repl.js +336 -0
  37. package/src/runtime/security-manager.js +244 -0
  38. package/src/runtime/source-map-generator.js +208 -0
  39. package/src/runtime/test-runner.js +394 -0
  40. package/src/runtime/transformer.js +277 -0
  41. package/src/runtime/type-system.js +244 -0
  42. package/src/runtime/vladx-object.js +250 -0
@@ -0,0 +1,658 @@
1
+ /**
2
+ * VladX Lexer — Лексический анализатор
3
+ * Преобразует исходный код в поток токенов
4
+ */
5
+
6
+ export class Lexer {
7
+ constructor(source, filename = '<anonymous>') {
8
+ this.source = source;
9
+ this.filename = filename;
10
+ this.pos = 0;
11
+ this.line = 1;
12
+ this.column = 0;
13
+ this.tokens = [];
14
+ this.currentChar = this.source[0] || null;
15
+ }
16
+
17
+ /**
18
+ * Основной метод токенизации
19
+ */
20
+ tokenize() {
21
+ try {
22
+ while (this.currentChar !== null) {
23
+ // Пропускаем пробелы и табы (но не переводы строк)
24
+ if (this.currentChar === ' ' || this.currentChar === '\t') {
25
+ this.advance();
26
+ continue;
27
+ }
28
+
29
+ // Перевод строки
30
+ if (this.currentChar === '\n') {
31
+ this.tokens.push({
32
+ type: 'NEWLINE',
33
+ value: '\n',
34
+ line: this.line,
35
+ column: this.column,
36
+ filename: this.filename
37
+ });
38
+ this.line++;
39
+ this.column = 0;
40
+ this.advance();
41
+ continue;
42
+ }
43
+
44
+ // Комментарии
45
+ if (this.currentChar === '#') {
46
+ this.skipComment();
47
+ continue;
48
+ }
49
+ if (this.currentChar === '/' && (this.peek() === '*' || this.peek() === '/')) {
50
+ this.skipComment();
51
+ continue;
52
+ }
53
+
54
+ // Строковые литералы
55
+ if (this.currentChar === '"' || this.currentChar === "'") {
56
+ const token = this.readStringLiteral();
57
+ this.tokens.push(token);
58
+ continue;
59
+ }
60
+
61
+ // Идентификаторы и ключевые слова
62
+ if (this.isAlpha(this.currentChar) || this.currentChar === '_') {
63
+ const token = this.readIdentifier();
64
+ this.tokens.push(token);
65
+ continue;
66
+ }
67
+
68
+ // Шаблонные строки (начинаются с `)
69
+ if (this.currentChar === '`') {
70
+ const token = this.readTemplateLiteral();
71
+ this.tokens.push(token);
72
+ continue;
73
+ }
74
+
75
+ // Числа
76
+ if (this.isDigit(this.currentChar)) {
77
+ const token = this.readNumber();
78
+ this.tokens.push(token);
79
+ continue;
80
+ }
81
+
82
+ // Операторы и символы
83
+ const token = this.readOperator();
84
+ if (token) {
85
+ this.tokens.push(token);
86
+ continue;
87
+ }
88
+
89
+ // Неизвестный символ
90
+ throw new Error(`[${this.filename}:${this.line}:${this.column}] Неизвестный символ: '${this.currentChar}' (код: ${this.currentChar.charCodeAt(0)})`);
91
+ }
92
+
93
+ // Добавляем EOF токен
94
+ this.tokens.push({
95
+ type: 'EOF',
96
+ value: null,
97
+ line: this.line,
98
+ column: this.column,
99
+ filename: this.filename
100
+ });
101
+
102
+ return this.tokens;
103
+ } catch (error) {
104
+ if (this.debug) {
105
+ console.error('[Lexer] Error during tokenization:', error);
106
+ console.error('[Lexer] Position:', this.pos, 'Char:', this.currentChar);
107
+ }
108
+ throw error;
109
+ }
110
+ }
111
+
112
+ /**
113
+ * Продвижение по строке
114
+ */
115
+ advance() {
116
+ this.pos++;
117
+ this.column++;
118
+ this.currentChar = this.source[this.pos] || null;
119
+ }
120
+
121
+ /**
122
+ * Пропуск комментариев
123
+ */
124
+ skipComment() {
125
+ if (this.currentChar === '#') {
126
+ // Однострочный комментарий
127
+ while (this.currentChar !== null && this.currentChar !== '\n') {
128
+ this.advance();
129
+ }
130
+ } else if (this.currentChar === '/' && this.peek() === '*') {
131
+ // Многострочный комментарий /* ... */
132
+ this.advance(); // пропускаем '/'
133
+ this.advance(); // пропускаем '*'
134
+ while (this.currentChar !== null) {
135
+ if (this.currentChar === '\n') {
136
+ this.line++;
137
+ this.column = 0;
138
+ } else {
139
+ this.column++;
140
+ }
141
+
142
+ if (this.currentChar === '*' && this.peek() === '/') {
143
+ this.advance(); // пропускаем '*'
144
+ this.advance(); // пропускаем '/'
145
+ break;
146
+ }
147
+ this.advance();
148
+ }
149
+ } else if (this.currentChar === '/' && this.peek() === '/') {
150
+ // Однострочный комментарий // ...
151
+ this.advance(); // пропускаем '/'
152
+ this.advance(); // пропускаем '/'
153
+ while (this.currentChar !== null && this.currentChar !== '\n') {
154
+ this.advance();
155
+ }
156
+ }
157
+ }
158
+
159
+ /**
160
+ * Чтение строкового литерала
161
+ */
162
+ readStringLiteral() {
163
+ const quote = this.currentChar;
164
+ this.advance(); // Пропускаем открывающую кавычку
165
+
166
+ let value = '';
167
+ const startLine = this.line;
168
+ const startColumn = this.column;
169
+
170
+ while (this.currentChar !== null && this.currentChar !== quote) {
171
+ if (this.currentChar === '\\') {
172
+ this.advance();
173
+ if (this.currentChar === null) {
174
+ throw new Error("Незавершённая escape-последовательность в строке");
175
+ }
176
+
177
+ switch (this.currentChar) {
178
+ case 'n':
179
+ value += '\n';
180
+ break;
181
+ case 't':
182
+ value += '\t';
183
+ break;
184
+ case 'r':
185
+ value += '\r';
186
+ break;
187
+ case '"':
188
+ value += '"';
189
+ break;
190
+ case "'":
191
+ value += "'";
192
+ break;
193
+ case '\\':
194
+ value += '\\';
195
+ break;
196
+ case 'x': // \xHH — hex код
197
+ this.advance();
198
+ const hex1 = this.currentChar;
199
+ this.advance();
200
+ const hex2 = this.currentChar;
201
+ if (hex1 && hex2 && /[0-9a-fA-F]{2}/.test(hex1 + hex2)) {
202
+ value += String.fromCharCode(parseInt(hex1 + hex2, 16));
203
+ } else {
204
+ throw new Error(`Неверный \\x escape: \\x${hex1 || ''}${hex2 || ''}`);
205
+ }
206
+ break;
207
+ case 'u': // \uHHHH — unicode
208
+ this.advance();
209
+ let unicode = '';
210
+ for (let i = 0; i < 4; i++) {
211
+ if (this.currentChar && /[0-9a-fA-F]/.test(this.currentChar)) {
212
+ unicode += this.currentChar;
213
+ this.advance();
214
+ } else {
215
+ throw new Error(`Неверный \\u escape: \\u${unicode}`);
216
+ }
217
+ }
218
+ value += String.fromCharCode(parseInt(unicode, 16));
219
+ this.advance(); // пропустить после \uHHHH
220
+ continue; // чтобы не добавлять последний символ как обычный
221
+ default:
222
+ value += '\\' + this.currentChar; // неизвестный escape — оставляем как есть
223
+ }
224
+ this.advance();
225
+ continue;
226
+ } else {
227
+ value += this.currentChar;
228
+ this.advance();
229
+ }
230
+ }
231
+
232
+ this.advance(); // Пропускаем закрывающую кавычку
233
+
234
+ return {
235
+ type: 'STRING',
236
+ value: value,
237
+ line: startLine,
238
+ column: startColumn,
239
+ filename: this.filename
240
+ };
241
+ }
242
+
243
+ /**
244
+ * Обработка escape-последовательностей
245
+ */
246
+ escapeChar(char) {
247
+ const escapes = {
248
+ 'n': '\n',
249
+ 't': '\t',
250
+ 'r': '\r',
251
+ '\\': '\\',
252
+ '"': '"',
253
+ "'": "'"
254
+ };
255
+ return escapes[char] || char;
256
+ }
257
+
258
+ /**
259
+ * Чтение шаблонной строки
260
+ */
261
+ readTemplateLiteral() {
262
+ this.advance(); // Пропускаем открывающую обратную кавычку
263
+
264
+ let value = '';
265
+ const startLine = this.line;
266
+ const startColumn = this.column;
267
+
268
+ while (this.currentChar !== null && this.currentChar !== '`') {
269
+ if (this.currentChar === '\\') {
270
+ this.advance();
271
+ if (this.currentChar === null) {
272
+ throw new Error("Незавершённая escape-последовательность в шаблонной строке");
273
+ }
274
+ value += this.escapeChar(this.currentChar);
275
+ this.advance();
276
+ } else if (this.currentChar === '$' && this.peek() === '{') {
277
+ // Это начало интерполяции ${...}
278
+ value += this.currentChar; // Добавляем $
279
+ this.advance(); // Пропускаем $
280
+ value += this.currentChar; // Добавляем {
281
+ this.advance(); // Пропускаем {
282
+ } else {
283
+ value += this.currentChar;
284
+ this.advance();
285
+ }
286
+ }
287
+
288
+ if (this.currentChar !== '`') {
289
+ throw new Error(`Незавершённая шаблонная строка, ожидалась обратная кавычка`);
290
+ }
291
+
292
+ this.advance(); // Пропускаем закрывающую обратную кавычку
293
+
294
+ return {
295
+ type: 'TEMPLATE_LITERAL',
296
+ value: value,
297
+ line: startLine,
298
+ column: startColumn,
299
+ filename: this.filename
300
+ };
301
+ }
302
+
303
+ /**
304
+ * Чтение идентификатора или ключевого слова
305
+ */
306
+ readIdentifier() {
307
+ let value = '';
308
+ const startLine = this.line;
309
+ const startColumn = this.column;
310
+
311
+ while (this.currentChar !== null && (this.isAlphaNumeric(this.currentChar) || this.currentChar === '_')) {
312
+ value += this.currentChar;
313
+ this.advance();
314
+ }
315
+
316
+ // Проверка на ключевые слова - маппинг русских слов к латинским типам токенов
317
+ const keywordToTokenType = {
318
+ 'let': 'LET',
319
+ 'пусть': 'LET',
320
+ 'const': 'CONST',
321
+ 'конст': 'CONST',
322
+ 'константа': 'CONST',
323
+ 'if': 'IF',
324
+ 'если': 'IF',
325
+ 'else': 'ELSE',
326
+ 'иначе': 'ELSE',
327
+ 'elseif': 'ELSEIF',
328
+ 'иначеесли': 'ELSEIF',
329
+ 'илиесли': 'ИЛИЕСЛИ',
330
+ 'while': 'WHILE',
331
+ 'пока': 'WHILE',
332
+ 'for': 'FOR',
333
+ 'для': 'FOR',
334
+ 'function': 'FUNCTION',
335
+ 'функция': 'FUNCTION',
336
+ 'return': 'RETURN',
337
+ 'вернуть': 'RETURN',
338
+ 'class': 'CLASS',
339
+ 'класс': 'CLASS',
340
+ 'class': 'CLASS',
341
+ 'this': 'THIS',
342
+ 'это': 'THIS',
343
+ 'super': 'SUPER',
344
+ 'супер': 'SUPER',
345
+ 'new': 'NEW',
346
+ 'новый': 'NEW',
347
+ 'extends': 'EXTENDS',
348
+ 'расширяет': 'EXTENDS',
349
+ 'try': 'TRY',
350
+ 'попытка': 'TRY',
351
+ 'catch': 'CATCH',
352
+ 'перехват': 'CATCH',
353
+ 'исключение': 'CATCH',
354
+ 'finally': 'FINALLY',
355
+ 'наконец': 'FINALLY',
356
+ 'throw': 'THROW',
357
+ 'бросить': 'THROW',
358
+ 'break': 'BREAK',
359
+ 'прервать': 'BREAK',
360
+ 'continue': 'CONTINUE',
361
+ 'продолжить': 'CONTINUE',
362
+ 'static': 'STATIC',
363
+ 'СТАТИЧЕСКИЙ': 'STATIC',
364
+ 'статический': 'STATIC',
365
+ 'get': 'GET',
366
+ 'set': 'SET',
367
+ 'true': 'TRUE',
368
+ 'истина': 'TRUE',
369
+ 'false': 'FALSE',
370
+ 'ложь': 'FALSE',
371
+ 'null': 'NULL',
372
+ 'ничто': 'NULL',
373
+ 'nothing': 'NULL',
374
+ 'import': 'IMPORT',
375
+ 'импорт': 'IMPORT',
376
+ 'export': 'EXPORT',
377
+ 'экспорт': 'EXPORT',
378
+ 'from': 'FROM',
379
+ 'из': 'FROM',
380
+ 'as': 'AS',
381
+ 'как': 'AS',
382
+ 'and': 'AND',
383
+ 'и': 'AND',
384
+ 'or': 'OR',
385
+ 'или': 'OR',
386
+ 'with': 'WITH',
387
+ 'с': 'WITH',
388
+ 'to': 'TO',
389
+ 'до': 'TO',
390
+ 'switch': 'SWITCH',
391
+ 'выбор': 'SWITCH',
392
+ 'case': 'CASE',
393
+ 'когда': 'CASE',
394
+ 'default': 'DEFAULT',
395
+ 'поумолчанию': 'DEFAULT',
396
+ 'async': 'ASYNC',
397
+ 'асинх': 'ASYNC',
398
+ 'await': 'AWAIT',
399
+ 'ожидать': 'AWAIT',
400
+ 'не': 'NOT'
401
+ };
402
+
403
+ const lowerValue = value.toLowerCase();
404
+ const type = Object.prototype.hasOwnProperty.call(keywordToTokenType, lowerValue) ? keywordToTokenType[lowerValue] : 'IDENTIFIER';
405
+
406
+ return {
407
+ type: type,
408
+ value: value,
409
+ line: startLine,
410
+ column: startColumn,
411
+ filename: this.filename
412
+ };
413
+ }
414
+ /**
415
+ * Чтение числа
416
+ */
417
+ readNumber() {
418
+ let value = '';
419
+ const startLine = this.line;
420
+ const startColumn = this.column;
421
+ let hasDecimal = false;
422
+
423
+ while (this.currentChar !== null) {
424
+ if (this.isDigit(this.currentChar)) {
425
+ value += this.currentChar;
426
+ this.advance();
427
+ } else if (this.currentChar === '.' && !hasDecimal) {
428
+ hasDecimal = true;
429
+ value += this.currentChar;
430
+ this.advance();
431
+ } else {
432
+ break;
433
+ }
434
+ }
435
+
436
+ return {
437
+ type: 'INT',
438
+ value: hasDecimal ? parseFloat(value) : parseInt(value, 10),
439
+ line: startLine,
440
+ column: startColumn,
441
+ filename: this.filename
442
+ };
443
+ }
444
+
445
+ /**
446
+ * Чтение операторов и символов
447
+ */
448
+ readOperator() {
449
+ const startLine = this.line;
450
+ const startColumn = this.column;
451
+ const char = this.currentChar;
452
+
453
+ // Многосимвольные операторы
454
+ if (char === '=' && this.peek() === '=') {
455
+ this.advance();
456
+ this.advance();
457
+ return { type: 'EQEQ', value: '==', line: startLine, column: startColumn, filename: this.filename };
458
+ }
459
+ if (char === '!' && this.peek() === '=') {
460
+ this.advance();
461
+ this.advance();
462
+ return { type: 'NEQ', value: '!=', line: startLine, column: startColumn, filename: this.filename };
463
+ }
464
+ if (char === '<' && this.peek() === '=') {
465
+ this.advance();
466
+ this.advance();
467
+ return { type: 'LTE', value: '<=', line: startLine, column: startColumn, filename: this.filename };
468
+ }
469
+ if (char === '>' && this.peek() === '=') {
470
+ this.advance();
471
+ this.advance();
472
+ return { type: 'GTE', value: '>=', line: startLine, column: startColumn, filename: this.filename };
473
+ }
474
+ if (char === '&' && this.peek() === '&') {
475
+ this.advance();
476
+ this.advance();
477
+ return { type: 'AND', value: '&&', line: startLine, column: startColumn, filename: this.filename };
478
+ }
479
+ if (char === '|' && this.peek() === '|') {
480
+ this.advance();
481
+ this.advance();
482
+ return { type: 'OR', value: '||', line: startLine, column: startColumn, filename: this.filename };
483
+ }
484
+ if (char === '*' && this.peek() === '*') {
485
+ this.advance();
486
+ this.advance();
487
+ return { type: 'EXP', value: '**', line: startLine, column: startColumn, filename: this.filename };
488
+ }
489
+ // Добавляем дополнительные операторы
490
+ if (char === '+' && this.peek() === '=') {
491
+ this.advance();
492
+ this.advance();
493
+ return { type: 'PLUSEQ', value: '+=', line: startLine, column: startColumn, filename: this.filename };
494
+ }
495
+ if (char === '-' && this.peek() === '=') {
496
+ this.advance();
497
+ this.advance();
498
+ return { type: 'MINUSEQ', value: '-=', line: startLine, column: startColumn, filename: this.filename };
499
+ }
500
+ if (char === '*' && this.peek() === '=') {
501
+ this.advance();
502
+ this.advance();
503
+ return { type: 'MULTEQ', value: '*=', line: startLine, column: startColumn, filename: this.filename };
504
+ }
505
+ if (char === '/' && this.peek() === '=') {
506
+ this.advance();
507
+ this.advance();
508
+ return { type: 'DIVEQ', value: '/=', line: startLine, column: startColumn, filename: this.filename };
509
+ }
510
+ if (char === '%' && this.peek() === '=') {
511
+ this.advance();
512
+ this.advance();
513
+ return { type: 'MODEQ', value: '%=', line: startLine, column: startColumn, filename: this.filename };
514
+ }
515
+ if (char === '<' && this.peek() === '<') {
516
+ this.advance();
517
+ this.advance();
518
+ return { type: 'LEFTSHIFT', value: '<<', line: startLine, column: startColumn, filename: this.filename };
519
+ }
520
+ if (char === '>' && this.peek() === '>') {
521
+ this.advance();
522
+ this.advance();
523
+ return { type: 'RIGHTSHIFT', value: '>>', line: startLine, column: startColumn, filename: this.filename };
524
+ }
525
+ if (char === '=' && this.peek() === '>') {
526
+ this.advance();
527
+ this.advance();
528
+ return { type: 'FATARROW', value: '=>', line: startLine, column: startColumn, filename: this.filename };
529
+ }
530
+
531
+ // Импорт/экспорт модулей
532
+ if (char === 'i' && this.peek() === 'm' && this.peek(1) === 'p' && this.peek(2) === 'o' && this.peek(3) === 'r' && this.peek(4) === 't') {
533
+ this.advance(); this.advance(); this.advance(); this.advance(); this.advance();
534
+ return { type: 'IMPORT', value: 'import', line: startLine, column: startColumn, filename: this.filename };
535
+ }
536
+ if (char === 'э' && this.peek() === 'к' && this.peek(1) === 'с' && this.peek(2) === 'п' && this.peek(3) === 'о' && this.peek(4) === 'р' && this.peek(5) === 'т') {
537
+ for (let i = 0; i < 7; i++) this.advance();
538
+ return { type: 'EXPORT', value: 'экспорт', line: startLine, column: startColumn, filename: this.filename };
539
+ }
540
+ if (char === 'e' && this.peek() === 'x' && this.peek(1) === 'p' && this.peek(2) === 'o' && this.peek(3) === 'r' && this.peek(4) === 't') {
541
+ this.advance(); this.advance(); this.advance(); this.advance(); this.advance();
542
+ return { type: 'EXPORT', value: 'export', line: startLine, column: startColumn, filename: this.filename };
543
+ }
544
+ if (char === 'и' && this.peek() === 'м' && this.peek(1) === 'п' && this.peek(2) === 'о' && this.peek(3) === 'р' && this.peek(4) === 'т') {
545
+ for (let i = 0; i < 5; i++) this.advance();
546
+ return { type: 'IMPORT', value: 'импорт', line: startLine, column: startColumn, filename: this.filename };
547
+ }
548
+ // Поддержка "как" и "as" в импорте
549
+ if (char === 'к' && this.peek() === 'а' && this.peek(1) === 'к') {
550
+ this.advance(); this.advance(); this.advance();
551
+ return { type: 'AS', value: 'как', line: startLine, column: startColumn, filename: this.filename };
552
+ }
553
+ if (char === 'a' && this.peek() === 's') {
554
+ this.advance(); this.advance();
555
+ return { type: 'AS', value: 'as', line: startLine, column: startColumn, filename: this.filename };
556
+ }
557
+
558
+ // Проверяем, не является ли '/' началом комментария
559
+ if (char === '/') {
560
+ if (this.peek() === '*') {
561
+ // Это начало многострочного комментария, не обрабатываем здесь
562
+ return null;
563
+ } else {
564
+ this.advance();
565
+ return {
566
+ type: 'DIV',
567
+ value: '/',
568
+ line: startLine,
569
+ column: startColumn,
570
+ filename: this.filename
571
+ };
572
+ }
573
+ }
574
+
575
+ // Многосимвольные операторы (до одиночных)
576
+ if (char === '.' && this.peek() === '.' && this.peek(1) === '.') {
577
+ this.advance();
578
+ this.advance();
579
+ this.advance();
580
+ return { type: 'SPREAD', value: '...', line: startLine, column: startColumn, filename: this.filename };
581
+ }
582
+
583
+ // Одиночные символы
584
+ const singleChars = {
585
+ '+': 'PLUS',
586
+ '-': 'MINUS',
587
+ '*': 'MULT',
588
+ '%': 'MOD',
589
+ '=': 'ASSIGN',
590
+ '.': 'DOT',
591
+ '(': 'LPAREN',
592
+ ')': 'RPAREN',
593
+ '{': 'LBRACE',
594
+ '}': 'RBRACE',
595
+ '[': 'LBRACKET',
596
+ ']': 'RBRACKET',
597
+ ',': 'COMMA',
598
+ ':': 'COLON',
599
+ ';': 'SEMICOLON',
600
+ '<': 'LT',
601
+ '>': 'GT',
602
+ '!': 'NOT',
603
+ '?': 'TERNARY'
604
+ };
605
+
606
+ if (singleChars[char]) {
607
+ this.advance();
608
+ return {
609
+ type: singleChars[char],
610
+ value: char,
611
+ line: startLine,
612
+ column: startColumn,
613
+ filename: this.filename
614
+ };
615
+ }
616
+
617
+ return null;
618
+ }
619
+
620
+ /**
621
+ * Подглядывание следующего символа
622
+ */
623
+ peek(offset = 1) {
624
+ return this.source[this.pos + offset] || null;
625
+ }
626
+
627
+ /**
628
+ * Проверка на букву (включая русские буквы)
629
+ */
630
+ isAlpha(char) {
631
+ const code = char.charCodeAt(0);
632
+ // Латинские буквы
633
+ if ((code >= 65 && code <= 90) || (code >= 97 && code <= 122) || char === '_') {
634
+ return true;
635
+ }
636
+ // Русские буквы (А-Я, а-я, Ё, ё)
637
+ if ((code >= 1040 && code <= 1103) || code === 1025 || code === 1105) {
638
+ return true;
639
+ }
640
+ return false;
641
+ }
642
+
643
+ /**
644
+ * Проверка на цифру
645
+ */
646
+ isDigit(char) {
647
+ return char >= '0' && char <= '9';
648
+ }
649
+
650
+ /**
651
+ * Проверка на букву или цифру
652
+ */
653
+ isAlphaNumeric(char) {
654
+ return this.isAlpha(char) || this.isDigit(char);
655
+ }
656
+ }
657
+
658
+ export default Lexer;