@boristype/bt-ir 0.1.0-alpha.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 (87) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +338 -0
  3. package/build/analyzer/index.d.ts +7 -0
  4. package/build/analyzer/index.d.ts.map +1 -0
  5. package/build/analyzer/index.js +7 -0
  6. package/build/analyzer/index.js.map +1 -0
  7. package/build/analyzer/scope-analyzer.d.ts +88 -0
  8. package/build/analyzer/scope-analyzer.d.ts.map +1 -0
  9. package/build/analyzer/scope-analyzer.js +565 -0
  10. package/build/analyzer/scope-analyzer.js.map +1 -0
  11. package/build/cli.d.ts +10 -0
  12. package/build/cli.d.ts.map +1 -0
  13. package/build/cli.js +96 -0
  14. package/build/cli.js.map +1 -0
  15. package/build/emitter/bt-emitter.d.ts +31 -0
  16. package/build/emitter/bt-emitter.d.ts.map +1 -0
  17. package/build/emitter/bt-emitter.js +919 -0
  18. package/build/emitter/bt-emitter.js.map +1 -0
  19. package/build/emitter/index.d.ts +7 -0
  20. package/build/emitter/index.d.ts.map +1 -0
  21. package/build/emitter/index.js +7 -0
  22. package/build/emitter/index.js.map +1 -0
  23. package/build/index.d.ts +25 -0
  24. package/build/index.d.ts.map +1 -0
  25. package/build/index.js +24 -0
  26. package/build/index.js.map +1 -0
  27. package/build/ir/builders.d.ts +235 -0
  28. package/build/ir/builders.d.ts.map +1 -0
  29. package/build/ir/builders.js +387 -0
  30. package/build/ir/builders.js.map +1 -0
  31. package/build/ir/index.d.ts +8 -0
  32. package/build/ir/index.d.ts.map +1 -0
  33. package/build/ir/index.js +8 -0
  34. package/build/ir/index.js.map +1 -0
  35. package/build/ir/nodes.d.ts +596 -0
  36. package/build/ir/nodes.d.ts.map +1 -0
  37. package/build/ir/nodes.js +16 -0
  38. package/build/ir/nodes.js.map +1 -0
  39. package/build/lowering/bare-visitors.d.ts +72 -0
  40. package/build/lowering/bare-visitors.d.ts.map +1 -0
  41. package/build/lowering/bare-visitors.js +287 -0
  42. package/build/lowering/bare-visitors.js.map +1 -0
  43. package/build/lowering/binding.d.ts +127 -0
  44. package/build/lowering/binding.d.ts.map +1 -0
  45. package/build/lowering/binding.js +194 -0
  46. package/build/lowering/binding.js.map +1 -0
  47. package/build/lowering/expressions.d.ts +106 -0
  48. package/build/lowering/expressions.d.ts.map +1 -0
  49. package/build/lowering/expressions.js +1172 -0
  50. package/build/lowering/expressions.js.map +1 -0
  51. package/build/lowering/function-builder.d.ts +111 -0
  52. package/build/lowering/function-builder.d.ts.map +1 -0
  53. package/build/lowering/function-builder.js +111 -0
  54. package/build/lowering/function-builder.js.map +1 -0
  55. package/build/lowering/helpers.d.ts +88 -0
  56. package/build/lowering/helpers.d.ts.map +1 -0
  57. package/build/lowering/helpers.js +347 -0
  58. package/build/lowering/helpers.js.map +1 -0
  59. package/build/lowering/index.d.ts +21 -0
  60. package/build/lowering/index.d.ts.map +1 -0
  61. package/build/lowering/index.js +28 -0
  62. package/build/lowering/index.js.map +1 -0
  63. package/build/lowering/precedence.d.ts +18 -0
  64. package/build/lowering/precedence.d.ts.map +1 -0
  65. package/build/lowering/precedence.js +104 -0
  66. package/build/lowering/precedence.js.map +1 -0
  67. package/build/lowering/spread-helpers.d.ts +12 -0
  68. package/build/lowering/spread-helpers.d.ts.map +1 -0
  69. package/build/lowering/spread-helpers.js +32 -0
  70. package/build/lowering/spread-helpers.js.map +1 -0
  71. package/build/lowering/statements.d.ts +85 -0
  72. package/build/lowering/statements.d.ts.map +1 -0
  73. package/build/lowering/statements.js +713 -0
  74. package/build/lowering/statements.js.map +1 -0
  75. package/build/lowering/visitor.d.ts +121 -0
  76. package/build/lowering/visitor.d.ts.map +1 -0
  77. package/build/lowering/visitor.js +165 -0
  78. package/build/lowering/visitor.js.map +1 -0
  79. package/build/pipeline/index.d.ts +84 -0
  80. package/build/pipeline/index.d.ts.map +1 -0
  81. package/build/pipeline/index.js +224 -0
  82. package/build/pipeline/index.js.map +1 -0
  83. package/build/polyfill-spec.d.ts +18 -0
  84. package/build/polyfill-spec.d.ts.map +1 -0
  85. package/build/polyfill-spec.js +26 -0
  86. package/build/polyfill-spec.js.map +1 -0
  87. package/package.json +48 -0
