@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.
- package/LICENSE +21 -0
- package/README.md +338 -0
- package/build/analyzer/index.d.ts +7 -0
- package/build/analyzer/index.d.ts.map +1 -0
- package/build/analyzer/index.js +7 -0
- package/build/analyzer/index.js.map +1 -0
- package/build/analyzer/scope-analyzer.d.ts +88 -0
- package/build/analyzer/scope-analyzer.d.ts.map +1 -0
- package/build/analyzer/scope-analyzer.js +565 -0
- package/build/analyzer/scope-analyzer.js.map +1 -0
- package/build/cli.d.ts +10 -0
- package/build/cli.d.ts.map +1 -0
- package/build/cli.js +96 -0
- package/build/cli.js.map +1 -0
- package/build/emitter/bt-emitter.d.ts +31 -0
- package/build/emitter/bt-emitter.d.ts.map +1 -0
- package/build/emitter/bt-emitter.js +919 -0
- package/build/emitter/bt-emitter.js.map +1 -0
- package/build/emitter/index.d.ts +7 -0
- package/build/emitter/index.d.ts.map +1 -0
- package/build/emitter/index.js +7 -0
- package/build/emitter/index.js.map +1 -0
- package/build/index.d.ts +25 -0
- package/build/index.d.ts.map +1 -0
- package/build/index.js +24 -0
- package/build/index.js.map +1 -0
- package/build/ir/builders.d.ts +235 -0
- package/build/ir/builders.d.ts.map +1 -0
- package/build/ir/builders.js +387 -0
- package/build/ir/builders.js.map +1 -0
- package/build/ir/index.d.ts +8 -0
- package/build/ir/index.d.ts.map +1 -0
- package/build/ir/index.js +8 -0
- package/build/ir/index.js.map +1 -0
- package/build/ir/nodes.d.ts +596 -0
- package/build/ir/nodes.d.ts.map +1 -0
- package/build/ir/nodes.js +16 -0
- package/build/ir/nodes.js.map +1 -0
- package/build/lowering/bare-visitors.d.ts +72 -0
- package/build/lowering/bare-visitors.d.ts.map +1 -0
- package/build/lowering/bare-visitors.js +287 -0
- package/build/lowering/bare-visitors.js.map +1 -0
- package/build/lowering/binding.d.ts +127 -0
- package/build/lowering/binding.d.ts.map +1 -0
- package/build/lowering/binding.js +194 -0
- package/build/lowering/binding.js.map +1 -0
- package/build/lowering/expressions.d.ts +106 -0
- package/build/lowering/expressions.d.ts.map +1 -0
- package/build/lowering/expressions.js +1172 -0
- package/build/lowering/expressions.js.map +1 -0
- package/build/lowering/function-builder.d.ts +111 -0
- package/build/lowering/function-builder.d.ts.map +1 -0
- package/build/lowering/function-builder.js +111 -0
- package/build/lowering/function-builder.js.map +1 -0
- package/build/lowering/helpers.d.ts +88 -0
- package/build/lowering/helpers.d.ts.map +1 -0
- package/build/lowering/helpers.js +347 -0
- package/build/lowering/helpers.js.map +1 -0
- package/build/lowering/index.d.ts +21 -0
- package/build/lowering/index.d.ts.map +1 -0
- package/build/lowering/index.js +28 -0
- package/build/lowering/index.js.map +1 -0
- package/build/lowering/precedence.d.ts +18 -0
- package/build/lowering/precedence.d.ts.map +1 -0
- package/build/lowering/precedence.js +104 -0
- package/build/lowering/precedence.js.map +1 -0
- package/build/lowering/spread-helpers.d.ts +12 -0
- package/build/lowering/spread-helpers.d.ts.map +1 -0
- package/build/lowering/spread-helpers.js +32 -0
- package/build/lowering/spread-helpers.js.map +1 -0
- package/build/lowering/statements.d.ts +85 -0
- package/build/lowering/statements.d.ts.map +1 -0
- package/build/lowering/statements.js +713 -0
- package/build/lowering/statements.js.map +1 -0
- package/build/lowering/visitor.d.ts +121 -0
- package/build/lowering/visitor.d.ts.map +1 -0
- package/build/lowering/visitor.js +165 -0
- package/build/lowering/visitor.js.map +1 -0
- package/build/pipeline/index.d.ts +84 -0
- package/build/pipeline/index.d.ts.map +1 -0
- package/build/pipeline/index.js +224 -0
- package/build/pipeline/index.js.map +1 -0
- package/build/polyfill-spec.d.ts +18 -0
- package/build/polyfill-spec.d.ts.map +1 -0
- package/build/polyfill-spec.js +26 -0
- package/build/polyfill-spec.js.map +1 -0
- 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
|