gofront 1.3.5 → 1.3.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/cli-core.js +118 -13
- package/src/codegen/expressions.js +408 -89
- package/src/codegen/index.js +207 -25
- package/src/codegen/runtime.js +22 -1
- package/src/codegen/statements.js +170 -45
- package/src/codegen/stdlib/builder.js +7 -2
- package/src/codegen/stdlib/fmt.js +2 -2
- package/src/codegen/stdlib/io.js +1 -1
- package/src/dev-server.js +22 -10
- package/src/index.js +8 -2
- package/src/parser/declarations.js +2 -1
- package/src/pwa.js +3 -1
- package/src/typechecker/assignability.js +50 -4
- package/src/typechecker/expressions.js +13 -1
- package/src/typechecker/index.js +46 -2
- package/src/typechecker/resolve.js +2 -0
- package/src/typechecker/statements.js +10 -1
- package/src/typechecker/stdlib/web.js +54 -26
- package/src/typechecker/types.js +3 -2
- package/src/vendor.js +12 -7
package/src/codegen/index.js
CHANGED
|
@@ -26,9 +26,11 @@ import {
|
|
|
26
26
|
HELPER_EQUAL,
|
|
27
27
|
HELPER_ERROR,
|
|
28
28
|
HELPER_ERROR_IS,
|
|
29
|
+
HELPER_IFACE_BOX,
|
|
29
30
|
HELPER_LEN,
|
|
30
31
|
HELPER_PATH_CLEAN,
|
|
31
32
|
HELPER_S,
|
|
33
|
+
HELPER_SCLONE,
|
|
32
34
|
HELPER_SPRINTF,
|
|
33
35
|
HELPER_TESTING,
|
|
34
36
|
HELPER_TIME_FMT,
|
|
@@ -39,6 +41,46 @@ import { statementGenMethods } from "./statements.js";
|
|
|
39
41
|
import { stdlibGenMethods } from "./stdlib/index.js";
|
|
40
42
|
import { templGenMethods } from "./templ.js";
|
|
41
43
|
|
|
44
|
+
// Valid Go identifiers that cannot be used as JS bindings; emitted with a `$` suffix.
|
|
45
|
+
const JS_RESERVED = new Set([
|
|
46
|
+
"arguments",
|
|
47
|
+
"await",
|
|
48
|
+
"catch",
|
|
49
|
+
"class",
|
|
50
|
+
"debugger",
|
|
51
|
+
"delete",
|
|
52
|
+
"do",
|
|
53
|
+
"enum",
|
|
54
|
+
"eval",
|
|
55
|
+
"export",
|
|
56
|
+
"extends",
|
|
57
|
+
"finally",
|
|
58
|
+
"function",
|
|
59
|
+
"implements",
|
|
60
|
+
"in",
|
|
61
|
+
"instanceof",
|
|
62
|
+
"let",
|
|
63
|
+
"new",
|
|
64
|
+
"null",
|
|
65
|
+
"private",
|
|
66
|
+
"protected",
|
|
67
|
+
"public",
|
|
68
|
+
"static",
|
|
69
|
+
"super",
|
|
70
|
+
"this",
|
|
71
|
+
"throw",
|
|
72
|
+
"try",
|
|
73
|
+
"typeof",
|
|
74
|
+
"undefined",
|
|
75
|
+
"void",
|
|
76
|
+
"while",
|
|
77
|
+
"with",
|
|
78
|
+
"yield",
|
|
79
|
+
]);
|
|
80
|
+
|
|
81
|
+
const jsSafeName = (n) =>
|
|
82
|
+
typeof n === "string" && JS_RESERVED.has(n) ? `${n}$` : n;
|
|
83
|
+
|
|
42
84
|
export class CodeGen {
|
|
43
85
|
// jsImports: Map<importPath, string[]> — npm package imports to emit at top of file
|
|
44
86
|
// bundledPackages: Set<string> — GoFront package names bundled inline;
|
|
@@ -74,6 +116,10 @@ export class CodeGen {
|
|
|
74
116
|
this._usesTimeFmt = false;
|
|
75
117
|
this._usesTimeParse = false;
|
|
76
118
|
this._usesTesting = false;
|
|
119
|
+
this._usesSClone = false;
|
|
120
|
+
this._usesIfaceBox = false;
|
|
121
|
+
// Per-function context for Go value semantics (see _withFnCtx)
|
|
122
|
+
this._fnCtx = null;
|
|
77
123
|
// Iterator (range-over-func) context
|
|
78
124
|
this._inIteratorBody = false;
|
|
79
125
|
this._iterDepth = 0;
|
|
@@ -153,6 +199,7 @@ export class CodeGen {
|
|
|
153
199
|
|
|
154
200
|
generate(program, options = {}) {
|
|
155
201
|
const isTest = options.isTest ?? false;
|
|
202
|
+
this._renameReservedIdents(program.decls);
|
|
156
203
|
const methods = this._collectDecls(program);
|
|
157
204
|
this._emitJsImports();
|
|
158
205
|
this._emitTypeDecls(program, methods);
|
|
@@ -189,6 +236,82 @@ export class CodeGen {
|
|
|
189
236
|
return initNames;
|
|
190
237
|
}
|
|
191
238
|
|
|
239
|
+
_renameParams(params) {
|
|
240
|
+
for (const p of params ?? []) p.name = jsSafeName(p.name);
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
_renameNamedReturns(node) {
|
|
244
|
+
for (const r of node.returnType?._namedReturns ?? [])
|
|
245
|
+
r.name = jsSafeName(r.name);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
_renameSignature(node) {
|
|
249
|
+
this._renameParams(node.params);
|
|
250
|
+
this._renameNamedReturns(node);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
_renameIdentNode(node) {
|
|
254
|
+
if (!node._isStructKey && node._type?.kind !== "builtin")
|
|
255
|
+
node.name = jsSafeName(node.name);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
_renameFuncDeclNode(node) {
|
|
259
|
+
node.name = jsSafeName(node.name);
|
|
260
|
+
this._renameSignature(node);
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
_renameMethodDeclNode(node) {
|
|
264
|
+
node.recvName = jsSafeName(node.recvName);
|
|
265
|
+
this._renameSignature(node);
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
_renameDeclSpecs(node) {
|
|
269
|
+
for (const spec of node.decls ?? [])
|
|
270
|
+
spec.names = spec.names.map(jsSafeName);
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
_renameTypeSwitchNode(node) {
|
|
274
|
+
node.assign = jsSafeName(node.assign);
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
_markStructKeys(node) {
|
|
278
|
+
const isStruct =
|
|
279
|
+
this._isStructType(node._type) ||
|
|
280
|
+
node.elems?.some((e) => e._positionalField);
|
|
281
|
+
if (!isStruct) return;
|
|
282
|
+
for (const e of node.elems ?? [])
|
|
283
|
+
if (e.kind === "KeyValueExpr" && e.key?.kind === "Ident")
|
|
284
|
+
e.key._isStructKey = true;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
static _RENAME_HANDLERS = {
|
|
288
|
+
Ident: "_renameIdentNode",
|
|
289
|
+
FuncDecl: "_renameFuncDeclNode",
|
|
290
|
+
MethodDecl: "_renameMethodDeclNode",
|
|
291
|
+
FuncLit: "_renameSignature",
|
|
292
|
+
TemplDecl: "_renameSignature",
|
|
293
|
+
VarDecl: "_renameDeclSpecs",
|
|
294
|
+
ConstDecl: "_renameDeclSpecs",
|
|
295
|
+
TypeSwitchStmt: "_renameTypeSwitchNode",
|
|
296
|
+
CompositeLit: "_markStructKeys",
|
|
297
|
+
};
|
|
298
|
+
|
|
299
|
+
_renameReservedIdents(node) {
|
|
300
|
+
if (!node || typeof node !== "object") return;
|
|
301
|
+
if (Array.isArray(node)) {
|
|
302
|
+
for (const n of node) this._renameReservedIdents(n);
|
|
303
|
+
return;
|
|
304
|
+
}
|
|
305
|
+
if (node._jsRenamed) return;
|
|
306
|
+
node._jsRenamed = true;
|
|
307
|
+
const handler = CodeGen._RENAME_HANDLERS[node.kind];
|
|
308
|
+
if (handler) this[handler](node);
|
|
309
|
+
for (const key of Object.keys(node)) {
|
|
310
|
+
if (key.startsWith("_")) continue;
|
|
311
|
+
this._renameReservedIdents(node[key]);
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
|
|
192
315
|
// Collects struct names, method map, and named wrapper names from program decls.
|
|
193
316
|
// Returns the method map (typeName → MethodDecl[]).
|
|
194
317
|
_collectStructNames(program) {
|
|
@@ -236,6 +359,8 @@ export class CodeGen {
|
|
|
236
359
|
[this._usesLen, HELPER_LEN],
|
|
237
360
|
[this._usesAppend, HELPER_APPEND],
|
|
238
361
|
[this._usesSliceGuard, HELPER_S],
|
|
362
|
+
[this._usesSClone, HELPER_SCLONE],
|
|
363
|
+
[this._usesIfaceBox, HELPER_IFACE_BOX],
|
|
239
364
|
[this._usesEqual, HELPER_EQUAL],
|
|
240
365
|
[this._usesCmul, HELPER_CMUL],
|
|
241
366
|
[this._usesCdiv, HELPER_CDIV],
|
|
@@ -357,13 +482,15 @@ export class CodeGen {
|
|
|
357
482
|
this.line(`constructor(${params}) {`);
|
|
358
483
|
this.indented(() => {
|
|
359
484
|
const first = binding(fields[0]);
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
485
|
+
// Legacy `new T({...})` support; only unambiguous when field 0 is a primitive.
|
|
486
|
+
if (/^(0|""|false)$/.test(fields[0].zero)) {
|
|
487
|
+
const pattern = fields
|
|
488
|
+
.map((f) => `${f.name}: ${binding(f)} = ${f.zero}`)
|
|
489
|
+
.join(", ");
|
|
490
|
+
this.line(
|
|
491
|
+
`if (typeof ${first} === "object" && ${first} !== null && ${first}.constructor === Object) ({ ${pattern} } = ${first});`,
|
|
492
|
+
);
|
|
493
|
+
}
|
|
367
494
|
for (const f of fields) {
|
|
368
495
|
this.line(`this.${f.name} = ${binding(f)};`);
|
|
369
496
|
}
|
|
@@ -371,6 +498,30 @@ export class CodeGen {
|
|
|
371
498
|
this.line("}");
|
|
372
499
|
}
|
|
373
500
|
|
|
501
|
+
_genStructClone(name, structTypeAst, fields) {
|
|
502
|
+
const resolved = this.checker?.types.get(name)?.underlying;
|
|
503
|
+
const astTypes = new Map();
|
|
504
|
+
for (const f of structTypeAst?.fields ?? [])
|
|
505
|
+
for (const n of f.names ?? []) astTypes.set(n, f.type);
|
|
506
|
+
const args = fields.map((f) => {
|
|
507
|
+
const t =
|
|
508
|
+
resolved?.fields?.get(f.name) ??
|
|
509
|
+
this._typeFromNode(astTypes.get(f.name));
|
|
510
|
+
return this._cloneJs(t, `this.${f.name}`);
|
|
511
|
+
});
|
|
512
|
+
this.blank();
|
|
513
|
+
this.line(`__clone() { return new ${name}(${args.join(", ")}); }`);
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
_structDefinesValue(name, fields, methodDecls) {
|
|
517
|
+
if (fields.some((f) => f.name === "value")) return true;
|
|
518
|
+
if (methodDecls.some((m) => m.name === "value")) return true;
|
|
519
|
+
const resolved = this.checker?.types.get(name)?.underlying;
|
|
520
|
+
return Boolean(
|
|
521
|
+
resolved?.fields?.has?.("value") || resolved?.methods?.has?.("value"),
|
|
522
|
+
);
|
|
523
|
+
}
|
|
524
|
+
|
|
374
525
|
genStruct(name, structTypeAst, methodDecls) {
|
|
375
526
|
const fields = this._collectStructFields(name, structTypeAst);
|
|
376
527
|
this.structFields.set(name, fields);
|
|
@@ -382,11 +533,15 @@ export class CodeGen {
|
|
|
382
533
|
this.genMethod(m);
|
|
383
534
|
}
|
|
384
535
|
this._genEmbeddedMethodStubs(name, methodDecls);
|
|
536
|
+
this._genStructClone(name, structTypeAst, fields);
|
|
385
537
|
});
|
|
386
538
|
this.line("}");
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
539
|
+
// Lets generic `p.value` dereferences resolve to the struct itself.
|
|
540
|
+
if (!this._structDefinesValue(name, fields, methodDecls)) {
|
|
541
|
+
this.line(
|
|
542
|
+
`Object.defineProperty(${name}.prototype, "value", { get() { return this; }, set(v) { Object.assign(this, v); }, configurable: true });`,
|
|
543
|
+
);
|
|
544
|
+
}
|
|
390
545
|
}
|
|
391
546
|
|
|
392
547
|
_genSingleEmbedStubs(embed, declared) {
|
|
@@ -421,22 +576,40 @@ export class CodeGen {
|
|
|
421
576
|
this._boxedVars = new Set();
|
|
422
577
|
this._scanAddressTaken(decl.body);
|
|
423
578
|
const prevUnwrapped = this._unwrappedRecv;
|
|
424
|
-
this.
|
|
425
|
-
|
|
426
|
-
if (
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
579
|
+
this._withFnCtx(decl.body, () =>
|
|
580
|
+
this.indented(() => {
|
|
581
|
+
if (decl.recvName && decl.recvName !== "_") {
|
|
582
|
+
if (recvField) {
|
|
583
|
+
this.line(`const ${decl.recvName} = this.${recvField};`);
|
|
584
|
+
this._unwrappedRecv = decl.recvName;
|
|
585
|
+
} else {
|
|
586
|
+
this.line(`const ${decl.recvName} = ${this._receiverValue(decl)};`);
|
|
587
|
+
}
|
|
431
588
|
}
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
589
|
+
this._emitParamCopies(decl.params);
|
|
590
|
+
this._withNamedReturns(decl, () => this._genBody(decl.body));
|
|
591
|
+
}),
|
|
592
|
+
);
|
|
435
593
|
this._unwrappedRecv = prevUnwrapped;
|
|
436
594
|
this._boxedVars = prevBoxed;
|
|
437
595
|
this.line("}");
|
|
438
596
|
}
|
|
439
597
|
|
|
598
|
+
// Value receivers are copies in Go; only materialize the copy when the body mutates it.
|
|
599
|
+
_receiverValue(decl) {
|
|
600
|
+
const name = decl.recvName;
|
|
601
|
+
if (decl.recvPointer || !this.structNames.has(decl.recvType.name)) {
|
|
602
|
+
this._markOwnership(name, false);
|
|
603
|
+
return "this";
|
|
604
|
+
}
|
|
605
|
+
if (this._fnMutates(name)) {
|
|
606
|
+
this._markOwnership(name, true);
|
|
607
|
+
return "this.__clone()";
|
|
608
|
+
}
|
|
609
|
+
this._markOwnership(name, false);
|
|
610
|
+
return "this";
|
|
611
|
+
}
|
|
612
|
+
|
|
440
613
|
// Returns the wrapper field name ("_fn", "_items", "_map") if `type` is a named
|
|
441
614
|
// non-struct type emitted as a wrapper class, or null otherwise.
|
|
442
615
|
// Pass `expr` so we can skip unwrapping when the expression is the method receiver
|
|
@@ -470,8 +643,11 @@ export class CodeGen {
|
|
|
470
643
|
const prevBoxed = this._boxedVars;
|
|
471
644
|
this._boxedVars = new Set();
|
|
472
645
|
this._scanAddressTaken(decl.body);
|
|
473
|
-
this.
|
|
474
|
-
this.
|
|
646
|
+
this._withFnCtx(decl.body, () =>
|
|
647
|
+
this.indented(() => {
|
|
648
|
+
this._emitParamCopies(decl.params);
|
|
649
|
+
this._withNamedReturns(decl, () => this._genBody(decl.body));
|
|
650
|
+
}),
|
|
475
651
|
);
|
|
476
652
|
this._boxedVars = prevBoxed;
|
|
477
653
|
this.line("}");
|
|
@@ -483,8 +659,10 @@ export class CodeGen {
|
|
|
483
659
|
if (named) {
|
|
484
660
|
// Emit zero-value declarations for named return vars
|
|
485
661
|
for (const { name, type } of named) {
|
|
486
|
-
if (name)
|
|
662
|
+
if (name) {
|
|
663
|
+
this._markOwnership(name, true);
|
|
487
664
|
this.line(`let ${name} = ${this.zeroValueForTypeNode(type)};`);
|
|
665
|
+
}
|
|
488
666
|
}
|
|
489
667
|
this.namedReturnVars = named.map((r) => r.name).filter(Boolean);
|
|
490
668
|
} else {
|
|
@@ -565,8 +743,11 @@ export class CodeGen {
|
|
|
565
743
|
genVarDecl(decl) {
|
|
566
744
|
for (const spec of decl.decls) {
|
|
567
745
|
if (spec.value) {
|
|
568
|
-
const
|
|
569
|
-
|
|
746
|
+
const paired = spec.value.length === spec.names.length;
|
|
747
|
+
const vals = spec.value.map((v, i) => {
|
|
748
|
+
const js = paired
|
|
749
|
+
? this._genDeclValue(spec.names[i], v)
|
|
750
|
+
: this.genExpr(v);
|
|
570
751
|
// Wrap numeric values assigned to complex-typed vars
|
|
571
752
|
if (
|
|
572
753
|
spec.type?.name === "complex128" ||
|
|
@@ -590,6 +771,7 @@ export class CodeGen {
|
|
|
590
771
|
} else {
|
|
591
772
|
const zero = spec.type ? this.zeroValueForTypeNode(spec.type) : "null";
|
|
592
773
|
for (const name of spec.names) {
|
|
774
|
+
this._markOwnership(name, true);
|
|
593
775
|
const val = this._boxedVars.has(name) ? `{ value: ${zero} }` : zero;
|
|
594
776
|
this.line(`let ${name} = ${val};`);
|
|
595
777
|
}
|
package/src/codegen/runtime.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
// multiple compiled files bundled together can safely repeat the declaration.
|
|
4
4
|
|
|
5
5
|
export const HELPER_LEN = `var __len = __len || function(a) {
|
|
6
|
-
if (a && typeof a === 'object' && !Array.isArray(a)) return Object.keys(a).length;
|
|
6
|
+
if (a && typeof a === 'object' && !Array.isArray(a) && !ArrayBuffer.isView(a)) return Object.keys(a).length;
|
|
7
7
|
return a?.length ?? 0;
|
|
8
8
|
};`;
|
|
9
9
|
|
|
@@ -19,6 +19,27 @@ export const HELPER_APPEND = `var __append = __append || function(a, ...b) {
|
|
|
19
19
|
|
|
20
20
|
export const HELPER_S = "var __s = __s || function(a) { return a || []; };";
|
|
21
21
|
|
|
22
|
+
// Copy of a struct value whose class is not statically known.
|
|
23
|
+
export const HELPER_SCLONE = `var __sclone = __sclone || function(x) {
|
|
24
|
+
if (x === null || typeof x !== "object") return x;
|
|
25
|
+
if (typeof x.__clone === "function") return x.__clone();
|
|
26
|
+
return Object.assign(Object.create(Object.getPrototypeOf(x)), x);
|
|
27
|
+
};`;
|
|
28
|
+
|
|
29
|
+
// Interface boxing: tag struct values (__v) vs struct pointers (__p) so type
|
|
30
|
+
// switches can tell T from *T; tags are non-enumerable so __equal/JSON ignore them.
|
|
31
|
+
export const HELPER_IFACE_BOX = `var __ifv = __ifv || function(x) {
|
|
32
|
+
if (x === null || typeof x !== "object") return x;
|
|
33
|
+
const c = typeof x.__clone === "function" ? x.__clone() : Object.assign(Object.create(Object.getPrototypeOf(x)), x);
|
|
34
|
+
Object.defineProperty(c, "__v", { value: true, configurable: true });
|
|
35
|
+
return c;
|
|
36
|
+
};
|
|
37
|
+
var __ifp = __ifp || function(x) {
|
|
38
|
+
if (x !== null && typeof x === "object" && x.__p !== true && Object.isExtensible(x))
|
|
39
|
+
Object.defineProperty(x, "__p", { value: true, configurable: true });
|
|
40
|
+
return x;
|
|
41
|
+
};`;
|
|
42
|
+
|
|
22
43
|
export const HELPER_EQUAL = `var __equal = __equal || function __equal(a, b) {
|
|
23
44
|
if (a === b) return true;
|
|
24
45
|
if (a === null || b === null) return false;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// CodeGen statement methods — installed as a mixin on CodeGen.prototype.
|
|
2
2
|
|
|
3
|
-
import { isComplex } from "../typechecker/types.js";
|
|
3
|
+
import { isComplex, isNumeric } from "../typechecker/types.js";
|
|
4
4
|
|
|
5
5
|
/** @typedef {import('./index.js').CodeGen} CodeGen */
|
|
6
6
|
|
|
@@ -122,14 +122,20 @@ export const statementGenMethods = {
|
|
|
122
122
|
|
|
123
123
|
_genDefineMixedRedecl(lhs, rhs, redecls) {
|
|
124
124
|
if (rhs.length === 1 && lhs.length > 1) {
|
|
125
|
-
|
|
126
|
-
|
|
125
|
+
this._tmpCounter = (this._tmpCounter ?? 0) + 1;
|
|
126
|
+
const tmp = `__t${this._tmpCounter}`;
|
|
127
|
+
this.line(`const ${tmp} = ${rhs[0]};`);
|
|
127
128
|
for (let i = 0; i < lhs.length; i++) {
|
|
129
|
+
if (lhs[i] === "_") continue;
|
|
128
130
|
const prefix = redecls[i] ? "" : "let ";
|
|
129
131
|
this.line(`${prefix}${lhs[i]} = ${tmp}[${i}];`);
|
|
130
132
|
}
|
|
131
133
|
} else {
|
|
132
134
|
for (let i = 0; i < lhs.length; i++) {
|
|
135
|
+
if (lhs[i] === "_") {
|
|
136
|
+
this.line(`${rhs[i]};`);
|
|
137
|
+
continue;
|
|
138
|
+
}
|
|
133
139
|
const prefix = redecls[i] ? "" : "let ";
|
|
134
140
|
this.line(`${prefix}${lhs[i]} = ${rhs[i]};`);
|
|
135
141
|
}
|
|
@@ -142,7 +148,13 @@ export const statementGenMethods = {
|
|
|
142
148
|
this._genCommaOkMapIndex(stmt, rhsNode, true);
|
|
143
149
|
return;
|
|
144
150
|
}
|
|
145
|
-
const
|
|
151
|
+
const paired = stmt.rhs.length === stmt.lhs.length;
|
|
152
|
+
const rhs = stmt.rhs.map((e, i) => {
|
|
153
|
+
const l = paired ? stmt.lhs[i] : null;
|
|
154
|
+
if (!l || l.name === "_") return this.genExpr(e);
|
|
155
|
+
if (l._redecl) return this.genValueExpr(e);
|
|
156
|
+
return this._genDeclValue(l.name, e);
|
|
157
|
+
});
|
|
146
158
|
const lhs = stmt.lhs.map((e) => e.name ?? this.genExpr(e));
|
|
147
159
|
const redecls = stmt.lhs.map((e) => !!e._redecl);
|
|
148
160
|
const anyRedecl = redecls.some((r) => r);
|
|
@@ -150,7 +162,8 @@ export const statementGenMethods = {
|
|
|
150
162
|
this._genDefineSingleVar(lhs, rhs, redecls);
|
|
151
163
|
} else if (!anyRedecl) {
|
|
152
164
|
const rhsStr = rhs.length === 1 ? rhs[0] : `[${rhs.join(", ")}]`;
|
|
153
|
-
|
|
165
|
+
const names = lhs.map((n) => (n === "_" ? "" : n));
|
|
166
|
+
this.line(`let [${names.join(", ")}] = ${rhsStr};`);
|
|
154
167
|
} else {
|
|
155
168
|
this._genDefineMixedRedecl(lhs, rhs, redecls);
|
|
156
169
|
}
|
|
@@ -218,7 +231,12 @@ export const statementGenMethods = {
|
|
|
218
231
|
this._genStructDerefAssign(stmt);
|
|
219
232
|
return;
|
|
220
233
|
}
|
|
221
|
-
const
|
|
234
|
+
const paired = stmt.rhs.length === stmt.lhs.length && stmt.op === "=";
|
|
235
|
+
const rhs = stmt.rhs.map((e, i) =>
|
|
236
|
+
paired && stmt.lhs[i].name !== "_"
|
|
237
|
+
? this.genValueExpr(e)
|
|
238
|
+
: this.genExpr(e),
|
|
239
|
+
);
|
|
222
240
|
this._markIndexLvalues(stmt.lhs);
|
|
223
241
|
const lhs = stmt.lhs.map((e) => this._genAssignLhsExpr(e));
|
|
224
242
|
const pairs = lhs.map((l, i) => ({ l, r: rhs[i] ?? rhs[0] }));
|
|
@@ -243,7 +261,14 @@ export const statementGenMethods = {
|
|
|
243
261
|
|
|
244
262
|
_genStructDerefAssign(stmt) {
|
|
245
263
|
const target = this.genExpr(stmt.lhs[0].operand);
|
|
246
|
-
const
|
|
264
|
+
const rhsNode = stmt.rhs[0];
|
|
265
|
+
const fields = this._aggregateBase(rhsNode._type)?.fields;
|
|
266
|
+
const shallowOk =
|
|
267
|
+
fields instanceof Map &&
|
|
268
|
+
![...fields.values()].some((t) => this._aggregateBase(t));
|
|
269
|
+
const source = shallowOk
|
|
270
|
+
? this.genExpr(rhsNode)
|
|
271
|
+
: this.genValueExpr(rhsNode);
|
|
247
272
|
this.line(`Object.assign(${target}, ${source});`);
|
|
248
273
|
},
|
|
249
274
|
|
|
@@ -293,7 +318,7 @@ export const statementGenMethods = {
|
|
|
293
318
|
}
|
|
294
319
|
const vals =
|
|
295
320
|
stmt.values.length > 0
|
|
296
|
-
? stmt.values.map((v) => this.
|
|
321
|
+
? stmt.values.map((v) => this._genReturnValue(v))
|
|
297
322
|
: (this.namedReturnVars ?? []);
|
|
298
323
|
const stored = vals.length === 1 ? vals[0] : `[${vals.join(", ")}]`;
|
|
299
324
|
this.line(
|
|
@@ -318,10 +343,10 @@ export const statementGenMethods = {
|
|
|
318
343
|
this.line("return;");
|
|
319
344
|
}
|
|
320
345
|
} else if (stmt.values.length === 1) {
|
|
321
|
-
this.line(`return ${this.
|
|
346
|
+
this.line(`return ${this._genReturnValue(stmt.values[0])};`);
|
|
322
347
|
} else {
|
|
323
348
|
this.line(
|
|
324
|
-
`return [${stmt.values.map((v) => this.
|
|
349
|
+
`return [${stmt.values.map((v) => this._genReturnValue(v)).join(", ")}];`,
|
|
325
350
|
);
|
|
326
351
|
}
|
|
327
352
|
},
|
|
@@ -425,12 +450,7 @@ export const statementGenMethods = {
|
|
|
425
450
|
},
|
|
426
451
|
|
|
427
452
|
_isIntRangeType(iterType) {
|
|
428
|
-
return (
|
|
429
|
-
(iterType?.kind === "basic" &&
|
|
430
|
-
(iterType.name === "int" || iterType.name === "float64")) ||
|
|
431
|
-
(iterType?.kind === "untyped" &&
|
|
432
|
-
(iterType.base === "int" || iterType.base === "float64"))
|
|
433
|
-
);
|
|
453
|
+
return isNumeric(iterType);
|
|
434
454
|
},
|
|
435
455
|
|
|
436
456
|
_isMapRangeType(iterType) {
|
|
@@ -447,47 +467,152 @@ export const statementGenMethods = {
|
|
|
447
467
|
);
|
|
448
468
|
},
|
|
449
469
|
|
|
450
|
-
|
|
470
|
+
_rangeVarTarget(e, isAssign) {
|
|
471
|
+
if (e.kind === "Ident") {
|
|
472
|
+
if (e.name === "_") return null;
|
|
473
|
+
const boxed = this._boxedVars.has(e.name);
|
|
474
|
+
if (isAssign) return boxed ? `${e.name}.value = ` : `${e.name} = `;
|
|
475
|
+
return boxed ? { name: e.name, boxed } : { name: e.name };
|
|
476
|
+
}
|
|
477
|
+
this._markIndexLvalues([e]);
|
|
478
|
+
return `${this._genAssignLhsExpr(e)} = `;
|
|
479
|
+
},
|
|
480
|
+
|
|
481
|
+
_emitRangeVar(e, valueJs, isAssign) {
|
|
482
|
+
if (!e) return;
|
|
483
|
+
const target = this._rangeVarTarget(e, isAssign);
|
|
484
|
+
if (target === null) return;
|
|
485
|
+
if (typeof target === "string") {
|
|
486
|
+
this.line(`${target}${valueJs};`);
|
|
487
|
+
return;
|
|
488
|
+
}
|
|
489
|
+
const v = target.boxed ? `{ value: ${valueJs} }` : valueJs;
|
|
490
|
+
this.line(`let ${target.name} = ${v};`);
|
|
491
|
+
},
|
|
492
|
+
|
|
493
|
+
// True when the body re-declares one of `names` at its top level (legal Go shadowing).
|
|
494
|
+
_bodyRedeclares(body, names) {
|
|
495
|
+
if (names.length === 0) return false;
|
|
496
|
+
for (const s of body.stmts ?? []) {
|
|
497
|
+
if (s.kind === "DefineStmt" && s.lhs.some((e) => names.includes(e.name)))
|
|
498
|
+
return true;
|
|
499
|
+
if (
|
|
500
|
+
(s.kind === "VarDecl" || s.kind === "ConstDecl") &&
|
|
501
|
+
s.decls?.some((d) => d.names?.some((n) => names.includes(n)))
|
|
502
|
+
)
|
|
503
|
+
return true;
|
|
504
|
+
}
|
|
505
|
+
return false;
|
|
506
|
+
},
|
|
507
|
+
|
|
508
|
+
// Range value variable: aliases the element unless the body writes the variable or the collection.
|
|
509
|
+
_rangeElemValue(stmt, valNode, js, isAssign) {
|
|
510
|
+
const iterNode = stmt.init.rhs[0].expr;
|
|
511
|
+
const iterType = iterNode._type;
|
|
512
|
+
const base = iterType?.kind === "named" ? iterType.underlying : iterType;
|
|
513
|
+
const elemType = base?.elem;
|
|
514
|
+
if (!valNode || !this._aggregateBase(elemType)) return js;
|
|
515
|
+
if (isAssign || valNode.kind !== "Ident")
|
|
516
|
+
return this._cloneJs(elemType, js);
|
|
517
|
+
if (valNode.name === "_") return js;
|
|
518
|
+
const root = this._rootIdentName(iterNode);
|
|
519
|
+
const mutated =
|
|
520
|
+
this._fnMutates(valNode.name) ||
|
|
521
|
+
(root !== null && this._nodeMutatesVar(stmt.body, root));
|
|
522
|
+
this._markOwnership(valNode.name, mutated);
|
|
523
|
+
return mutated ? this._cloneJs(elemType, js) : js;
|
|
524
|
+
},
|
|
525
|
+
|
|
526
|
+
_genSliceRangeFor(stmt, iteree, isAssign) {
|
|
527
|
+
this._loopDepth = this._loopDepth ?? 0;
|
|
528
|
+
const d = this._loopDepth++;
|
|
529
|
+
const [idxNode, valNode] = stmt.init.lhs;
|
|
530
|
+
const i = `__i${d}`;
|
|
451
531
|
const arr = `__arr${d}`;
|
|
452
532
|
const len = `__len${d}`;
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
this.
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
533
|
+
this.line(
|
|
534
|
+
`for (let ${i} = 0, ${arr} = ${iteree}, ${len} = ${arr} ? ${arr}.length : 0; ${i} < ${len}; ${i}++) {`,
|
|
535
|
+
);
|
|
536
|
+
this.indented(() => {
|
|
537
|
+
this._emitRangeVar(idxNode, i, isAssign);
|
|
538
|
+
this._emitRangeVar(
|
|
539
|
+
valNode,
|
|
540
|
+
this._rangeElemValue(stmt, valNode, `${arr}[${i}]`, isAssign),
|
|
541
|
+
isAssign,
|
|
462
542
|
);
|
|
463
|
-
|
|
543
|
+
const declared = isAssign
|
|
544
|
+
? []
|
|
545
|
+
: [idxNode, valNode]
|
|
546
|
+
.filter((e) => e?.kind === "Ident" && e.name !== "_")
|
|
547
|
+
.map((e) => e.name);
|
|
548
|
+
if (this._bodyRedeclares(stmt.body, declared)) {
|
|
549
|
+
this.line("{");
|
|
550
|
+
this.indented(() => this.genBlock(stmt.body));
|
|
551
|
+
this.line("}");
|
|
552
|
+
} else {
|
|
553
|
+
this.genBlock(stmt.body);
|
|
554
|
+
}
|
|
555
|
+
});
|
|
556
|
+
this.line("}");
|
|
557
|
+
this._loopDepth--;
|
|
464
558
|
},
|
|
465
559
|
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
this.
|
|
560
|
+
// Whether `for i := range n` can be emitted as a plain `for (let i = 0; …)` without hidden registers.
|
|
561
|
+
_isSimpleIntRange(stmt, name, isAssign) {
|
|
562
|
+
if (isAssign) return false;
|
|
563
|
+
if (!name || name === "_") return true;
|
|
564
|
+
return !this._boxedVars.has(name) && !this._nodeAssigns(stmt.body, name);
|
|
471
565
|
},
|
|
472
566
|
|
|
473
|
-
|
|
567
|
+
_genIntRangeBody(stmt, name, isAssign) {
|
|
568
|
+
const wrap = !isAssign && name && this._bodyRedeclares(stmt.body, [name]);
|
|
569
|
+
if (!wrap) return this.genBlock(stmt.body);
|
|
570
|
+
this.line("{");
|
|
571
|
+
this.indented(() => this.genBlock(stmt.body));
|
|
572
|
+
this.line("}");
|
|
573
|
+
},
|
|
574
|
+
|
|
575
|
+
_genIntRangeFor(stmt, iteree, isAssign) {
|
|
576
|
+
const varNode = stmt.init.lhs[0];
|
|
577
|
+
const name = varNode?.kind === "Ident" ? varNode.name : null;
|
|
578
|
+
if (this._isSimpleIntRange(stmt, name, isAssign)) {
|
|
579
|
+
const v = !name || name === "_" ? "_$" : name;
|
|
580
|
+
this.line(`for (let ${v} = 0; ${v} < ${iteree}; ${v}++) {`);
|
|
581
|
+
this.indented(() => this.genBlock(stmt.body));
|
|
582
|
+
this.line("}");
|
|
583
|
+
return;
|
|
584
|
+
}
|
|
474
585
|
this._loopDepth = this._loopDepth ?? 0;
|
|
475
|
-
const d = this._loopDepth
|
|
476
|
-
|
|
477
|
-
|
|
586
|
+
const d = this._loopDepth++;
|
|
587
|
+
const i = `__i${d}`;
|
|
588
|
+
const n = `__n${d}`;
|
|
589
|
+
this.line(`for (let ${i} = 0, ${n} = ${iteree}; ${i} < ${n}; ${i}++) {`);
|
|
478
590
|
this.indented(() => {
|
|
479
|
-
this.
|
|
480
|
-
this.
|
|
591
|
+
this._emitRangeVar(varNode, i, isAssign);
|
|
592
|
+
this._genIntRangeBody(stmt, name, isAssign);
|
|
481
593
|
});
|
|
482
594
|
this.line("}");
|
|
483
595
|
this._loopDepth--;
|
|
484
596
|
},
|
|
485
597
|
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
598
|
+
// True when `node` contains an assignment or ++/-- targeting identifier `name`.
|
|
599
|
+
_nodeAssigns(node, name) {
|
|
600
|
+
if (!node || typeof node !== "object") return false;
|
|
601
|
+
if (Array.isArray(node))
|
|
602
|
+
return node.some((n) => this._nodeAssigns(n, name));
|
|
603
|
+
if (
|
|
604
|
+
(node.kind === "AssignStmt" &&
|
|
605
|
+
node.lhs.some((e) => e.kind === "Ident" && e.name === name)) ||
|
|
606
|
+
(node.kind === "IncDecStmt" &&
|
|
607
|
+
node.expr.kind === "Ident" &&
|
|
608
|
+
node.expr.name === name)
|
|
609
|
+
)
|
|
610
|
+
return true;
|
|
611
|
+
for (const key of Object.keys(node)) {
|
|
612
|
+
if (key.startsWith("_")) continue;
|
|
613
|
+
if (this._nodeAssigns(node[key], name)) return true;
|
|
614
|
+
}
|
|
615
|
+
return false;
|
|
491
616
|
},
|
|
492
617
|
|
|
493
618
|
_genIterFor(init, iterType, iteree, lhs, body) {
|
|
@@ -515,12 +640,12 @@ export const statementGenMethods = {
|
|
|
515
640
|
: this.genExpr(range.expr);
|
|
516
641
|
|
|
517
642
|
if (lhs.length <= 1 && this._isIntRangeType(iterType)) {
|
|
518
|
-
this._genIntRangeFor(
|
|
643
|
+
this._genIntRangeFor(stmt, iteree, init.kind === "AssignStmt");
|
|
519
644
|
return;
|
|
520
645
|
}
|
|
521
646
|
|
|
522
647
|
if (!this._isMapRangeType(iterType) && !this._isStringRangeType(iterType)) {
|
|
523
|
-
this._genSliceRangeFor(stmt, iteree,
|
|
648
|
+
this._genSliceRangeFor(stmt, iteree, init.kind === "AssignStmt");
|
|
524
649
|
return;
|
|
525
650
|
}
|
|
526
651
|
|