gofront 0.0.3 → 0.0.5

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.
@@ -1,5 +1,9 @@
1
1
  // CodeGen expression methods and helpers — installed as a mixin on CodeGen.prototype.
2
2
 
3
+ import { ERROR, isComplex } from "../typechecker/types.js";
4
+
5
+ const globalError = ERROR;
6
+
3
7
  const INT_TYPE_NAMES = new Set([
4
8
  "int",
5
9
  "uint",
@@ -23,17 +27,68 @@ export const expressionGenMethods = {
23
27
  if (expr.litKind === "STRING") return JSON.stringify(expr.value);
24
28
  return expr.value; // int, float, bool, nil(null)
25
29
 
30
+ case "ImagLit":
31
+ return `{ re: 0, im: ${expr.value} }`;
32
+
26
33
  case "Ident":
34
+ if (this._boxedVars.has(expr.name) && !expr._isAddressOf)
35
+ return `${expr.name}.value`;
27
36
  return expr.name;
28
37
 
29
38
  case "UnaryExpr": {
30
39
  const op = expr.op === "^" ? "~" : expr.op; // bitwise NOT
31
- // Dereference/address-of — transparent in JS
32
- if (op === "*" || op === "&") return this.genExpr(expr.operand);
40
+ // Address-of: &x
41
+ if (op === "&") {
42
+ if (expr.operand.kind === "Ident") {
43
+ // Mark so Ident codegen does NOT append .value
44
+ expr.operand._isAddressOf = true;
45
+ // For boxed vars, &x returns the box itself
46
+ if (this._boxedVars.has(expr.operand.name)) {
47
+ return this.genExpr(expr.operand);
48
+ }
49
+ }
50
+ // For non-boxed vars (structs, etc.), wrap in { value: x }
51
+ return `{ value: ${this.genExpr(expr.operand)} }`;
52
+ }
53
+ // Dereference: *p → p.value
54
+ if (op === "*") {
55
+ return `${this.genExpr(expr.operand)}.value`;
56
+ }
57
+ // Unary minus/plus on complex
58
+ if ((op === "-" || op === "+") && isComplex(expr.operand._type)) {
59
+ const inner = this.genExpr(expr.operand);
60
+ if (op === "-") return `{ re: -${inner}.re, im: -${inner}.im }`;
61
+ return inner;
62
+ }
33
63
  return `${op}${this.genExpr(expr.operand)}`;
34
64
  }
35
65
 
36
66
  case "BinaryExpr": {
67
+ // Complex binary operations
68
+ if (
69
+ isComplex(expr._type) ||
70
+ isComplex(expr.left._type) ||
71
+ isComplex(expr.right._type)
72
+ ) {
73
+ const l = this._genComplexOperand(expr.left);
74
+ const r = this._genComplexOperand(expr.right);
75
+ switch (expr.op) {
76
+ case "+":
77
+ return `{ re: ${l}.re + ${r}.re, im: ${l}.im + ${r}.im }`;
78
+ case "-":
79
+ return `{ re: ${l}.re - ${r}.re, im: ${l}.im - ${r}.im }`;
80
+ case "*":
81
+ this._usesCmul = true;
82
+ return `__cmul(${l}, ${r})`;
83
+ case "/":
84
+ this._usesCdiv = true;
85
+ return `__cdiv(${l}, ${r})`;
86
+ case "==":
87
+ return `(${l}.re === ${r}.re && ${l}.im === ${r}.im)`;
88
+ case "!=":
89
+ return `(${l}.re !== ${r}.re || ${l}.im !== ${r}.im)`;
90
+ }
91
+ }
37
92
  const l = this.genExpr(expr.left);
38
93
  const r = this.genExpr(expr.right);
39
94
  // Integer division: only when both sides are int
@@ -48,6 +103,15 @@ export const expressionGenMethods = {
48
103
  if (expr.op === "&^") {
49
104
  return `${l} & ~${r}`;
50
105
  }
106
+ // Struct/array equality: use __equal for value comparison
107
+ if (expr.op === "==" || expr.op === "!=") {
108
+ const lt = expr.left._type;
109
+ if (this._isStructOrArrayType(lt)) {
110
+ this._usesEqual = true;
111
+ const cmp = `__equal(${l}, ${r})`;
112
+ return expr.op === "==" ? cmp : `!${cmp}`;
113
+ }
114
+ }
51
115
  // Go == and != are strict — map to JS === / !==
52
116
  const op =
53
117
  expr.op === "==" ? "===" : expr.op === "!=" ? "!==" : expr.op;
@@ -57,7 +121,15 @@ export const expressionGenMethods = {
57
121
  case "CallExpr":
58
122
  return this.genCall(expr);
59
123
 
124
+ case "InstantiationExpr":
125
+ return this.genExpr(expr.expr); // type erasure
126
+
60
127
  case "SelectorExpr": {
128
+ // Method expression: TypeName.MethodName → (recv, ...args) => recv.method(...args)
129
+ if (expr._isMethodExpr) {
130
+ const method = expr.field;
131
+ return `((recv, ...args) => recv.${method}(...args))`;
132
+ }
61
133
  // math constants
62
134
  if (expr.expr.kind === "Ident" && expr.expr.name === "math") {
63
135
  switch (expr.field) {
@@ -75,6 +147,13 @@ export const expressionGenMethods = {
75
147
  return "Number.MIN_SAFE_INTEGER";
76
148
  }
77
149
  }
150
+ // os constants/vars
151
+ if (expr.expr.kind === "Ident" && expr.expr.name === "os") {
152
+ switch (expr.field) {
153
+ case "Args":
154
+ return "process.argv";
155
+ }
156
+ }
78
157
  // time constants (nanosecond durations)
79
158
  if (expr.expr.kind === "Ident" && expr.expr.name === "time") {
80
159
  switch (expr.field) {
@@ -91,11 +170,17 @@ export const expressionGenMethods = {
91
170
  const base = this.genExpr(expr.expr);
92
171
  // Bundled GoFront packages are inlined — drop the qualifier.
93
172
  if (this.bundledPackages.has(base)) return expr.field;
94
- // Pointer types are wrapped as { value: T }; route through .value
173
+ // Pointer types: route through .value
95
174
  if (expr.expr._type?.kind === "pointer" && expr.field !== "value") {
96
- return `${base}.value.${expr.field}`;
175
+ const sel = `${base}.value.${expr.field}`;
176
+ if (expr._isMethodValue && !expr._callee)
177
+ return `${sel}.bind(${base}.value)`;
178
+ return sel;
97
179
  }
98
- return `${base}.${expr.field}`;
180
+ const sel = `${base}.${expr.field}`;
181
+ // Method used as value (not called) — bind the receiver
182
+ if (expr._isMethodValue && !expr._callee) return `${sel}.bind(${base})`;
183
+ return sel;
99
184
  }
100
185
 
101
186
  case "IndexExpr": {
@@ -110,6 +195,14 @@ export const expressionGenMethods = {
110
195
  }
111
196
  return `(${base}[${idx}] ?? ${zero})`;
112
197
  }
198
+ // String indexing returns a byte (charCodeAt), not a JS character
199
+ const exprType = expr.expr._type;
200
+ const isStr =
201
+ (exprType?.kind === "basic" && exprType.name === "string") ||
202
+ (exprType?.kind === "untyped" && exprType.base === "string");
203
+ if (isStr && !expr._lvalue) {
204
+ return `${base}.charCodeAt(${idx})`;
205
+ }
113
206
  return `${base}[${idx}]`;
114
207
  }
115
208
 
@@ -129,10 +222,14 @@ export const expressionGenMethods = {
129
222
  const params = expr.params.map((p) => p.name).join(", ");
130
223
  const asyncPrefix = expr.async ? "async " : "";
131
224
  const saved = this.out;
225
+ const prevBoxed = this._boxedVars;
226
+ this._boxedVars = new Set(prevBoxed); // inherit parent's boxed vars (closures)
227
+ this._scanAddressTaken(expr.body);
132
228
  this.out = [];
133
229
  this.indented(() => this._genBody(expr.body));
134
230
  const body = this.out.join("\n");
135
231
  this.out = saved;
232
+ this._boxedVars = prevBoxed;
136
233
  return `${asyncPrefix}function(${params}) {\n${body}\n${" ".repeat(this.indent)}}`;
137
234
  }
138
235
 
@@ -140,6 +237,26 @@ export const expressionGenMethods = {
140
237
  const inner = this.genExpr(expr.expr);
141
238
  const t = expr.targetType;
142
239
 
240
+ // Complex type conversions
241
+ if (t?.name === "complex128" || t?.name === "complex64") {
242
+ const srcType = expr.expr._type;
243
+ if (isComplex(srcType)) return inner; // complex→complex identity
244
+ return `{ re: ${inner}, im: 0 }`; // numeric→complex
245
+ }
246
+
247
+ // Array type conversion: [N]T(slice) → slice(0, N)
248
+ if (t?.kind === "ArrayType") {
249
+ const srcType = expr.expr._type;
250
+ const srcResolved =
251
+ srcType?.kind === "named" ? srcType.underlying : srcType;
252
+ if (srcResolved?.kind === "slice") {
253
+ const size =
254
+ t.size?.value !== undefined ? Number(t.size.value) : t.size;
255
+ return `${inner}.slice(0, ${size})`;
256
+ }
257
+ return inner;
258
+ }
259
+
143
260
  // Slice type conversions
144
261
  if (t?.kind === "SliceType") {
145
262
  const elem = t.elem?.name;
@@ -162,6 +279,11 @@ export const expressionGenMethods = {
162
279
  }
163
280
 
164
281
  const target = t?.name;
282
+ // error("msg") → __error("msg")
283
+ if (target === "error") {
284
+ this._usesError = true;
285
+ return `__error(${inner})`;
286
+ }
165
287
  switch (target) {
166
288
  case "string": {
167
289
  // Go string(65) → "A" (Unicode code point), not "65"
@@ -169,6 +291,14 @@ export const expressionGenMethods = {
169
291
  if (srcType && this.isIntType(srcType)) {
170
292
  return `String.fromCodePoint(${inner})`;
171
293
  }
294
+ // string([]byte) → decode byte array to string
295
+ if (
296
+ srcType &&
297
+ srcType.kind === "slice" &&
298
+ srcType.elem?.name === "int"
299
+ ) {
300
+ return `${inner}.map(c => String.fromCharCode(c)).join("")`;
301
+ }
172
302
  return `String(${inner})`;
173
303
  }
174
304
  case "int":
@@ -209,12 +339,16 @@ export const expressionGenMethods = {
209
339
  },
210
340
 
211
341
  genCall(expr) {
342
+ // Unwrap InstantiationExpr for function calls: Foo[int](42) → Foo(42)
343
+ const funcExpr =
344
+ expr.func.kind === "InstantiationExpr" ? expr.func.expr : expr.func;
212
345
  // Handle built-ins that need special JS translation
213
- if (expr.func.kind === "Ident") {
214
- switch (expr.func.name) {
346
+ if (funcExpr.kind === "Ident") {
347
+ switch (funcExpr.name) {
215
348
  case "append":
216
349
  return this.genAppend(expr);
217
350
  case "len": {
351
+ if (expr._constLen != null) return String(expr._constLen);
218
352
  const arg = expr.args[0];
219
353
  const t = arg?._type;
220
354
  const js = this.genExpr(arg);
@@ -250,9 +384,9 @@ export const expressionGenMethods = {
250
384
  case "recover":
251
385
  return `(typeof __panic !== "undefined" && __panic !== null ? (() => { const __r = __panic.message ?? String(__panic); __panic = null; return __r; })() : null)`;
252
386
  case "error": {
253
- // errors are plain strings; nil (null) means no error
387
+ this._usesError = true;
254
388
  const arg = this.genExpr(expr.args[0]);
255
- return arg;
389
+ return `__error(${arg})`;
256
390
  }
257
391
  case "min": {
258
392
  const args = expr.args.map((a) => this.genExpr(a)).join(", ");
@@ -274,225 +408,50 @@ export const expressionGenMethods = {
274
408
  }
275
409
  return `(${js}).length = 0`;
276
410
  }
411
+ case "complex": {
412
+ const re = this.genExpr(expr.args[0]);
413
+ const im = this.genExpr(expr.args[1]);
414
+ return `{ re: ${re}, im: ${im} }`;
415
+ }
416
+ case "real":
417
+ return `(${this.genExpr(expr.args[0])}).re`;
418
+ case "imag":
419
+ return `(${this.genExpr(expr.args[0])}).im`;
277
420
  }
278
421
  }
279
422
 
280
- // error.Error() → the error string itself (errors are plain strings at runtime)
423
+ // error.Error() → real method call
281
424
  if (
282
425
  expr.func.kind === "SelectorExpr" &&
283
426
  expr.func.field === "Error" &&
284
- expr.func.expr._type?.name === "error"
427
+ expr.func.expr._type &&
428
+ (expr.func.expr._type === globalError ||
429
+ expr.func.expr._type?.name === "error" ||
430
+ (expr.func.expr._type?.kind === "named" &&
431
+ expr.func.expr._type?.underlying?.kind === "interface"))
285
432
  ) {
286
- return this.genExpr(expr.func.expr);
433
+ return `${this.genExpr(expr.func.expr)}.Error()`;
287
434
  }
288
435
 
289
436
  // fmt.Sprintf / fmt.Printf / fmt.Println / fmt.Print / fmt.Errorf
290
- if (
291
- expr.func.kind === "SelectorExpr" &&
292
- expr.func.expr.kind === "Ident" &&
293
- expr.func.expr.name === "fmt"
294
- ) {
295
- const fmtArgs = expr.args.map((a) => this.genExpr(a)).join(", ");
296
- switch (expr.func.field) {
297
- case "Sprintf":
298
- this._usesSprintf = true;
299
- return `__sprintf(${fmtArgs})`;
300
- case "Errorf":
301
- this._usesSprintf = true;
302
- return `__sprintf(${fmtArgs})`;
303
- case "Printf":
304
- this._usesSprintf = true;
305
- return `process?.stdout?.write(__sprintf(${fmtArgs}))`;
306
- case "Println":
307
- this._usesSprintf = true;
308
- return `console.log(__sprintf(${fmtArgs}))`;
309
- case "Print":
310
- this._usesSprintf = true;
311
- return `process?.stdout?.write(__sprintf(${fmtArgs}))`;
312
- }
313
- }
314
-
315
- // strings.* — direct JS string method mappings
316
- if (
317
- expr.func.kind === "SelectorExpr" &&
318
- expr.func.expr.kind === "Ident" &&
319
- expr.func.expr.name === "strings"
320
- ) {
321
- const a = expr.args.map((e) => this.genExpr(e));
322
- switch (expr.func.field) {
323
- case "Contains":
324
- return `${a[0]}.includes(${a[1]})`;
325
- case "HasPrefix":
326
- return `${a[0]}.startsWith(${a[1]})`;
327
- case "HasSuffix":
328
- return `${a[0]}.endsWith(${a[1]})`;
329
- case "Index":
330
- return `${a[0]}.indexOf(${a[1]})`;
331
- case "LastIndex":
332
- return `${a[0]}.lastIndexOf(${a[1]})`;
333
- case "Count":
334
- return `${a[0]}.split(${a[1]}).length - 1`;
335
- case "Repeat":
336
- return `${a[0]}.repeat(${a[1]})`;
337
- case "Replace":
338
- return `${a[0]}.replace(${a[1]}, ${a[2]})`;
339
- case "ReplaceAll":
340
- return `${a[0]}.replaceAll(${a[1]}, ${a[2]})`;
341
- case "ToUpper":
342
- return `${a[0]}.toUpperCase()`;
343
- case "ToLower":
344
- return `${a[0]}.toLowerCase()`;
345
- case "TrimSpace":
346
- return `${a[0]}.trim()`;
347
- case "Trim":
348
- return `${a[0]}.replace(new RegExp(\`^[\${${a[1]}}]+|[\${${a[1]}}]+$\`, "g"), "")`;
349
- case "TrimPrefix":
350
- return `(${a[0]}.startsWith(${a[1]}) ? ${a[0]}.slice(${a[1]}.length) : ${a[0]})`;
351
- case "TrimSuffix":
352
- return `(${a[0]}.endsWith(${a[1]}) ? ${a[0]}.slice(0, -${a[1]}.length) : ${a[0]})`;
353
- case "TrimLeft":
354
- return `${a[0]}.replace(new RegExp(\`^[\${${a[1]}}]+\`), "")`;
355
- case "TrimRight":
356
- return `${a[0]}.replace(new RegExp(\`[\${${a[1]}}]+$\`), "")`;
357
- case "Split":
358
- return `${a[0]}.split(${a[1]})`;
359
- case "Join":
360
- return `${a[0]}.join(${a[1]})`;
361
- case "EqualFold":
362
- return `${a[0]}.toLowerCase() === ${a[1]}.toLowerCase()`;
363
- }
364
- }
365
-
366
- // strconv.* — number/string conversion
367
- if (
368
- expr.func.kind === "SelectorExpr" &&
369
- expr.func.expr.kind === "Ident" &&
370
- expr.func.expr.name === "strconv"
371
- ) {
372
- const a = expr.args.map((e) => this.genExpr(e));
373
- switch (expr.func.field) {
374
- case "Itoa":
375
- return `String(${a[0]})`;
376
- case "Atoi":
377
- return `(Number.isNaN(Number(${a[0]})) ? [0, "invalid syntax"] : [Number(${a[0]}) | 0, null])`;
378
- case "FormatBool":
379
- return `String(${a[0]})`;
380
- case "FormatInt":
381
- return `(${a[0]}).toString(${a[1]})`;
382
- case "FormatFloat":
383
- return `String(${a[0]})`;
384
- case "ParseFloat":
385
- return `(Number.isNaN(Number(${a[0]})) ? [0, "invalid syntax"] : [Number(${a[0]}), null])`;
386
- case "ParseInt":
387
- return `(Number.isNaN(parseInt(${a[0]}, ${a[1]} || 10)) ? [0, "invalid syntax"] : [parseInt(${a[0]}, ${a[1]} || 10), null])`;
388
- case "ParseBool":
389
- return `(${a[0]} === "true" || ${a[0]} === "1" ? [true, null] : ${a[0]} === "false" || ${a[0]} === "0" ? [false, null] : [false, "invalid syntax"])`;
390
- }
391
- }
392
-
393
- // sort.* — in-place sorting
394
- if (
395
- expr.func.kind === "SelectorExpr" &&
396
- expr.func.expr.kind === "Ident" &&
397
- expr.func.expr.name === "sort"
398
- ) {
399
- const a = expr.args.map((e) => this.genExpr(e));
400
- switch (expr.func.field) {
401
- case "Ints":
402
- case "Float64s":
403
- return `${a[0]}.sort((a, b) => a - b)`;
404
- case "Strings":
405
- return `${a[0]}.sort()`;
406
- case "Slice":
407
- return `${a[0]}.sort((a, b) => ${a[1]}(a, b) ? -1 : ${a[1]}(b, a) ? 1 : 0)`;
408
- case "SliceStable":
409
- return `${a[0]}.sort((a, b) => ${a[1]}(a, b) ? -1 : ${a[1]}(b, a) ? 1 : 0)`;
410
- case "SliceIsSorted":
411
- return `${a[0]}.every((v, i, a) => i === 0 || ${a[1]}(a[i - 1], v))`;
412
- }
413
- }
414
-
415
- // math.* — math functions
416
- if (
417
- expr.func.kind === "SelectorExpr" &&
418
- expr.func.expr.kind === "Ident" &&
419
- expr.func.expr.name === "math"
420
- ) {
421
- const a = expr.args.map((e) => this.genExpr(e));
422
- switch (expr.func.field) {
423
- case "Abs":
424
- return `Math.abs(${a[0]})`;
425
- case "Floor":
426
- return `Math.floor(${a[0]})`;
427
- case "Ceil":
428
- return `Math.ceil(${a[0]})`;
429
- case "Round":
430
- return `Math.round(${a[0]})`;
431
- case "Sqrt":
432
- return `Math.sqrt(${a[0]})`;
433
- case "Cbrt":
434
- return `Math.cbrt(${a[0]})`;
435
- case "Pow":
436
- return `Math.pow(${a[0]}, ${a[1]})`;
437
- case "Log":
438
- return `Math.log(${a[0]})`;
439
- case "Log2":
440
- return `Math.log2(${a[0]})`;
441
- case "Log10":
442
- return `Math.log10(${a[0]})`;
443
- case "Sin":
444
- return `Math.sin(${a[0]})`;
445
- case "Cos":
446
- return `Math.cos(${a[0]})`;
447
- case "Tan":
448
- return `Math.tan(${a[0]})`;
449
- case "Min":
450
- return `Math.min(${a[0]}, ${a[1]})`;
451
- case "Max":
452
- return `Math.max(${a[0]}, ${a[1]})`;
453
- case "Mod":
454
- return `${a[0]} % ${a[1]}`;
455
- case "Inf":
456
- return `(${a[0]} >= 0 ? Infinity : -Infinity)`;
457
- case "IsNaN":
458
- return `Number.isNaN(${a[0]})`;
459
- case "IsInf":
460
- return `(${a[1]} > 0 ? ${a[0]} === Infinity : ${a[1]} < 0 ? ${a[0]} === -Infinity : !Number.isFinite(${a[0]}))`;
461
- case "NaN":
462
- return "NaN";
463
- }
464
- }
465
-
466
- // errors.New — identity (errors are plain strings)
467
- if (
468
- expr.func.kind === "SelectorExpr" &&
469
- expr.func.expr.kind === "Ident" &&
470
- expr.func.expr.name === "errors"
471
- ) {
472
- const a = expr.args.map((e) => this.genExpr(e));
473
- if (expr.func.field === "New") return a[0];
474
- }
475
-
476
- // time.* — partial JS-friendly time support
477
- if (
478
- expr.func.kind === "SelectorExpr" &&
479
- expr.func.expr.kind === "Ident" &&
480
- expr.func.expr.name === "time"
481
- ) {
482
- const a = expr.args.map((e) => this.genExpr(e));
483
- switch (expr.func.field) {
484
- case "Now":
485
- return "Date.now()";
486
- case "Since":
487
- return `(Date.now() - ${a[0]})`;
488
- case "Sleep":
489
- return `await new Promise(r => setTimeout(r, ${a[0]} / 1000000))`;
490
- }
437
+ // Standard library namespace dispatch — all follow the pattern:
438
+ // pkg.Func(args) → inline JS
439
+ if (expr.func.kind === "SelectorExpr" && expr.func.expr.kind === "Ident") {
440
+ const ns = expr.func.expr.name;
441
+ const fn = expr.func.field;
442
+ const result = this._genStdlibCall(ns, fn, expr);
443
+ if (result !== undefined) return result;
491
444
  }
492
445
 
446
+ // Mark the callee so SelectorExpr knows it's being called, not used as a value
447
+ expr.func._callee = true;
493
448
  const rawFn = this.genExpr(expr.func);
494
449
  // Wrap function literals in parens so `function(){}()` → `(function(){})()`
495
450
  const fn = expr.func.kind === "FuncLit" ? `(${rawFn})` : rawFn;
451
+ // Multi-value forwarding: f(g()) where g() returns multiple values → f(...g())
452
+ if (expr._multiForward) {
453
+ return `${fn}(...${this.genExpr(expr.args[0])})`;
454
+ }
496
455
  const args = expr.args
497
456
  .map((a) => (a._spread ? `...${this.genExpr(a)}` : this.genExpr(a)))
498
457
  .join(", ");
@@ -537,34 +496,40 @@ export const expressionGenMethods = {
537
496
  return "{}";
538
497
  },
539
498
 
540
- // Render KeyValueExpr elements as a JS field list string.
541
- // Handles embedded-spread (_isEmbedInit) and plain key: value pairs.
499
+ // Render struct elements as a JS field list string.
500
+ // Handles positional (_positionalField), embedded-spread (_isEmbedInit), and keyed (KeyValueExpr).
542
501
  _genStructFields(elems) {
543
502
  return elems
544
- .filter((e) => e.kind === "KeyValueExpr")
545
- .map((e) =>
546
- e._isEmbedInit
547
- ? `...${this.genExpr(e.value)}`
548
- : `${e.key.name ?? this.genExpr(e.key)}: ${this.genExpr(e.value)}`,
549
- )
503
+ .map((e) => {
504
+ if (e._positionalField) {
505
+ return `${e._positionalField}: ${this.genExpr(e)}`;
506
+ }
507
+ if (e.kind !== "KeyValueExpr") return null;
508
+ if (e._isEmbedInit) return `...${this.genExpr(e.value)}`;
509
+ return `${e.key.name ?? this.genExpr(e.key)}: ${this.genExpr(e.value)}`;
510
+ })
511
+ .filter((s) => s !== null)
550
512
  .join(", ");
551
513
  },
552
514
 
553
515
  genCompositeLit(expr) {
554
516
  const t = expr.typeExpr;
555
517
 
556
- // Implicit composite literal: {X: 1} inside a slice/map — type inferred from context
518
+ // Implicit composite literal: {X: 1} or {1, 2} inside a slice/map — type inferred from context
557
519
  if (t === null) {
558
- if (expr.elems.length > 0 && expr.elems[0]?.kind === "KeyValueExpr") {
559
- const typeName = expr._type?.name ?? expr._type?.underlying?.name;
520
+ const typeName = expr._type?.name ?? expr._type?.underlying?.name;
521
+ const hasPositional = expr.elems.some((e) => e._positionalField);
522
+ const hasKeyed = expr.elems.some((e) => e.kind === "KeyValueExpr");
523
+ if (hasPositional || hasKeyed) {
560
524
  if (typeName && this.structNames.has(typeName)) {
561
525
  return `new ${typeName}({ ${this._genStructFields(expr.elems)} })`;
562
526
  }
563
527
  const fields = expr.elems
564
- .map(
565
- (e) =>
566
- `${e.key.name ?? this.genExpr(e.key)}: ${this.genExpr(e.value)}`,
567
- )
528
+ .map((e) => {
529
+ if (e._positionalField)
530
+ return `${e._positionalField}: ${this.genExpr(e)}`;
531
+ return `${e.key.name ?? this.genExpr(e.key)}: ${this.genExpr(e.value)}`;
532
+ })
568
533
  .join(", ");
569
534
  return `{ ${fields} }`;
570
535
  }
@@ -573,11 +538,18 @@ export const expressionGenMethods = {
573
538
 
574
539
  const typeName = this.getTypeName(t);
575
540
 
576
- // Struct: new Foo({ X: 1, Y: 2 })
541
+ // Struct: new Foo({ X: 1, Y: 2 }) — keyed or positional
577
542
  if (typeName && this.structNames.has(typeName)) {
578
543
  return `new ${typeName}({ ${this._genStructFields(expr.elems)} })`;
579
544
  }
580
545
 
546
+ // Fallback: named type that's not a struct but has positional elements → treat as struct
547
+ if (expr.elems.some((e) => e._positionalField)) {
548
+ if (typeName) {
549
+ return `new ${typeName}({ ${this._genStructFields(expr.elems)} })`;
550
+ }
551
+ }
552
+
581
553
  // Slice/array: [1, 2, 3]
582
554
  if (t?.kind === "SliceType" || t?.kind === "ArrayType") {
583
555
  const elems = expr.elems
@@ -620,10 +592,18 @@ export const expressionGenMethods = {
620
592
 
621
593
  // ── Helpers ───────────────────────────────────────────────────
622
594
 
595
+ _genComplexOperand(expr) {
596
+ if (isComplex(expr._type)) return this.genExpr(expr);
597
+ return `{ re: ${this.genExpr(expr)}, im: 0 }`;
598
+ },
599
+
623
600
  getTypeName(typeNode) {
624
601
  if (!typeNode) return null;
625
602
  if (typeNode.kind === "TypeName") return typeNode.name;
603
+ if (typeNode.kind === "GenericTypeName") return typeNode.name;
626
604
  if (typeNode.kind === "Ident") return typeNode.name;
605
+ if (typeNode.kind === "InstantiationExpr")
606
+ return this.getTypeName(typeNode.expr);
627
607
  if (typeNode.kind === "SelectorExpr") return typeNode.field;
628
608
  return null;
629
609
  },
@@ -664,6 +644,9 @@ export const expressionGenMethods = {
664
644
  case "byte":
665
645
  case "rune":
666
646
  return "0";
647
+ case "complex64":
648
+ case "complex128":
649
+ return "{ re: 0, im: 0 }";
667
650
  case "string":
668
651
  return '""';
669
652
  case "bool":
@@ -710,10 +693,8 @@ export const expressionGenMethods = {
710
693
  zeroValueForType(t) {
711
694
  if (!t) return "null";
712
695
  switch (t.kind) {
713
- case "basic": {
714
- const basic = this._zeroForBasicName(t.name);
715
- return basic !== null ? basic : "null";
716
- }
696
+ case "basic":
697
+ return this._zeroForBasicName(t.name) ?? "null";
717
698
  case "slice":
718
699
  return "null";
719
700
  case "map":
@@ -760,7 +741,7 @@ export const expressionGenMethods = {
760
741
  case "nil":
761
742
  return `${val} === null`;
762
743
  case "error":
763
- return `typeof ${val} === "string"`;
744
+ return `(typeof ${val} === "object" && ${val} !== null && typeof ${val}.Error === "function")`;
764
745
  default:
765
746
  if (this.structNames.has(typeNode.name))
766
747
  return `${val} instanceof ${typeNode.name}`;
@@ -788,6 +769,13 @@ export const expressionGenMethods = {
788
769
  return "null";
789
770
  },
790
771
 
772
+ // Returns true if the type is a struct or array (uses value comparison with __equal).
773
+ _isStructOrArrayType(t) {
774
+ if (!t) return false;
775
+ const base = t.kind === "named" ? t.underlying : t;
776
+ return base?.kind === "struct" || base?.kind === "array";
777
+ },
778
+
791
779
  typeComment(typeNode) {
792
780
  if (!typeNode) return "unknown";
793
781
  switch (typeNode.kind) {
@@ -801,4 +789,311 @@ export const expressionGenMethods = {
801
789
  return typeNode.kind;
802
790
  }
803
791
  },
792
+
793
+ // Dispatch table for stdlib namespace calls (pkg.Func → inline JS).
794
+ // Returns the generated JS string, or undefined if not handled.
795
+ _genStdlibCall(ns, fn, expr) {
796
+ const a = () => expr.args.map((e) => this.genExpr(e));
797
+ switch (ns) {
798
+ case "fmt":
799
+ return this._genFmt(fn, a, expr);
800
+ case "strings":
801
+ return this._genStrings(fn, a);
802
+ case "bytes":
803
+ return this._genBytes(fn, a);
804
+ case "strconv":
805
+ return this._genStrconv(fn, a);
806
+ case "sort":
807
+ return this._genSort(fn, a);
808
+ case "math":
809
+ return this._genMath(fn, a);
810
+ case "unicode":
811
+ return this._genUnicode(fn, a);
812
+ case "os":
813
+ return this._genOs(fn, a);
814
+ case "errors":
815
+ return this._genErrors(fn, a);
816
+ case "time":
817
+ return this._genTime(fn, a);
818
+ default:
819
+ return undefined;
820
+ }
821
+ },
822
+
823
+ _genFmt(fn, _a, expr) {
824
+ const fmtArgs = expr.args.map((e) => this.genExpr(e)).join(", ");
825
+ switch (fn) {
826
+ case "Sprintf":
827
+ this._usesSprintf = true;
828
+ return `__sprintf(${fmtArgs})`;
829
+ case "Errorf": {
830
+ this._usesSprintf = true;
831
+ this._usesError = true;
832
+ const fmtStr = expr.args[0];
833
+ if (fmtStr?.kind === "BasicLit" && fmtStr.value?.includes("%w")) {
834
+ const lastArg = this.genExpr(expr.args[expr.args.length - 1]);
835
+ return `__error(__sprintf(${fmtArgs}), ${lastArg})`;
836
+ }
837
+ return `__error(__sprintf(${fmtArgs}))`;
838
+ }
839
+ case "Printf":
840
+ case "Print":
841
+ this._usesSprintf = true;
842
+ return `process?.stdout?.write(__sprintf(${fmtArgs}))`;
843
+ case "Println":
844
+ this._usesSprintf = true;
845
+ return `console.log(__sprintf(${fmtArgs}))`;
846
+ default:
847
+ return undefined;
848
+ }
849
+ },
850
+
851
+ _genStrings(fn, a) {
852
+ const args = a();
853
+ switch (fn) {
854
+ case "Contains":
855
+ return `${args[0]}.includes(${args[1]})`;
856
+ case "HasPrefix":
857
+ return `${args[0]}.startsWith(${args[1]})`;
858
+ case "HasSuffix":
859
+ return `${args[0]}.endsWith(${args[1]})`;
860
+ case "Index":
861
+ return `${args[0]}.indexOf(${args[1]})`;
862
+ case "LastIndex":
863
+ return `${args[0]}.lastIndexOf(${args[1]})`;
864
+ case "Count":
865
+ return `${args[0]}.split(${args[1]}).length - 1`;
866
+ case "Repeat":
867
+ return `${args[0]}.repeat(${args[1]})`;
868
+ case "Replace":
869
+ return `${args[0]}.replace(${args[1]}, ${args[2]})`;
870
+ case "ReplaceAll":
871
+ return `${args[0]}.replaceAll(${args[1]}, ${args[2]})`;
872
+ case "ToUpper":
873
+ return `${args[0]}.toUpperCase()`;
874
+ case "ToLower":
875
+ return `${args[0]}.toLowerCase()`;
876
+ case "TrimSpace":
877
+ return `${args[0]}.trim()`;
878
+ case "Trim":
879
+ return `${args[0]}.replace(new RegExp(\`^[\${${args[1]}}]+|[\${${args[1]}}]+$\`, "g"), "")`;
880
+ case "TrimPrefix":
881
+ return `(${args[0]}.startsWith(${args[1]}) ? ${args[0]}.slice(${args[1]}.length) : ${args[0]})`;
882
+ case "TrimSuffix":
883
+ return `(${args[0]}.endsWith(${args[1]}) ? ${args[0]}.slice(0, -${args[1]}.length) : ${args[0]})`;
884
+ case "TrimLeft":
885
+ return `${args[0]}.replace(new RegExp(\`^[\${${args[1]}}]+\`), "")`;
886
+ case "TrimRight":
887
+ return `${args[0]}.replace(new RegExp(\`[\${${args[1]}}]+$\`), "")`;
888
+ case "Split":
889
+ return `${args[0]}.split(${args[1]})`;
890
+ case "Join":
891
+ return `${args[0]}.join(${args[1]})`;
892
+ case "EqualFold":
893
+ return `${args[0]}.toLowerCase() === ${args[1]}.toLowerCase()`;
894
+ default:
895
+ return undefined;
896
+ }
897
+ },
898
+
899
+ _genBytes(fn, a) {
900
+ const __bs = `(b => String.fromCharCode(...b))`;
901
+ const __sb = `(s => [...s].map(c => c.charCodeAt(0)))`;
902
+ const args = a();
903
+ switch (fn) {
904
+ case "Contains":
905
+ return `${__bs}(${args[0]}).includes(${__bs}(${args[1]}))`;
906
+ case "HasPrefix":
907
+ return `((b, p) => { for (let i = 0; i < p.length; i++) if (b[i] !== p[i]) return false; return b.length >= p.length; })(${args[0]}, ${args[1]})`;
908
+ case "HasSuffix":
909
+ return `((b, s) => { const off = b.length - s.length; if (off < 0) return false; for (let i = 0; i < s.length; i++) if (b[off + i] !== s[i]) return false; return true; })(${args[0]}, ${args[1]})`;
910
+ case "Index":
911
+ return `${__bs}(${args[0]}).indexOf(${__bs}(${args[1]}))`;
912
+ case "Count":
913
+ return `${__bs}(${args[0]}).split(${__bs}(${args[1]})).length - 1`;
914
+ case "Repeat":
915
+ return `${__sb}(${__bs}(${args[0]}).repeat(${args[1]}))`;
916
+ case "Replace":
917
+ return `((b, o, n, cnt) => { let s = ${__bs}(b), os = ${__bs}(o), ns = ${__bs}(n); if (cnt < 0) return ${__sb}(s.replaceAll(os, ns)); for (let i = 0; i < cnt; i++) s = s.replace(os, ns); return ${__sb}(s); })(${args[0]}, ${args[1]}, ${args[2]}, ${args[3]})`;
918
+ case "ToUpper":
919
+ return `${__sb}(${__bs}(${args[0]}).toUpperCase())`;
920
+ case "ToLower":
921
+ return `${__sb}(${__bs}(${args[0]}).toLowerCase())`;
922
+ case "TrimSpace":
923
+ return `${__sb}(${__bs}(${args[0]}).trim())`;
924
+ case "Trim":
925
+ return `${__sb}(${__bs}(${args[0]}).replace(new RegExp(\`^[\${${args[1]}}]+|[\${${args[1]}}]+$\`, "g"), ""))`;
926
+ case "Equal":
927
+ return `((a, b) => { if (a.length !== b.length) return false; for (let i = 0; i < a.length; i++) if (a[i] !== b[i]) return false; return true; })(${args[0]}, ${args[1]})`;
928
+ case "Split":
929
+ return `${__bs}(${args[0]}).split(${__bs}(${args[1]})).map(p => ${__sb}(p))`;
930
+ case "Join":
931
+ return `${__sb}(${args[0]}.map(p => ${__bs}(p)).join(${__bs}(${args[1]})))`;
932
+ default:
933
+ return undefined;
934
+ }
935
+ },
936
+
937
+ _genStrconv(fn, a) {
938
+ const args = a();
939
+ switch (fn) {
940
+ case "Itoa":
941
+ return `String(${args[0]})`;
942
+ case "Atoi":
943
+ return `(Number.isNaN(Number(${args[0]})) ? [0, "invalid syntax"] : [Number(${args[0]}) | 0, null])`;
944
+ case "FormatBool":
945
+ return `String(${args[0]})`;
946
+ case "FormatInt":
947
+ return `(${args[0]}).toString(${args[1]})`;
948
+ case "FormatFloat":
949
+ return `String(${args[0]})`;
950
+ case "ParseFloat":
951
+ return `(Number.isNaN(Number(${args[0]})) ? [0, "invalid syntax"] : [Number(${args[0]}), null])`;
952
+ case "ParseInt":
953
+ return `(Number.isNaN(parseInt(${args[0]}, ${args[1]} || 10)) ? [0, "invalid syntax"] : [parseInt(${args[0]}, ${args[1]} || 10), null])`;
954
+ case "ParseBool":
955
+ return `(${args[0]} === "true" || ${args[0]} === "1" ? [true, null] : ${args[0]} === "false" || ${args[0]} === "0" ? [false, null] : [false, "invalid syntax"])`;
956
+ default:
957
+ return undefined;
958
+ }
959
+ },
960
+
961
+ _genSort(fn, a) {
962
+ const args = a();
963
+ switch (fn) {
964
+ case "Ints":
965
+ case "Float64s":
966
+ return `${args[0]}.sort((a, b) => a - b)`;
967
+ case "Strings":
968
+ return `${args[0]}.sort()`;
969
+ case "Slice":
970
+ case "SliceStable":
971
+ return `${args[0]}.sort((a, b) => ${args[1]}(a, b) ? -1 : ${args[1]}(b, a) ? 1 : 0)`;
972
+ case "SliceIsSorted":
973
+ return `${args[0]}.every((v, i, a) => i === 0 || ${args[1]}(a[i - 1], v))`;
974
+ default:
975
+ return undefined;
976
+ }
977
+ },
978
+
979
+ _genMath(fn, a) {
980
+ const args = a();
981
+ switch (fn) {
982
+ case "Abs":
983
+ return `Math.abs(${args[0]})`;
984
+ case "Floor":
985
+ return `Math.floor(${args[0]})`;
986
+ case "Ceil":
987
+ return `Math.ceil(${args[0]})`;
988
+ case "Round":
989
+ return `Math.round(${args[0]})`;
990
+ case "Sqrt":
991
+ return `Math.sqrt(${args[0]})`;
992
+ case "Cbrt":
993
+ return `Math.cbrt(${args[0]})`;
994
+ case "Pow":
995
+ return `Math.pow(${args[0]}, ${args[1]})`;
996
+ case "Log":
997
+ return `Math.log(${args[0]})`;
998
+ case "Log2":
999
+ return `Math.log2(${args[0]})`;
1000
+ case "Log10":
1001
+ return `Math.log10(${args[0]})`;
1002
+ case "Sin":
1003
+ return `Math.sin(${args[0]})`;
1004
+ case "Cos":
1005
+ return `Math.cos(${args[0]})`;
1006
+ case "Tan":
1007
+ return `Math.tan(${args[0]})`;
1008
+ case "Min":
1009
+ return `Math.min(${args[0]}, ${args[1]})`;
1010
+ case "Max":
1011
+ return `Math.max(${args[0]}, ${args[1]})`;
1012
+ case "Mod":
1013
+ return `${args[0]} % ${args[1]}`;
1014
+ case "Inf":
1015
+ return `(${args[0]} >= 0 ? Infinity : -Infinity)`;
1016
+ case "IsNaN":
1017
+ return `Number.isNaN(${args[0]})`;
1018
+ case "IsInf":
1019
+ return `(${args[1]} > 0 ? ${args[0]} === Infinity : ${args[1]} < 0 ? ${args[0]} === -Infinity : !Number.isFinite(${args[0]}))`;
1020
+ case "NaN":
1021
+ return "NaN";
1022
+ default:
1023
+ return undefined;
1024
+ }
1025
+ },
1026
+
1027
+ _genUnicode(fn, a) {
1028
+ const args = a();
1029
+ const cp = `String.fromCodePoint(${args[0]})`;
1030
+ switch (fn) {
1031
+ case "IsLetter":
1032
+ return `/\\p{L}/u.test(${cp})`;
1033
+ case "IsDigit":
1034
+ return `/\\p{Nd}/u.test(${cp})`;
1035
+ case "IsSpace":
1036
+ return `/\\s/.test(${cp})`;
1037
+ case "IsUpper":
1038
+ return `((__c) => __c === __c.toUpperCase() && /\\p{L}/u.test(__c))(${cp})`;
1039
+ case "IsLower":
1040
+ return `((__c) => __c === __c.toLowerCase() && /\\p{L}/u.test(__c))(${cp})`;
1041
+ case "IsPunct":
1042
+ return `/\\p{P}/u.test(${cp})`;
1043
+ case "IsControl":
1044
+ return `/\\p{Cc}/u.test(${cp})`;
1045
+ case "IsPrint":
1046
+ return `!/\\p{Cc}/u.test(${cp})`;
1047
+ case "IsGraphic":
1048
+ return `!/\\p{Cc}/u.test(${cp})`;
1049
+ case "ToUpper":
1050
+ return `${cp}.toUpperCase().codePointAt(0)`;
1051
+ case "ToLower":
1052
+ return `${cp}.toLowerCase().codePointAt(0)`;
1053
+ default:
1054
+ return undefined;
1055
+ }
1056
+ },
1057
+
1058
+ _genOs(fn, a) {
1059
+ const args = a();
1060
+ switch (fn) {
1061
+ case "Exit":
1062
+ return `process.exit(${args[0]})`;
1063
+ case "Getenv":
1064
+ return `(process.env[${args[0]}] ?? "")`;
1065
+ default:
1066
+ return undefined;
1067
+ }
1068
+ },
1069
+
1070
+ _genErrors(fn, a) {
1071
+ const args = a();
1072
+ switch (fn) {
1073
+ case "New":
1074
+ this._usesError = true;
1075
+ return `__error(${args[0]})`;
1076
+ case "Is":
1077
+ this._usesErrorIs = true;
1078
+ return `__errorIs(${args[0]}, ${args[1]})`;
1079
+ case "Unwrap":
1080
+ return `(${args[0]}?._cause ?? null)`;
1081
+ default:
1082
+ return undefined;
1083
+ }
1084
+ },
1085
+
1086
+ _genTime(fn, a) {
1087
+ const args = a();
1088
+ switch (fn) {
1089
+ case "Now":
1090
+ return "Date.now()";
1091
+ case "Since":
1092
+ return `(Date.now() - ${args[0]})`;
1093
+ case "Sleep":
1094
+ return `await new Promise(r => setTimeout(r, ${args[0]} / 1000000))`;
1095
+ default:
1096
+ return undefined;
1097
+ }
1098
+ },
804
1099
  };