@ball-lang/compiler 0.1.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/bin/ball-ts-compile.mjs +56 -0
- package/dist/compiler.d.ts +70 -0
- package/dist/compiler.d.ts.map +1 -0
- package/dist/compiler.js +1674 -0
- package/dist/compiler.js.map +1 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -0
- package/dist/preamble.d.ts +16 -0
- package/dist/preamble.d.ts.map +1 -0
- package/dist/preamble.js +474 -0
- package/dist/preamble.js.map +1 -0
- package/dist/types.d.ts +115 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +9 -0
- package/dist/types.js.map +1 -0
- package/package.json +65 -0
- package/src/compiler.ts +1742 -0
- package/src/index.ts +4 -0
- package/src/preamble.ts +473 -0
- package/src/types.ts +127 -0
package/dist/compiler.js
ADDED
|
@@ -0,0 +1,1674 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ball → TypeScript compiler.
|
|
3
|
+
*
|
|
4
|
+
* Walks a Ball `Program` and emits idiomatic TypeScript via ts-morph.
|
|
5
|
+
* Runs in-process in TS — no Dart subprocess.
|
|
6
|
+
*
|
|
7
|
+
* Mirrors the semantics of the original Dart `ts_compiler.dart`:
|
|
8
|
+
* - Declarations (functions, classes, enums, typedefs) go through
|
|
9
|
+
* ts-morph's structure API — indentation / ordering / formatting
|
|
10
|
+
* handled by the library.
|
|
11
|
+
* - Expressions / statements are emitted as raw TS source strings
|
|
12
|
+
* into an internal buffer. ts-morph re-indents them inside each
|
|
13
|
+
* declaration block.
|
|
14
|
+
*/
|
|
15
|
+
import { Project, StructureKind } from "ts-morph";
|
|
16
|
+
import { TS_RUNTIME_PREAMBLE } from "./preamble.js";
|
|
17
|
+
export class BallCompiler {
|
|
18
|
+
program;
|
|
19
|
+
/** Buffer that _emit* functions write into. */
|
|
20
|
+
out = "";
|
|
21
|
+
depth = 0;
|
|
22
|
+
/** Catch-bound variables currently in scope — subject to bracket access. */
|
|
23
|
+
catchVars = new Set();
|
|
24
|
+
/** Fields of the class currently being emitted (method bodies). */
|
|
25
|
+
currentClassFields = new Set();
|
|
26
|
+
/** Parameters of the currently-emitting method (shadow fields). */
|
|
27
|
+
currentMethodParams = new Set();
|
|
28
|
+
/** Short method names of the current class (for `this.foo()` routing). */
|
|
29
|
+
currentClassMethodNames = new Set();
|
|
30
|
+
/** All function names in the entry module (function vs ctor routing). */
|
|
31
|
+
allFunctionNames = new Set();
|
|
32
|
+
/** typeDefs in the entry module (typeName → definition). */
|
|
33
|
+
typeDefByName = new Map();
|
|
34
|
+
constructor(program) {
|
|
35
|
+
this.program = program;
|
|
36
|
+
}
|
|
37
|
+
/** Compile to TS source. */
|
|
38
|
+
compile(options = {}) {
|
|
39
|
+
const { includePreamble = true, fileName = "program.ts" } = options;
|
|
40
|
+
const project = new Project({
|
|
41
|
+
useInMemoryFileSystem: true,
|
|
42
|
+
compilerOptions: { target: 99 /* ESNext */ },
|
|
43
|
+
});
|
|
44
|
+
const sf = project.createSourceFile(fileName, "", { overwrite: true });
|
|
45
|
+
const entryMod = this.program.modules.find((m) => m.name === this.program.entryModule);
|
|
46
|
+
if (!entryMod) {
|
|
47
|
+
throw new Error(`Entry module "${this.program.entryModule}" not found`);
|
|
48
|
+
}
|
|
49
|
+
// Seed the function-name + typeDef lookup tables.
|
|
50
|
+
this.allFunctionNames = new Set(entryMod.functions.map((f) => f.name));
|
|
51
|
+
this.typeDefByName = new Map((entryMod.typeDefs ?? []).map((td) => [td.name, td]));
|
|
52
|
+
// Group functions by their enclosing class (if any) — matches the
|
|
53
|
+
// `<typeDef.name>.<member>` naming convention from the encoder.
|
|
54
|
+
const classMembers = new Map();
|
|
55
|
+
const freeFunctions = [];
|
|
56
|
+
for (const fn of entryMod.functions) {
|
|
57
|
+
if (fn.isBase)
|
|
58
|
+
continue;
|
|
59
|
+
if (fn.name === this.program.entryFunction)
|
|
60
|
+
continue;
|
|
61
|
+
const enclosing = this.enclosingTypeName(fn.name);
|
|
62
|
+
if (enclosing) {
|
|
63
|
+
const list = classMembers.get(enclosing) ?? [];
|
|
64
|
+
list.push(fn);
|
|
65
|
+
classMembers.set(enclosing, list);
|
|
66
|
+
}
|
|
67
|
+
else {
|
|
68
|
+
freeFunctions.push(fn);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
// Typedefs → TsTypeAlias.
|
|
72
|
+
for (const ta of entryMod.typeAliases ?? []) {
|
|
73
|
+
sf.addTypeAlias({
|
|
74
|
+
name: ta.name,
|
|
75
|
+
type: this.dartTypeToTs(ta.targetType),
|
|
76
|
+
isExported: true,
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
// Classes.
|
|
80
|
+
for (const td of entryMod.typeDefs ?? []) {
|
|
81
|
+
this.emitClass(sf, td, classMembers.get(td.name) ?? []);
|
|
82
|
+
}
|
|
83
|
+
// Top-level variables (kind == 'top_level_variable') emit as
|
|
84
|
+
// `const <name> = <body>;` before free functions.
|
|
85
|
+
for (const fn of freeFunctions.filter((f) => f.metadata?.kind === "top_level_variable")) {
|
|
86
|
+
const name = sanitize(fn.name);
|
|
87
|
+
const body = fn.body ? this.captureInto(() => {
|
|
88
|
+
this.writeln(`return ${this.expr(fn.body)};`);
|
|
89
|
+
}) : "undefined";
|
|
90
|
+
sf.addStatements(`const ${name} = (() => { ${body} })();`);
|
|
91
|
+
}
|
|
92
|
+
// Free top-level functions (exclude top-level variables).
|
|
93
|
+
for (const fn of freeFunctions.filter((f) => f.metadata?.kind !== "top_level_variable")) {
|
|
94
|
+
this.emitFreeFunction(sf, fn);
|
|
95
|
+
}
|
|
96
|
+
// Entry function as `main()` + immediate call.
|
|
97
|
+
const entryFn = entryMod.functions.find((f) => f.name === this.program.entryFunction);
|
|
98
|
+
if (entryFn) {
|
|
99
|
+
this.emitFreeFunction(sf, entryFn, "main");
|
|
100
|
+
sf.addStatements("main();");
|
|
101
|
+
}
|
|
102
|
+
sf.formatText({ indentSize: 2, convertTabsToSpaces: true });
|
|
103
|
+
const body = sf.getFullText();
|
|
104
|
+
return includePreamble ? TS_RUNTIME_PREAMBLE + "\n" + body : body;
|
|
105
|
+
}
|
|
106
|
+
// ───────────────────────── Declarations ────────────────────────────
|
|
107
|
+
emitFreeFunction(sf, fn, forceName) {
|
|
108
|
+
const params = extractParams(fn);
|
|
109
|
+
const name = forceName ?? sanitize(fn.name);
|
|
110
|
+
const body = this.captureInto(() => {
|
|
111
|
+
if (params.length === 1 && params[0] !== "input") {
|
|
112
|
+
this.writeln(`const input = ${sanitize(params[0])};`);
|
|
113
|
+
}
|
|
114
|
+
if (fn.body)
|
|
115
|
+
this.emitStatementOrExpression(fn.body, true);
|
|
116
|
+
});
|
|
117
|
+
const isAsync = functionIsAsync(fn);
|
|
118
|
+
sf.addFunction({
|
|
119
|
+
kind: StructureKind.Function,
|
|
120
|
+
name,
|
|
121
|
+
isAsync,
|
|
122
|
+
parameters: params.map((p) => ({ name: sanitize(p), type: "any" })),
|
|
123
|
+
returnType: isAsync ? "Promise<any>" : "any",
|
|
124
|
+
statements: body,
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
emitClass(sf, td, members) {
|
|
128
|
+
const meta = td.metadata ?? {};
|
|
129
|
+
const tsName = classTsName(td.name);
|
|
130
|
+
// Fields: prefer metadata.fields (richer) with descriptor fallback.
|
|
131
|
+
const fieldSpecs = Array.isArray(meta["fields"])
|
|
132
|
+
? meta["fields"]
|
|
133
|
+
: [];
|
|
134
|
+
const properties = [];
|
|
135
|
+
const fieldNames = new Set();
|
|
136
|
+
for (const raw of fieldSpecs) {
|
|
137
|
+
if (raw == null || typeof raw !== "object")
|
|
138
|
+
continue;
|
|
139
|
+
const r = raw;
|
|
140
|
+
const fname = typeof r.name === "string" ? r.name : undefined;
|
|
141
|
+
if (!fname)
|
|
142
|
+
continue;
|
|
143
|
+
fieldNames.add(fname);
|
|
144
|
+
properties.push({
|
|
145
|
+
name: fname,
|
|
146
|
+
type: typeof r.type === "string" ? this.dartTypeToTs(r.type) : "any",
|
|
147
|
+
rawDartType: typeof r.type === "string" ? r.type : "",
|
|
148
|
+
isStatic: r.is_static === true,
|
|
149
|
+
isReadonly: r.is_final === true,
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
if (properties.length === 0 && td.descriptor?.field) {
|
|
153
|
+
for (const f of td.descriptor.field) {
|
|
154
|
+
fieldNames.add(f.name);
|
|
155
|
+
properties.push({ name: f.name, type: "any", rawDartType: "", isStatic: false, isReadonly: false });
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
// Method-name set for `this.foo()` routing inside bodies.
|
|
159
|
+
// Exclude static fields — they're emitted as module-level consts
|
|
160
|
+
// and referenced WITHOUT `this.`.
|
|
161
|
+
const methodNames = new Set();
|
|
162
|
+
const staticFieldNames = new Set();
|
|
163
|
+
for (const fn of members) {
|
|
164
|
+
const mMeta = fn.metadata ?? {};
|
|
165
|
+
if (mMeta.kind === "static_field") {
|
|
166
|
+
staticFieldNames.add(memberShortName(fn.name));
|
|
167
|
+
}
|
|
168
|
+
else {
|
|
169
|
+
methodNames.add(memberShortName(fn.name));
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
const savedClassMethods = this.currentClassMethodNames;
|
|
173
|
+
const deferredStaticFields = [];
|
|
174
|
+
this.currentClassMethodNames = methodNames;
|
|
175
|
+
const hasExtends = typeof meta["superclass"] === "string";
|
|
176
|
+
const ctors = [];
|
|
177
|
+
const methods = [];
|
|
178
|
+
const getters = [];
|
|
179
|
+
const setters = [];
|
|
180
|
+
for (const fn of members) {
|
|
181
|
+
const mMeta = fn.metadata ?? {};
|
|
182
|
+
const kind = typeof mMeta["kind"] === "string" ? mMeta["kind"] : "method";
|
|
183
|
+
if (kind === "constructor") {
|
|
184
|
+
// Named ctors become static factory methods; default `.new`
|
|
185
|
+
// becomes the real ctor.
|
|
186
|
+
const lastDot = fn.name.lastIndexOf(".");
|
|
187
|
+
const rawShort = lastDot < 0 ? fn.name : fn.name.slice(lastDot + 1);
|
|
188
|
+
if (rawShort === "new") {
|
|
189
|
+
ctors.push(this.buildCtor(fn, mMeta, fieldNames, hasExtends));
|
|
190
|
+
}
|
|
191
|
+
else {
|
|
192
|
+
methods.push(this.buildMethod(fn, { ...mMeta, is_static: true }, fieldNames));
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
else if (mMeta["is_getter"] === true) {
|
|
196
|
+
getters.push(this.buildGetter(fn, mMeta, fieldNames));
|
|
197
|
+
}
|
|
198
|
+
else if (mMeta["is_setter"] === true) {
|
|
199
|
+
setters.push(this.buildSetter(fn, mMeta, fieldNames));
|
|
200
|
+
}
|
|
201
|
+
else if (kind === "static_field") {
|
|
202
|
+
// Static field → module-level const emitted BEFORE the class
|
|
203
|
+
// so it's accessible as a bare name inside instance methods
|
|
204
|
+
// (matching Dart's behavior where static fields are visible
|
|
205
|
+
// without qualification). We capture the initializer body
|
|
206
|
+
// and emit it above the class via a deferred statement.
|
|
207
|
+
const sfName = memberShortName(fn.name);
|
|
208
|
+
const initBody = fn.body ? this.expr(fn.body) : "undefined";
|
|
209
|
+
deferredStaticFields.push(`const ${sfName} = ${initBody};`);
|
|
210
|
+
}
|
|
211
|
+
else {
|
|
212
|
+
methods.push(this.buildMethod(fn, mMeta, fieldNames));
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
this.currentClassMethodNames = savedClassMethods;
|
|
216
|
+
// Inheritance.
|
|
217
|
+
const superName = typeof meta["superclass"] === "string" ? meta["superclass"] : undefined;
|
|
218
|
+
const interfaces = Array.isArray(meta["interfaces"])
|
|
219
|
+
? meta["interfaces"]
|
|
220
|
+
.filter((i) => typeof i === "string")
|
|
221
|
+
: undefined;
|
|
222
|
+
const tsInterfaces = interfaces
|
|
223
|
+
?.filter((i) => i !== "Exception")
|
|
224
|
+
.map((i) => this.dartTypeToTs(i));
|
|
225
|
+
// Static fields are emitted as module-level constants before the
|
|
226
|
+
// class so they're accessible without qualification.
|
|
227
|
+
for (const stmt of deferredStaticFields) {
|
|
228
|
+
sf.addStatements(stmt);
|
|
229
|
+
}
|
|
230
|
+
sf.addClass({
|
|
231
|
+
name: tsName,
|
|
232
|
+
isExported: true,
|
|
233
|
+
isAbstract: meta["is_abstract"] === true,
|
|
234
|
+
extends: superName ? this.dartTypeToTs(superName) : undefined,
|
|
235
|
+
implements: tsInterfaces && tsInterfaces.length > 0 ? tsInterfaces : undefined,
|
|
236
|
+
properties: properties.map((p) => ({
|
|
237
|
+
name: p.name,
|
|
238
|
+
type: p.type,
|
|
239
|
+
isStatic: p.isStatic,
|
|
240
|
+
isReadonly: p.isReadonly,
|
|
241
|
+
initializer: defaultInitializer(p.type, p.rawDartType),
|
|
242
|
+
})),
|
|
243
|
+
ctors,
|
|
244
|
+
methods,
|
|
245
|
+
getAccessors: getters,
|
|
246
|
+
setAccessors: setters,
|
|
247
|
+
});
|
|
248
|
+
}
|
|
249
|
+
buildCtor(fn, meta, classFields, hasExtends) {
|
|
250
|
+
const rawParams = extractCtorParams(meta);
|
|
251
|
+
// Dart constructors may mix positional + named params. When named
|
|
252
|
+
// params exist AND there are also positional params, callers may
|
|
253
|
+
// pass named params as a trailing object `{label: x, value: y}`.
|
|
254
|
+
//
|
|
255
|
+
// Emit ALL params as positional but add a prologue that tries to
|
|
256
|
+
// destructure the LAST arg as a named-params object when there's
|
|
257
|
+
// a mix. This handles both calling conventions:
|
|
258
|
+
// new Foo('a', {label: 'b'}) → named destructured
|
|
259
|
+
// new Foo('a', 'b', 'c') → positional passthrough
|
|
260
|
+
const positionalParams = rawParams.filter((p) => !p.isNamed);
|
|
261
|
+
const namedParams = rawParams.filter((p) => p.isNamed);
|
|
262
|
+
const parameters = rawParams.map((p) => ({ name: sanitize(p.name), type: "any" }));
|
|
263
|
+
const prologueParts = [];
|
|
264
|
+
if (hasExtends)
|
|
265
|
+
prologueParts.push("super();");
|
|
266
|
+
// If there are named params AND the last positional+1 arg is an
|
|
267
|
+
// object, destructure named params from it (handles the encoder's
|
|
268
|
+
// MessageCreation calling convention where named args are packed).
|
|
269
|
+
if (positionalParams.length > 0 && namedParams.length > 0) {
|
|
270
|
+
// First named param slot might contain a {named args} object.
|
|
271
|
+
// Detect and destructure if so.
|
|
272
|
+
const firstNamedName = sanitize(namedParams[0].name);
|
|
273
|
+
prologueParts.push(`if (typeof ${firstNamedName} === 'object' && ${firstNamedName} !== null && !Array.isArray(${firstNamedName}) && (` +
|
|
274
|
+
namedParams.map((p) => `'${p.name}' in ${firstNamedName}`).join(" || ") +
|
|
275
|
+
`)) { let __n = ${firstNamedName}; ` +
|
|
276
|
+
namedParams.map((p) => `${sanitize(p.name)} = __n.${p.name}`).join("; ") +
|
|
277
|
+
`; }`);
|
|
278
|
+
}
|
|
279
|
+
for (const p of rawParams) {
|
|
280
|
+
if (p.isThis || classFields.has(p.name)) {
|
|
281
|
+
prologueParts.push(`this.${p.name} = ${sanitize(p.name)};`);
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
const prologue = prologueParts.join("\n");
|
|
285
|
+
const captured = this.withMethodContext(new Set(rawParams.map((p) => p.name)), classFields, () => this.captureInto(() => {
|
|
286
|
+
if (fn.body)
|
|
287
|
+
this.emitStatementOrExpression(fn.body, false);
|
|
288
|
+
}));
|
|
289
|
+
const body = prologue === "" ? captured : captured === "" ? prologue : `${prologue}\n${captured}`;
|
|
290
|
+
return { parameters, statements: body };
|
|
291
|
+
}
|
|
292
|
+
buildMethod(fn, meta, classFields) {
|
|
293
|
+
const params = extractParams(fn);
|
|
294
|
+
const body = this.withMethodContext(new Set(params), classFields, () => this.captureInto(() => {
|
|
295
|
+
if (params.length === 1 && params[0] !== "input") {
|
|
296
|
+
this.writeln(`const input = ${sanitize(params[0])};`);
|
|
297
|
+
}
|
|
298
|
+
if (fn.body)
|
|
299
|
+
this.emitStatementOrExpression(fn.body, true);
|
|
300
|
+
}));
|
|
301
|
+
const isAsync = functionIsAsync(fn);
|
|
302
|
+
return {
|
|
303
|
+
name: memberShortName(fn.name),
|
|
304
|
+
isAsync,
|
|
305
|
+
isStatic: meta["is_static"] === true,
|
|
306
|
+
parameters: params.map((p) => ({ name: sanitize(p), type: "any" })),
|
|
307
|
+
returnType: isAsync ? "Promise<any>" : "any",
|
|
308
|
+
statements: body,
|
|
309
|
+
};
|
|
310
|
+
}
|
|
311
|
+
buildGetter(fn, meta, classFields) {
|
|
312
|
+
const body = this.withMethodContext(new Set(), classFields, () => this.captureInto(() => {
|
|
313
|
+
if (fn.body)
|
|
314
|
+
this.emitStatementOrExpression(fn.body, true);
|
|
315
|
+
}));
|
|
316
|
+
return {
|
|
317
|
+
name: memberShortName(fn.name),
|
|
318
|
+
isStatic: meta["is_static"] === true,
|
|
319
|
+
returnType: "any",
|
|
320
|
+
statements: body,
|
|
321
|
+
};
|
|
322
|
+
}
|
|
323
|
+
buildSetter(fn, meta, classFields) {
|
|
324
|
+
const params = extractParams(fn);
|
|
325
|
+
const body = this.withMethodContext(new Set(params), classFields, () => this.captureInto(() => {
|
|
326
|
+
if (fn.body)
|
|
327
|
+
this.emitStatementOrExpression(fn.body, false);
|
|
328
|
+
}));
|
|
329
|
+
return {
|
|
330
|
+
name: memberShortName(fn.name),
|
|
331
|
+
isStatic: meta["is_static"] === true,
|
|
332
|
+
parameters: params.map((p) => ({ name: sanitize(p), type: "any" })),
|
|
333
|
+
statements: body,
|
|
334
|
+
};
|
|
335
|
+
}
|
|
336
|
+
// ───────────────────────── Buffer helpers ──────────────────────────
|
|
337
|
+
captureInto(body) {
|
|
338
|
+
const savedOut = this.out;
|
|
339
|
+
const savedDepth = this.depth;
|
|
340
|
+
this.out = "";
|
|
341
|
+
this.depth = 0;
|
|
342
|
+
try {
|
|
343
|
+
body();
|
|
344
|
+
return this.out.replace(/\s+$/, "");
|
|
345
|
+
}
|
|
346
|
+
finally {
|
|
347
|
+
this.out = savedOut;
|
|
348
|
+
this.depth = savedDepth;
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
writeln(s) {
|
|
352
|
+
this.out += " ".repeat(this.depth) + s + "\n";
|
|
353
|
+
}
|
|
354
|
+
get ind() {
|
|
355
|
+
return " ".repeat(this.depth);
|
|
356
|
+
}
|
|
357
|
+
withMethodContext(params, fields, fn) {
|
|
358
|
+
const sParams = this.currentMethodParams;
|
|
359
|
+
const sFields = this.currentClassFields;
|
|
360
|
+
this.currentMethodParams = params;
|
|
361
|
+
this.currentClassFields = fields;
|
|
362
|
+
try {
|
|
363
|
+
return fn();
|
|
364
|
+
}
|
|
365
|
+
finally {
|
|
366
|
+
this.currentMethodParams = sParams;
|
|
367
|
+
this.currentClassFields = sFields;
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
// ───────────────────────── Statements ──────────────────────────────
|
|
371
|
+
emitStatementOrExpression(expr, isFunctionBody) {
|
|
372
|
+
// Bare expression used as a function body → emit `return`.
|
|
373
|
+
if (isFunctionBody && !expr.block) {
|
|
374
|
+
this.writeln(`return ${this.expr(expr)};`);
|
|
375
|
+
return;
|
|
376
|
+
}
|
|
377
|
+
if (expr.block) {
|
|
378
|
+
this.emitBlock(expr.block, isFunctionBody);
|
|
379
|
+
return;
|
|
380
|
+
}
|
|
381
|
+
if (expr.call && this.isControlFlow(expr.call)) {
|
|
382
|
+
this.emitControlFlowStatement(expr.call);
|
|
383
|
+
return;
|
|
384
|
+
}
|
|
385
|
+
this.writeln(`${this.expr(expr)};`);
|
|
386
|
+
}
|
|
387
|
+
emitBlock(block, isFunctionBody) {
|
|
388
|
+
for (const s of block.statements ?? [])
|
|
389
|
+
this.emitStatement(s);
|
|
390
|
+
if (block.result !== undefined && isFunctionBody) {
|
|
391
|
+
const r = block.result;
|
|
392
|
+
// notSet literal → skip.
|
|
393
|
+
const isNotSet = r.literal !== undefined &&
|
|
394
|
+
r.literal.intValue === undefined &&
|
|
395
|
+
r.literal.doubleValue === undefined &&
|
|
396
|
+
r.literal.stringValue === undefined &&
|
|
397
|
+
r.literal.boolValue === undefined &&
|
|
398
|
+
r.literal.listValue === undefined &&
|
|
399
|
+
r.literal.bytesValue === undefined;
|
|
400
|
+
if (!isNotSet)
|
|
401
|
+
this.writeln(`return ${this.expr(r)};`);
|
|
402
|
+
}
|
|
403
|
+
else if (block.result !== undefined) {
|
|
404
|
+
this.writeln(`${this.expr(block.result)};`);
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
emitStatement(stmt) {
|
|
408
|
+
if (stmt.let) {
|
|
409
|
+
const meta = stmt.let.metadata ?? {};
|
|
410
|
+
const keyword = typeof meta["keyword"] === "string" ? meta["keyword"] : "final";
|
|
411
|
+
// Use `let` for all declarations — we can't reliably determine
|
|
412
|
+
// if a variable is ever reassigned without whole-method analysis,
|
|
413
|
+
// and Dart's `final` guarantee doesn't help when the compiled
|
|
414
|
+
// output re-assigns in patterns the encoder generates (e.g.
|
|
415
|
+
// _tryOperatorOverride's `left = input['left']`).
|
|
416
|
+
const kw = "let";
|
|
417
|
+
const name = sanitize(stmt.let.name);
|
|
418
|
+
if (stmt.let.value !== undefined) {
|
|
419
|
+
this.writeln(`${kw} ${name} = ${this.expr(stmt.let.value)};`);
|
|
420
|
+
}
|
|
421
|
+
else {
|
|
422
|
+
this.writeln(`${kw} ${name};`);
|
|
423
|
+
}
|
|
424
|
+
return;
|
|
425
|
+
}
|
|
426
|
+
if (stmt.expression) {
|
|
427
|
+
const e = stmt.expression;
|
|
428
|
+
if (e.call && this.isControlFlow(e.call)) {
|
|
429
|
+
this.emitControlFlowStatement(e.call);
|
|
430
|
+
return;
|
|
431
|
+
}
|
|
432
|
+
// Block-expression-as-statement with no result: hoist inner stmts.
|
|
433
|
+
if (e.block && e.block.result === undefined) {
|
|
434
|
+
for (const inner of e.block.statements ?? [])
|
|
435
|
+
this.emitStatement(inner);
|
|
436
|
+
return;
|
|
437
|
+
}
|
|
438
|
+
this.writeln(`${this.expr(e)};`);
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
// ───────────────────────── Control flow ────────────────────────────
|
|
442
|
+
isControlFlow(call) {
|
|
443
|
+
const kinds = new Set([
|
|
444
|
+
"if", "for", "for_in", "while", "do_while", "try",
|
|
445
|
+
"return", "break", "continue", "labeled", "throw", "rethrow",
|
|
446
|
+
"assign", "switch", "switch_expr",
|
|
447
|
+
]);
|
|
448
|
+
if (!kinds.has(call.function))
|
|
449
|
+
return false;
|
|
450
|
+
// Accept both explicit std module AND empty module (the encoder
|
|
451
|
+
// sometimes omits the module for control-flow operations).
|
|
452
|
+
return isStd(call.module) || !call.module;
|
|
453
|
+
}
|
|
454
|
+
emitControlFlowStatement(call) {
|
|
455
|
+
switch (call.function) {
|
|
456
|
+
case "if":
|
|
457
|
+
this.emitIfStmt(call);
|
|
458
|
+
break;
|
|
459
|
+
case "for":
|
|
460
|
+
this.emitForStmt(call);
|
|
461
|
+
break;
|
|
462
|
+
case "for_in":
|
|
463
|
+
this.emitForInStmt(call);
|
|
464
|
+
break;
|
|
465
|
+
case "while":
|
|
466
|
+
this.emitWhileStmt(call);
|
|
467
|
+
break;
|
|
468
|
+
case "do_while":
|
|
469
|
+
this.emitDoWhileStmt(call);
|
|
470
|
+
break;
|
|
471
|
+
case "try":
|
|
472
|
+
this.emitTryStmt(call);
|
|
473
|
+
break;
|
|
474
|
+
case "return": {
|
|
475
|
+
const v = field(call, "value");
|
|
476
|
+
this.writeln(v ? `return ${this.expr(v)};` : "return;");
|
|
477
|
+
break;
|
|
478
|
+
}
|
|
479
|
+
case "break": {
|
|
480
|
+
const label = stringField(call, "label");
|
|
481
|
+
this.writeln(label ? `break ${label};` : "break;");
|
|
482
|
+
break;
|
|
483
|
+
}
|
|
484
|
+
case "continue": {
|
|
485
|
+
const label = stringField(call, "label");
|
|
486
|
+
this.writeln(label ? `continue ${label};` : "continue;");
|
|
487
|
+
break;
|
|
488
|
+
}
|
|
489
|
+
case "labeled": {
|
|
490
|
+
const label = stringField(call, "label");
|
|
491
|
+
this.writeln(`${label}:`);
|
|
492
|
+
const body = field(call, "body");
|
|
493
|
+
if (body)
|
|
494
|
+
this.emitStatementOrExpression(body, false);
|
|
495
|
+
break;
|
|
496
|
+
}
|
|
497
|
+
case "throw": {
|
|
498
|
+
const v = field(call, "value");
|
|
499
|
+
if (v) {
|
|
500
|
+
const str = this.compileThrowValue(v) ?? this.expr(v);
|
|
501
|
+
this.writeln(`throw ${str};`);
|
|
502
|
+
}
|
|
503
|
+
else {
|
|
504
|
+
this.writeln("throw null;");
|
|
505
|
+
}
|
|
506
|
+
break;
|
|
507
|
+
}
|
|
508
|
+
case "rethrow":
|
|
509
|
+
this.writeln("throw __ball_active_error;");
|
|
510
|
+
break;
|
|
511
|
+
case "assign":
|
|
512
|
+
this.emitAssignStmt(call);
|
|
513
|
+
break;
|
|
514
|
+
case "switch":
|
|
515
|
+
case "switch_expr": {
|
|
516
|
+
// When a switch appears as a STATEMENT (not expression), emit
|
|
517
|
+
// as an if/else chain so each case can `return` independently
|
|
518
|
+
// without the ternary's default arm causing an early exit.
|
|
519
|
+
const subjectExpr = field(call, "subject");
|
|
520
|
+
const casesField = field(call, "cases");
|
|
521
|
+
if (!subjectExpr || !casesField) {
|
|
522
|
+
this.writeln("/* malformed switch */");
|
|
523
|
+
break;
|
|
524
|
+
}
|
|
525
|
+
const subjectStr = this.expr(subjectExpr);
|
|
526
|
+
this.writeln(`{ const __sw = ${subjectStr};`);
|
|
527
|
+
this.depth++;
|
|
528
|
+
const caseExprs = casesField.literal?.listValue?.elements ?? [];
|
|
529
|
+
let defaultBody;
|
|
530
|
+
let first = true;
|
|
531
|
+
// Parse all cases, detecting fall-through (empty body = merge
|
|
532
|
+
// with next case via ||).
|
|
533
|
+
const parsedCases = [];
|
|
534
|
+
const pendingConds = [];
|
|
535
|
+
for (const ce of caseExprs) {
|
|
536
|
+
if (!ce.messageCreation)
|
|
537
|
+
continue;
|
|
538
|
+
let pattern;
|
|
539
|
+
let body;
|
|
540
|
+
for (const fd of ce.messageCreation.fields ?? []) {
|
|
541
|
+
if (fd.name === "pattern")
|
|
542
|
+
pattern = fd.value;
|
|
543
|
+
if (fd.name === "body")
|
|
544
|
+
body = fd.value;
|
|
545
|
+
}
|
|
546
|
+
if (!pattern) {
|
|
547
|
+
defaultBody = body;
|
|
548
|
+
continue;
|
|
549
|
+
}
|
|
550
|
+
const patText = patternLiteralText(pattern);
|
|
551
|
+
const cond = patText !== undefined
|
|
552
|
+
? patternToTsCondition(patText, "__sw")
|
|
553
|
+
: `((__sw) === ${this.expr(pattern)})`;
|
|
554
|
+
if (cond === "true") {
|
|
555
|
+
defaultBody = body;
|
|
556
|
+
break;
|
|
557
|
+
}
|
|
558
|
+
// Empty body = fall-through: accumulate conditions.
|
|
559
|
+
const isEmpty = body && body.block &&
|
|
560
|
+
(body.block.statements ?? []).length === 0 &&
|
|
561
|
+
body.block.result === undefined;
|
|
562
|
+
if (!body || isEmpty) {
|
|
563
|
+
pendingConds.push(cond);
|
|
564
|
+
continue;
|
|
565
|
+
}
|
|
566
|
+
pendingConds.push(cond);
|
|
567
|
+
parsedCases.push({ conds: [...pendingConds], body });
|
|
568
|
+
pendingConds.length = 0;
|
|
569
|
+
}
|
|
570
|
+
for (const pc of parsedCases) {
|
|
571
|
+
const combinedCond = pc.conds.join(" || ");
|
|
572
|
+
const kw = first ? "if" : "else if";
|
|
573
|
+
this.writeln(`${kw} (${combinedCond}) {`);
|
|
574
|
+
this.depth++;
|
|
575
|
+
this.emitStatementOrExpression(pc.body, false);
|
|
576
|
+
this.depth--;
|
|
577
|
+
this.writeln("}");
|
|
578
|
+
first = false;
|
|
579
|
+
}
|
|
580
|
+
if (defaultBody) {
|
|
581
|
+
if (!first) {
|
|
582
|
+
this.writeln("else {");
|
|
583
|
+
this.depth++;
|
|
584
|
+
}
|
|
585
|
+
this.emitStatementOrExpression(defaultBody, false);
|
|
586
|
+
if (!first) {
|
|
587
|
+
this.depth--;
|
|
588
|
+
this.writeln("}");
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
this.depth--;
|
|
592
|
+
this.writeln("}");
|
|
593
|
+
break;
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
emitIfStmt(call) {
|
|
598
|
+
const cond = field(call, "condition");
|
|
599
|
+
const then_ = field(call, "then");
|
|
600
|
+
const else_ = field(call, "else");
|
|
601
|
+
this.writeln(`if (${this.expr(cond)}) {`);
|
|
602
|
+
this.depth++;
|
|
603
|
+
if (then_)
|
|
604
|
+
this.emitStatementOrExpression(then_, false);
|
|
605
|
+
this.depth--;
|
|
606
|
+
if (else_) {
|
|
607
|
+
this.writeln(`} else {`);
|
|
608
|
+
this.depth++;
|
|
609
|
+
this.emitStatementOrExpression(else_, false);
|
|
610
|
+
this.depth--;
|
|
611
|
+
}
|
|
612
|
+
this.writeln(`}`);
|
|
613
|
+
}
|
|
614
|
+
emitForStmt(call) {
|
|
615
|
+
const init = field(call, "init");
|
|
616
|
+
const cond = field(call, "condition");
|
|
617
|
+
const update = field(call, "update");
|
|
618
|
+
const body = field(call, "body");
|
|
619
|
+
let initStr;
|
|
620
|
+
if (init && init.literal?.stringValue !== undefined) {
|
|
621
|
+
initStr = translateInitString(init.literal.stringValue);
|
|
622
|
+
}
|
|
623
|
+
else if (init) {
|
|
624
|
+
initStr = this.expr(init);
|
|
625
|
+
}
|
|
626
|
+
else {
|
|
627
|
+
initStr = "";
|
|
628
|
+
}
|
|
629
|
+
const condStr = cond ? this.expr(cond) : "";
|
|
630
|
+
const updateStr = update ? this.expr(update) : "";
|
|
631
|
+
this.writeln(`for (${initStr}; ${condStr}; ${updateStr}) {`);
|
|
632
|
+
this.depth++;
|
|
633
|
+
if (body)
|
|
634
|
+
this.emitStatementOrExpression(body, false);
|
|
635
|
+
this.depth--;
|
|
636
|
+
this.writeln(`}`);
|
|
637
|
+
}
|
|
638
|
+
emitForInStmt(call) {
|
|
639
|
+
const variable = stringField(call, "variable") ?? "item";
|
|
640
|
+
const iterable = field(call, "iterable");
|
|
641
|
+
const body = field(call, "body");
|
|
642
|
+
this.writeln(`for (const ${variable} of ${this.expr(iterable)}) {`);
|
|
643
|
+
this.depth++;
|
|
644
|
+
if (body)
|
|
645
|
+
this.emitStatementOrExpression(body, false);
|
|
646
|
+
this.depth--;
|
|
647
|
+
this.writeln(`}`);
|
|
648
|
+
}
|
|
649
|
+
emitWhileStmt(call) {
|
|
650
|
+
const cond = field(call, "condition");
|
|
651
|
+
const body = field(call, "body");
|
|
652
|
+
this.writeln(`while (${this.expr(cond)}) {`);
|
|
653
|
+
this.depth++;
|
|
654
|
+
if (body)
|
|
655
|
+
this.emitStatementOrExpression(body, false);
|
|
656
|
+
this.depth--;
|
|
657
|
+
this.writeln(`}`);
|
|
658
|
+
}
|
|
659
|
+
emitDoWhileStmt(call) {
|
|
660
|
+
const cond = field(call, "condition");
|
|
661
|
+
const body = field(call, "body");
|
|
662
|
+
this.writeln(`do {`);
|
|
663
|
+
this.depth++;
|
|
664
|
+
if (body)
|
|
665
|
+
this.emitStatementOrExpression(body, false);
|
|
666
|
+
this.depth--;
|
|
667
|
+
this.writeln(`} while (${this.expr(cond)});`);
|
|
668
|
+
}
|
|
669
|
+
emitTryStmt(call) {
|
|
670
|
+
const body = field(call, "body");
|
|
671
|
+
const catches = field(call, "catches");
|
|
672
|
+
const fin = field(call, "finally");
|
|
673
|
+
this.writeln(`try {`);
|
|
674
|
+
this.depth++;
|
|
675
|
+
if (body)
|
|
676
|
+
this.emitStatementOrExpression(body, false);
|
|
677
|
+
this.depth--;
|
|
678
|
+
this.writeln(`} catch (__ball_active_error) {`);
|
|
679
|
+
this.depth++;
|
|
680
|
+
if (catches && catches.literal?.listValue) {
|
|
681
|
+
const clauses = catches.literal.listValue.elements ?? [];
|
|
682
|
+
let first = true;
|
|
683
|
+
let untypedBody;
|
|
684
|
+
let untypedVar = "e";
|
|
685
|
+
let untypedStackVar;
|
|
686
|
+
for (const ce of clauses) {
|
|
687
|
+
if (!ce.messageCreation)
|
|
688
|
+
continue;
|
|
689
|
+
const cf = fieldMap(ce.messageCreation.fields ?? []);
|
|
690
|
+
const type = stringFieldVal(cf, "type");
|
|
691
|
+
const variable = stringFieldVal(cf, "variable") ?? "e";
|
|
692
|
+
const stackVar = stringFieldVal(cf, "stack_trace");
|
|
693
|
+
const cbody = cf.get("body");
|
|
694
|
+
if (!type) {
|
|
695
|
+
untypedBody = cbody;
|
|
696
|
+
untypedVar = variable;
|
|
697
|
+
untypedStackVar = stackVar;
|
|
698
|
+
continue;
|
|
699
|
+
}
|
|
700
|
+
const cond = this.typedCatchCondition(type);
|
|
701
|
+
const keyword = first ? "if" : "else if";
|
|
702
|
+
this.writeln(`${keyword} (${cond}) {`);
|
|
703
|
+
this.depth++;
|
|
704
|
+
this.writeln(`const ${variable} = __ball_active_error;`);
|
|
705
|
+
if (stackVar) {
|
|
706
|
+
this.writeln(`const ${stackVar} = (__ball_active_error instanceof Error && __ball_active_error.stack != null ? __ball_active_error.stack : (new Error().stack ?? ''));`);
|
|
707
|
+
}
|
|
708
|
+
const treatAsMap = !this.typeIsUserDefinedClass(`main:${type}`) &&
|
|
709
|
+
!this.typeIsUserDefinedClass(type);
|
|
710
|
+
if (treatAsMap)
|
|
711
|
+
this.catchVars.add(variable);
|
|
712
|
+
if (cbody)
|
|
713
|
+
this.emitStatementOrExpression(cbody, false);
|
|
714
|
+
if (treatAsMap)
|
|
715
|
+
this.catchVars.delete(variable);
|
|
716
|
+
this.depth--;
|
|
717
|
+
this.writeln(`}`);
|
|
718
|
+
first = false;
|
|
719
|
+
}
|
|
720
|
+
if (!first)
|
|
721
|
+
this.writeln(`else {`);
|
|
722
|
+
if (!first)
|
|
723
|
+
this.depth++;
|
|
724
|
+
if (untypedBody) {
|
|
725
|
+
this.writeln(`const ${untypedVar} = __ball_active_error;`);
|
|
726
|
+
if (untypedStackVar) {
|
|
727
|
+
this.writeln(`const ${untypedStackVar} = (__ball_active_error instanceof Error && __ball_active_error.stack != null ? __ball_active_error.stack : (new Error().stack ?? ''));`);
|
|
728
|
+
}
|
|
729
|
+
this.catchVars.add(untypedVar);
|
|
730
|
+
this.emitStatementOrExpression(untypedBody, false);
|
|
731
|
+
this.catchVars.delete(untypedVar);
|
|
732
|
+
}
|
|
733
|
+
else {
|
|
734
|
+
this.writeln(`throw __ball_active_error;`);
|
|
735
|
+
}
|
|
736
|
+
if (!first) {
|
|
737
|
+
this.depth--;
|
|
738
|
+
this.writeln(`}`);
|
|
739
|
+
}
|
|
740
|
+
}
|
|
741
|
+
else {
|
|
742
|
+
this.writeln(`throw __ball_active_error;`);
|
|
743
|
+
}
|
|
744
|
+
this.depth--;
|
|
745
|
+
if (fin) {
|
|
746
|
+
this.writeln(`} finally {`);
|
|
747
|
+
this.depth++;
|
|
748
|
+
this.emitStatementOrExpression(fin, false);
|
|
749
|
+
this.depth--;
|
|
750
|
+
}
|
|
751
|
+
this.writeln(`}`);
|
|
752
|
+
}
|
|
753
|
+
emitAssignStmt(call) {
|
|
754
|
+
const target = field(call, "target");
|
|
755
|
+
const value = field(call, "value");
|
|
756
|
+
if (!target || !value)
|
|
757
|
+
return;
|
|
758
|
+
const op = stringField(call, "op") || "=";
|
|
759
|
+
this.writeln(`${this.expr(target)} ${op} ${this.expr(value)};`);
|
|
760
|
+
}
|
|
761
|
+
typedCatchCondition(type) {
|
|
762
|
+
const builtins = new Set([
|
|
763
|
+
"Error", "TypeError", "RangeError", "SyntaxError",
|
|
764
|
+
"ReferenceError", "URIError", "EvalError",
|
|
765
|
+
]);
|
|
766
|
+
if (builtins.has(type))
|
|
767
|
+
return `__ball_active_error instanceof ${type}`;
|
|
768
|
+
if (type === "FormatException") {
|
|
769
|
+
return "(__ball_active_error instanceof Error && __ball_active_error.message.startsWith('FormatException'))";
|
|
770
|
+
}
|
|
771
|
+
return `(__ball_active_error instanceof ${type} || (typeof __ball_active_error === 'object' && __ball_active_error !== null && __ball_active_error['__type'] === '${type}'))`;
|
|
772
|
+
}
|
|
773
|
+
// ───────────────────────── Expressions ─────────────────────────────
|
|
774
|
+
expr(e) {
|
|
775
|
+
if (e.call)
|
|
776
|
+
return this.compileCall(e.call);
|
|
777
|
+
if (e.literal)
|
|
778
|
+
return this.compileLiteral(e.literal);
|
|
779
|
+
if (e.reference) {
|
|
780
|
+
const name = e.reference.name;
|
|
781
|
+
if (name === "this")
|
|
782
|
+
return "this";
|
|
783
|
+
// Inside a class method: bare references to fields need this.
|
|
784
|
+
// prefix. Method references also need .bind(this) because Dart
|
|
785
|
+
// tear-offs auto-bind but JS method references do not.
|
|
786
|
+
if (!this.currentMethodParams.has(name)) {
|
|
787
|
+
if (this.currentClassFields.has(name)) {
|
|
788
|
+
return `this.${sanitize(name)}`;
|
|
789
|
+
}
|
|
790
|
+
if (this.currentClassMethodNames.has(name)) {
|
|
791
|
+
return `this.${sanitize(name)}.bind(this)`;
|
|
792
|
+
}
|
|
793
|
+
}
|
|
794
|
+
return sanitize(name);
|
|
795
|
+
}
|
|
796
|
+
if (e.fieldAccess)
|
|
797
|
+
return this.compileFieldAccess(e.fieldAccess);
|
|
798
|
+
if (e.messageCreation)
|
|
799
|
+
return this.compileMessageCreation(e.messageCreation);
|
|
800
|
+
if (e.block)
|
|
801
|
+
return this.compileBlockExpression(e.block);
|
|
802
|
+
if (e.lambda)
|
|
803
|
+
return this.compileLambda(e.lambda);
|
|
804
|
+
return "null /* notSet */";
|
|
805
|
+
}
|
|
806
|
+
compileLiteral(lit) {
|
|
807
|
+
if (lit.intValue !== undefined)
|
|
808
|
+
return String(lit.intValue);
|
|
809
|
+
if (lit.doubleValue !== undefined)
|
|
810
|
+
return String(lit.doubleValue);
|
|
811
|
+
if (lit.stringValue !== undefined)
|
|
812
|
+
return jsStringLiteral(lit.stringValue);
|
|
813
|
+
if (lit.boolValue !== undefined)
|
|
814
|
+
return lit.boolValue ? "true" : "false";
|
|
815
|
+
if (lit.listValue) {
|
|
816
|
+
const parts = (lit.listValue.elements ?? []).map((x) => this.expr(x));
|
|
817
|
+
return `[${parts.join(", ")}]`;
|
|
818
|
+
}
|
|
819
|
+
if (lit.bytesValue !== undefined)
|
|
820
|
+
return "/* bytes */ new Uint8Array()";
|
|
821
|
+
return "null";
|
|
822
|
+
}
|
|
823
|
+
compileFieldAccess(fa) {
|
|
824
|
+
const obj = this.expr(fa.object);
|
|
825
|
+
const f = fa.field;
|
|
826
|
+
if (f === "length")
|
|
827
|
+
return `${obj}.length`;
|
|
828
|
+
// Positional record field: `.$1` / `.$2` → [0] / [1].
|
|
829
|
+
const recMatch = /^\$(\d+)$/.exec(f);
|
|
830
|
+
if (recMatch) {
|
|
831
|
+
const idx = parseInt(recMatch[1], 10) - 1;
|
|
832
|
+
return `${obj}[${idx}]`;
|
|
833
|
+
}
|
|
834
|
+
if (fa.object.reference !== undefined &&
|
|
835
|
+
this.catchVars.has(fa.object.reference.name)) {
|
|
836
|
+
return `${obj}['${f}']`;
|
|
837
|
+
}
|
|
838
|
+
return `${obj}.${f}`;
|
|
839
|
+
}
|
|
840
|
+
compileMessageCreation(mc) {
|
|
841
|
+
const tn = mc.typeName ?? "";
|
|
842
|
+
const fields = mc.fields ?? [];
|
|
843
|
+
if (tn === "") {
|
|
844
|
+
const entries = fields
|
|
845
|
+
.map((f) => `'${f.name}': ${this.expr(f.value)}`)
|
|
846
|
+
.join(", ");
|
|
847
|
+
return `{${entries}}`;
|
|
848
|
+
}
|
|
849
|
+
// Function call encoded as MessageCreation: `foo()` / `this.foo()`
|
|
850
|
+
// with no explicit receiver. typeName = function qualified name.
|
|
851
|
+
const shortName = memberShortName(tn);
|
|
852
|
+
if (this.allFunctionNames.has(tn)) {
|
|
853
|
+
const args = this.extractPositionalAndNamed(fields);
|
|
854
|
+
if (this.currentClassMethodNames.has(shortName)) {
|
|
855
|
+
return `this.${shortName}(${args})`;
|
|
856
|
+
}
|
|
857
|
+
return `${shortName}(${args})`;
|
|
858
|
+
}
|
|
859
|
+
if (this.currentClassMethodNames.has(shortName)) {
|
|
860
|
+
const args = this.extractPositionalAndNamed(fields);
|
|
861
|
+
return `this.${shortName}(${args})`;
|
|
862
|
+
}
|
|
863
|
+
// User-defined class → `new X(...)`.
|
|
864
|
+
if (this.typeIsUserDefinedClass(tn)) {
|
|
865
|
+
const args = this.extractPositionalAndNamed(fields);
|
|
866
|
+
return `new ${classTsName(tn)}(${args})`;
|
|
867
|
+
}
|
|
868
|
+
// Dart/JS built-in constructors: RegExp, Map, Set, Error, etc.
|
|
869
|
+
// The encoder emits these as MessageCreation with typeName
|
|
870
|
+
// including the module prefix (e.g., 'main:RegExp'). Strip the
|
|
871
|
+
// prefix and emit as native constructors.
|
|
872
|
+
const builtinCtors = new Set([
|
|
873
|
+
"RegExp", "Map", "Set", "Error", "TypeError", "RangeError",
|
|
874
|
+
"DateTime", "Duration", "Uri", "BigInt", "Int64",
|
|
875
|
+
]);
|
|
876
|
+
const shortTn = classTsName(tn);
|
|
877
|
+
if (builtinCtors.has(shortTn)) {
|
|
878
|
+
const args = this.extractPositionalAndNamed(fields);
|
|
879
|
+
return `new ${shortTn}(${args})`;
|
|
880
|
+
}
|
|
881
|
+
// Fallback: tagged object literal.
|
|
882
|
+
const entries = [
|
|
883
|
+
`'__type': '${tn}'`,
|
|
884
|
+
...fields.map((f) => `'${f.name}': ${this.expr(f.value)}`),
|
|
885
|
+
].join(", ");
|
|
886
|
+
return `{${entries}}`;
|
|
887
|
+
}
|
|
888
|
+
extractPositionalAndNamed(fields) {
|
|
889
|
+
const positional = [];
|
|
890
|
+
const named = [];
|
|
891
|
+
const argRe = /^arg(\d+)$/;
|
|
892
|
+
for (const f of fields) {
|
|
893
|
+
if (f.name === "__type_args__" || f.name === "__const__")
|
|
894
|
+
continue;
|
|
895
|
+
if (argRe.test(f.name)) {
|
|
896
|
+
positional.push(this.expr(f.value));
|
|
897
|
+
}
|
|
898
|
+
else {
|
|
899
|
+
named.push([f.name, this.expr(f.value)]);
|
|
900
|
+
}
|
|
901
|
+
}
|
|
902
|
+
const parts = [
|
|
903
|
+
...positional,
|
|
904
|
+
...(named.length > 0
|
|
905
|
+
? [`{ ${named.map(([k, v]) => `${k}: ${v}`).join(", ")} }`]
|
|
906
|
+
: []),
|
|
907
|
+
];
|
|
908
|
+
return parts.join(", ");
|
|
909
|
+
}
|
|
910
|
+
typeIsUserDefinedClass(tn) {
|
|
911
|
+
return tn !== "" && this.typeDefByName.has(tn);
|
|
912
|
+
}
|
|
913
|
+
compileBlockExpression(block) {
|
|
914
|
+
const innerText = this.captureInto(() => {
|
|
915
|
+
this.writeln(""); // leading newline
|
|
916
|
+
for (const s of block.statements ?? [])
|
|
917
|
+
this.emitStatement(s);
|
|
918
|
+
if (block.result !== undefined) {
|
|
919
|
+
this.writeln(`return ${this.expr(block.result)};`);
|
|
920
|
+
}
|
|
921
|
+
}) + "\n";
|
|
922
|
+
const usesAwait = containsBareKeyword(innerText, "await");
|
|
923
|
+
const usesYield = containsBareKeyword(innerText, "yield");
|
|
924
|
+
if (usesAwait)
|
|
925
|
+
return `(await (async () => {${innerText}})())`;
|
|
926
|
+
if (usesYield)
|
|
927
|
+
return `(yield* (function* () {${innerText}})())`;
|
|
928
|
+
return `(() => {${innerText}})()`;
|
|
929
|
+
}
|
|
930
|
+
compileLambda(fn) {
|
|
931
|
+
const params = extractParams(fn);
|
|
932
|
+
const paramList = params.map(sanitize).join(", ");
|
|
933
|
+
const innerText = this.captureInto(() => {
|
|
934
|
+
this.writeln("");
|
|
935
|
+
if (params.length === 1 && params[0] !== "input") {
|
|
936
|
+
this.writeln(`const input = ${sanitize(params[0])};`);
|
|
937
|
+
}
|
|
938
|
+
if (fn.body)
|
|
939
|
+
this.emitStatementOrExpression(fn.body, true);
|
|
940
|
+
}) + "\n";
|
|
941
|
+
const isAsync = functionIsAsync(fn) || containsBareKeyword(innerText, "await");
|
|
942
|
+
const isGenerator = containsBareKeyword(innerText, "yield");
|
|
943
|
+
if (isGenerator) {
|
|
944
|
+
return `(function* (${paramList}) {${innerText}})`;
|
|
945
|
+
}
|
|
946
|
+
return `(${isAsync ? "async " : ""}(${paramList}) => {${innerText}})`;
|
|
947
|
+
}
|
|
948
|
+
// ───────────────────────── Calls ──────────────────────────────────
|
|
949
|
+
compileCall(call) {
|
|
950
|
+
const emptyModuleStd = new Set([
|
|
951
|
+
"labeled", "paren", "switch_expr", "set_create", "map_create",
|
|
952
|
+
"yield_each", "rethrow", "assert",
|
|
953
|
+
]);
|
|
954
|
+
if (isStd(call.module) ||
|
|
955
|
+
((call.module === undefined || call.module === "") && emptyModuleStd.has(call.function))) {
|
|
956
|
+
return this.compileStdCall(call);
|
|
957
|
+
}
|
|
958
|
+
const fn = sanitize(call.function);
|
|
959
|
+
// Prefix with this. when the function name is a class method OR
|
|
960
|
+
// a class field holding a callable (like `stdout` which is a
|
|
961
|
+
// void Function(String) field). Both need this. in TS since Dart
|
|
962
|
+
// resolves implicitly.
|
|
963
|
+
const thisPrefix = !this.currentMethodParams.has(call.function) &&
|
|
964
|
+
(this.currentClassMethodNames.has(call.function) ||
|
|
965
|
+
this.currentClassFields.has(call.function))
|
|
966
|
+
? "this."
|
|
967
|
+
: "";
|
|
968
|
+
if (!call.input)
|
|
969
|
+
return `${thisPrefix}${fn}()`;
|
|
970
|
+
const input = call.input;
|
|
971
|
+
if (input.messageCreation) {
|
|
972
|
+
const fields = input.messageCreation.fields ?? [];
|
|
973
|
+
const selfField = fields.find((f) => f.name === "self");
|
|
974
|
+
if (selfField) {
|
|
975
|
+
const selfStr = this.expr(selfField.value);
|
|
976
|
+
const otherArgs = fields
|
|
977
|
+
.filter((f) => f.name !== "self" && f.name !== "__type_args__")
|
|
978
|
+
.map((f) => this.expr(f.value))
|
|
979
|
+
.join(", ");
|
|
980
|
+
return otherArgs === ""
|
|
981
|
+
? `${selfStr}.${fn}()`
|
|
982
|
+
: `${selfStr}.${fn}(${otherArgs})`;
|
|
983
|
+
}
|
|
984
|
+
const args = fields.map((f) => this.expr(f.value)).join(", ");
|
|
985
|
+
return `${thisPrefix}${fn}(${args})`;
|
|
986
|
+
}
|
|
987
|
+
return `${thisPrefix}${fn}(${this.expr(input)})`;
|
|
988
|
+
}
|
|
989
|
+
compileStdCall(call) {
|
|
990
|
+
const fn = call.function;
|
|
991
|
+
const f = fieldMap(call.input?.messageCreation?.fields ?? []);
|
|
992
|
+
const bin = (op) => `(${this.expr(f.get("left"))} ${op} ${this.expr(f.get("right"))})`;
|
|
993
|
+
const un = (op) => {
|
|
994
|
+
const inner = this.expr(f.get("value"));
|
|
995
|
+
if (op === "-" && inner.startsWith("-"))
|
|
996
|
+
return `-(${inner})`;
|
|
997
|
+
return `${op}${inner}`;
|
|
998
|
+
};
|
|
999
|
+
switch (fn) {
|
|
1000
|
+
// Arithmetic
|
|
1001
|
+
case "add": return bin("+");
|
|
1002
|
+
case "subtract": return bin("-");
|
|
1003
|
+
case "multiply": return bin("*");
|
|
1004
|
+
case "divide": return `Math.trunc(${this.expr(f.get("left"))} / ${this.expr(f.get("right"))})`;
|
|
1005
|
+
case "divide_double": return bin("/");
|
|
1006
|
+
case "modulo": return bin("%");
|
|
1007
|
+
case "negate": return un("-");
|
|
1008
|
+
// Comparison
|
|
1009
|
+
case "equals": {
|
|
1010
|
+
// Use loose == when comparing against null so undefined matches
|
|
1011
|
+
// too (Dart has no undefined; JS returns undefined for missing
|
|
1012
|
+
// map keys, unset fields, etc.)
|
|
1013
|
+
const l = f.get("left"), r = f.get("right");
|
|
1014
|
+
if (l && r) {
|
|
1015
|
+
const le = this.expr(l), re = this.expr(r);
|
|
1016
|
+
const op = le === "null" || re === "null" ? "==" : "===";
|
|
1017
|
+
return `(${le} ${op} ${re})`;
|
|
1018
|
+
}
|
|
1019
|
+
return bin("===");
|
|
1020
|
+
}
|
|
1021
|
+
case "not_equals": {
|
|
1022
|
+
const l = f.get("left"), r = f.get("right");
|
|
1023
|
+
if (l && r) {
|
|
1024
|
+
const le = this.expr(l), re = this.expr(r);
|
|
1025
|
+
const op = le === "null" || re === "null" ? "!=" : "!==";
|
|
1026
|
+
return `(${le} ${op} ${re})`;
|
|
1027
|
+
}
|
|
1028
|
+
return bin("!==");
|
|
1029
|
+
}
|
|
1030
|
+
case "less_than": return bin("<");
|
|
1031
|
+
case "greater_than": return bin(">");
|
|
1032
|
+
case "lte": return bin("<=");
|
|
1033
|
+
case "gte": return bin(">=");
|
|
1034
|
+
// Logical
|
|
1035
|
+
case "and": return bin("&&");
|
|
1036
|
+
case "or": return bin("||");
|
|
1037
|
+
case "not": return un("!");
|
|
1038
|
+
// Bitwise
|
|
1039
|
+
case "bitwise_and": return bin("&");
|
|
1040
|
+
case "bitwise_or": return bin("|");
|
|
1041
|
+
case "bitwise_xor": return bin("^");
|
|
1042
|
+
case "bitwise_not": return un("~");
|
|
1043
|
+
case "left_shift": return bin("<<");
|
|
1044
|
+
case "right_shift": return bin(">>");
|
|
1045
|
+
case "unsigned_right_shift": return bin(">>>");
|
|
1046
|
+
case "integer_divide":
|
|
1047
|
+
return `Math.trunc(${this.expr(f.get("left"))} / ${this.expr(f.get("right"))})`;
|
|
1048
|
+
case "concat": return bin("+");
|
|
1049
|
+
case "to_string": return `__ball_to_string(${this.expr(f.get("value"))})`;
|
|
1050
|
+
case "int_to_string": return `String(${this.expr(f.get("value"))})`;
|
|
1051
|
+
case "double_to_string": return `__ball_double_to_string(${this.expr(f.get("value"))})`;
|
|
1052
|
+
case "string_to_int": return `__ball_parse_int(${this.expr(f.get("value"))})`;
|
|
1053
|
+
case "string_to_double": return `__ball_parse_double(${this.expr(f.get("value"))})`;
|
|
1054
|
+
case "string_length": return `${this.expr(f.get("value"))}.length`;
|
|
1055
|
+
case "string_to_upper": return `${this.wrapIfNeeded(f.get("value"))}.toUpperCase()`;
|
|
1056
|
+
case "string_to_lower": return `${this.wrapIfNeeded(f.get("value"))}.toLowerCase()`;
|
|
1057
|
+
case "string_trim": return `${this.wrapIfNeeded(f.get("value"))}.trim()`;
|
|
1058
|
+
case "string_trim_start": return `${this.wrapIfNeeded(f.get("value"))}.trimStart()`;
|
|
1059
|
+
case "string_trim_end": return `${this.wrapIfNeeded(f.get("value"))}.trimEnd()`;
|
|
1060
|
+
case "string_contains": return `${this.expr(f.get("left"))}.includes(${this.expr(f.get("right"))})`;
|
|
1061
|
+
case "string_starts_with": return `${this.expr(f.get("left"))}.startsWith(${this.expr(f.get("right"))})`;
|
|
1062
|
+
case "string_ends_with": return `${this.expr(f.get("left"))}.endsWith(${this.expr(f.get("right"))})`;
|
|
1063
|
+
case "string_is_empty": return `(${this.expr(f.get("value"))}.length === 0)`;
|
|
1064
|
+
case "string_split": return `${this.expr(f.get("value"))}.split(${this.expr(f.get("separator"))})`;
|
|
1065
|
+
case "string_substring": {
|
|
1066
|
+
const v = this.expr(f.get("value"));
|
|
1067
|
+
const s = this.expr(f.get("start"));
|
|
1068
|
+
const end = f.get("end");
|
|
1069
|
+
return end ? `${v}.substring(${s}, ${this.expr(end)})` : `${v}.substring(${s})`;
|
|
1070
|
+
}
|
|
1071
|
+
case "string_interpolation": {
|
|
1072
|
+
const parts = f.get("parts");
|
|
1073
|
+
if (parts?.literal?.listValue) {
|
|
1074
|
+
const pieces = (parts.literal.listValue.elements ?? [])
|
|
1075
|
+
.map((p) => `(${this.expr(p)})`)
|
|
1076
|
+
.join(" + ");
|
|
1077
|
+
return `(${pieces})`;
|
|
1078
|
+
}
|
|
1079
|
+
return `''`;
|
|
1080
|
+
}
|
|
1081
|
+
// Math
|
|
1082
|
+
case "math_abs": return `Math.abs(${this.expr(f.get("value"))})`;
|
|
1083
|
+
case "math_round": return `Math.round(${this.expr(f.get("value"))})`;
|
|
1084
|
+
case "math_floor": return `Math.floor(${this.expr(f.get("value"))})`;
|
|
1085
|
+
case "math_ceil": return `Math.ceil(${this.expr(f.get("value"))})`;
|
|
1086
|
+
case "math_trunc": return `Math.trunc(${this.expr(f.get("value"))})`;
|
|
1087
|
+
case "math_sqrt": return `Math.sqrt(${this.expr(f.get("value"))})`;
|
|
1088
|
+
case "math_pow": return `Math.pow(${this.expr(f.get("left"))}, ${this.expr(f.get("right"))})`;
|
|
1089
|
+
case "math_min": return `Math.min(${this.expr(f.get("left"))}, ${this.expr(f.get("right"))})`;
|
|
1090
|
+
case "math_max": return `Math.max(${this.expr(f.get("left"))}, ${this.expr(f.get("right"))})`;
|
|
1091
|
+
case "math_pi": return "Math.PI";
|
|
1092
|
+
case "math_e": return "Math.E";
|
|
1093
|
+
case "print": return `console.log(__ball_to_string(${this.expr(f.get("message"))}))`;
|
|
1094
|
+
case "index": return `${this.expr(f.get("target"))}[${this.expr(f.get("index"))}]`;
|
|
1095
|
+
case "null_coalesce": return `(${this.expr(f.get("left"))} ?? ${this.expr(f.get("right"))})`;
|
|
1096
|
+
case "null_check": return this.expr(f.get("value"));
|
|
1097
|
+
case "is": {
|
|
1098
|
+
const val = f.get("value");
|
|
1099
|
+
const typ = f.get("type");
|
|
1100
|
+
if (val && typ) {
|
|
1101
|
+
const v = this.expr(val);
|
|
1102
|
+
const t = typ.literal?.stringValue ?? "";
|
|
1103
|
+
return this.emitIsCheck(v, t);
|
|
1104
|
+
}
|
|
1105
|
+
return `(${this.expr(f.get("value"))} != null)`;
|
|
1106
|
+
}
|
|
1107
|
+
case "is_not": {
|
|
1108
|
+
const val = f.get("value");
|
|
1109
|
+
const typ = f.get("type");
|
|
1110
|
+
if (val && typ) {
|
|
1111
|
+
const v = this.expr(val);
|
|
1112
|
+
const t = typ.literal?.stringValue ?? "";
|
|
1113
|
+
return `!(${this.emitIsCheck(v, t)})`;
|
|
1114
|
+
}
|
|
1115
|
+
return `(${this.expr(f.get("value"))} == null)`;
|
|
1116
|
+
}
|
|
1117
|
+
case "as": return this.expr(f.get("value"));
|
|
1118
|
+
case "if": {
|
|
1119
|
+
const cond = this.expr(f.get("condition"));
|
|
1120
|
+
const t = this.expr(f.get("then"));
|
|
1121
|
+
const elseE = f.get("else");
|
|
1122
|
+
const e = elseE ? this.expr(elseE) : "undefined";
|
|
1123
|
+
return `(${cond} ? ${t} : ${e})`;
|
|
1124
|
+
}
|
|
1125
|
+
case "paren": return `(${this.expr(f.get("value"))})`;
|
|
1126
|
+
case "assert": return `console.assert(${this.expr(f.get("condition"))})`;
|
|
1127
|
+
case "assign": {
|
|
1128
|
+
const op = stringField(call, "op") || "=";
|
|
1129
|
+
return `(${this.expr(f.get("target"))} ${op} ${this.expr(f.get("value"))})`;
|
|
1130
|
+
}
|
|
1131
|
+
case "pre_increment": return `(++${this.expr(f.get("value"))})`;
|
|
1132
|
+
case "pre_decrement": return `(--${this.expr(f.get("value"))})`;
|
|
1133
|
+
case "post_increment": return `(${this.expr(f.get("value"))}++)`;
|
|
1134
|
+
case "post_decrement": return `(${this.expr(f.get("value"))}--)`;
|
|
1135
|
+
case "throw": {
|
|
1136
|
+
const v = f.get("value");
|
|
1137
|
+
if (!v)
|
|
1138
|
+
return "(() => { throw null; })()";
|
|
1139
|
+
const str = this.compileThrowValue(v) ?? this.expr(v);
|
|
1140
|
+
return `(() => { throw ${str}; })()`;
|
|
1141
|
+
}
|
|
1142
|
+
case "rethrow": return "(() => { throw __ball_active_error; })()";
|
|
1143
|
+
case "await": return `await ${this.expr(f.get("value"))}`;
|
|
1144
|
+
case "switch":
|
|
1145
|
+
case "switch_expr": return this.compileSwitchExpr(call);
|
|
1146
|
+
case "return": {
|
|
1147
|
+
// In expression position: unwrap to the bare value. When this
|
|
1148
|
+
// appears inside a switch-expr case body, the switch handler
|
|
1149
|
+
// detects the return and emits `return <ternary>` at statement
|
|
1150
|
+
// level.
|
|
1151
|
+
const v = f.get("value");
|
|
1152
|
+
return v ? this.expr(v) : "undefined";
|
|
1153
|
+
}
|
|
1154
|
+
case "null_aware_call": {
|
|
1155
|
+
const target = f.get("target");
|
|
1156
|
+
const method = f.get("method");
|
|
1157
|
+
if (!target || !method)
|
|
1158
|
+
return "/* null_aware_call missing */";
|
|
1159
|
+
const methodName = method.literal?.stringValue ?? "";
|
|
1160
|
+
const inputFields = call.input?.messageCreation?.fields ?? [];
|
|
1161
|
+
const otherArgs = inputFields
|
|
1162
|
+
.filter((fd) => fd.name !== "target" && fd.name !== "method" && fd.name !== "__type_args__")
|
|
1163
|
+
.map((fd) => this.expr(fd.value))
|
|
1164
|
+
.join(", ");
|
|
1165
|
+
return `${this.expr(target)}?.${methodName}(${otherArgs})`;
|
|
1166
|
+
}
|
|
1167
|
+
case "null_aware_index": {
|
|
1168
|
+
const self_ = f.get("self") ?? f.get("target");
|
|
1169
|
+
const idx = f.get("index") ?? f.get("key");
|
|
1170
|
+
if (!self_ || !idx)
|
|
1171
|
+
return "/* null_aware_index missing */";
|
|
1172
|
+
return `${this.expr(self_)}[${this.expr(idx)}]`;
|
|
1173
|
+
}
|
|
1174
|
+
case "null_aware_access": {
|
|
1175
|
+
const target = f.get("target");
|
|
1176
|
+
const fieldE = f.get("field");
|
|
1177
|
+
if (!target || !fieldE)
|
|
1178
|
+
return "/* null_aware_access missing */";
|
|
1179
|
+
return `${this.expr(target)}?.${fieldE.literal?.stringValue ?? ""}`;
|
|
1180
|
+
}
|
|
1181
|
+
case "typed_list": {
|
|
1182
|
+
const elements = f.get("elements");
|
|
1183
|
+
if (elements?.literal?.listValue) {
|
|
1184
|
+
const parts = (elements.literal.listValue.elements ?? []).map((x) => this.expr(x));
|
|
1185
|
+
return `[${parts.join(", ")}]`;
|
|
1186
|
+
}
|
|
1187
|
+
if (elements)
|
|
1188
|
+
return this.expr(elements);
|
|
1189
|
+
return "[]";
|
|
1190
|
+
}
|
|
1191
|
+
case "typed_map": {
|
|
1192
|
+
const entries = f.get("entries");
|
|
1193
|
+
if (!entries)
|
|
1194
|
+
return "new Map()";
|
|
1195
|
+
const entryExprs = entries.literal?.listValue?.elements ?? [];
|
|
1196
|
+
if (entryExprs.length === 0)
|
|
1197
|
+
return "new Map()";
|
|
1198
|
+
const pairs = [];
|
|
1199
|
+
for (const e of entryExprs) {
|
|
1200
|
+
if (e.messageCreation) {
|
|
1201
|
+
const mc = e.messageCreation;
|
|
1202
|
+
const mFields = mc.fields ?? [];
|
|
1203
|
+
const k = mFields.find((fd) => fd.name === "key")?.value;
|
|
1204
|
+
const v = mFields.find((fd) => fd.name === "value")?.value;
|
|
1205
|
+
if (k && v)
|
|
1206
|
+
pairs.push(`[${this.expr(k)}, ${this.expr(v)}]`);
|
|
1207
|
+
}
|
|
1208
|
+
}
|
|
1209
|
+
return `new Map([${pairs.join(", ")}])`;
|
|
1210
|
+
}
|
|
1211
|
+
case "set_create": {
|
|
1212
|
+
const elements = [];
|
|
1213
|
+
const inputFields = call.input?.messageCreation?.fields ?? [];
|
|
1214
|
+
for (const fd of inputFields) {
|
|
1215
|
+
elements.push(this.expr(fd.value));
|
|
1216
|
+
}
|
|
1217
|
+
if (elements.length === 0)
|
|
1218
|
+
return "new Set()";
|
|
1219
|
+
return `new Set([${elements.join(", ")}])`;
|
|
1220
|
+
}
|
|
1221
|
+
case "map_create": {
|
|
1222
|
+
// map_create can have entries passed as `entry` fields on the
|
|
1223
|
+
// input MessageCreation. Each entry is a message with key/value.
|
|
1224
|
+
// Emit as plain object {} (not new Map()) — JS Map doesn't
|
|
1225
|
+
// support bracket access (m['k'] = v) but the compiled engine
|
|
1226
|
+
// uses it throughout.
|
|
1227
|
+
const mapEntries = [];
|
|
1228
|
+
const inputFields = call.input?.messageCreation?.fields ?? [];
|
|
1229
|
+
for (const fd of inputFields) {
|
|
1230
|
+
if (fd.name === "entry" && fd.value.messageCreation) {
|
|
1231
|
+
const mc = fd.value.messageCreation;
|
|
1232
|
+
const mFields = mc.fields ?? [];
|
|
1233
|
+
const kf = mFields.find((f) => f.name === "key");
|
|
1234
|
+
const vf = mFields.find((f) => f.name === "value");
|
|
1235
|
+
if (kf && vf) {
|
|
1236
|
+
mapEntries.push(`${this.expr(kf.value)}: ${this.expr(vf.value)}`);
|
|
1237
|
+
}
|
|
1238
|
+
}
|
|
1239
|
+
}
|
|
1240
|
+
if (mapEntries.length === 0)
|
|
1241
|
+
return "{}";
|
|
1242
|
+
return `{${mapEntries.join(", ")}}`;
|
|
1243
|
+
}
|
|
1244
|
+
case "record": {
|
|
1245
|
+
const positional = [];
|
|
1246
|
+
const named = [];
|
|
1247
|
+
const posRe = /^(?:\$|arg)(\d+)$/;
|
|
1248
|
+
for (const fd of call.input?.messageCreation?.fields ?? []) {
|
|
1249
|
+
if (fd.name === "__type_args__")
|
|
1250
|
+
continue;
|
|
1251
|
+
if (posRe.test(fd.name)) {
|
|
1252
|
+
positional.push(this.expr(fd.value));
|
|
1253
|
+
}
|
|
1254
|
+
else {
|
|
1255
|
+
named.push([fd.name, this.expr(fd.value)]);
|
|
1256
|
+
}
|
|
1257
|
+
}
|
|
1258
|
+
if (named.length === 0)
|
|
1259
|
+
return `[${positional.join(", ")}]`;
|
|
1260
|
+
if (positional.length === 0) {
|
|
1261
|
+
return `{ ${named.map(([k, v]) => `${k}: ${v}`).join(", ")} }`;
|
|
1262
|
+
}
|
|
1263
|
+
const entries = [
|
|
1264
|
+
...positional.map((v, i) => `"${i}": ${v}`),
|
|
1265
|
+
...named.map(([k, v]) => `${k}: ${v}`),
|
|
1266
|
+
];
|
|
1267
|
+
return `{ ${entries.join(", ")} }`;
|
|
1268
|
+
}
|
|
1269
|
+
case "yield": return `yield ${this.expr(f.get("value"))}`;
|
|
1270
|
+
case "yield_each": return `yield* ${this.expr(f.get("value"))}`;
|
|
1271
|
+
default: {
|
|
1272
|
+
const args = Array.from(f.values()).map((e) => this.expr(e)).join(", ");
|
|
1273
|
+
return `/* std.${fn} */ ${sanitize(fn)}(${args})`;
|
|
1274
|
+
}
|
|
1275
|
+
}
|
|
1276
|
+
}
|
|
1277
|
+
emitIsCheck(value, type) {
|
|
1278
|
+
// Strip generic args: Map<String, Object?> → Map
|
|
1279
|
+
const baseType = type.includes("<") ? type.slice(0, type.indexOf("<")).trim() : type.trim();
|
|
1280
|
+
// Strip nullable: Map? → Map
|
|
1281
|
+
const t = baseType.endsWith("?") ? baseType.slice(0, -1) : baseType;
|
|
1282
|
+
switch (t) {
|
|
1283
|
+
case "int": return `(typeof ${value} === 'number' && Number.isInteger(${value}))`;
|
|
1284
|
+
case "double":
|
|
1285
|
+
case "num":
|
|
1286
|
+
case "number": return `(typeof ${value} === 'number')`;
|
|
1287
|
+
case "String":
|
|
1288
|
+
case "string": return `(typeof ${value} === 'string')`;
|
|
1289
|
+
case "bool":
|
|
1290
|
+
case "boolean": return `(typeof ${value} === 'boolean')`;
|
|
1291
|
+
case "List":
|
|
1292
|
+
case "Iterable": return `Array.isArray(${value})`;
|
|
1293
|
+
case "Map": return `(typeof ${value} === 'object' && ${value} !== null && !Array.isArray(${value}))`;
|
|
1294
|
+
case "Set": return `(${value} instanceof Set)`;
|
|
1295
|
+
case "Null": return `(${value} == null)`;
|
|
1296
|
+
case "Function": return `(typeof ${value} === 'function')`;
|
|
1297
|
+
default:
|
|
1298
|
+
if (this.typeIsUserDefinedClass(`main:${t}`) || this.typeIsUserDefinedClass(t)) {
|
|
1299
|
+
return `(${value} instanceof ${classTsName(t)})`;
|
|
1300
|
+
}
|
|
1301
|
+
return `(${value} != null)`;
|
|
1302
|
+
}
|
|
1303
|
+
}
|
|
1304
|
+
wrapIfNeeded(e) {
|
|
1305
|
+
const s = this.expr(e);
|
|
1306
|
+
if (s === "")
|
|
1307
|
+
return s;
|
|
1308
|
+
const first = s.charCodeAt(0);
|
|
1309
|
+
if (first === 0x21 /* ! */ || first === 0x7e /* ~ */ || first === 0x2d /* - */) {
|
|
1310
|
+
return `(${s})`;
|
|
1311
|
+
}
|
|
1312
|
+
return s;
|
|
1313
|
+
}
|
|
1314
|
+
compileSwitchExpr(call) {
|
|
1315
|
+
const subjectExpr = field(call, "subject");
|
|
1316
|
+
const casesField = field(call, "cases");
|
|
1317
|
+
if (!subjectExpr || !casesField)
|
|
1318
|
+
return "/* malformed switch */ undefined";
|
|
1319
|
+
const subjectStr = this.expr(subjectExpr);
|
|
1320
|
+
const caseExprs = casesField.literal?.listValue?.elements ?? [];
|
|
1321
|
+
let defaultBody;
|
|
1322
|
+
const branches = [];
|
|
1323
|
+
for (const ce of caseExprs) {
|
|
1324
|
+
if (!ce.messageCreation)
|
|
1325
|
+
continue;
|
|
1326
|
+
let pattern;
|
|
1327
|
+
let body;
|
|
1328
|
+
for (const fd of ce.messageCreation.fields ?? []) {
|
|
1329
|
+
if (fd.name === "pattern")
|
|
1330
|
+
pattern = fd.value;
|
|
1331
|
+
if (fd.name === "body")
|
|
1332
|
+
body = fd.value;
|
|
1333
|
+
}
|
|
1334
|
+
if (!body)
|
|
1335
|
+
continue;
|
|
1336
|
+
if (!pattern) {
|
|
1337
|
+
defaultBody = body;
|
|
1338
|
+
continue;
|
|
1339
|
+
}
|
|
1340
|
+
const patText = patternLiteralText(pattern);
|
|
1341
|
+
if (patText === undefined) {
|
|
1342
|
+
branches.push({
|
|
1343
|
+
cond: `((${subjectStr}) === ${this.expr(pattern)})`,
|
|
1344
|
+
body: this.expr(body),
|
|
1345
|
+
});
|
|
1346
|
+
continue;
|
|
1347
|
+
}
|
|
1348
|
+
const cond = patternToTsCondition(patText, subjectStr);
|
|
1349
|
+
if (cond === "true") {
|
|
1350
|
+
defaultBody = body;
|
|
1351
|
+
break;
|
|
1352
|
+
}
|
|
1353
|
+
branches.push({ cond, body: this.expr(body) });
|
|
1354
|
+
}
|
|
1355
|
+
const tail = defaultBody ? this.expr(defaultBody) : "undefined";
|
|
1356
|
+
if (branches.length === 0)
|
|
1357
|
+
return tail;
|
|
1358
|
+
let result = tail;
|
|
1359
|
+
for (const { cond, body } of [...branches].reverse()) {
|
|
1360
|
+
result = `(${cond} ? (${body}) : ${result})`;
|
|
1361
|
+
}
|
|
1362
|
+
return result;
|
|
1363
|
+
}
|
|
1364
|
+
compileThrowValue(v) {
|
|
1365
|
+
if (!v.messageCreation)
|
|
1366
|
+
return undefined;
|
|
1367
|
+
if (v.messageCreation.typeName)
|
|
1368
|
+
return undefined;
|
|
1369
|
+
const entries = (v.messageCreation.fields ?? [])
|
|
1370
|
+
.map((fd) => `'${fd.name}': ${this.expr(fd.value)}`)
|
|
1371
|
+
.join(", ");
|
|
1372
|
+
return `{${entries}}`;
|
|
1373
|
+
}
|
|
1374
|
+
// ───────────────────────── Helpers ─────────────────────────────────
|
|
1375
|
+
enclosingTypeName(fnName) {
|
|
1376
|
+
let best;
|
|
1377
|
+
for (const name of this.typeDefByName.keys()) {
|
|
1378
|
+
if (fnName.startsWith(`${name}.`) && (best === undefined || name.length > best.length)) {
|
|
1379
|
+
best = name;
|
|
1380
|
+
}
|
|
1381
|
+
}
|
|
1382
|
+
return best;
|
|
1383
|
+
}
|
|
1384
|
+
dartTypeToTs(dart) {
|
|
1385
|
+
const t = dart.trim();
|
|
1386
|
+
if (t === "")
|
|
1387
|
+
return "any";
|
|
1388
|
+
if (t.startsWith("("))
|
|
1389
|
+
return "any";
|
|
1390
|
+
if (t.includes(" Function("))
|
|
1391
|
+
return "any";
|
|
1392
|
+
const nonNull = t.endsWith("?") ? t.slice(0, -1) : t;
|
|
1393
|
+
const lt = nonNull.indexOf("<");
|
|
1394
|
+
if (lt > 0 && nonNull.endsWith(">")) {
|
|
1395
|
+
const outer = nonNull.slice(0, lt);
|
|
1396
|
+
const inner = nonNull.slice(lt + 1, -1);
|
|
1397
|
+
const innerArgs = splitTopLevelCommas(inner).map((x) => this.dartTypeToTs(x));
|
|
1398
|
+
switch (outer) {
|
|
1399
|
+
case "List":
|
|
1400
|
+
case "Iterable":
|
|
1401
|
+
case "Set":
|
|
1402
|
+
return `Array<${innerArgs.join(", ")}>`;
|
|
1403
|
+
case "Map":
|
|
1404
|
+
return `Map<${innerArgs.join(", ")}>`;
|
|
1405
|
+
case "Future":
|
|
1406
|
+
return innerArgs.length === 0 ? "Promise<any>" : `Promise<${innerArgs.join(", ")}>`;
|
|
1407
|
+
case "FutureOr": {
|
|
1408
|
+
const a = innerArgs.length === 0 ? "any" : innerArgs[0];
|
|
1409
|
+
return `${a} | Promise<${a}>`;
|
|
1410
|
+
}
|
|
1411
|
+
default:
|
|
1412
|
+
return `${this.dartTypeToTs(outer)}<${innerArgs.join(", ")}>`;
|
|
1413
|
+
}
|
|
1414
|
+
}
|
|
1415
|
+
switch (nonNull) {
|
|
1416
|
+
case "int":
|
|
1417
|
+
case "double":
|
|
1418
|
+
case "num":
|
|
1419
|
+
return "number";
|
|
1420
|
+
case "bool":
|
|
1421
|
+
return "boolean";
|
|
1422
|
+
case "String":
|
|
1423
|
+
return "string";
|
|
1424
|
+
case "void":
|
|
1425
|
+
return "void";
|
|
1426
|
+
case "dynamic":
|
|
1427
|
+
case "Object":
|
|
1428
|
+
return "any";
|
|
1429
|
+
}
|
|
1430
|
+
if (nonNull.startsWith("main:"))
|
|
1431
|
+
return nonNull.slice(5);
|
|
1432
|
+
return nonNull;
|
|
1433
|
+
}
|
|
1434
|
+
}
|
|
1435
|
+
// ───────────────────────── Free helpers ───────────────────────────────
|
|
1436
|
+
function extractParams(fn) {
|
|
1437
|
+
const params = fn.metadata?.["params"];
|
|
1438
|
+
if (!Array.isArray(params))
|
|
1439
|
+
return [];
|
|
1440
|
+
const out = [];
|
|
1441
|
+
for (const p of params) {
|
|
1442
|
+
if (p && typeof p === "object" && "name" in p && typeof p.name === "string") {
|
|
1443
|
+
out.push(p.name);
|
|
1444
|
+
}
|
|
1445
|
+
}
|
|
1446
|
+
return out;
|
|
1447
|
+
}
|
|
1448
|
+
function extractCtorParams(meta) {
|
|
1449
|
+
const raw = meta["params"];
|
|
1450
|
+
if (!Array.isArray(raw))
|
|
1451
|
+
return [];
|
|
1452
|
+
const out = [];
|
|
1453
|
+
for (const p of raw) {
|
|
1454
|
+
if (p && typeof p === "object" && "name" in p && typeof p.name === "string") {
|
|
1455
|
+
out.push({
|
|
1456
|
+
name: p.name,
|
|
1457
|
+
isThis: p.is_this === true,
|
|
1458
|
+
isNamed: p.is_named === true,
|
|
1459
|
+
});
|
|
1460
|
+
}
|
|
1461
|
+
}
|
|
1462
|
+
return out;
|
|
1463
|
+
}
|
|
1464
|
+
function functionIsAsync(fn) {
|
|
1465
|
+
return fn.metadata?.["is_async"] === true;
|
|
1466
|
+
}
|
|
1467
|
+
function field(call, name) {
|
|
1468
|
+
const fields = call.input?.messageCreation?.fields;
|
|
1469
|
+
if (!fields)
|
|
1470
|
+
return undefined;
|
|
1471
|
+
for (const f of fields)
|
|
1472
|
+
if (f.name === name)
|
|
1473
|
+
return f.value;
|
|
1474
|
+
return undefined;
|
|
1475
|
+
}
|
|
1476
|
+
function stringField(call, name) {
|
|
1477
|
+
const e = field(call, name);
|
|
1478
|
+
return e?.literal?.stringValue;
|
|
1479
|
+
}
|
|
1480
|
+
function stringFieldVal(m, name) {
|
|
1481
|
+
return m.get(name)?.literal?.stringValue;
|
|
1482
|
+
}
|
|
1483
|
+
function fieldMap(fields) {
|
|
1484
|
+
const m = new Map();
|
|
1485
|
+
for (const f of fields)
|
|
1486
|
+
m.set(f.name, f.value);
|
|
1487
|
+
return m;
|
|
1488
|
+
}
|
|
1489
|
+
function memberShortName(qualified) {
|
|
1490
|
+
const dot = qualified.lastIndexOf(".");
|
|
1491
|
+
return sanitize(dot < 0 ? qualified : qualified.slice(dot + 1));
|
|
1492
|
+
}
|
|
1493
|
+
function classTsName(qualified) {
|
|
1494
|
+
const colon = qualified.lastIndexOf(":");
|
|
1495
|
+
return colon < 0 ? qualified : qualified.slice(colon + 1);
|
|
1496
|
+
}
|
|
1497
|
+
function isStd(module) {
|
|
1498
|
+
return module === "std" || module === "dart_std";
|
|
1499
|
+
}
|
|
1500
|
+
function containsBareKeyword(text, kw) {
|
|
1501
|
+
return new RegExp(`(^|[^A-Za-z0-9_$])${kw}([^A-Za-z0-9_$]|$)`).test(text);
|
|
1502
|
+
}
|
|
1503
|
+
function translateInitString(raw) {
|
|
1504
|
+
for (const kw of ["var", "final", "int", "double", "String", "bool", "num"]) {
|
|
1505
|
+
if (raw.length > kw.length + 1 && raw.slice(0, kw.length) === kw && raw[kw.length] === " ") {
|
|
1506
|
+
return `let ${raw.slice(kw.length + 1)}`;
|
|
1507
|
+
}
|
|
1508
|
+
}
|
|
1509
|
+
return `let ${raw}`;
|
|
1510
|
+
}
|
|
1511
|
+
function splitTopLevelCommas(s) {
|
|
1512
|
+
const out = [];
|
|
1513
|
+
let depth = 0;
|
|
1514
|
+
let start = 0;
|
|
1515
|
+
for (let i = 0; i < s.length; i++) {
|
|
1516
|
+
const c = s.charCodeAt(i);
|
|
1517
|
+
if (c === 0x3c /* < */ || c === 0x28 /* ( */ || c === 0x5b /* [ */)
|
|
1518
|
+
depth++;
|
|
1519
|
+
else if (c === 0x3e /* > */ || c === 0x29 /* ) */ || c === 0x5d /* ] */)
|
|
1520
|
+
depth--;
|
|
1521
|
+
else if (c === 0x2c /* , */ && depth === 0) {
|
|
1522
|
+
out.push(s.slice(start, i).trim());
|
|
1523
|
+
start = i + 1;
|
|
1524
|
+
}
|
|
1525
|
+
}
|
|
1526
|
+
if (start < s.length)
|
|
1527
|
+
out.push(s.slice(start).trim());
|
|
1528
|
+
return out;
|
|
1529
|
+
}
|
|
1530
|
+
function splitTopLevel(text, delim) {
|
|
1531
|
+
const out = [];
|
|
1532
|
+
let depth = 0;
|
|
1533
|
+
let quote = 0;
|
|
1534
|
+
let start = 0;
|
|
1535
|
+
for (let i = 0; i <= text.length - delim.length; i++) {
|
|
1536
|
+
const c = text.charCodeAt(i);
|
|
1537
|
+
if (quote === 0) {
|
|
1538
|
+
if (c === 0x28 || c === 0x5b || c === 0x3c)
|
|
1539
|
+
depth++;
|
|
1540
|
+
else if (c === 0x29 || c === 0x5d || c === 0x3e)
|
|
1541
|
+
depth--;
|
|
1542
|
+
else if (c === 0x27 || c === 0x22)
|
|
1543
|
+
quote = c;
|
|
1544
|
+
else if (depth === 0 && text.slice(i, i + delim.length) === delim) {
|
|
1545
|
+
out.push(text.slice(start, i).trim());
|
|
1546
|
+
start = i + delim.length;
|
|
1547
|
+
i += delim.length - 1;
|
|
1548
|
+
}
|
|
1549
|
+
}
|
|
1550
|
+
else if (c === quote) {
|
|
1551
|
+
quote = 0;
|
|
1552
|
+
}
|
|
1553
|
+
}
|
|
1554
|
+
out.push(text.slice(start).trim());
|
|
1555
|
+
return out;
|
|
1556
|
+
}
|
|
1557
|
+
function patternLiteralText(pat) {
|
|
1558
|
+
return pat.literal?.stringValue;
|
|
1559
|
+
}
|
|
1560
|
+
function patternToTsCondition(pat, subject) {
|
|
1561
|
+
const trimmed = pat.trim();
|
|
1562
|
+
if (trimmed === "")
|
|
1563
|
+
return "true";
|
|
1564
|
+
if (trimmed === "_")
|
|
1565
|
+
return "true";
|
|
1566
|
+
const whenMatch = /^_\s+when\s+(.+)$/.exec(trimmed);
|
|
1567
|
+
if (whenMatch)
|
|
1568
|
+
return `(${whenMatch[1]})`;
|
|
1569
|
+
if (trimmed.includes("||")) {
|
|
1570
|
+
const parts = splitTopLevel(trimmed, "||");
|
|
1571
|
+
if (parts.length > 1) {
|
|
1572
|
+
return `(${parts.map((p) => patternToTsCondition(p, subject)).join(" || ")})`;
|
|
1573
|
+
}
|
|
1574
|
+
}
|
|
1575
|
+
if (/^-?\d+(\.\d+)?$/.test(trimmed))
|
|
1576
|
+
return `(${subject} === ${trimmed})`;
|
|
1577
|
+
if (trimmed === "true" || trimmed === "false" || trimmed === "null") {
|
|
1578
|
+
return `(${subject} === ${trimmed})`;
|
|
1579
|
+
}
|
|
1580
|
+
if ((trimmed.startsWith("'") && trimmed.endsWith("'")) ||
|
|
1581
|
+
(trimmed.startsWith('"') && trimmed.endsWith('"'))) {
|
|
1582
|
+
const inner = trimmed.slice(1, -1);
|
|
1583
|
+
return `(${subject} === ${jsStringLiteral(inner)})`;
|
|
1584
|
+
}
|
|
1585
|
+
return `(${subject} === (${trimmed}))`;
|
|
1586
|
+
}
|
|
1587
|
+
function jsStringLiteral(s) {
|
|
1588
|
+
let out = "'";
|
|
1589
|
+
for (let i = 0; i < s.length; i++) {
|
|
1590
|
+
const cu = s.charCodeAt(i);
|
|
1591
|
+
if (cu === 0x27)
|
|
1592
|
+
out += "\\'";
|
|
1593
|
+
else if (cu === 0x5c)
|
|
1594
|
+
out += "\\\\";
|
|
1595
|
+
else if (cu === 0x0a)
|
|
1596
|
+
out += "\\n";
|
|
1597
|
+
else if (cu === 0x0d)
|
|
1598
|
+
out += "\\r";
|
|
1599
|
+
else if (cu === 0x09)
|
|
1600
|
+
out += "\\t";
|
|
1601
|
+
else if (cu >= 0x20 && cu < 0x7f)
|
|
1602
|
+
out += s[i];
|
|
1603
|
+
else
|
|
1604
|
+
out += "\\u" + cu.toString(16).padStart(4, "0");
|
|
1605
|
+
}
|
|
1606
|
+
return out + "'";
|
|
1607
|
+
}
|
|
1608
|
+
/** Return a default initializer expression for a TS type string so
|
|
1609
|
+
* class fields don't start as `undefined`. Dart fields are implicitly
|
|
1610
|
+
* initialized (Map→{}, List→[], bool→false, etc.); TS fields are not.
|
|
1611
|
+
*
|
|
1612
|
+
* Also accepts the raw Dart type string for cases where the TS mapper
|
|
1613
|
+
* lost precision (e.g. `Map<K, V Function(X)>` → `any` because the
|
|
1614
|
+
* function type triggered the early-return). Checking the Dart type
|
|
1615
|
+
* catches these.
|
|
1616
|
+
*/
|
|
1617
|
+
function defaultInitializer(type, rawDartType) {
|
|
1618
|
+
const t = type.trim();
|
|
1619
|
+
const raw = (rawDartType ?? "").trim();
|
|
1620
|
+
// Nullable types (ending in ?) default to null, not the type's
|
|
1621
|
+
// default value. Dart's `Set<String>? _allowlist = null` must stay
|
|
1622
|
+
// null, not become `new Set()` which breaks null-guard patterns.
|
|
1623
|
+
// Nullable types default to null EXCEPT Maps — null-aware access
|
|
1624
|
+
// (?.[] / ?.) isn't in the compiled output and null Maps crash
|
|
1625
|
+
// on bracket access. Empty {} is safe. Sets and Lists stay null
|
|
1626
|
+
// because code like `if (allowlist != null)` uses null to mean
|
|
1627
|
+
// "not active" and an empty collection would incorrectly activate
|
|
1628
|
+
// the filter.
|
|
1629
|
+
if (raw.endsWith("?")) {
|
|
1630
|
+
const inner = raw.slice(0, -1).trim();
|
|
1631
|
+
if (inner.startsWith("Map<") || inner === "Map")
|
|
1632
|
+
return "{}";
|
|
1633
|
+
return "null";
|
|
1634
|
+
}
|
|
1635
|
+
const d = raw.replace(/\?$/, "");
|
|
1636
|
+
// Check both mapped TS type and raw Dart type.
|
|
1637
|
+
// Use plain {} instead of new Map() — JS Map doesn't support
|
|
1638
|
+
// bracket access (m['k'] = v), but the compiled engine uses it
|
|
1639
|
+
// throughout for dispatch tables and caches.
|
|
1640
|
+
if (t.startsWith("Map<") || d.startsWith("Map<") || d === "Map")
|
|
1641
|
+
return "{}";
|
|
1642
|
+
if (t.startsWith("Array<") || t === "Array" || d.startsWith("List<") || d === "List")
|
|
1643
|
+
return "[]";
|
|
1644
|
+
if (t.startsWith("Set<") || t === "Set" || d.startsWith("Set<") || d === "Set")
|
|
1645
|
+
return "new Set()";
|
|
1646
|
+
if (t === "number" || d === "int" || d === "double" || d === "num")
|
|
1647
|
+
return "0";
|
|
1648
|
+
if (t === "boolean" || d === "bool")
|
|
1649
|
+
return "false";
|
|
1650
|
+
if (t === "string" || d === "String")
|
|
1651
|
+
return "''";
|
|
1652
|
+
return undefined;
|
|
1653
|
+
}
|
|
1654
|
+
function sanitize(name) {
|
|
1655
|
+
let out = name;
|
|
1656
|
+
const colon = out.indexOf(":");
|
|
1657
|
+
if (colon >= 0)
|
|
1658
|
+
out = out.slice(colon + 1);
|
|
1659
|
+
out = out.replace(/[.-]/g, "_");
|
|
1660
|
+
const reserved = new Set([
|
|
1661
|
+
"class", "function", "return", "new", "delete", "var", "let", "const",
|
|
1662
|
+
"typeof", "instanceof", "interface", "enum", "export", "import", "yield",
|
|
1663
|
+
"package", "private", "protected", "public", "static", "super", "this",
|
|
1664
|
+
"true", "false", "null", "undefined",
|
|
1665
|
+
]);
|
|
1666
|
+
if (reserved.has(out))
|
|
1667
|
+
out += "_";
|
|
1668
|
+
return out;
|
|
1669
|
+
}
|
|
1670
|
+
/** Convenience: compile a Program directly. */
|
|
1671
|
+
export function compile(program, options) {
|
|
1672
|
+
return new BallCompiler(program).compile(options);
|
|
1673
|
+
}
|
|
1674
|
+
//# sourceMappingURL=compiler.js.map
|