vladx 1.5.0 → 1.6.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/package.json +1 -1
- package/src/index.js +1 -1
- package/src/index-standalone.js +0 -1326
package/package.json
CHANGED
package/src/index.js
CHANGED
|
@@ -27,7 +27,7 @@ export { Profiler } from './runtime/profiler.js';
|
|
|
27
27
|
export { REPL } from './runtime/repl.js';
|
|
28
28
|
export { AsyncManager } from './runtime/async-manager.js';
|
|
29
29
|
export { Functional } from './runtime/functional.js';
|
|
30
|
-
export DataStructures from './runtime/data-structures.js';
|
|
30
|
+
export {DataStructures} from './runtime/data-structures.js';
|
|
31
31
|
export { TestRunner } from './runtime/test-runner.js';
|
|
32
32
|
export { Bundle } from './runtime/bundler.js';
|
|
33
33
|
export { Minifier } from './runtime/minifier.js';
|
package/src/index-standalone.js
DELETED
|
@@ -1,1326 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* VladX - Полностью самодостаточный интерпретатор
|
|
3
|
-
* Все в одном файле для простоты использования
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
// === Типы данных ===
|
|
7
|
-
const types = {
|
|
8
|
-
NULL: 'null',
|
|
9
|
-
NUMBER: 'number',
|
|
10
|
-
STRING: 'string',
|
|
11
|
-
BOOLEAN: 'boolean',
|
|
12
|
-
ARRAY: 'array',
|
|
13
|
-
OBJECT: 'object',
|
|
14
|
-
FUNCTION: 'function',
|
|
15
|
-
CLOSURE: 'closure'
|
|
16
|
-
};
|
|
17
|
-
|
|
18
|
-
// === Базовый объект ===
|
|
19
|
-
class VladXObject {
|
|
20
|
-
constructor(type, value = null) {
|
|
21
|
-
this.type = type;
|
|
22
|
-
this.value = value;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
static null() { return new VladXObject(types.NULL, null); }
|
|
26
|
-
static number(value) { return new VladXObject(types.NUMBER, Number(value)); }
|
|
27
|
-
static string(value) { return new VladXObject(types.STRING, String(value)); }
|
|
28
|
-
static boolean(value) { return new VladXObject(types.BOOLEAN, Boolean(value)); }
|
|
29
|
-
static array(value) { return new VladXObject(types.ARRAY, Array.isArray(value) ? value : [value]); }
|
|
30
|
-
static object(value) { return new VladXObject(types.OBJECT, value && typeof value === 'object' ? value : {}); }
|
|
31
|
-
static function(value, name = '<function>') { return new VladXObject(types.FUNCTION, value, { name }); }
|
|
32
|
-
static closure(ast, env, name = '<closure>') { return new VladXObject(types.CLOSURE, null, { env, name, ast }); }
|
|
33
|
-
|
|
34
|
-
toString() {
|
|
35
|
-
switch (this.type) {
|
|
36
|
-
case types.NULL: return 'ничто';
|
|
37
|
-
case types.STRING: return this.value;
|
|
38
|
-
case types.NUMBER: return String(this.value);
|
|
39
|
-
case types.BOOLEAN: return this.value ? 'истина' : 'ложь';
|
|
40
|
-
case types.ARRAY: return '[' + this.value.map(v => v?.toString() || String(v)).join(', ') + ']';
|
|
41
|
-
case types.OBJECT: return '{' + Object.entries(this.value).map(([k, v]) => `${k}: ${v?.toString() || String(v)}`).join(', ') + '}';
|
|
42
|
-
case types.FUNCTION: return `[функция: ${this.name}]`;
|
|
43
|
-
case types.CLOSURE: return `[замыкание: ${this.name}]`;
|
|
44
|
-
default: return String(this.value);
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
// === Кэш ===
|
|
50
|
-
class Cache {
|
|
51
|
-
constructor(maxSize = 1000, ttl = 300000) {
|
|
52
|
-
this.cache = new Map();
|
|
53
|
-
this.maxSize = maxSize;
|
|
54
|
-
this.ttl = ttl;
|
|
55
|
-
this.hits = 0;
|
|
56
|
-
this.misses = 0;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
get(key) {
|
|
60
|
-
const entry = this.cache.get(key);
|
|
61
|
-
if (!entry) {
|
|
62
|
-
this.misses++;
|
|
63
|
-
return undefined;
|
|
64
|
-
}
|
|
65
|
-
if (this.ttl > 0 && Date.now() - entry.timestamp > this.ttl) {
|
|
66
|
-
this.cache.delete(key);
|
|
67
|
-
this.misses++;
|
|
68
|
-
return undefined;
|
|
69
|
-
}
|
|
70
|
-
this.hits++;
|
|
71
|
-
return entry.value;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
set(key, value) {
|
|
75
|
-
if (this.cache.size >= this.maxSize) {
|
|
76
|
-
this.evictLRU();
|
|
77
|
-
}
|
|
78
|
-
this.cache.set(key, { value, timestamp: Date.now() });
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
evictLRU() {
|
|
82
|
-
let lruKey = null;
|
|
83
|
-
let lruTime = Infinity;
|
|
84
|
-
for (const [key, entry] of this.cache) {
|
|
85
|
-
if (entry.timestamp < lruTime) {
|
|
86
|
-
lruTime = entry.timestamp;
|
|
87
|
-
lruKey = key;
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
if (lruKey) this.cache.delete(lruKey);
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
clear() {
|
|
94
|
-
this.cache.clear();
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
getStats() {
|
|
98
|
-
return {
|
|
99
|
-
size: this.cache.size,
|
|
100
|
-
hits: this.hits,
|
|
101
|
-
misses: this.misses,
|
|
102
|
-
hitRate: this.hits + this.misses > 0 ? ((this.hits / (this.hits + this.misses)) * 100).toFixed(2) + '%' : '0%'
|
|
103
|
-
};
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
// === Функциональное программирование ===
|
|
108
|
-
const Functional = {
|
|
109
|
-
curry(fn) {
|
|
110
|
-
const curried = (...args) => {
|
|
111
|
-
if (args.length >= fn.length) return fn(...args);
|
|
112
|
-
return (...more) => curried(...args, ...more);
|
|
113
|
-
};
|
|
114
|
-
return curried;
|
|
115
|
-
},
|
|
116
|
-
|
|
117
|
-
compose(...fns) {
|
|
118
|
-
return (arg) => fns.reduceRight((acc, fn) => fn(acc), arg);
|
|
119
|
-
},
|
|
120
|
-
|
|
121
|
-
pipe(...fns) {
|
|
122
|
-
return (arg) => fns.reduce((acc, fn) => fn(acc), arg);
|
|
123
|
-
},
|
|
124
|
-
|
|
125
|
-
memoize(fn, keyFn = (...args) => JSON.stringify(args)) {
|
|
126
|
-
const cache = new Map();
|
|
127
|
-
return (...args) => {
|
|
128
|
-
const key = keyFn(...args);
|
|
129
|
-
if (cache.has(key)) return cache.get(key);
|
|
130
|
-
const result = fn(...args);
|
|
131
|
-
cache.set(key, result);
|
|
132
|
-
return result;
|
|
133
|
-
};
|
|
134
|
-
},
|
|
135
|
-
|
|
136
|
-
partial(fn, ...presetArgs) {
|
|
137
|
-
return (...args) => fn(...presetArgs, ...args);
|
|
138
|
-
},
|
|
139
|
-
|
|
140
|
-
flip(fn) {
|
|
141
|
-
return (...args) => fn(...args.reverse());
|
|
142
|
-
},
|
|
143
|
-
|
|
144
|
-
once(fn) {
|
|
145
|
-
let called = false;
|
|
146
|
-
return (...args) => {
|
|
147
|
-
if (called) return result;
|
|
148
|
-
called = true;
|
|
149
|
-
result = fn(...args);
|
|
150
|
-
return result;
|
|
151
|
-
};
|
|
152
|
-
},
|
|
153
|
-
|
|
154
|
-
trace(label = 'trace') => {
|
|
155
|
-
return function(x) {
|
|
156
|
-
console.log(label, x);
|
|
157
|
-
return x;
|
|
158
|
-
};
|
|
159
|
-
},
|
|
160
|
-
|
|
161
|
-
flip(fn) {
|
|
162
|
-
return (...args) => fn(...args.reverse());
|
|
163
|
-
},
|
|
164
|
-
|
|
165
|
-
once(fn) {
|
|
166
|
-
let called = false;
|
|
167
|
-
return (...args) => {
|
|
168
|
-
if (called) return;
|
|
169
|
-
called = true;
|
|
170
|
-
return fn(...args);
|
|
171
|
-
};
|
|
172
|
-
},
|
|
173
|
-
|
|
174
|
-
trace: (label) => {
|
|
175
|
-
return function(x) {
|
|
176
|
-
console.log(label, x);
|
|
177
|
-
return x;
|
|
178
|
-
};
|
|
179
|
-
}
|
|
180
|
-
};
|
|
181
|
-
},
|
|
182
|
-
|
|
183
|
-
trace(label = 'trace') => {
|
|
184
|
-
return function(x) {
|
|
185
|
-
console.log(label, x);
|
|
186
|
-
return x;
|
|
187
|
-
};
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
once(fn) {
|
|
191
|
-
let called = false;
|
|
192
|
-
return (...args) => {
|
|
193
|
-
if (called) return result;
|
|
194
|
-
called = true;
|
|
195
|
-
result = fn(...args);
|
|
196
|
-
return result;
|
|
197
|
-
};
|
|
198
|
-
},
|
|
199
|
-
|
|
200
|
-
trace(label = 'trace') => {
|
|
201
|
-
return function(x) {
|
|
202
|
-
console.log(label, x);
|
|
203
|
-
return x;
|
|
204
|
-
};
|
|
205
|
-
}
|
|
206
|
-
};
|
|
207
|
-
},
|
|
208
|
-
|
|
209
|
-
trace(label = 'trace') => {
|
|
210
|
-
return (x) => {
|
|
211
|
-
console.log(label, x);
|
|
212
|
-
return x;
|
|
213
|
-
};
|
|
214
|
-
}
|
|
215
|
-
};
|
|
216
|
-
|
|
217
|
-
// === Асинхронность ===
|
|
218
|
-
class AsyncManager {
|
|
219
|
-
constructor(options = {}) {
|
|
220
|
-
this.maxConcurrent = options.maxConcurrent || 10;
|
|
221
|
-
this.timeout = options.timeout || 30000;
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
async parallel(tasks) {
|
|
225
|
-
return Promise.all(tasks.map(t => t()));
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
async sequential(tasks) {
|
|
229
|
-
const results = [];
|
|
230
|
-
for (const task of tasks) {
|
|
231
|
-
results.push(await task());
|
|
232
|
-
}
|
|
233
|
-
return results;
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
async race(tasks) {
|
|
237
|
-
return Promise.race(tasks.map(t => t()));
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
async allSettled(tasks) {
|
|
241
|
-
return Promise.allSettled(tasks.map(t => t()));
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
async any(tasks) {
|
|
245
|
-
return Promise.any(tasks.map(t => t()));
|
|
246
|
-
}
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
// === Окружение ===
|
|
250
|
-
class Environment {
|
|
251
|
-
constructor(parent = null, name = '<env>') {
|
|
252
|
-
this.parent = parent;
|
|
253
|
-
this.name = name;
|
|
254
|
-
this.variables = new Map();
|
|
255
|
-
this.constants = new Set();
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
define(name, value, isConst = false) {
|
|
259
|
-
if (this.constants.has(name)) {
|
|
260
|
-
throw new Error(`Константа ${name} уже объявлена`);
|
|
261
|
-
}
|
|
262
|
-
this.variables.set(name, value);
|
|
263
|
-
if (isConst) {
|
|
264
|
-
this.constants.add(name);
|
|
265
|
-
}
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
get(name) {
|
|
269
|
-
if (this.variables.has(name)) {
|
|
270
|
-
return this.variables.get(name);
|
|
271
|
-
}
|
|
272
|
-
if (this.parent) {
|
|
273
|
-
return this.parent.get(name);
|
|
274
|
-
}
|
|
275
|
-
throw new Error(`Переменная "${name}" не найдена в окружении ${this.name}`);
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
assign(name, value) {
|
|
279
|
-
if (this.variables.has(name)) {
|
|
280
|
-
if (this.constants.has(name)) {
|
|
281
|
-
throw new Error(`Нельзя изменить константу ${name}`);
|
|
282
|
-
}
|
|
283
|
-
this.variables.set(name, value);
|
|
284
|
-
return true;
|
|
285
|
-
}
|
|
286
|
-
if (this.parent) {
|
|
287
|
-
return this.parent.assign(name, value);
|
|
288
|
-
}
|
|
289
|
-
throw new Error(`Переменная ${name} не найдена`);
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
child(name = '<child>') {
|
|
293
|
-
return new Environment(this, name);
|
|
294
|
-
}
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
// === Лексер ===
|
|
298
|
-
class Lexer {
|
|
299
|
-
constructor(source, filename = '<unknown>') {
|
|
300
|
-
this.source = source;
|
|
301
|
-
this.filename = filename;
|
|
302
|
-
this.tokens = [];
|
|
303
|
-
this.pos = 0;
|
|
304
|
-
this.line = 1;
|
|
305
|
-
this.column = 1;
|
|
306
|
-
}
|
|
307
|
-
|
|
308
|
-
tokenize() {
|
|
309
|
-
while (this.pos < this.source.length) {
|
|
310
|
-
this.skipWhitespace();
|
|
311
|
-
|
|
312
|
-
if (this.pos >= this.source.length) break;
|
|
313
|
-
|
|
314
|
-
const char = this.source[this.pos];
|
|
315
|
-
|
|
316
|
-
// Комменатарии
|
|
317
|
-
if (char === '#') {
|
|
318
|
-
this.skipComment();
|
|
319
|
-
continue;
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
// Строки
|
|
323
|
-
if (char === '"' || char === "'") {
|
|
324
|
-
this.readString();
|
|
325
|
-
continue;
|
|
326
|
-
}
|
|
327
|
-
|
|
328
|
-
// Числа
|
|
329
|
-
if (this.isDigit(char)) {
|
|
330
|
-
this.readNumber();
|
|
331
|
-
continue;
|
|
332
|
-
}
|
|
333
|
-
|
|
334
|
-
// Ключевые слова и идентификаторы
|
|
335
|
-
if (this.isAlpha(char)) {
|
|
336
|
-
this.readWordOrKeyword();
|
|
337
|
-
continue;
|
|
338
|
-
}
|
|
339
|
-
|
|
340
|
-
// Операторы
|
|
341
|
-
this.readOperator();
|
|
342
|
-
}
|
|
343
|
-
|
|
344
|
-
return this.tokens;
|
|
345
|
-
}
|
|
346
|
-
|
|
347
|
-
skipWhitespace() {
|
|
348
|
-
while (this.pos < this.source.length && /[\s\n\r]/.test(this.source[this.pos])) {
|
|
349
|
-
if (this.source[this.pos] === '\n') {
|
|
350
|
-
this.line++;
|
|
351
|
-
this.column = 1;
|
|
352
|
-
} else {
|
|
353
|
-
this.column++;
|
|
354
|
-
}
|
|
355
|
-
this.pos++;
|
|
356
|
-
}
|
|
357
|
-
}
|
|
358
|
-
|
|
359
|
-
skipComment() {
|
|
360
|
-
while (this.pos < this.source.length && this.source[this.pos] !== '\n') {
|
|
361
|
-
this.pos++;
|
|
362
|
-
}
|
|
363
|
-
if (this.source[this.pos] === '\n') {
|
|
364
|
-
this.line++;
|
|
365
|
-
this.column = 1;
|
|
366
|
-
this.pos++;
|
|
367
|
-
}
|
|
368
|
-
}
|
|
369
|
-
|
|
370
|
-
readString() {
|
|
371
|
-
const quote = this.source[this.pos];
|
|
372
|
-
this.pos++;
|
|
373
|
-
this.column++;
|
|
374
|
-
let value = '';
|
|
375
|
-
|
|
376
|
-
while (this.pos < this.source.length && this.source[this.pos] !== quote) {
|
|
377
|
-
if (this.source[this.pos] === '\\' && this.pos + 1 < this.source.length) {
|
|
378
|
-
this.pos += 2;
|
|
379
|
-
value += this.source[this.pos - 1];
|
|
380
|
-
} else {
|
|
381
|
-
value += this.source[this.pos];
|
|
382
|
-
this.pos++;
|
|
383
|
-
}
|
|
384
|
-
this.column++;
|
|
385
|
-
}
|
|
386
|
-
|
|
387
|
-
this.pos++; // закрывающая кавычка
|
|
388
|
-
this.column++;
|
|
389
|
-
this.tokens.push({
|
|
390
|
-
type: 'STRING',
|
|
391
|
-
value,
|
|
392
|
-
line: this.line,
|
|
393
|
-
column: this.column
|
|
394
|
-
});
|
|
395
|
-
}
|
|
396
|
-
|
|
397
|
-
readNumber() {
|
|
398
|
-
let start = this.pos;
|
|
399
|
-
let hasDot = false;
|
|
400
|
-
|
|
401
|
-
while (this.pos < this.source.length) {
|
|
402
|
-
const char = this.source[this.pos];
|
|
403
|
-
if (char === '.') {
|
|
404
|
-
if (hasDot) break;
|
|
405
|
-
hasDot = true;
|
|
406
|
-
} else if (!this.isDigit(char)) {
|
|
407
|
-
break;
|
|
408
|
-
}
|
|
409
|
-
this.pos++;
|
|
410
|
-
}
|
|
411
|
-
|
|
412
|
-
const value = this.source.substring(start, this.pos);
|
|
413
|
-
const number = parseFloat(value);
|
|
414
|
-
this.column += this.pos - start;
|
|
415
|
-
this.tokens.push({
|
|
416
|
-
type: 'NUMBER',
|
|
417
|
-
value: number,
|
|
418
|
-
line: this.line,
|
|
419
|
-
column: this.column
|
|
420
|
-
});
|
|
421
|
-
}
|
|
422
|
-
|
|
423
|
-
readWordOrKeyword() {
|
|
424
|
-
const start = this.pos;
|
|
425
|
-
while (this.pos < this.source.length && (this.isAlphaNum(this.source[this.pos]) || this.source[this.pos] === '_')) {
|
|
426
|
-
this.pos++;
|
|
427
|
-
}
|
|
428
|
-
|
|
429
|
-
const word = this.source.substring(start, this.pos);
|
|
430
|
-
this.column += this.pos - start;
|
|
431
|
-
|
|
432
|
-
// Ключевые слова
|
|
433
|
-
const keywords = {
|
|
434
|
-
'пусть': 'LET', 'let': 'LET',
|
|
435
|
-
'константа': 'CONST', 'const': 'CONST',
|
|
436
|
-
'функция': 'FUNCTION', 'function': 'FUNCTION',
|
|
437
|
-
'вернуть': 'RETURN', 'return': 'RETURN',
|
|
438
|
-
'если': 'IF', 'if': 'IF',
|
|
439
|
-
'иначе': 'ELSE', 'else': 'ELSE',
|
|
440
|
-
'пока': 'WHILE', 'while': 'WHILE',
|
|
441
|
-
'для': 'FOR', 'for': 'FOR',
|
|
442
|
-
'истина': 'TRUE', 'true': 'TRUE',
|
|
443
|
-
'ложь': 'FALSE', 'false': 'FALSE',
|
|
444
|
-
'ничто': 'NULL', 'null': 'NULL',
|
|
445
|
-
'печать': 'PRINT', 'console.log': 'PRINT',
|
|
446
|
-
'класс': 'CLASS', 'class': 'CLASS',
|
|
447
|
-
'новый': 'NEW', 'new': 'NEW',
|
|
448
|
-
'этот': 'THIS', 'this': 'THIS'
|
|
449
|
-
};
|
|
450
|
-
|
|
451
|
-
const type = keywords[word] || 'IDENTIFIER';
|
|
452
|
-
this.tokens.push({
|
|
453
|
-
type,
|
|
454
|
-
value: word,
|
|
455
|
-
line: this.line,
|
|
456
|
-
column: this.column
|
|
457
|
-
});
|
|
458
|
-
}
|
|
459
|
-
|
|
460
|
-
readOperator() {
|
|
461
|
-
const twoCharOps = [
|
|
462
|
-
'==', '!=', '<=', '>=', '&&', '||',
|
|
463
|
-
'+=', '-=', '*=', '/=', '%=', '**'
|
|
464
|
-
];
|
|
465
|
-
|
|
466
|
-
const twoChar = this.source.substring(this.pos, this.pos + 2);
|
|
467
|
-
if (twoCharOps.includes(twoChar)) {
|
|
468
|
-
this.tokens.push({
|
|
469
|
-
type: 'OPERATOR',
|
|
470
|
-
value: twoChar,
|
|
471
|
-
line: this.line,
|
|
472
|
-
column: this.column
|
|
473
|
-
});
|
|
474
|
-
this.pos += 2;
|
|
475
|
-
this.column += 2;
|
|
476
|
-
return;
|
|
477
|
-
}
|
|
478
|
-
|
|
479
|
-
const oneCharOps = '+-*/%=!<>,.:()[]{};';
|
|
480
|
-
|
|
481
|
-
if (oneCharOps.includes(this.source[this.pos])) {
|
|
482
|
-
this.tokens.push({
|
|
483
|
-
type: 'OPERATOR',
|
|
484
|
-
value: this.source[this.pos],
|
|
485
|
-
line: this.line,
|
|
486
|
-
column: this.column
|
|
487
|
-
});
|
|
488
|
-
this.pos++;
|
|
489
|
-
this.column++;
|
|
490
|
-
}
|
|
491
|
-
}
|
|
492
|
-
|
|
493
|
-
isDigit(char) {
|
|
494
|
-
return char >= '0' && char <= '9';
|
|
495
|
-
}
|
|
496
|
-
|
|
497
|
-
isAlpha(char) {
|
|
498
|
-
return (char >= 'a' && char <= 'z') || (char >= 'A' && char <= 'Z') ||
|
|
499
|
-
(char >= 'а' && char <= 'я') || (char >= 'А' && char <= 'Я');
|
|
500
|
-
}
|
|
501
|
-
|
|
502
|
-
isAlphaNum(char) {
|
|
503
|
-
return this.isAlpha(char) || this.isDigit(char);
|
|
504
|
-
}
|
|
505
|
-
}
|
|
506
|
-
|
|
507
|
-
// === Парсер ===
|
|
508
|
-
class Parser {
|
|
509
|
-
constructor(tokens) {
|
|
510
|
-
this.tokens = tokens;
|
|
511
|
-
this.pos = 0;
|
|
512
|
-
}
|
|
513
|
-
|
|
514
|
-
parse() {
|
|
515
|
-
const statements = [];
|
|
516
|
-
|
|
517
|
-
while (this.pos < this.tokens.length) {
|
|
518
|
-
const stmt = this.parseStatement();
|
|
519
|
-
if (stmt) statements.push(stmt);
|
|
520
|
-
}
|
|
521
|
-
|
|
522
|
-
return { type: 'Program', body: statements };
|
|
523
|
-
}
|
|
524
|
-
|
|
525
|
-
parseStatement() {
|
|
526
|
-
const token = this.current();
|
|
527
|
-
|
|
528
|
-
if (!token) return null;
|
|
529
|
-
|
|
530
|
-
switch (token.type) {
|
|
531
|
-
case 'LET':
|
|
532
|
-
return this.parseLetStatement();
|
|
533
|
-
case 'CONST':
|
|
534
|
-
return this.parseConstStatement();
|
|
535
|
-
case 'FUNCTION':
|
|
536
|
-
return this.parseFunctionDeclaration();
|
|
537
|
-
case 'RETURN':
|
|
538
|
-
return this.parseReturnStatement();
|
|
539
|
-
case 'IF':
|
|
540
|
-
return this.parseIfStatement();
|
|
541
|
-
case 'WHILE':
|
|
542
|
-
return this.parseWhileStatement();
|
|
543
|
-
case 'FOR':
|
|
544
|
-
return this.parseForStatement();
|
|
545
|
-
case 'PRINT':
|
|
546
|
-
return this.parsePrintStatement();
|
|
547
|
-
case 'IDENTIFIER':
|
|
548
|
-
return this.parseExpressionStatement();
|
|
549
|
-
default:
|
|
550
|
-
throw new Error(`Неизвестный токен: ${token.type} "${token.value}" на линии ${token.line}`);
|
|
551
|
-
}
|
|
552
|
-
}
|
|
553
|
-
|
|
554
|
-
parseLetStatement() {
|
|
555
|
-
const letToken = this.advance(); // 'пусть'
|
|
556
|
-
const nameToken = this.expect('IDENTIFIER');
|
|
557
|
-
const name = nameToken.value;
|
|
558
|
-
|
|
559
|
-
let init = null;
|
|
560
|
-
if (this.current() && this.current().value === '=') {
|
|
561
|
-
this.advance(); // '='
|
|
562
|
-
init = this.parseExpression();
|
|
563
|
-
}
|
|
564
|
-
|
|
565
|
-
return { type: 'LetStatement', name, initializer: init };
|
|
566
|
-
}
|
|
567
|
-
|
|
568
|
-
parseConstStatement() {
|
|
569
|
-
const constToken = this.advance(); // 'константа'
|
|
570
|
-
const nameToken = this.expect('IDENTIFIER');
|
|
571
|
-
const name = nameToken.value;
|
|
572
|
-
|
|
573
|
-
this.expect('=');
|
|
574
|
-
|
|
575
|
-
const init = this.parseExpression();
|
|
576
|
-
|
|
577
|
-
return { type: 'ConstStatement', name, initializer: init };
|
|
578
|
-
}
|
|
579
|
-
|
|
580
|
-
parseFunctionDeclaration() {
|
|
581
|
-
const funcToken = this.advance(); // 'функция'
|
|
582
|
-
const nameToken = this.expect('IDENTIFIER');
|
|
583
|
-
const name = nameToken.value;
|
|
584
|
-
|
|
585
|
-
this.expect('(');
|
|
586
|
-
|
|
587
|
-
const params = [];
|
|
588
|
-
if (this.current() && this.current().type !== ')') {
|
|
589
|
-
params.push(this.expect('IDENTIFIER').value);
|
|
590
|
-
while (this.current() && this.current().value === ',') {
|
|
591
|
-
this.advance(); // ','
|
|
592
|
-
params.push(this.expect('IDENTIFIER').value);
|
|
593
|
-
}
|
|
594
|
-
}
|
|
595
|
-
|
|
596
|
-
this.expect(')');
|
|
597
|
-
|
|
598
|
-
const body = this.parseBlock();
|
|
599
|
-
|
|
600
|
-
return { type: 'FunctionDeclaration', name, params, body };
|
|
601
|
-
}
|
|
602
|
-
|
|
603
|
-
parseReturnStatement() {
|
|
604
|
-
this.advance(); // 'вернуть'
|
|
605
|
-
const value = this.parseExpression();
|
|
606
|
-
return { type: 'ReturnStatement', value };
|
|
607
|
-
}
|
|
608
|
-
|
|
609
|
-
parseIfStatement() {
|
|
610
|
-
this.advance(); // 'если'
|
|
611
|
-
this.expect('(');
|
|
612
|
-
const condition = this.parseExpression();
|
|
613
|
-
this.expect(')');
|
|
614
|
-
|
|
615
|
-
const thenBranch = this.parseBlock();
|
|
616
|
-
let elseBranch = null;
|
|
617
|
-
|
|
618
|
-
if (this.current() && (this.current().value === 'иначе' || this.current().value === 'else')) {
|
|
619
|
-
this.advance(); // 'иначе'
|
|
620
|
-
elseBranch = this.parseBlock();
|
|
621
|
-
}
|
|
622
|
-
|
|
623
|
-
return { type: 'IfStatement', condition, thenBranch, elseBranch };
|
|
624
|
-
}
|
|
625
|
-
|
|
626
|
-
parseWhileStatement() {
|
|
627
|
-
this.advance(); // 'пока'
|
|
628
|
-
this.expect('(');
|
|
629
|
-
const condition = this.parseExpression();
|
|
630
|
-
this.expect(')');
|
|
631
|
-
|
|
632
|
-
const body = this.parseBlock();
|
|
633
|
-
|
|
634
|
-
return { type: 'WhileStatement', condition, body };
|
|
635
|
-
}
|
|
636
|
-
|
|
637
|
-
parseForStatement() {
|
|
638
|
-
this.advance(); // 'для'
|
|
639
|
-
this.expect('(');
|
|
640
|
-
|
|
641
|
-
let init = null;
|
|
642
|
-
if (this.current() && this.current().type !== ';') {
|
|
643
|
-
init = this.parseExpression();
|
|
644
|
-
}
|
|
645
|
-
|
|
646
|
-
this.expect(';');
|
|
647
|
-
|
|
648
|
-
const condition = this.parseExpression();
|
|
649
|
-
this.expect(';');
|
|
650
|
-
|
|
651
|
-
const update = this.parseExpression();
|
|
652
|
-
this.expect(')');
|
|
653
|
-
|
|
654
|
-
const body = this.parseBlock();
|
|
655
|
-
|
|
656
|
-
return { type: 'ForStatement', initializer: init, condition, update, body };
|
|
657
|
-
}
|
|
658
|
-
|
|
659
|
-
parsePrintStatement() {
|
|
660
|
-
this.advance(); // 'печать'
|
|
661
|
-
const args = [];
|
|
662
|
-
|
|
663
|
-
if (this.current() && this.current().value === '(') {
|
|
664
|
-
this.advance(); // '('
|
|
665
|
-
while (this.current() && this.current().value !== ')') {
|
|
666
|
-
args.push(this.parseExpression());
|
|
667
|
-
if (this.current() && this.current().value === ',') {
|
|
668
|
-
this.advance();
|
|
669
|
-
}
|
|
670
|
-
}
|
|
671
|
-
this.expect(')');
|
|
672
|
-
}
|
|
673
|
-
|
|
674
|
-
return { type: 'PrintStatement', args };
|
|
675
|
-
}
|
|
676
|
-
|
|
677
|
-
parseExpressionStatement() {
|
|
678
|
-
const expr = this.parseExpression();
|
|
679
|
-
return { type: 'ExpressionStatement', expression: expr };
|
|
680
|
-
}
|
|
681
|
-
|
|
682
|
-
parseExpression() {
|
|
683
|
-
return this.parseAssignment();
|
|
684
|
-
}
|
|
685
|
-
|
|
686
|
-
parseAssignment() {
|
|
687
|
-
const left = this.parseEquality();
|
|
688
|
-
|
|
689
|
-
if (this.current() && this.current().value === '=') {
|
|
690
|
-
this.advance(); // '='
|
|
691
|
-
const right = this.parseAssignment();
|
|
692
|
-
return { type: 'AssignmentExpression', left, right };
|
|
693
|
-
}
|
|
694
|
-
|
|
695
|
-
return left;
|
|
696
|
-
}
|
|
697
|
-
|
|
698
|
-
parseEquality() {
|
|
699
|
-
let left = this.parseComparison();
|
|
700
|
-
|
|
701
|
-
while (this.current() && ['==', '!='].includes(this.current().value)) {
|
|
702
|
-
const operator = this.advance().value;
|
|
703
|
-
const right = this.parseComparison();
|
|
704
|
-
left = { type: 'BinaryExpression', operator, left, right };
|
|
705
|
-
}
|
|
706
|
-
|
|
707
|
-
return left;
|
|
708
|
-
}
|
|
709
|
-
|
|
710
|
-
parseComparison() {
|
|
711
|
-
let left = this.parseTerm();
|
|
712
|
-
|
|
713
|
-
while (this.current() && ['<', '<=', '>', '>='].includes(this.current().value)) {
|
|
714
|
-
const operator = this.advance().value;
|
|
715
|
-
const right = this.parseTerm();
|
|
716
|
-
left = { type: 'BinaryExpression', operator, left, right };
|
|
717
|
-
}
|
|
718
|
-
|
|
719
|
-
return left;
|
|
720
|
-
}
|
|
721
|
-
|
|
722
|
-
parseTerm() {
|
|
723
|
-
let left = this.parseFactor();
|
|
724
|
-
|
|
725
|
-
while (this.current() && ['+', '-'].includes(this.current().value)) {
|
|
726
|
-
const operator = this.advance().value;
|
|
727
|
-
const right = this.parseFactor();
|
|
728
|
-
left = { type: 'BinaryExpression', operator, left, right };
|
|
729
|
-
}
|
|
730
|
-
|
|
731
|
-
return left;
|
|
732
|
-
}
|
|
733
|
-
|
|
734
|
-
parseFactor() {
|
|
735
|
-
let left = this.parseUnary();
|
|
736
|
-
|
|
737
|
-
while (this.current() && ['*', '/', '%'].includes(this.current().value)) {
|
|
738
|
-
const operator = this.advance().value;
|
|
739
|
-
const right = this.parseUnary();
|
|
740
|
-
left = { type: 'BinaryExpression', operator, left, right };
|
|
741
|
-
}
|
|
742
|
-
|
|
743
|
-
return left;
|
|
744
|
-
}
|
|
745
|
-
|
|
746
|
-
parseUnary() {
|
|
747
|
-
if (this.current() && ['!', '-', '+'].includes(this.current().value)) {
|
|
748
|
-
const operator = this.advance().value;
|
|
749
|
-
const operand = this.parseUnary();
|
|
750
|
-
return { type: 'UnaryExpression', operator, operand };
|
|
751
|
-
}
|
|
752
|
-
|
|
753
|
-
return this.parseCallMember();
|
|
754
|
-
}
|
|
755
|
-
|
|
756
|
-
parseCallMember() {
|
|
757
|
-
let left = this.parsePrimary();
|
|
758
|
-
|
|
759
|
-
while (this.current() && this.current().value === '(') {
|
|
760
|
-
const args = this.parseArguments();
|
|
761
|
-
left = { type: 'CallExpression', callee: left, arguments: args };
|
|
762
|
-
}
|
|
763
|
-
|
|
764
|
-
return left;
|
|
765
|
-
}
|
|
766
|
-
|
|
767
|
-
parsePrimary() {
|
|
768
|
-
const token = this.current();
|
|
769
|
-
|
|
770
|
-
if (!token) {
|
|
771
|
-
throw new Error('Неожиданный конец кода');
|
|
772
|
-
}
|
|
773
|
-
|
|
774
|
-
if (token.type === 'NUMBER') {
|
|
775
|
-
this.advance();
|
|
776
|
-
return { type: 'Literal', value: token.value };
|
|
777
|
-
}
|
|
778
|
-
|
|
779
|
-
if (token.type === 'STRING') {
|
|
780
|
-
this.advance();
|
|
781
|
-
return { type: 'Literal', value: token.value };
|
|
782
|
-
}
|
|
783
|
-
|
|
784
|
-
if (token.type === 'IDENTIFIER') {
|
|
785
|
-
const name = this.advance().value;
|
|
786
|
-
return { type: 'Identifier', name };
|
|
787
|
-
}
|
|
788
|
-
|
|
789
|
-
if (token.type === 'TRUE' || token.type === 'FALSE') {
|
|
790
|
-
this.advance();
|
|
791
|
-
return { type: 'Literal', value: token.type === 'TRUE' };
|
|
792
|
-
}
|
|
793
|
-
|
|
794
|
-
if (token.type === 'NULL') {
|
|
795
|
-
this.advance();
|
|
796
|
-
return { type: 'Literal', value: null };
|
|
797
|
-
}
|
|
798
|
-
|
|
799
|
-
if (token.type === '(') {
|
|
800
|
-
this.advance();
|
|
801
|
-
const expr = this.parseExpression();
|
|
802
|
-
this.expect(')');
|
|
803
|
-
return expr;
|
|
804
|
-
}
|
|
805
|
-
|
|
806
|
-
if (token.value === 'новый') {
|
|
807
|
-
this.advance();
|
|
808
|
-
const callee = this.parsePrimary();
|
|
809
|
-
return { type: 'NewExpression', callee };
|
|
810
|
-
}
|
|
811
|
-
|
|
812
|
-
if (token.value === 'этот') {
|
|
813
|
-
this.advance();
|
|
814
|
-
return { type: 'ThisExpression' };
|
|
815
|
-
}
|
|
816
|
-
|
|
817
|
-
throw new Error(`Неожиданный токен: ${token.type} "${token.value}" на линии ${token.line}`);
|
|
818
|
-
}
|
|
819
|
-
|
|
820
|
-
parseArguments() {
|
|
821
|
-
this.expect('(');
|
|
822
|
-
|
|
823
|
-
const args = [];
|
|
824
|
-
if (this.current() && this.current().value !== ')') {
|
|
825
|
-
args.push(this.parseExpression());
|
|
826
|
-
while (this.current() && this.current().value === ',') {
|
|
827
|
-
this.advance();
|
|
828
|
-
args.push(this.parseExpression());
|
|
829
|
-
}
|
|
830
|
-
}
|
|
831
|
-
|
|
832
|
-
this.expect(')');
|
|
833
|
-
return args;
|
|
834
|
-
}
|
|
835
|
-
|
|
836
|
-
parseBlock() {
|
|
837
|
-
this.expect('{');
|
|
838
|
-
const statements = [];
|
|
839
|
-
|
|
840
|
-
while (this.current() && this.current().value !== '}') {
|
|
841
|
-
const stmt = this.parseStatement();
|
|
842
|
-
if (stmt) statements.push(stmt);
|
|
843
|
-
}
|
|
844
|
-
|
|
845
|
-
this.expect('}');
|
|
846
|
-
return { type: 'BlockStatement', body: statements };
|
|
847
|
-
}
|
|
848
|
-
|
|
849
|
-
current() {
|
|
850
|
-
return this.tokens[this.pos] || null;
|
|
851
|
-
}
|
|
852
|
-
|
|
853
|
-
advance() {
|
|
854
|
-
return this.tokens[this.pos++];
|
|
855
|
-
}
|
|
856
|
-
|
|
857
|
-
expect(expectedType) {
|
|
858
|
-
const token = this.current();
|
|
859
|
-
if (!token) {
|
|
860
|
-
throw new Error(`Ожидалось ${expectedType}, но достигнут конец кода`);
|
|
861
|
-
}
|
|
862
|
-
if (token.type !== expectedType && token.value !== expectedType) {
|
|
863
|
-
throw new Error(`Ожидалось ${expectedType}, но получено ${token.type} "${token.value}" на линии ${token.line}`);
|
|
864
|
-
}
|
|
865
|
-
return token;
|
|
866
|
-
}
|
|
867
|
-
}
|
|
868
|
-
|
|
869
|
-
// === Интерпретатор ===
|
|
870
|
-
class Interpreter {
|
|
871
|
-
constructor(options = {}) {
|
|
872
|
-
this.debug = options.debug || false;
|
|
873
|
-
this.globalEnv = new Environment(null, '<global>');
|
|
874
|
-
this.currentEnv = this.globalEnv;
|
|
875
|
-
this.isReturn = false;
|
|
876
|
-
this.returnValue = null;
|
|
877
|
-
this.cache = new Cache();
|
|
878
|
-
this.asyncManager = new AsyncManager(options.async);
|
|
879
|
-
this.builtins = new Map();
|
|
880
|
-
this.registerBuiltins();
|
|
881
|
-
}
|
|
882
|
-
|
|
883
|
-
async interpret(ast) {
|
|
884
|
-
let result = VladXObject.null();
|
|
885
|
-
|
|
886
|
-
for (const stmt of ast.body) {
|
|
887
|
-
result = await this.evaluateStatement(stmt);
|
|
888
|
-
|
|
889
|
-
if (this.isReturn) {
|
|
890
|
-
return this.returnValue;
|
|
891
|
-
}
|
|
892
|
-
}
|
|
893
|
-
|
|
894
|
-
return result;
|
|
895
|
-
}
|
|
896
|
-
|
|
897
|
-
async evaluateStatement(stmt) {
|
|
898
|
-
switch (stmt.type) {
|
|
899
|
-
case 'LetStatement':
|
|
900
|
-
const value = stmt.initializer ? await this.evaluateExpression(stmt.initializer) : VladXObject.null();
|
|
901
|
-
this.currentEnv.define(stmt.name, value, false);
|
|
902
|
-
return VladXObject.null();
|
|
903
|
-
|
|
904
|
-
case 'ConstStatement':
|
|
905
|
-
const constValue = await this.evaluateExpression(stmt.initializer);
|
|
906
|
-
this.currentEnv.define(stmt.name, constValue, true);
|
|
907
|
-
return VladXObject.null();
|
|
908
|
-
|
|
909
|
-
case 'FunctionDeclaration':
|
|
910
|
-
const closure = VladXObject.closure(
|
|
911
|
-
{ type: 'FunctionDeclaration', name: stmt.name, params: stmt.params, body: stmt.body },
|
|
912
|
-
this.currentEnv,
|
|
913
|
-
stmt.name
|
|
914
|
-
);
|
|
915
|
-
this.currentEnv.define(stmt.name, closure, true);
|
|
916
|
-
return VladXObject.null();
|
|
917
|
-
|
|
918
|
-
case 'ReturnStatement':
|
|
919
|
-
this.isReturn = true;
|
|
920
|
-
this.returnValue = stmt.value ? await this.evaluateExpression(stmt.value) : VladXObject.null();
|
|
921
|
-
return this.returnValue;
|
|
922
|
-
|
|
923
|
-
case 'IfStatement':
|
|
924
|
-
const condition = await this.evaluateExpression(stmt.condition);
|
|
925
|
-
const conditionValue = this.getNativeValue(condition);
|
|
926
|
-
|
|
927
|
-
if (conditionValue) {
|
|
928
|
-
return await this.evaluateBlock(stmt.thenBranch);
|
|
929
|
-
} else if (stmt.elseBranch) {
|
|
930
|
-
return await this.evaluateBlock(stmt.elseBranch);
|
|
931
|
-
}
|
|
932
|
-
return VladXObject.null();
|
|
933
|
-
|
|
934
|
-
case 'WhileStatement':
|
|
935
|
-
let whileResult = VladXObject.null();
|
|
936
|
-
while (true) {
|
|
937
|
-
const cond = await this.evaluateExpression(stmt.condition);
|
|
938
|
-
if (!this.getNativeValue(cond)) break;
|
|
939
|
-
whileResult = await this.evaluateBlock(stmt.body);
|
|
940
|
-
}
|
|
941
|
-
return whileResult;
|
|
942
|
-
|
|
943
|
-
case 'ForStatement':
|
|
944
|
-
await this.evaluateStatement(stmt.initializer);
|
|
945
|
-
while (true) {
|
|
946
|
-
const cond = await this.evaluateExpression(stmt.condition);
|
|
947
|
-
if (!this.getNativeValue(cond)) break;
|
|
948
|
-
await this.evaluateStatement(stmt.update);
|
|
949
|
-
await this.evaluateBlock(stmt.body);
|
|
950
|
-
}
|
|
951
|
-
return VladXObject.null();
|
|
952
|
-
|
|
953
|
-
case 'PrintStatement':
|
|
954
|
-
const values = await Promise.all(stmt.args.map(arg => this.evaluateExpression(arg)));
|
|
955
|
-
console.log(...values.map(v => this.getDisplayValue(v)));
|
|
956
|
-
return VladXObject.null();
|
|
957
|
-
|
|
958
|
-
case 'ExpressionStatement':
|
|
959
|
-
return await this.evaluateExpression(stmt.expression);
|
|
960
|
-
|
|
961
|
-
default:
|
|
962
|
-
throw new Error(`Неизвестный оператор: ${stmt.type}`);
|
|
963
|
-
}
|
|
964
|
-
}
|
|
965
|
-
|
|
966
|
-
async evaluateBlock(block) {
|
|
967
|
-
const parentEnv = this.currentEnv;
|
|
968
|
-
this.currentEnv = parentEnv.child('<block>');
|
|
969
|
-
|
|
970
|
-
let result = VladXObject.null();
|
|
971
|
-
for (const stmt of block.body) {
|
|
972
|
-
result = await this.evaluateStatement(stmt);
|
|
973
|
-
|
|
974
|
-
if (this.isReturn) {
|
|
975
|
-
break;
|
|
976
|
-
}
|
|
977
|
-
}
|
|
978
|
-
|
|
979
|
-
this.currentEnv = parentEnv;
|
|
980
|
-
return result;
|
|
981
|
-
}
|
|
982
|
-
|
|
983
|
-
async evaluateExpression(expr) {
|
|
984
|
-
switch (expr.type) {
|
|
985
|
-
case 'Literal':
|
|
986
|
-
if (expr.value === null) return VladXObject.null();
|
|
987
|
-
if (typeof expr.value === 'number') return VladXObject.number(expr.value);
|
|
988
|
-
if (typeof expr.value === 'boolean') return VladXObject.boolean(expr.value);
|
|
989
|
-
if (typeof expr.value === 'string') return VladXObject.string(expr.value);
|
|
990
|
-
return VladXObject.object(expr.value);
|
|
991
|
-
|
|
992
|
-
case 'Identifier':
|
|
993
|
-
return this.currentEnv.get(expr.name);
|
|
994
|
-
|
|
995
|
-
case 'BinaryExpression':
|
|
996
|
-
const left = await this.evaluateExpression(expr.left);
|
|
997
|
-
const right = await this.evaluateExpression(expr.right);
|
|
998
|
-
const leftVal = this.getNativeValue(left);
|
|
999
|
-
const rightVal = this.getNativeValue(right);
|
|
1000
|
-
|
|
1001
|
-
switch (expr.operator) {
|
|
1002
|
-
case '+': return VladXObject.number(leftVal + rightVal);
|
|
1003
|
-
case '-': return VladXObject.number(leftVal - rightVal);
|
|
1004
|
-
case '*': return VladXObject.number(leftVal * rightVal);
|
|
1005
|
-
case '/': return VladXObject.number(leftVal / rightVal);
|
|
1006
|
-
case '%': return VladXObject.number(leftVal % rightVal);
|
|
1007
|
-
case '**': return VladXObject.number(Math.pow(leftVal, rightVal));
|
|
1008
|
-
case '==': return VladXObject.boolean(leftVal === rightVal);
|
|
1009
|
-
case '!=': return VladXObject.boolean(leftVal !== rightVal);
|
|
1010
|
-
case '<': return VladXObject.boolean(leftVal < rightVal);
|
|
1011
|
-
case '<=': return VladXObject.boolean(leftVal <= rightVal);
|
|
1012
|
-
case '>': return VladXObject.boolean(leftVal > rightVal);
|
|
1013
|
-
case '>=': return VladXObject.boolean(leftVal >= rightVal);
|
|
1014
|
-
case '&&': return VladXObject.boolean(leftVal && rightVal);
|
|
1015
|
-
case '||': return VladXObject.boolean(leftVal || rightVal);
|
|
1016
|
-
default:
|
|
1017
|
-
throw new Error(`Неизвестный оператор: ${expr.operator}`);
|
|
1018
|
-
}
|
|
1019
|
-
|
|
1020
|
-
case 'UnaryExpression':
|
|
1021
|
-
const operand = await this.evaluateExpression(expr.operand);
|
|
1022
|
-
const operandVal = this.getNativeValue(operand);
|
|
1023
|
-
|
|
1024
|
-
switch (expr.operator) {
|
|
1025
|
-
case '-': return VladXObject.number(-operandVal);
|
|
1026
|
-
case '+': return VladXObject.number(+operandVal);
|
|
1027
|
-
case '!': return VladXObject.boolean(!operandVal);
|
|
1028
|
-
default:
|
|
1029
|
-
throw new Error(`Неизвестный унарный оператор: ${expr.operator}`);
|
|
1030
|
-
}
|
|
1031
|
-
|
|
1032
|
-
case 'AssignmentExpression':
|
|
1033
|
-
const assignRight = await this.evaluateExpression(expr.right);
|
|
1034
|
-
const assignValue = this.getNativeValue(assignRight);
|
|
1035
|
-
this.currentEnv.assign(expr.left.name, VladXObject.fromJS(assignValue));
|
|
1036
|
-
return assignRight;
|
|
1037
|
-
|
|
1038
|
-
case 'CallExpression':
|
|
1039
|
-
return await this.evaluateCallExpression(expr);
|
|
1040
|
-
|
|
1041
|
-
case 'NewExpression':
|
|
1042
|
-
return await this.evaluateNewExpression(expr);
|
|
1043
|
-
|
|
1044
|
-
case 'ThisExpression':
|
|
1045
|
-
return this.currentEnv.get('этот') || VladXObject.null();
|
|
1046
|
-
|
|
1047
|
-
default:
|
|
1048
|
-
throw new Error(`Неизвестное выражение: ${expr.type}`);
|
|
1049
|
-
}
|
|
1050
|
-
}
|
|
1051
|
-
|
|
1052
|
-
async evaluateCallExpression(expr) {
|
|
1053
|
-
const callee = await this.evaluateExpression(expr.callee);
|
|
1054
|
-
const args = await Promise.all(expr.arguments.map(arg => this.evaluateExpression(arg)));
|
|
1055
|
-
const nativeArgs = args.map(arg => this.getNativeValue(arg));
|
|
1056
|
-
|
|
1057
|
-
// Проверка на встроенную функцию
|
|
1058
|
-
if (callee.type === 'function' && this.builtins.has(callee.name)) {
|
|
1059
|
-
const fn = this.builtins.get(callee.name);
|
|
1060
|
-
const result = fn(...nativeArgs);
|
|
1061
|
-
|
|
1062
|
-
// Если это промис, ожидаем его
|
|
1063
|
-
if (result && typeof result.then === 'function') {
|
|
1064
|
-
return VladXObject.fromJS(await result);
|
|
1065
|
-
}
|
|
1066
|
-
|
|
1067
|
-
return VladXObject.fromJS(result);
|
|
1068
|
-
}
|
|
1069
|
-
|
|
1070
|
-
// Проверка на замыкание
|
|
1071
|
-
if (callee.type === 'closure') {
|
|
1072
|
-
const closure = callee;
|
|
1073
|
-
const parentEnv = this.currentEnv;
|
|
1074
|
-
this.currentEnv = closure.env.child(`<closure:${callee.name}>`);
|
|
1075
|
-
|
|
1076
|
-
for (let i = 0; i < closure.ast.params.length; i++) {
|
|
1077
|
-
const paramName = closure.ast.params[i];
|
|
1078
|
-
const argValue = nativeArgs[i] !== undefined ? nativeArgs[i] : VladXObject.null();
|
|
1079
|
-
this.currentEnv.define(paramName, argValue);
|
|
1080
|
-
}
|
|
1081
|
-
|
|
1082
|
-
let result = VladXObject.null();
|
|
1083
|
-
for (const stmt of closure.ast.body.body) {
|
|
1084
|
-
result = await this.evaluateStatement(stmt);
|
|
1085
|
-
if (this.isReturn) {
|
|
1086
|
-
this.isReturn = false;
|
|
1087
|
-
this.currentEnv = parentEnv;
|
|
1088
|
-
return this.returnValue;
|
|
1089
|
-
}
|
|
1090
|
-
}
|
|
1091
|
-
|
|
1092
|
-
this.currentEnv = parentEnv;
|
|
1093
|
-
return result;
|
|
1094
|
-
}
|
|
1095
|
-
|
|
1096
|
-
throw new Error(`${callee.name} не является функцией`);
|
|
1097
|
-
}
|
|
1098
|
-
|
|
1099
|
-
async evaluateNewExpression(expr) {
|
|
1100
|
-
return VladXObject.object({});
|
|
1101
|
-
}
|
|
1102
|
-
|
|
1103
|
-
getNativeValue(obj) {
|
|
1104
|
-
if (obj && obj.type !== undefined) {
|
|
1105
|
-
if (obj.type === 'number') return obj.value;
|
|
1106
|
-
if (obj.type === 'string') return obj.value;
|
|
1107
|
-
if (obj.type === 'boolean') return obj.value;
|
|
1108
|
-
if (obj.type === 'null') return null;
|
|
1109
|
-
if (obj.type === 'array') return obj.value;
|
|
1110
|
-
if (obj.type === 'object') return obj.value;
|
|
1111
|
-
}
|
|
1112
|
-
return obj;
|
|
1113
|
-
}
|
|
1114
|
-
|
|
1115
|
-
registerBuiltins() {
|
|
1116
|
-
// Математика
|
|
1117
|
-
this.builtins.set('максимум', (...args) => Math.max(...args));
|
|
1118
|
-
this.builtins.set('минимум', (...args) => Math.min(...args));
|
|
1119
|
-
this.builtins.set('случайный', () => Math.random());
|
|
1120
|
-
this.builtins.set('случайноеЦелое', (min, max) =>
|
|
1121
|
-
Math.floor(Math.random() * (max - min + 1)) + min);
|
|
1122
|
-
this.builtins.set('abs', (n) => Math.abs(n));
|
|
1123
|
-
this.builtins.set('округлить', (n) => Math.round(n));
|
|
1124
|
-
this.builtins.set('корень', (n) => Math.sqrt(n));
|
|
1125
|
-
|
|
1126
|
-
// Строки
|
|
1127
|
-
this.builtins.set('длина', (str) => String(str)?.length ?? 0);
|
|
1128
|
-
this.builtins.set('нижнийРегистр', (str) => String(str)?.toLowerCase() || '');
|
|
1129
|
-
this.builtins.set('верхнийРегистр', (str) => String(str)?.toUpperCase() || '');
|
|
1130
|
-
|
|
1131
|
-
// Кэш
|
|
1132
|
-
this.builtins.set('кэшУстановить', (key, value) => {
|
|
1133
|
-
this.cache.set(key, value);
|
|
1134
|
-
return true;
|
|
1135
|
-
});
|
|
1136
|
-
this.builtins.set('кэшПолучить', (key) => this.cache.get(key));
|
|
1137
|
-
this.builtins.set('кэшОчистить', () => {
|
|
1138
|
-
this.cache.clear();
|
|
1139
|
-
return true;
|
|
1140
|
-
});
|
|
1141
|
-
this.builtins.set('кэшСтатистика', () => this.cache.getStats());
|
|
1142
|
-
|
|
1143
|
-
// Функциональное
|
|
1144
|
-
this.builtins.set('каррировать', Functional.curry);
|
|
1145
|
-
this.builtins.set('композиция', Functional.compose);
|
|
1146
|
-
this.builtins.set('труба', Functional.pipe);
|
|
1147
|
-
this.builtins.set('мемоизировать', Functional.memoize);
|
|
1148
|
-
this.builtins.set('частично', Functional.partial);
|
|
1149
|
-
this.builtins.set('инвертировать', Functional.flip);
|
|
1150
|
-
this.builtins.set('одинРаз', Functional.once);
|
|
1151
|
-
this.builtins.set('отладить', Functional.trace);
|
|
1152
|
-
|
|
1153
|
-
// Структуры данных
|
|
1154
|
-
this.builtins.set('Стек', () => ({
|
|
1155
|
-
push: (x) => { return { __type: 'stack', __data: [] } },
|
|
1156
|
-
pop: () => undefined
|
|
1157
|
-
}));
|
|
1158
|
-
this.builtins.set('Очередь', () => ({
|
|
1159
|
-
enqueue: (x) => undefined,
|
|
1160
|
-
dequeue: () => undefined
|
|
1161
|
-
}));
|
|
1162
|
-
|
|
1163
|
-
// Асинхронность
|
|
1164
|
-
this.builtins.set('параллельно', (tasks) => this.asyncManager.parallel(tasks));
|
|
1165
|
-
this.builtins.set('последовательно', (tasks) => this.asyncManager.sequential(tasks));
|
|
1166
|
-
this.builtins.set('гонка', (tasks) => this.asyncManager.race(tasks));
|
|
1167
|
-
this.builtins.set('всеSettled', (tasks) => this.asyncManager.allSettled(tasks));
|
|
1168
|
-
}
|
|
1169
|
-
}
|
|
1170
|
-
|
|
1171
|
-
// === Главный движок ===
|
|
1172
|
-
export class VladXEngine {
|
|
1173
|
-
constructor(options = {}) {
|
|
1174
|
-
this.debug = options.debug || false;
|
|
1175
|
-
}
|
|
1176
|
-
|
|
1177
|
-
async execute(source, options = {}) {
|
|
1178
|
-
const filename = options.filename || '<unknown>';
|
|
1179
|
-
|
|
1180
|
-
if (this.debug) {
|
|
1181
|
-
console.log(`[VladX] Начинаем выполнение: ${filename}`);
|
|
1182
|
-
}
|
|
1183
|
-
|
|
1184
|
-
try {
|
|
1185
|
-
// Лексический анализ
|
|
1186
|
-
const lexer = new Lexer(source, filename);
|
|
1187
|
-
const tokens = lexer.tokenize();
|
|
1188
|
-
|
|
1189
|
-
if (this.debug) {
|
|
1190
|
-
console.log(`[VladX] Токенов: ${tokens.length}`);
|
|
1191
|
-
}
|
|
1192
|
-
|
|
1193
|
-
// Синтаксический анализ
|
|
1194
|
-
const parser = new Parser(tokens);
|
|
1195
|
-
const ast = parser.parse();
|
|
1196
|
-
|
|
1197
|
-
if (this.debug) {
|
|
1198
|
-
console.log(`[VladX] AST: ${ast.body.length} узлов`);
|
|
1199
|
-
}
|
|
1200
|
-
|
|
1201
|
-
// Интерпретация
|
|
1202
|
-
const interpreter = new Interpreter({
|
|
1203
|
-
debug: this.debug
|
|
1204
|
-
});
|
|
1205
|
-
|
|
1206
|
-
const result = await interpreter.interpret(ast);
|
|
1207
|
-
|
|
1208
|
-
if (this.debug) {
|
|
1209
|
-
console.log(`[VladX] Результат:`, result);
|
|
1210
|
-
}
|
|
1211
|
-
|
|
1212
|
-
return result;
|
|
1213
|
-
} catch (error) {
|
|
1214
|
-
console.error(`[VladX] Ошибка:`, error.message);
|
|
1215
|
-
if (error.stack) {
|
|
1216
|
-
console.error(error.stack);
|
|
1217
|
-
}
|
|
1218
|
-
throw error;
|
|
1219
|
-
}
|
|
1220
|
-
}
|
|
1221
|
-
|
|
1222
|
-
async executeFile(filepath) {
|
|
1223
|
-
const { readFileSync } = await import('fs');
|
|
1224
|
-
const { resolve, dirname } = await import('path');
|
|
1225
|
-
|
|
1226
|
-
try {
|
|
1227
|
-
const source = readFileSync(filepath, 'utf-8');
|
|
1228
|
-
return this.execute(source, { filename: filepath });
|
|
1229
|
-
} catch (error) {
|
|
1230
|
-
throw error;
|
|
1231
|
-
}
|
|
1232
|
-
}
|
|
1233
|
-
|
|
1234
|
-
async repl(inputStream = process.stdin, outputStream = process.stdout) {
|
|
1235
|
-
const readline = await import('readline');
|
|
1236
|
-
const rl = readline.createInterface({
|
|
1237
|
-
input: inputStream,
|
|
1238
|
-
output: outputStream,
|
|
1239
|
-
prompt: 'vladx> '
|
|
1240
|
-
});
|
|
1241
|
-
|
|
1242
|
-
console.log('VladX REPL (введите .exit для выхода)');
|
|
1243
|
-
|
|
1244
|
-
let buffer = '';
|
|
1245
|
-
|
|
1246
|
-
rl.on('line', async (line) => {
|
|
1247
|
-
buffer += line + '\n';
|
|
1248
|
-
|
|
1249
|
-
let complete = false;
|
|
1250
|
-
|
|
1251
|
-
// Простая проверка на завершение
|
|
1252
|
-
if (line.trim() === '') {
|
|
1253
|
-
// Пустая строка - может быть конец блока
|
|
1254
|
-
} else {
|
|
1255
|
-
// Проверяем скобки
|
|
1256
|
-
const openParens = (buffer.match(/\(/g) || []).length;
|
|
1257
|
-
const openBraces = (buffer.match(/{/g) || []).length;
|
|
1258
|
-
const openBrackets = (buffer.match(/\[/g) || []).length;
|
|
1259
|
-
|
|
1260
|
-
if (openParens === 0 && openBraces === 0 && openBrackets === 0) {
|
|
1261
|
-
complete = true;
|
|
1262
|
-
}
|
|
1263
|
-
}
|
|
1264
|
-
|
|
1265
|
-
if (complete || line.trim() === '') {
|
|
1266
|
-
if (buffer.trim()) {
|
|
1267
|
-
try {
|
|
1268
|
-
const result = await this.execute(buffer, { filename: '<repl>' });
|
|
1269
|
-
if (result !== undefined) {
|
|
1270
|
-
outputStream.write(`=> ${this.getDisplayValue(result)}\n`);
|
|
1271
|
-
}
|
|
1272
|
-
} catch (error) {
|
|
1273
|
-
outputStream.write(`Ошибка: ${error.message}\n`);
|
|
1274
|
-
}
|
|
1275
|
-
}
|
|
1276
|
-
buffer = '';
|
|
1277
|
-
}
|
|
1278
|
-
});
|
|
1279
|
-
|
|
1280
|
-
rl.on('close', () => {
|
|
1281
|
-
outputStream.write('\nДо встречи!\n');
|
|
1282
|
-
});
|
|
1283
|
-
}
|
|
1284
|
-
|
|
1285
|
-
getDisplayValue(obj) {
|
|
1286
|
-
if (!obj) return 'ничто';
|
|
1287
|
-
if (obj.type === 'string') return obj.value;
|
|
1288
|
-
if (obj.type === 'number') return String(obj.value);
|
|
1289
|
-
if (obj.type === 'boolean') return obj.value ? 'истина' : 'ложь';
|
|
1290
|
-
if (obj.type === 'null') return 'ничто';
|
|
1291
|
-
if (obj.type === 'array') return obj.value.map(v => this.getDisplayValue(v));
|
|
1292
|
-
if (obj.type === 'object') {
|
|
1293
|
-
const entries = Object.entries(obj.value || {}).map(([k, v]) => `${k}: ${this.getDisplayValue(v)}`);
|
|
1294
|
-
return '{ ' + entries.join(', ') + ' }';
|
|
1295
|
-
}
|
|
1296
|
-
return String(obj);
|
|
1297
|
-
}
|
|
1298
|
-
}
|
|
1299
|
-
|
|
1300
|
-
// Для ES модулей
|
|
1301
|
-
if (import.meta.url === `file://${process.argv[1]}`) {
|
|
1302
|
-
const engine = new VladXEngine();
|
|
1303
|
-
|
|
1304
|
-
const command = process.argv[2];
|
|
1305
|
-
const args = process.argv.slice(3);
|
|
1306
|
-
|
|
1307
|
-
if (command === 'run' && args[0]) {
|
|
1308
|
-
engine.executeFile(args[0]);
|
|
1309
|
-
} else if (command === 'repl') {
|
|
1310
|
-
engine.repl();
|
|
1311
|
-
} else {
|
|
1312
|
-
console.log(`
|
|
1313
|
-
VladX v1.0.0
|
|
1314
|
-
|
|
1315
|
-
Использование:
|
|
1316
|
-
node vladx run <файл> - выполнить файл
|
|
1317
|
-
node vladx repl - REPL режим
|
|
1318
|
-
|
|
1319
|
-
Примеры:
|
|
1320
|
-
node vladx run examples/demo.vx
|
|
1321
|
-
node vladx repl
|
|
1322
|
-
`.trim());
|
|
1323
|
-
}
|
|
1324
|
-
}
|
|
1325
|
-
|
|
1326
|
-
export default VladXEngine;
|