gofront 1.3.5 → 1.3.7

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.
@@ -58,42 +58,45 @@ const NS_CONSTANTS = {
58
58
  utf8: { RuneError: "0xFFFD", MaxRune: "0x10FFFF", UTFMax: "4" },
59
59
  };
60
60
 
61
- const BINARY_OP_PREC = {
61
+ // JavaScript precedence of the operators emitted for Go binary expressions.
62
+ const JS_OP_PREC = {
62
63
  "||": 1,
63
64
  "&&": 2,
64
- "==": 3,
65
- "!=": 3,
66
- "<": 3,
67
- ">": 3,
68
- "<=": 3,
69
- ">=": 3,
70
- "+": 4,
71
- "-": 4,
72
- "|": 4,
65
+ "|": 3,
73
66
  "^": 4,
74
- "*": 5,
75
- "/": 5,
76
- "%": 5,
77
- "<<": 5,
78
- ">>": 5,
79
67
  "&": 5,
80
- "&^": 5,
68
+ "==": 6,
69
+ "!=": 6,
70
+ "===": 6,
71
+ "!==": 6,
72
+ "<": 7,
73
+ ">": 7,
74
+ "<=": 7,
75
+ ">=": 7,
76
+ "<<": 8,
77
+ ">>": 8,
78
+ ">>>": 8,
79
+ "+": 9,
80
+ "-": 9,
81
+ "*": 10,
82
+ "/": 10,
83
+ "%": 10,
81
84
  };
82
85
 
83
- function formatBinaryOperand(genExpr, child, parentOp, isRight) {
84
- const code = genExpr(child);
85
- if (child.kind !== "BinaryExpr") return code;
86
- const parentPrec = BINARY_OP_PREC[parentOp] || 0;
87
- const childPrec = BINARY_OP_PREC[child.op] || 0;
88
- return (isRight ? childPrec <= parentPrec : childPrec < parentPrec)
89
- ? `(${code})`
90
- : code;
86
+ function wrapForJsOp(code, childOp, parentOp, isRight) {
87
+ if (!childOp) return code;
88
+ const p = JS_OP_PREC[parentOp];
89
+ const c = JS_OP_PREC[childOp];
90
+ return (isRight ? c <= p : c < p) ? `(${code})` : code;
91
91
  }
92
92
 
93
- function formatUnaryOperand(genExpr, child) {
94
- const code = genExpr(child);
95
- return child.kind === "BinaryExpr" ? `(${code})` : code;
96
- }
93
+ const WEB_PASCAL_ALIAS_TYPES = new Set([
94
+ "WebGLRenderingContext",
95
+ "WebGL2RenderingContext",
96
+ "GPUDevice",
97
+ "GPUQueue",
98
+ "GPUAdapter",
99
+ ]);
97
100
 
98
101
  const INT_TYPE_NAMES = new Set([
99
102
  "int",
@@ -200,6 +203,16 @@ const EXPR_GEN_DELEGATE = {
200
203
  /** @type {ThisType<CodeGen>} */
201
204
  export const expressionGenMethods = {
202
205
  genExpr(expr) {
206
+ if (expr._ifaceBox && !expr._ifaceBoxing) {
207
+ expr._ifaceBoxing = true;
208
+ try {
209
+ this._usesIfaceBox = true;
210
+ const fn = expr._ifaceBox === "ptr" ? "__ifp" : "__ifv";
211
+ return `${fn}(${this.genExpr(expr)})`;
212
+ } finally {
213
+ expr._ifaceBoxing = false;
214
+ }
215
+ }
203
216
  switch (expr.kind) {
204
217
  case "BasicLit":
205
218
  if (expr.litKind === "STRING") return JSON.stringify(expr.value);
@@ -253,6 +266,204 @@ export const expressionGenMethods = {
253
266
  return this._isStructType(expr._type);
254
267
  },
255
268
 
269
+ // ── Go value semantics for structs and arrays ─────────────────
270
+
271
+ _aggregateBase(t) {
272
+ if (!t) return null;
273
+ const b =
274
+ t.kind === "named"
275
+ ? (t.underlying ?? this.checker?.types.get(t.name)?.underlying)
276
+ : t;
277
+ return b?.kind === "struct" || b?.kind === "array" ? b : null;
278
+ },
279
+
280
+ _structClassName(t) {
281
+ const name =
282
+ typeof t?.name === "string" ? t.name.replace(/\[.*$/, "") : null;
283
+ return name && this.structNames.has(name) ? name : null;
284
+ },
285
+
286
+ // Pseudo type object for an AST type node (used where no checker type is available).
287
+ _typeFromNode(node) {
288
+ if (!node) return null;
289
+ if (node.kind === "TypeName" && this.structNames.has(node.name))
290
+ return { kind: "named", name: node.name, underlying: { kind: "struct" } };
291
+ if (node.kind === "ArrayType")
292
+ return { kind: "array", elem: this._typeFromNode(node.elem) };
293
+ return null;
294
+ },
295
+
296
+ _cloneJs(t, js) {
297
+ const base = this._aggregateBase(t);
298
+ if (!base) return js;
299
+ if (base.kind === "struct") {
300
+ if (this._structClassName(t) || this._structClassName(base))
301
+ return `${js}.__clone()`;
302
+ this._usesSClone = true;
303
+ return `__sclone(${js})`;
304
+ }
305
+ const elemClone = this._cloneJs(base.elem, "__e");
306
+ return elemClone === "__e"
307
+ ? `${js}?.slice()`
308
+ : `${js}?.map((__e) => ${elemClone})`;
309
+ },
310
+
311
+ _isAddressableExpr(e) {
312
+ switch (e?.kind) {
313
+ case "Ident":
314
+ return e.name !== "_" && !e._isTypeRef;
315
+ case "SelectorExpr":
316
+ return !e._isMethodValue && !e._isMethodExpr;
317
+ case "IndexExpr":
318
+ return true;
319
+ case "UnaryExpr":
320
+ return e.op === "*";
321
+ case "TypeAssertExpr":
322
+ return !e._commaOk;
323
+ default:
324
+ return false;
325
+ }
326
+ },
327
+
328
+ // Emits `expr` as a Go value: struct/array operands that alias existing storage are copied.
329
+ genValueExpr(expr) {
330
+ const js = this.genExpr(expr);
331
+ if (expr._ifaceBox) return js;
332
+ if (!this._aggregateBase(expr._type) || !this._isAddressableExpr(expr))
333
+ return js;
334
+ return this._cloneJs(expr._type, js);
335
+ },
336
+
337
+ _rootIdentName(e) {
338
+ while (e) {
339
+ if (e.kind === "Ident") return e.name;
340
+ if (e.kind === "SelectorExpr" || e.kind === "IndexExpr") e = e.expr;
341
+ else if (e.kind === "UnaryExpr" && e.op === "*") e = e.operand;
342
+ else return null;
343
+ }
344
+ return null;
345
+ },
346
+
347
+ // True when `node` itself (not its children) writes to variable `name`.
348
+ _nodeWritesVar(node, name) {
349
+ const root = (e) => this._rootIdentName(e) === name;
350
+ switch (node.kind) {
351
+ case "AssignStmt":
352
+ return node.lhs.some(root);
353
+ case "DefineStmt":
354
+ return node.lhs.some((e) => e._redecl && e.name === name);
355
+ case "IncDecStmt":
356
+ return root(node.expr);
357
+ case "SelectorExpr":
358
+ return Boolean(
359
+ node._isMethodValue && node._type?._ptrRecv && root(node.expr),
360
+ );
361
+ default:
362
+ return false;
363
+ }
364
+ },
365
+
366
+ // True when `node` may modify (or take the address of) the value held by variable `name`.
367
+ _nodeMutatesVar(node, name, addrOnly = false) {
368
+ if (!node || typeof node !== "object") return false;
369
+ if (Array.isArray(node))
370
+ return node.some((n) => this._nodeMutatesVar(n, name, addrOnly));
371
+ if (
372
+ node.kind === "UnaryExpr" &&
373
+ node.op === "&" &&
374
+ this._rootIdentName(node.operand) === name
375
+ )
376
+ return true;
377
+ if (!addrOnly && this._nodeWritesVar(node, name)) return true;
378
+ for (const key of Object.keys(node)) {
379
+ if (key.startsWith("_")) continue;
380
+ if (this._nodeMutatesVar(node[key], name, addrOnly)) return true;
381
+ }
382
+ return false;
383
+ },
384
+
385
+ _fnMutates(name, addrOnly = false) {
386
+ const ctx = this._fnCtx;
387
+ if (!ctx) return true;
388
+ const key = `${addrOnly ? "&" : ""}${name}`;
389
+ if (!ctx.mut.has(key))
390
+ ctx.mut.set(key, this._nodeMutatesVar(ctx.body, name, addrOnly));
391
+ return ctx.mut.get(key);
392
+ },
393
+
394
+ _withFnCtx(body, fn) {
395
+ const prev = this._fnCtx;
396
+ this._fnCtx = {
397
+ body,
398
+ mut: new Map(),
399
+ owned: new Set(),
400
+ borrowed: new Set(),
401
+ };
402
+ try {
403
+ fn();
404
+ } finally {
405
+ this._fnCtx = prev;
406
+ }
407
+ },
408
+
409
+ _markOwnership(name, owned) {
410
+ if (!this._fnCtx || !name || name === "_") return;
411
+ (owned ? this._fnCtx.owned : this._fnCtx.borrowed).add(name);
412
+ },
413
+
414
+ // Value for a newly declared local: addressable aggregates are copied (Go value semantics).
415
+ _genDeclValue(name, rhsNode) {
416
+ this._markOwnership(name, true);
417
+ return this.genValueExpr(rhsNode);
418
+ },
419
+
420
+ // Copies aggregate params/receivers that the function body mutates.
421
+ _emitParamCopies(params) {
422
+ for (const p of params) {
423
+ if (p.variadic || !p.name || p.name === "_") continue;
424
+ const t = this._typeFromNode(p.type);
425
+ if (!t || this._boxedVars.has(p.name)) continue;
426
+ if (this._fnMutates(p.name)) {
427
+ this.line(`${p.name} = ${this._cloneJs(t, p.name)};`);
428
+ this._markOwnership(p.name, true);
429
+ } else {
430
+ this._markOwnership(p.name, false);
431
+ }
432
+ }
433
+ },
434
+
435
+ // An owned local can be returned without a copy unless something else may still write it.
436
+ _genReturnValue(v) {
437
+ const ctx = this._fnCtx;
438
+ if (
439
+ v.kind === "Ident" &&
440
+ ctx?.owned.has(v.name) &&
441
+ !ctx.borrowed.has(v.name) &&
442
+ !this._fnMutates(v.name, true) &&
443
+ !this._closureMutates(v.name)
444
+ )
445
+ return this.genExpr(v);
446
+ return this.genValueExpr(v);
447
+ },
448
+
449
+ _closureMutates(name) {
450
+ const ctx = this._fnCtx;
451
+ const key = `λ${name}`;
452
+ if (!ctx.mut.has(key)) {
453
+ const walk = (node) => {
454
+ if (!node || typeof node !== "object") return false;
455
+ if (Array.isArray(node)) return node.some(walk);
456
+ if (node.kind === "FuncLit")
457
+ return this._nodeMutatesVar(node.body, name);
458
+ return Object.keys(node).some(
459
+ (k) => !k.startsWith("_") && walk(node[k]),
460
+ );
461
+ };
462
+ ctx.mut.set(key, walk(ctx.body));
463
+ }
464
+ return ctx.mut.get(key);
465
+ },
466
+
256
467
  _genUnaryExpr(expr) {
257
468
  const op = expr.op === "^" ? "~" : expr.op; // bitwise NOT
258
469
  // Address-of: &x
@@ -287,7 +498,11 @@ export const expressionGenMethods = {
287
498
  if (op === "-") return `{ re: -${inner}.re, im: -${inner}.im }`;
288
499
  return inner;
289
500
  }
290
- return `${op}${formatUnaryOperand((e) => this.genExpr(e), expr.operand)}`;
501
+ const code = this.genExpr(expr.operand);
502
+ const needsParens =
503
+ this._emittedBinaryOp(expr.operand) !== null ||
504
+ ((op === "-" || op === "+") && /^[-+]/.test(code));
505
+ return needsParens ? `${op}(${code})` : `${op}${code}`;
291
506
  },
292
507
 
293
508
  _genSelectorExpr(expr) {
@@ -302,11 +517,27 @@ export const expressionGenMethods = {
302
517
  if (this.bundledPackages.has(base)) return expr.field;
303
518
  if (expr.expr._type?.kind === "pointer" && expr.field !== "value")
304
519
  return this._genPointerSelectorField(expr, base);
305
- const sel = `${base}.${expr.field}`;
520
+ const sel = `${base}.${this._jsMemberName(expr)}`;
306
521
  if (expr._isMethodValue && !expr._callee) return `${sel}.bind(${base})`;
307
522
  return sel;
308
523
  },
309
524
 
525
+ // PascalCase aliases of Web API methods (gl.ClearColor) map to the real camelCase member.
526
+ _jsMemberName(expr) {
527
+ const f = expr.field;
528
+ const t = expr.expr._type;
529
+ if (
530
+ t?.kind === "named" &&
531
+ WEB_PASCAL_ALIAS_TYPES.has(t.name) &&
532
+ /^[A-Z]/.test(f) &&
533
+ /[a-z]/.test(f)
534
+ ) {
535
+ const camel = f[0].toLowerCase() + f.slice(1);
536
+ if (t.underlying?.methods?.has(camel)) return camel;
537
+ }
538
+ return f;
539
+ },
540
+
310
541
  _genPointerSelectorField(expr, base) {
311
542
  if (this._isStructPointerType(expr.expr._type)) {
312
543
  const sel = `${base}.${expr.field}`;
@@ -366,7 +597,12 @@ export const expressionGenMethods = {
366
597
  this._boxedVars = new Set(prevBoxed); // inherit parent's boxed vars (closures)
367
598
  this._scanAddressTaken(expr.body);
368
599
  this.out = [];
369
- this.indented(() => this._genBody(expr.body));
600
+ this._withFnCtx(expr.body, () =>
601
+ this.indented(() => {
602
+ this._emitParamCopies(expr.params);
603
+ this._genBody(expr.body);
604
+ }),
605
+ );
370
606
  const body = this.out.join("\n");
371
607
  this.out = saved;
372
608
  this._boxedVars = prevBoxed;
@@ -500,7 +736,9 @@ export const expressionGenMethods = {
500
736
  const sliceJS = `${this.genExpr(expr.args[0])}.${wrapField}`;
501
737
  const elems = expr.args
502
738
  .slice(1)
503
- .map((a) => (a._spread ? `...${this.genExpr(a)}` : this.genExpr(a)));
739
+ .map((a) =>
740
+ a._spread ? `...${this.genExpr(a)}` : this.genValueExpr(a),
741
+ );
504
742
  if (elems.length === 0) return this.genExpr(expr.args[0]);
505
743
  this._usesAppend = true;
506
744
  return `new ${retType.name}(__append(${sliceJS}, ${elems.join(", ")}))`;
@@ -508,9 +746,17 @@ export const expressionGenMethods = {
508
746
  const slice = this.genExpr(expr.args[0]);
509
747
  const elems = expr.args
510
748
  .slice(1)
511
- .map((a) => (a._spread ? `...${this.genExpr(a)}` : this.genExpr(a)));
749
+ .map((a) => (a._spread ? `...${this.genExpr(a)}` : this.genValueExpr(a)));
512
750
  if (elems.length === 0) return slice;
513
751
  this._usesAppend = true;
752
+ const sliceType = expr._type ?? expr.args[0]._type;
753
+ const typedCtor = isTypedArraySlice(sliceType)
754
+ ? typedArrayConstructorForElem(
755
+ (sliceType.kind === "named" ? sliceType.underlying : sliceType).elem,
756
+ )
757
+ : null;
758
+ if (typedCtor)
759
+ return `__append((${slice}) ?? new ${typedCtor}(0), ${elems.join(", ")})`;
514
760
  return `__append(${slice}, ${elems.join(", ")})`;
515
761
  },
516
762
 
@@ -585,9 +831,9 @@ export const expressionGenMethods = {
585
831
  e.key.litKind === "STRING"
586
832
  ? JSON.stringify(e.key.value)
587
833
  : `[${this.genExpr(e.key)}]`;
588
- return `${k}: ${this.genExpr(e.value)}`;
834
+ return `${k}: ${this.genValueExpr(e.value)}`;
589
835
  }
590
- return this.genExpr(e);
836
+ return this.genValueExpr(e);
591
837
  })
592
838
  .join(", ");
593
839
  },
@@ -616,7 +862,9 @@ export const expressionGenMethods = {
616
862
  : null);
617
863
  const idx = name ? fields.findIndex((f) => f.name === name) : i;
618
864
  if (idx >= 0 && idx < fields.length) {
619
- values[idx] = this.genExpr(e.kind === "KeyValueExpr" ? e.value : e);
865
+ values[idx] = this.genValueExpr(
866
+ e.kind === "KeyValueExpr" ? e.value : e,
867
+ );
620
868
  if (idx > maxIdx) maxIdx = idx;
621
869
  }
622
870
  }
@@ -631,7 +879,7 @@ export const expressionGenMethods = {
631
879
  (ce) => (ce._positionalField ?? ce.key?.name) === ef,
632
880
  );
633
881
  values[idx] = match
634
- ? this.genExpr(match.value ?? match)
882
+ ? this.genValueExpr(match.value ?? match)
635
883
  : fields[idx].zero;
636
884
  } else {
637
885
  values[idx] = `${this.genExpr(e.value)}.${ef}`;
@@ -655,7 +903,7 @@ export const expressionGenMethods = {
655
903
  } else {
656
904
  const idx = fields.findIndex((f) => f.name === k);
657
905
  if (idx >= 0) {
658
- values[idx] = this.genExpr(e.value);
906
+ values[idx] = this.genValueExpr(e.value);
659
907
  if (idx > maxIdx) maxIdx = idx;
660
908
  }
661
909
  }
@@ -695,9 +943,7 @@ export const expressionGenMethods = {
695
943
  }
696
944
  if (this._isSliceOrArrayType(t)) {
697
945
  const elems = expr.elems
698
- .map((e) =>
699
- e.kind === "KeyValueExpr" ? this.genExpr(e.value) : this.genExpr(e),
700
- )
946
+ .map((e) => this.genValueExpr(e.kind === "KeyValueExpr" ? e.value : e))
701
947
  .join(", ");
702
948
  const elemNode =
703
949
  t.kind === "SliceType" || t.kind === "ArrayType" ? t.elem : null;
@@ -734,13 +980,13 @@ export const expressionGenMethods = {
734
980
  const fields = expr.elems
735
981
  .map((e) => {
736
982
  if (e._positionalField)
737
- return `${e._positionalField}: ${this.genExpr(e)}`;
738
- return `${e.key.name ?? this.genExpr(e.key)}: ${this.genExpr(e.value)}`;
983
+ return `${e._positionalField}: ${this.genValueExpr(e)}`;
984
+ return `${e.key.name ?? this.genExpr(e.key)}: ${this.genValueExpr(e.value)}`;
739
985
  })
740
986
  .join(", ");
741
987
  return `{ ${fields} }`;
742
988
  }
743
- const elems = expr.elems.map((e) => this.genExpr(e)).join(", ");
989
+ const elems = expr.elems.map((e) => this.genValueExpr(e)).join(", ");
744
990
  if (isTypedArraySlice(expr._type)) {
745
991
  const typedCtor = typedArrayConstructorForElem(expr._type.elem);
746
992
  if (typedCtor) return `new ${typedCtor}([${elems}])`;
@@ -759,18 +1005,16 @@ export const expressionGenMethods = {
759
1005
  e.key.litKind === "STRING"
760
1006
  ? JSON.stringify(e.key.value)
761
1007
  : `[${this.genExpr(e.key)}]`;
762
- return `${k}: ${this.genExpr(e.value)}`;
1008
+ return `${k}: ${this.genValueExpr(e.value)}`;
763
1009
  }
764
- return this.genExpr(e);
1010
+ return this.genValueExpr(e);
765
1011
  })
766
1012
  .join(", ");
767
1013
  return `new ${typeName}({ ${entries} })`;
768
1014
  }
769
1015
  // Default: slice
770
1016
  const elems = expr.elems
771
- .map((e) =>
772
- e.kind === "KeyValueExpr" ? this.genExpr(e.value) : this.genExpr(e),
773
- )
1017
+ .map((e) => this.genValueExpr(e.kind === "KeyValueExpr" ? e.value : e))
774
1018
  .join(", ");
775
1019
  return `new ${typeName}([${elems}])`;
776
1020
  },
@@ -833,8 +1077,20 @@ export const expressionGenMethods = {
833
1077
  }
834
1078
  case "SliceType":
835
1079
  return "null";
836
- case "ArrayType":
837
- return "[]";
1080
+ case "ArrayType": {
1081
+ const n = Number(typeNode.size?.value ?? 0) || 0;
1082
+ const ctor = typeNode.elem?.name
1083
+ ? typedArrayConstructorForElem({
1084
+ kind: "basic",
1085
+ name: typeNode.elem.name,
1086
+ })
1087
+ : null;
1088
+ return this._arrayZero(
1089
+ n,
1090
+ ctor,
1091
+ this.zeroValueForTypeNode(typeNode.elem),
1092
+ );
1093
+ }
838
1094
  case "MapType":
839
1095
  return "{}";
840
1096
  case "PointerType":
@@ -854,6 +1110,14 @@ export const expressionGenMethods = {
854
1110
  }
855
1111
  },
856
1112
 
1113
+ _arrayZero(n, ctor, elemZero) {
1114
+ if (ctor) return `new ${ctor}(${n})`;
1115
+ if (n === 0) return "[]";
1116
+ if (/^(new |\{|\[|Array)/.test(elemZero))
1117
+ return `Array.from({ length: ${n} }, () => ${elemZero})`;
1118
+ return `new Array(${n}).fill(${elemZero})`;
1119
+ },
1120
+
857
1121
  // zeroValueForType operates on typechecker type objects (not AST type nodes).
858
1122
  zeroValueForType(t) {
859
1123
  if (!t) return "null";
@@ -862,6 +1126,12 @@ export const expressionGenMethods = {
862
1126
  return this._zeroForBasicName(t.name) ?? "null";
863
1127
  case "slice":
864
1128
  return "null";
1129
+ case "array":
1130
+ return this._arrayZero(
1131
+ Number(t.size ?? 0) || 0,
1132
+ typedArrayConstructorForElem(t.elem),
1133
+ this.zeroValueForType(t.elem),
1134
+ );
865
1135
  case "map":
866
1136
  return "{}";
867
1137
  case "struct": {
@@ -888,9 +1158,18 @@ export const expressionGenMethods = {
888
1158
  if (name === "nil") return `${val} === null`;
889
1159
  if (name === "error")
890
1160
  return `(typeof ${val} === "object" && ${val} !== null && typeof ${val}.Error === "function")`;
891
- if (this.structNames.has(name)) return `${val} instanceof ${name}`;
1161
+ if (this.structNames.has(name))
1162
+ return `(${val} instanceof ${name} && ${val}.__p !== true)`;
892
1163
  return "true"; // unknown type — can't check at runtime
893
1164
  }
1165
+ if (
1166
+ typeNode.kind === "PointerType" &&
1167
+ typeNode.base?.kind === "TypeName" &&
1168
+ this.structNames.has(typeNode.base.name)
1169
+ ) {
1170
+ const name = typeNode.base.name;
1171
+ return `(${val} instanceof ${name} && ${val}.__v !== true)`;
1172
+ }
894
1173
  return "true";
895
1174
  },
896
1175
 
@@ -948,39 +1227,72 @@ export const expressionGenMethods = {
948
1227
  )
949
1228
  return this._genComplexBinary(expr);
950
1229
 
951
- const l = formatBinaryOperand(
952
- (e) => this.genExpr(e),
953
- expr.left,
954
- expr.op,
955
- false,
956
- );
957
- const r = formatBinaryOperand(
958
- (e) => this.genExpr(e),
959
- expr.right,
960
- expr.op,
961
- true,
1230
+ if (this._isStructEquality(expr)) {
1231
+ this._usesEqual = true;
1232
+ const cmp = `__equal(${this.genExpr(expr.left)}, ${this.genExpr(expr.right)})`;
1233
+ return expr.op === "==" ? cmp : `!${cmp}`;
1234
+ }
1235
+
1236
+ const jsOp = this._emittedBinaryOp(expr) ?? "/";
1237
+ const operand = (child, isRight) =>
1238
+ wrapForJsOp(
1239
+ this.genExpr(child),
1240
+ this._emittedBinaryOp(child),
1241
+ jsOp,
1242
+ isRight,
1243
+ );
1244
+ const l = operand(expr.left, false);
1245
+
1246
+ if (expr.op === "&^") {
1247
+ const rCode = this.genExpr(expr.right);
1248
+ const r =
1249
+ this._emittedBinaryOp(expr.right) !== null ? `(${rCode})` : rCode;
1250
+ return `${l} & ~${r}`;
1251
+ }
1252
+ const r = operand(expr.right, true);
1253
+ if (this._isIntDivision(expr)) return `Math.trunc(${l} / ${r})`;
1254
+ return `${l} ${jsOp} ${r}`;
1255
+ },
1256
+
1257
+ _isStructEquality(expr) {
1258
+ return (
1259
+ (expr.op === "==" || expr.op === "!=") &&
1260
+ this._isStructOrArrayType(expr.left._type)
962
1261
  );
1262
+ },
963
1263
 
964
- if (
1264
+ _isIntDivision(expr) {
1265
+ return (
965
1266
  expr.op === "/" &&
966
1267
  this.isIntType(expr.left._type) &&
967
1268
  this.isIntType(expr.right._type)
1269
+ );
1270
+ },
1271
+
1272
+ _isNilLiteral(expr) {
1273
+ return (
1274
+ (expr.kind === "Ident" && expr.name === "nil") ||
1275
+ (expr.kind === "BasicLit" && expr.value === "null")
1276
+ );
1277
+ },
1278
+
1279
+ // Top-level JS operator a BinaryExpr compiles to, or null when the output is atomic.
1280
+ _emittedBinaryOp(expr) {
1281
+ if (expr?.kind !== "BinaryExpr") return null;
1282
+ if (
1283
+ isComplex(expr._type) ||
1284
+ isComplex(expr.left._type) ||
1285
+ isComplex(expr.right._type)
968
1286
  )
969
- return `Math.trunc(${l} / ${r})`;
970
- if (expr.op === "&^") return `${l} & ~${r}`;
1287
+ return null;
1288
+ if (this._isStructEquality(expr) || this._isIntDivision(expr)) return null;
1289
+ if (expr.op === "&^") return "&";
971
1290
  if (expr.op === "==" || expr.op === "!=") {
972
- if (this._isStructOrArrayType(expr.left._type)) {
973
- this._usesEqual = true;
974
- const cmp = `__equal(${l}, ${r})`;
975
- return expr.op === "==" ? cmp : `!${cmp}`;
976
- }
977
- if (l === "null" || r === "null") {
978
- const op = expr.op === "==" ? "==" : "!=";
979
- return `${l} ${op} ${r}`;
980
- }
1291
+ if (this._isNilLiteral(expr.left) || this._isNilLiteral(expr.right))
1292
+ return expr.op;
1293
+ return expr.op === "==" ? "===" : "!==";
981
1294
  }
982
- const op = expr.op === "==" ? "===" : expr.op === "!=" ? "!==" : expr.op;
983
- return `${l} ${op} ${r}`;
1295
+ return expr.op;
984
1296
  },
985
1297
 
986
1298
  _genTypeConversion(expr) {
@@ -1032,13 +1344,14 @@ export const expressionGenMethods = {
1032
1344
  const typedCtor = typedArrayConstructorForElem(
1033
1345
  t.elem ? { kind: "basic", name: t.elem.name } : null,
1034
1346
  );
1347
+ const fromString = this._isStringSource(expr.expr._type);
1035
1348
  if (typedCtor) {
1036
- return `new ${typedCtor}(${inner})`;
1037
- }
1038
- if (elem === "rune" || elem === "int32" || elem === "int") {
1039
- if (this._isStringSource(expr.expr._type))
1040
- return `Array.from(${inner}, __c => __c.codePointAt(0))`;
1349
+ return fromString
1350
+ ? `${typedCtor}.from(${inner}, __c => __c.codePointAt(0))`
1351
+ : `new ${typedCtor}(${inner})`;
1041
1352
  }
1353
+ if ((elem === "rune" || elem === "int32" || elem === "int") && fromString)
1354
+ return `Array.from(${inner}, __c => __c.codePointAt(0))`;
1042
1355
  return `Array.from(${inner})`;
1043
1356
  },
1044
1357
 
@@ -1060,19 +1373,25 @@ export const expressionGenMethods = {
1060
1373
  case "string":
1061
1374
  return this._genStringConversion(expr.expr._type, inner);
1062
1375
  case "int":
1376
+ case "int64":
1377
+ case "uint":
1378
+ case "uint64":
1379
+ case "uintptr":
1380
+ return `Math.trunc(Number(${inner}))`;
1063
1381
  case "int8":
1382
+ return `(Number(${inner}) << 24 >> 24)`;
1064
1383
  case "int16":
1384
+ return `(Number(${inner}) << 16 >> 16)`;
1065
1385
  case "int32":
1066
- case "int64":
1067
- case "uint":
1386
+ case "rune":
1387
+ return `(Number(${inner}) | 0)`;
1068
1388
  case "uint8":
1389
+ case "byte":
1390
+ return `(Number(${inner}) & 0xFF)`;
1069
1391
  case "uint16":
1392
+ return `(Number(${inner}) & 0xFFFF)`;
1070
1393
  case "uint32":
1071
- case "uint64":
1072
- case "uintptr":
1073
- case "byte":
1074
- case "rune":
1075
- return `Math.trunc(Number(${inner}))`;
1394
+ return `(Number(${inner}) >>> 0)`;
1076
1395
  case "float32":
1077
1396
  return `Math.fround(Number(${inner}))`;
1078
1397
  case "float64":