@@ -0,0 +1,919 @@
1
+ /**
2
+ * BT Emitter - генерация BorisScript кода из IR
3
+ *
4
+ * @module emitter
5
+ */
6
+ const POLYFILL_SPEC = {
7
+ Array: {
8
+ at: { argCount: 2 },
9
+ copyWithin: { argCount: 4 },
10
+ entries: { argCount: 1 },
11
+ fill: { argCount: 4 },
12
+ flat: { argCount: 2 },
13
+ includes: { argCount: 3 },
14
+ indexOf: { argCount: 3 },
15
+ join: { argCount: 2 },
16
+ keys: { argCount: 1 },
17
+ lastIndexOf: { argCount: 3 },
18
+ pop: { argCount: 1 },
19
+ push: { rule: { kind: "direct", emit: (t, a) => `${t}.push(${a.join(", ")})` } },
20
+ reverse: { argCount: 1 },
21
+ shift: { argCount: 1 },
22
+ slice: { argCount: 3 },
23
+ splice: { argCount: 4, restAsArray: true },
24
+ toReversed: { argCount: 1 },
25
+ toSpliced: { argCount: 4, restAsArray: true },
26
+ unshift: { argCount: 2, restAsArray: true },
27
+ values: { argCount: 1 },
28
+ with: { rule: { kind: "rename", polyfillMethod: "_with" }, argCount: 3 },
29
+ concat: { rule: { kind: "builtin", fn: "ArrayUnion" } },
30
+ },
31
+ String: {
32
+ // split: { rule: { kind: "direct", emit: (t, a) => `${t}.split(${a.join(", ")})` } },
33
+ },
34
+ };
35
+ /**
36
+ * Генерирует BorisScript код из IR программы
37
+ */
38
+ export function emit(program, options = {}) {
39
+ const ctx = {
40
+ indent: 0,
41
+ indentStr: "",
42
+ options: {
43
+ indentSize: options.indentSize ?? 4,
44
+ useTabs: options.useTabs ?? false,
45
+ sourceMap: options.sourceMap ?? false,
46
+ },
47
+ noHoist: program.noHoist,
48
+ };
49
+ const code = emitProgram(program, ctx);
50
+ return { code };
51
+ }
52
+ /**
53
+ * Генерирует код программы с hoisting переменных
54
+ * Порядок:
55
+ * - Обычный режим: 1) функции, 2) объявления переменных, 3) остальной код
56
+ * - Bare-режим: statements в исходном порядке без hoisting на top-level.
57
+ * Hoisting переменных выполняется только внутри функций.
58
+ */
59
+ function emitProgram(program, ctx) {
60
+ const lines = [];
61
+ // Bare mode: top-level — 1:1, без hoisting.
62
+ // Переменные хоистятся только внутри функций (в emitFunction).
63
+ if (ctx.noHoist) {
64
+ for (const stmt of program.body) {
65
+ lines.push(emitStatement(stmt, ctx));
66
+ }
67
+ return lines.join("\n");
68
+ }
69
+ // Разделяем функции и остальные statements
70
+ const functions = [];
71
+ const otherStmts = [];
72
+ for (const stmt of program.body) {
73
+ if (stmt.kind === "FunctionDeclaration") {
74
+ functions.push(stmt);
75
+ }
76
+ else {
77
+ otherStmts.push(stmt);
78
+ }
79
+ }
80
+ // 1. Выводим функции
81
+ for (const fn of functions) {
82
+ lines.push(emitStatement(fn, ctx));
83
+ }
84
+ // 2. Собираем и выводим объявления переменных
85
+ const varNames = collectVariableNames(otherStmts);
86
+ for (const name of varNames) {
87
+ lines.push(`var ${name};`);
88
+ }
89
+ // 3. Выводим остальной код (с заменой var на присваивания)
90
+ for (const stmt of otherStmts) {
91
+ lines.push(emitStatementHoisted(stmt, ctx));
92
+ }
93
+ return lines.join("\n");
94
+ }
95
+ /**
96
+ * Генерирует код statement
97
+ */
98
+ function emitStatement(stmt, ctx) {
99
+ const pad = getIndent(ctx);
100
+ switch (stmt.kind) {
101
+ case "FunctionDeclaration":
102
+ return emitFunction(stmt, ctx);
103
+ case "VariableDeclaration":
104
+ return emitVarDecl(stmt, ctx);
105
+ case "ReturnStatement":
106
+ return emitReturn(stmt, ctx);
107
+ case "ExpressionStatement":
108
+ return `${pad}${emitExpression(stmt.expression, ctx)};`;
109
+ case "IfStatement":
110
+ return emitIf(stmt, ctx);
111
+ case "ForStatement":
112
+ return emitFor(stmt, ctx);
113
+ case "ForInStatement":
114
+ return emitForIn(stmt, ctx);
115
+ case "WhileStatement":
116
+ return emitWhile(stmt, ctx);
117
+ case "DoWhileStatement":
118
+ return emitDoWhile(stmt, ctx);
119
+ case "SwitchStatement":
120
+ return emitSwitch(stmt, ctx);
121
+ case "TryStatement":
122
+ return emitTry(stmt, ctx);
123
+ case "ThrowStatement":
124
+ return `${pad}throw ${emitExpression(stmt.argument, ctx)};`;
125
+ case "BreakStatement":
126
+ return stmt.label ? `${pad}break ${stmt.label};` : `${pad}break;`;
127
+ case "ContinueStatement":
128
+ return stmt.label ? `${pad}continue ${stmt.label};` : `${pad}continue;`;
129
+ case "BlockStatement":
130
+ return emitBlock(stmt, ctx);
131
+ case "EmptyStatement":
132
+ return `${pad};`;
133
+ case "EnvDeclaration":
134
+ return emitEnvDecl(stmt, ctx);
135
+ case "EnvAssign":
136
+ return emitEnvAssign(stmt, ctx);
137
+ case "FunctionDescriptor":
138
+ return emitFuncDescriptor(stmt, ctx);
139
+ case "EnvRegisterFunction":
140
+ return emitEnvRegisterFunc(stmt, ctx);
141
+ default:
142
+ return `${pad}/* Unknown statement: ${stmt.kind} */`;
143
+ }
144
+ }
145
+ // =========================================================================
146
+ // Statement Emitters
147
+ // =========================================================================
148
+ /**
149
+ * Генерирует код функции с hoisting переменных и вложенных функций.
150
+ *
151
+ * В bare-режиме (ctx.noHoist):
152
+ * - Хоистятся только переменные
153
+ * - Вложенные функции остаются на своих местах
154
+ *
155
+ * В обычном режиме:
156
+ * - Хоистятся и функции, и переменные
157
+ */
158
+ function emitFunction(fn, ctx) {
159
+ const pad = getIndent(ctx);
160
+ const innerCtx = increaseIndent(ctx);
161
+ const innerPad = getIndent(innerCtx);
162
+ const lines = [];
163
+ const isPlain = fn.plainSignature === true;
164
+ // Сигнатура: BT или plain (ObjectUnion и др.)
165
+ if (isPlain) {
166
+ const paramList = fn.originalParams.map(p => p.name).join(", ");
167
+ lines.push(`${pad}function ${fn.name}(${paramList}) {`);
168
+ }
169
+ else {
170
+ lines.push(`${pad}function ${fn.name}(__env, __this, __args) {`);
171
+ }
172
+ // Имена параметров (уже объявлены)
173
+ const paramNames = new Set(fn.originalParams.map(p => p.name));
174
+ // Bare mode: хоистим только переменные, всё остальное по порядку
175
+ if (ctx.noHoist && isPlain) {
176
+ const bodyVars = collectVariableNames(fn.body);
177
+ for (const name of bodyVars) {
178
+ if (!paramNames.has(name)) {
179
+ lines.push(`${innerPad}var ${name};`);
180
+ }
181
+ }
182
+ for (const stmt of fn.body) {
183
+ lines.push(emitStatementHoisted(stmt, innerCtx));
184
+ }
185
+ lines.push(`${pad}}`);
186
+ return lines.join("\n");
187
+ }
188
+ // Обычный режим: разделяем body на функции и остальные statements
189
+ const nestedFunctions = [];
190
+ const otherStatements = [];
191
+ for (const stmt of fn.body) {
192
+ if (stmt.kind === "FunctionDeclaration") {
193
+ nestedFunctions.push(stmt);
194
+ }
195
+ else {
196
+ otherStatements.push(stmt);
197
+ }
198
+ }
199
+ // 1. Вложенные функции (hoisted наверх)
200
+ for (const nestedFn of nestedFunctions) {
201
+ lines.push(emitFunction(nestedFn, innerCtx));
202
+ }
203
+ // 2. Извлечение параметров из __args (только для BT-сигнатуры)
204
+ if (!isPlain) {
205
+ fn.originalParams.forEach((param, index) => {
206
+ if (param.rest) {
207
+ // Rest параметр: var ...rest = __args.slice(index)
208
+ lines.push(`${innerPad}var ${param.name} = bt.Array.slice(__args, ${index});`);
209
+ }
210
+ else if (param.defaultValue) {
211
+ // Параметр с default value
212
+ const defaultExpr = emitExpression(param.defaultValue, innerCtx);
213
+ lines.push(`${innerPad}var ${param.name} = __args.length > ${index} ? __args[${index}] : ${defaultExpr};`);
214
+ }
215
+ else {
216
+ // Обычный параметр
217
+ lines.push(`${innerPad}var ${param.name} = __args.length > ${index} ? __args[${index}] : undefined;`);
218
+ }
219
+ });
220
+ }
221
+ // 3. Собираем переменные из тела (кроме имён параметров)
222
+ const bodyVars = collectVariableNames(otherStatements);
223
+ for (const name of bodyVars) {
224
+ if (!paramNames.has(name)) {
225
+ lines.push(`${innerPad}var ${name};`);
226
+ }
227
+ }
228
+ // 4. Остальное тело функции с hoisting
229
+ for (const stmt of otherStatements) {
230
+ lines.push(emitStatementHoisted(stmt, innerCtx));
231
+ }
232
+ lines.push(`${pad}}`);
233
+ return lines.join("\n");
234
+ }
235
+ /**
236
+ * Генерирует код объявления переменной
237
+ */
238
+ function emitVarDecl(decl, ctx) {
239
+ const pad = getIndent(ctx);
240
+ if (decl.init) {
241
+ // Для объектов нужно многострочное форматирование
242
+ if (decl.init.kind === "ObjectExpression") {
243
+ return `${pad}var ${decl.name} = ${emitObjectExpression(decl.init, ctx)};`;
244
+ }
245
+ return `${pad}var ${decl.name} = ${emitExpression(decl.init, ctx)};`;
246
+ }
247
+ return `${pad}var ${decl.name};`;
248
+ }
249
+ /**
250
+ * Генерирует код return
251
+ */
252
+ function emitReturn(ret, ctx) {
253
+ const pad = getIndent(ctx);
254
+ if (ret.argument) {
255
+ return `${pad}return ${emitExpression(ret.argument, ctx)};`;
256
+ }
257
+ return `${pad}return;`;
258
+ }
259
+ /**
260
+ * Генерирует код if
261
+ */
262
+ function emitIf(ifStmt, ctx) {
263
+ const pad = getIndent(ctx);
264
+ const lines = [];
265
+ lines.push(`${pad}if (${emitExpression(ifStmt.test, ctx)}) ${emitStatementOrBlock(ifStmt.consequent, ctx)}`);
266
+ if (ifStmt.alternate) {
267
+ if (ifStmt.alternate.kind === "IfStatement") {
268
+ // else if
269
+ const elseIfCode = emitIf(ifStmt.alternate, ctx).trimStart();
270
+ lines.push(`${pad}else ${elseIfCode}`);
271
+ }
272
+ else {
273
+ lines.push(`${pad}else ${emitStatementOrBlock(ifStmt.alternate, ctx)}`);
274
+ }
275
+ }
276
+ return lines.join("\n");
277
+ }
278
+ /**
279
+ * Генерирует код for
280
+ */
281
+ function emitFor(forStmt, ctx) {
282
+ const pad = getIndent(ctx);
283
+ const init = forStmt.init
284
+ ? forStmt.init.kind === "VariableDeclaration"
285
+ ? `var ${forStmt.init.name} = ${forStmt.init.init ? emitExpression(forStmt.init.init, ctx) : "undefined"}`
286
+ : emitExpression(forStmt.init, ctx)
287
+ : "";
288
+ const test = forStmt.test ? emitExpression(forStmt.test, ctx) : "";
289
+ const update = forStmt.update ? emitExpression(forStmt.update, ctx) : "";
290
+ return `${pad}for (${init}; ${test}; ${update}) ${emitStatementOrBlock(forStmt.body, ctx)}`;
291
+ }
292
+ /**
293
+ * Генерирует код for-in
294
+ */
295
+ function emitForIn(forStmt, ctx) {
296
+ const pad = getIndent(ctx);
297
+ const left = forStmt.left.kind === "VariableDeclaration"
298
+ ? `var ${forStmt.left.name}`
299
+ : emitExpression(forStmt.left, ctx);
300
+ return `${pad}for (${left} in ${emitExpression(forStmt.right, ctx)}) ${emitStatementOrBlock(forStmt.body, ctx)}`;
301
+ }
302
+ /**
303
+ * Генерирует код while
304
+ */
305
+ function emitWhile(whileStmt, ctx) {
306
+ const pad = getIndent(ctx);
307
+ return `${pad}while (${emitExpression(whileStmt.test, ctx)}) ${emitStatementOrBlock(whileStmt.body, ctx)}`;
308
+ }
309
+ /**
310
+ * Генерирует код do-while
311
+ */
312
+ function emitDoWhile(doWhileStmt, ctx) {
313
+ const pad = getIndent(ctx);
314
+ return `${pad}do ${emitStatementOrBlock(doWhileStmt.body, ctx)} while (${emitExpression(doWhileStmt.test, ctx)});`;
315
+ }
316
+ /**
317
+ * Генерирует код switch
318
+ */
319
+ function emitSwitch(switchStmt, ctx) {
320
+ const pad = getIndent(ctx);
321
+ const innerCtx = increaseIndent(ctx);
322
+ const innerPad = getIndent(innerCtx);
323
+ const caseCtx = increaseIndent(innerCtx);
324
+ const lines = [];
325
+ lines.push(`${pad}switch (${emitExpression(switchStmt.discriminant, ctx)}) {`);
326
+ for (const caseClause of switchStmt.cases) {
327
+ if (caseClause.test) {
328
+ lines.push(`${innerPad}case ${emitExpression(caseClause.test, innerCtx)}:`);
329
+ }
330
+ else {
331
+ lines.push(`${innerPad}default:`);
332
+ }
333
+ for (const stmt of caseClause.consequent) {
334
+ lines.push(emitStatement(stmt, caseCtx));
335
+ }
336
+ }
337
+ lines.push(`${pad}}`);
338
+ return lines.join("\n");
339
+ }
340
+ /**
341
+ * Генерирует код try
342
+ */
343
+ function emitTry(tryStmt, ctx) {
344
+ const pad = getIndent(ctx);
345
+ const lines = [];
346
+ lines.push(`${pad}try ${emitBlock(tryStmt.block, ctx)}`);
347
+ if (tryStmt.handler) {
348
+ const param = tryStmt.handler.param ? `(${tryStmt.handler.param})` : "";
349
+ lines.push(`${pad}catch ${param} ${emitBlock(tryStmt.handler.body, ctx)}`);
350
+ }
351
+ if (tryStmt.finalizer) {
352
+ lines.push(`${pad}finally ${emitBlock(tryStmt.finalizer, ctx)}`);
353
+ }
354
+ return lines.join("\n");
355
+ }
356
+ /**
357
+ * Генерирует код блока
358
+ */
359
+ function emitBlock(block, ctx) {
360
+ const pad = getIndent(ctx);
361
+ const innerCtx = increaseIndent(ctx);
362
+ const lines = [];
363
+ lines.push("{");
364
+ for (const stmt of block.body) {
365
+ lines.push(emitStatement(stmt, innerCtx));
366
+ }
367
+ lines.push(`${pad}}`);
368
+ return lines.join("\n");
369
+ }
370
+ /**
371
+ * Генерирует statement или блок (для if/for/while)
372
+ */
373
+ function emitStatementOrBlock(stmt, ctx) {
374
+ if (stmt.kind === "BlockStatement") {
375
+ return emitBlock(stmt, ctx);
376
+ }
377
+ // Оборачиваем в блок для консистентности
378
+ const innerCtx = increaseIndent(ctx);
379
+ return `{\n${emitStatement(stmt, innerCtx)}\n${getIndent(ctx)}}`;
380
+ }
381
+ // =========================================================================
382
+ // Environment Emitters
383
+ // =========================================================================
384
+ /**
385
+ * Генерирует код env declaration
386
+ */
387
+ function emitEnvDecl(decl, ctx) {
388
+ const pad = getIndent(ctx);
389
+ if (decl.parentEnv) {
390
+ return `${pad}var ${decl.name} = { __parent: ${decl.parentEnv} };`;
391
+ }
392
+ return `${pad}var ${decl.name} = {};`;
393
+ }
394
+ /**
395
+ * Генерирует код env assign
396
+ */
397
+ function emitEnvAssign(assign, ctx) {
398
+ const pad = getIndent(ctx);
399
+ return `${pad}${assign.envName}.${assign.key} = ${emitExpression(assign.value, ctx)};`;
400
+ }
401
+ /**
402
+ * Генерирует код function descriptor
403
+ */
404
+ function emitFuncDescriptor(desc, ctx) {
405
+ const pad = getIndent(ctx);
406
+ const thisStr = desc.thisValue ? emitExpression(desc.thisValue, ctx) : "null";
407
+ return `${pad}var ${desc.name} = { __func: ${desc.funcName}, __env: ${desc.envName}, __this: ${thisStr} };`;
408
+ }
409
+ /**
410
+ * Генерирует код env register function
411
+ */
412
+ function emitEnvRegisterFunc(reg, ctx) {
413
+ const pad = getIndent(ctx);
414
+ return `${pad}${reg.envName}.${reg.funcName} = ${reg.descriptorName};`;
415
+ }
416
+ // =========================================================================
417
+ // Expression Emitters
418
+ // =========================================================================
419
+ /**
420
+ * Генерирует код выражения
421
+ */
422
+ function emitExpression(expr, ctx) {
423
+ switch (expr.kind) {
424
+ case "Identifier":
425
+ return expr.name;
426
+ case "Literal":
427
+ return expr.raw;
428
+ case "BinaryExpression":
429
+ return emitBinary(expr, ctx);
430
+ case "UnaryExpression":
431
+ return emitUnary(expr, ctx);
432
+ case "ConditionalExpression":
433
+ return emitConditional(expr, ctx);
434
+ case "LogicalExpression":
435
+ return emitLogical(expr, ctx);
436
+ case "CallExpression":
437
+ return emitCall(expr, ctx);
438
+ case "MemberExpression":
439
+ return emitMember(expr, ctx);
440
+ case "ArrayExpression":
441
+ return emitArray(expr, ctx);
442
+ case "ObjectExpression":
443
+ return emitObjectExpression(expr, ctx);
444
+ case "AssignmentExpression":
445
+ return emitAssignment(expr, ctx);
446
+ case "UpdateExpression":
447
+ return emitUpdate(expr, ctx);
448
+ case "SequenceExpression":
449
+ return `(${expr.expressions.map(e => emitExpression(e, ctx)).join(", ")})`;
450
+ case "ArgsAccess":
451
+ return expr.originalName;
452
+ case "EnvAccess":
453
+ return emitEnvAccess(expr);
454
+ case "PolyfillCall":
455
+ return emitPolyfillCall(expr, ctx);
456
+ case "RuntimeCall":
457
+ return emitRuntimeCall(expr, ctx);
458
+ case "BTGetProperty":
459
+ return emitBTGetProperty(expr, ctx);
460
+ case "BTSetProperty":
461
+ return emitBTSetProperty(expr, ctx);
462
+ case "BTCallFunction":
463
+ return emitBTCallFunction(expr, ctx);
464
+ case "BTIsFunction":
465
+ return emitBTIsFunction(expr, ctx);
466
+ case "GroupingExpression":
467
+ return `(${emitExpression(expr.expression, ctx)})`;
468
+ default:
469
+ return `/* unknown expression: ${expr.kind} */`;
470
+ }
471
+ }
472
+ /**
473
+ * Генерирует код binary expression
474
+ */
475
+ function emitBinary(expr, ctx) {
476
+ const left = emitExpression(expr.left, ctx);
477
+ const right = emitExpression(expr.right, ctx);
478
+ return `${left} ${expr.operator} ${right}`;
479
+ }
480
+ /**
481
+ * Генерирует код unary expression
482
+ */
483
+ function emitUnary(expr, ctx) {
484
+ const arg = emitExpression(expr.argument, ctx);
485
+ if (expr.prefix) {
486
+ // typeof, void, delete нужен пробел
487
+ if (expr.operator === "typeof" || expr.operator === "void" || expr.operator === "delete") {
488
+ return `${expr.operator} ${arg}`;
489
+ }
490
+ return `${expr.operator}${arg}`;
491
+ }
492
+ return `${arg}${expr.operator}`;
493
+ }
494
+ /**
495
+ * Генерирует код conditional expression
496
+ */
497
+ function emitConditional(expr, ctx) {
498
+ const test = emitExpression(expr.test, ctx);
499
+ const consequent = emitExpression(expr.consequent, ctx);
500
+ const alternate = emitExpression(expr.alternate, ctx);
501
+ return `${test} ? ${consequent} : ${alternate}`;
502
+ }
503
+ /**
504
+ * Генерирует код logical expression
505
+ */
506
+ function emitLogical(expr, ctx) {
507
+ const left = emitExpression(expr.left, ctx);
508
+ const right = emitExpression(expr.right, ctx);
509
+ return `${left} ${expr.operator} ${right}`;
510
+ }
511
+ /**
512
+ * Генерирует код call expression
513
+ */
514
+ function emitCall(expr, ctx) {
515
+ const callee = emitExpression(expr.callee, ctx);
516
+ const args = expr.arguments.map(a => emitExpression(a, ctx)).join(", ");
517
+ return `${callee}(${args})`;
518
+ }
519
+ /**
520
+ * Генерирует код member expression
521
+ */
522
+ function emitMember(expr, ctx) {
523
+ const object = emitExpression(expr.object, ctx);
524
+ if (expr.computed) {
525
+ return `${object}[${emitExpression(expr.property, ctx)}]`;
526
+ }
527
+ return `${object}.${emitExpression(expr.property, ctx)}`;
528
+ }
529
+ /**
530
+ * Генерирует код array expression
531
+ */
532
+ function emitArray(expr, ctx) {
533
+ const elements = expr.elements.map(e => (e ? emitExpression(e, ctx) : "")).join(", ");
534
+ return `[${elements}]`;
535
+ }
536
+ /**
537
+ * Генерирует код object expression
538
+ */
539
+ function emitObjectExpression(obj, ctx) {
540
+ if (obj.properties.length === 0) {
541
+ return "{}";
542
+ }
543
+ const innerCtx = increaseIndent(ctx);
544
+ const innerPad = getIndent(innerCtx);
545
+ const pad = getIndent(ctx);
546
+ const lines = ["{"];
547
+ obj.properties.forEach((prop, i) => {
548
+ const comma = i < obj.properties.length - 1 ? "," : "";
549
+ const value = emitExpression(prop.value, innerCtx);
550
+ // Computed keys use [], non-identifier keys need quotes
551
+ const key = prop.computed
552
+ ? `[${prop.key}]`
553
+ : isValidIdentifier(prop.key)
554
+ ? prop.key
555
+ : `"${prop.key}"`;
556
+ lines.push(`${innerPad}${key}: ${value}${comma}`);
557
+ });
558
+ lines.push(`${pad}}`);
559
+ return lines.join("\n");
560
+ }
561
+ /**
562
+ * Генерирует код assignment expression
563
+ */
564
+ function emitAssignment(expr, ctx) {
565
+ const left = expr.left.kind === "EnvAccess"
566
+ ? emitEnvAccess(expr.left)
567
+ : emitExpression(expr.left, ctx);
568
+ const right = emitExpression(expr.right, ctx);
569
+ return `${left} ${expr.operator} ${right}`;
570
+ }
571
+ /**
572
+ * Генерирует код update expression
573
+ */
574
+ function emitUpdate(expr, ctx) {
575
+ const arg = emitExpression(expr.argument, ctx);
576
+ return expr.prefix ? `${expr.operator}${arg}` : `${arg}${expr.operator}`;
577
+ }
578
+ /**
579
+ * Генерирует код env access
580
+ */
581
+ function emitEnvAccess(access) {
582
+ let result = "__env";
583
+ for (let i = 0; i < access.depth; i++) {
584
+ result += ".__parent";
585
+ }
586
+ return `${result}.${access.key}`;
587
+ }
588
+ /**
589
+ * Генерирует код polyfill call.
590
+ * BT полифиллы требуют точное количество аргументов — дополняем undefined при необходимости.
591
+ */
592
+ function emitPolyfillCall(call, ctx) {
593
+ const target = emitExpression(call.target, ctx);
594
+ let args = call.arguments.map((a) => emitExpression(a, ctx));
595
+ const spec = POLYFILL_SPEC[call.polyfillType]?.[call.method];
596
+ // Pad args to exact count (BT polyfill semantics)
597
+ if (spec?.argCount !== undefined) {
598
+ const needed = spec.argCount - 1; // target + method params
599
+ while (args.length < needed) {
600
+ args = [...args, "undefined"];
601
+ }
602
+ }
603
+ const argsStr = args.length > 0 ? `, ${args.join(", ")}` : "";
604
+ const rule = spec?.rule;
605
+ if (rule) {
606
+ switch (rule.kind) {
607
+ case "direct":
608
+ return rule.emit(target, args);
609
+ case "builtin":
610
+ return `${rule.fn}(${target}${argsStr})`;
611
+ case "rename":
612
+ return `bt.polyfill.${call.polyfillType}.${rule.polyfillMethod}(${target}${argsStr})`;
613
+ }
614
+ }
615
+ return `bt.polyfill.${call.polyfillType}.${call.method}(${target}${argsStr})`;
616
+ }
617
+ /**
618
+ * Генерирует код runtime call
619
+ */
620
+ function emitRuntimeCall(call, ctx) {
621
+ const args = call.arguments.map(a => emitExpression(a, ctx)).join(", ");
622
+ return `bt.${call.namespace}.${call.method}(${args})`;
623
+ }
624
+ /**
625
+ * Генерирует код bt.getProperty(obj, prop)
626
+ */
627
+ function emitBTGetProperty(expr, ctx) {
628
+ const obj = emitExpression(expr.object, ctx);
629
+ const prop = emitExpression(expr.property, ctx);
630
+ return `bt.getProperty(${obj}, ${prop})`;
631
+ }
632
+ /**
633
+ * Генерирует код bt.setProperty(obj, prop, value)
634
+ */
635
+ function emitBTSetProperty(expr, ctx) {
636
+ const obj = emitExpression(expr.object, ctx);
637
+ const prop = emitExpression(expr.property, ctx);
638
+ const value = emitExpression(expr.value, ctx);
639
+ return `bt.setProperty(${obj}, ${prop}, ${value})`;
640
+ }
641
+ /**
642
+ * Генерирует код bt.callFunction(func, [args])
643
+ */
644
+ function emitBTCallFunction(expr, ctx) {
645
+ const callee = emitExpression(expr.callee, ctx);
646
+ const args = expr.arguments.map(a => emitExpression(a, ctx)).join(", ");
647
+ return `bt.callFunction(${callee}, [${args}])`;
648
+ }
649
+ /**
650
+ * Генерирует код bt.isFunction(value)
651
+ */
652
+ function emitBTIsFunction(expr, ctx) {
653
+ const value = emitExpression(expr.value, ctx);
654
+ return `bt.isFunction(${value})`;
655
+ }
656
+ // =========================================================================
657
+ // Helpers
658
+ // =========================================================================
659
+ /**
660
+ * Возвращает строку отступа
661
+ */
662
+ function getIndent(ctx) {
663
+ const char = ctx.options.useTabs ? "\t" : " ".repeat(ctx.options.indentSize);
664
+ return char.repeat(ctx.indent);
665
+ }
666
+ /**
667
+ * Возвращает новый контекст с увеличенным отступом
668
+ */
669
+ function increaseIndent(ctx) {
670
+ return { ...ctx, indent: ctx.indent + 1 };
671
+ }
672
+ // =========================================================================
673
+ // Variable Hoisting
674
+ // =========================================================================
675
+ /**
676
+ * Собирает все уникальные имена переменных из statements (рекурсивно)
677
+ * Не заходит внутрь функций (у них своя область видимости)
678
+ */
679
+ function collectVariableNames(statements) {
680
+ const vars = new Set();
681
+ function visit(stmt) {
682
+ switch (stmt.kind) {
683
+ case "VariableDeclaration":
684
+ // Пропускаем captured переменные - они живут в __env
685
+ if (!stmt.isCaptured) {
686
+ vars.add(stmt.name);
687
+ }
688
+ break;
689
+ case "BlockStatement":
690
+ stmt.body.forEach(visit);
691
+ break;
692
+ case "IfStatement":
693
+ visit(stmt.consequent);
694
+ if (stmt.alternate)
695
+ visit(stmt.alternate);
696
+ break;
697
+ case "ForStatement":
698
+ if (stmt.init && stmt.init.kind === "VariableDeclaration") {
699
+ if (!stmt.init.isCaptured) {
700
+ vars.add(stmt.init.name);
701
+ }
702
+ }
703
+ visit(stmt.body);
704
+ break;
705
+ case "ForInStatement":
706
+ if (stmt.left.kind === "VariableDeclaration") {
707
+ if (!stmt.left.isCaptured) {
708
+ vars.add(stmt.left.name);
709
+ }
710
+ }
711
+ visit(stmt.body);
712
+ break;
713
+ case "WhileStatement":
714
+ case "DoWhileStatement":
715
+ visit(stmt.body);
716
+ break;
717
+ case "SwitchStatement":
718
+ for (const c of stmt.cases) {
719
+ c.consequent.forEach(visit);
720
+ }
721
+ break;
722
+ case "TryStatement":
723
+ visit(stmt.block);
724
+ if (stmt.handler) {
725
+ if (stmt.handler.param) {
726
+ vars.add(stmt.handler.param);
727
+ }
728
+ visit(stmt.handler.body);
729
+ }
730
+ if (stmt.finalizer)
731
+ visit(stmt.finalizer);
732
+ break;
733
+ // FunctionDeclaration - не заходим внутрь
734
+ // Остальные statements не содержат переменных
735
+ }
736
+ }
737
+ statements.forEach(visit);
738
+ return vars;
739
+ }
740
+ /**
741
+ * Генерирует statement, заменяя var declarations на assignments
742
+ */
743
+ function emitStatementHoisted(stmt, ctx) {
744
+ const pad = getIndent(ctx);
745
+ // Для VariableDeclaration - только присваивание (hoistOnly только в collectVariableNames)
746
+ if (stmt.kind === "VariableDeclaration") {
747
+ if (stmt.hoistOnly)
748
+ return ""; // не эмитим присваивание, только var в hoisting
749
+ // Для captured переменных - присваивание в env (__env или __block0_env)
750
+ const target = stmt.isCaptured
751
+ ? `${stmt.envRef ?? "__env"}.${stmt.name}`
752
+ : stmt.name;
753
+ if (stmt.init) {
754
+ if (stmt.init.kind === "ObjectExpression") {
755
+ return `${pad}${target} = ${emitObjectExpression(stmt.init, ctx)};`;
756
+ }
757
+ return `${pad}${target} = ${emitExpression(stmt.init, ctx)};`;
758
+ }
759
+ return `${pad}${target} = undefined;`;
760
+ }
761
+ // Для блоков - рекурсивно
762
+ if (stmt.kind === "BlockStatement") {
763
+ return emitBlockHoisted(stmt, ctx);
764
+ }
765
+ // Для if - обработать consequent и alternate
766
+ if (stmt.kind === "IfStatement") {
767
+ return emitIfHoisted(stmt, ctx);
768
+ }
769
+ // Для for - обработать init и body
770
+ if (stmt.kind === "ForStatement") {
771
+ return emitForHoisted(stmt, ctx);
772
+ }
773
+ // Для for-in - обработать left и body
774
+ if (stmt.kind === "ForInStatement") {
775
+ return emitForInHoisted(stmt, ctx);
776
+ }
777
+ // Для while/do-while - обработать body
778
+ if (stmt.kind === "WhileStatement") {
779
+ return emitWhileHoisted(stmt, ctx);
780
+ }
781
+ if (stmt.kind === "DoWhileStatement") {
782
+ return emitDoWhileHoisted(stmt, ctx);
783
+ }
784
+ // Для switch - обработать cases
785
+ if (stmt.kind === "SwitchStatement") {
786
+ return emitSwitchHoisted(stmt, ctx);
787
+ }
788
+ // Для try - обработать все блоки
789
+ if (stmt.kind === "TryStatement") {
790
+ return emitTryHoisted(stmt, ctx);
791
+ }
792
+ // Остальные - без изменений
793
+ return emitStatement(stmt, ctx);
794
+ }
795
+ function emitBlockHoisted(block, ctx) {
796
+ const pad = getIndent(ctx);
797
+ const innerCtx = increaseIndent(ctx);
798
+ const lines = [];
799
+ lines.push(`${pad}{`);
800
+ for (const s of block.body) {
801
+ lines.push(emitStatementHoisted(s, innerCtx));
802
+ }
803
+ lines.push(`${pad}}`);
804
+ return lines.join("\n");
805
+ }
806
+ function emitIfHoisted(ifStmt, ctx) {
807
+ const pad = getIndent(ctx);
808
+ const lines = [];
809
+ lines.push(`${pad}if (${emitExpression(ifStmt.test, ctx)}) ${emitStatementOrBlockHoisted(ifStmt.consequent, ctx)}`);
810
+ if (ifStmt.alternate) {
811
+ if (ifStmt.alternate.kind === "IfStatement") {
812
+ const elseIfCode = emitIfHoisted(ifStmt.alternate, ctx).trimStart();
813
+ lines.push(`${pad}else ${elseIfCode}`);
814
+ }
815
+ else {
816
+ lines.push(`${pad}else ${emitStatementOrBlockHoisted(ifStmt.alternate, ctx)}`);
817
+ }
818
+ }
819
+ return lines.join("\n");
820
+ }
821
+ function emitForHoisted(forStmt, ctx) {
822
+ const pad = getIndent(ctx);
823
+ // init - если это var decl, выводим только присваивание
824
+ let init = "";
825
+ if (forStmt.init) {
826
+ if (forStmt.init.kind === "VariableDeclaration") {
827
+ init = `${forStmt.init.name} = ${forStmt.init.init ? emitExpression(forStmt.init.init, ctx) : "undefined"}`;
828
+ }
829
+ else {
830
+ init = emitExpression(forStmt.init, ctx);
831
+ }
832
+ }
833
+ const test = forStmt.test ? emitExpression(forStmt.test, ctx) : "";
834
+ const update = forStmt.update ? emitExpression(forStmt.update, ctx) : "";
835
+ return `${pad}for (${init}; ${test}; ${update}) ${emitStatementOrBlockHoisted(forStmt.body, ctx)}`;
836
+ }
837
+ function emitForInHoisted(forInStmt, ctx) {
838
+ const pad = getIndent(ctx);
839
+ // left - если это var decl, выводим только имя
840
+ const left = forInStmt.left.kind === "VariableDeclaration"
841
+ ? forInStmt.left.name
842
+ : emitExpression(forInStmt.left, ctx);
843
+ const right = emitExpression(forInStmt.right, ctx);
844
+ return `${pad}for (${left} in ${right}) ${emitStatementOrBlockHoisted(forInStmt.body, ctx)}`;
845
+ }
846
+ function emitWhileHoisted(whileStmt, ctx) {
847
+ const pad = getIndent(ctx);
848
+ return `${pad}while (${emitExpression(whileStmt.test, ctx)}) ${emitStatementOrBlockHoisted(whileStmt.body, ctx)}`;
849
+ }
850
+ function emitDoWhileHoisted(doWhileStmt, ctx) {
851
+ const pad = getIndent(ctx);
852
+ return `${pad}do ${emitStatementOrBlockHoisted(doWhileStmt.body, ctx)} while (${emitExpression(doWhileStmt.test, ctx)});`;
853
+ }
854
+ function emitSwitchHoisted(switchStmt, ctx) {
855
+ const pad = getIndent(ctx);
856
+ const innerCtx = increaseIndent(ctx);
857
+ const casePad = getIndent(innerCtx);
858
+ const caseBodyCtx = increaseIndent(innerCtx);
859
+ const lines = [];
860
+ lines.push(`${pad}switch (${emitExpression(switchStmt.discriminant, ctx)}) {`);
861
+ for (const c of switchStmt.cases) {
862
+ if (c.test) {
863
+ lines.push(`${casePad}case ${emitExpression(c.test, innerCtx)}:`);
864
+ }
865
+ else {
866
+ lines.push(`${casePad}default:`);
867
+ }
868
+ for (const s of c.consequent) {
869
+ lines.push(emitStatementHoisted(s, caseBodyCtx));
870
+ }
871
+ }
872
+ lines.push(`${pad}}`);
873
+ return lines.join("\n");
874
+ }
875
+ function emitTryHoisted(tryStmt, ctx) {
876
+ const pad = getIndent(ctx);
877
+ const innerCtx = increaseIndent(ctx);
878
+ const lines = [];
879
+ lines.push(`${pad}try {`);
880
+ for (const s of tryStmt.block.body) {
881
+ lines.push(emitStatementHoisted(s, innerCtx));
882
+ }
883
+ lines.push(`${pad}}`);
884
+ if (tryStmt.handler) {
885
+ const paramStr = tryStmt.handler.param ? `(${tryStmt.handler.param})` : "";
886
+ lines.push(`${pad}catch ${paramStr} {`);
887
+ for (const s of tryStmt.handler.body.body) {
888
+ lines.push(emitStatementHoisted(s, innerCtx));
889
+ }
890
+ lines.push(`${pad}}`);
891
+ }
892
+ if (tryStmt.finalizer) {
893
+ lines.push(`${pad}finally {`);
894
+ for (const s of tryStmt.finalizer.body) {
895
+ lines.push(emitStatementHoisted(s, innerCtx));
896
+ }
897
+ lines.push(`${pad}}`);
898
+ }
899
+ return lines.join("\n");
900
+ }
901
+ function emitStatementOrBlockHoisted(stmt, ctx) {
902
+ if (stmt.kind === "BlockStatement") {
903
+ const innerCtx = increaseIndent(ctx);
904
+ const lines = ["{"];
905
+ for (const s of stmt.body) {
906
+ lines.push(emitStatementHoisted(s, innerCtx));
907
+ }
908
+ lines.push(`${getIndent(ctx)}}`);
909
+ return lines.join("\n");
910
+ }
911
+ return emitStatementHoisted(stmt, ctx);
912
+ }
913
+ /**
914
+ * Проверяет является ли строка валидным JS идентификатором
915
+ */
916
+ function isValidIdentifier(str) {
917
+ return /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(str);
918
+ }
919
+ //# sourceMappingURL=bt-emitter.js.map