gofront 0.0.9 → 1.0.1

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.
Files changed (53) hide show
  1. package/README.md +5 -5
  2. package/package.json +7 -4
  3. package/src/cli-core.js +65 -0
  4. package/src/codegen/expressions.js +523 -1717
  5. package/src/codegen/index.js +576 -0
  6. package/src/codegen/runtime.js +151 -0
  7. package/src/codegen/statements.js +308 -296
  8. package/src/codegen/stdlib/builder.js +39 -0
  9. package/src/codegen/stdlib/bytes.js +65 -0
  10. package/src/codegen/stdlib/errors.js +20 -0
  11. package/src/codegen/stdlib/fmt.js +104 -0
  12. package/src/codegen/stdlib/gom.js +155 -0
  13. package/src/codegen/stdlib/html.js +20 -0
  14. package/src/codegen/stdlib/index.js +98 -0
  15. package/src/codegen/stdlib/io.js +36 -0
  16. package/src/codegen/stdlib/maps.js +45 -0
  17. package/src/codegen/stdlib/math.js +56 -0
  18. package/src/codegen/stdlib/os.js +13 -0
  19. package/src/codegen/stdlib/path.js +34 -0
  20. package/src/codegen/stdlib/rand.js +27 -0
  21. package/src/codegen/stdlib/regexp.js +54 -0
  22. package/src/codegen/stdlib/slices.js +51 -0
  23. package/src/codegen/stdlib/sort.js +31 -0
  24. package/src/codegen/stdlib/strconv.js +33 -0
  25. package/src/codegen/stdlib/strings.js +86 -0
  26. package/src/codegen/stdlib/time.js +57 -0
  27. package/src/codegen/stdlib/unicode.js +25 -0
  28. package/src/codegen/stdlib/utf8.js +26 -0
  29. package/src/codegen/templ.js +284 -0
  30. package/src/compiler.js +142 -77
  31. package/src/dts-parser.js +355 -371
  32. package/src/index.js +32 -162
  33. package/src/lexer.js +186 -272
  34. package/src/minifier.js +305 -318
  35. package/src/{parser.js → parser/declarations.js} +18 -175
  36. package/src/parser/expressions.js +51 -34
  37. package/src/parser/index.js +161 -0
  38. package/src/parser/statements.js +24 -34
  39. package/src/parser/types.js +31 -29
  40. package/src/templ-parser.js +2 -2
  41. package/src/tokens.js +109 -0
  42. package/src/typechecker/assignability.js +261 -0
  43. package/src/typechecker/expressions.js +568 -549
  44. package/src/typechecker/index.js +556 -0
  45. package/src/typechecker/resolve.js +428 -0
  46. package/src/typechecker/statements.js +270 -280
  47. package/src/typechecker/stdlib/core.js +592 -0
  48. package/src/typechecker/stdlib/extended.js +332 -0
  49. package/src/typechecker/stdlib.js +9 -1060
  50. package/src/typechecker/termination.js +86 -0
  51. package/src/typechecker/types.js +1 -0
  52. package/src/codegen.js +0 -882
  53. package/src/typechecker.js +0 -1299
@@ -2,6 +2,59 @@
2
2
 
3
3
  import { ERROR, isComplex } from "../typechecker/types.js";
4
4
 
5
+ /** @typedef {import('./index.js').CodeGen} CodeGen */
6
+
7
+ // Namespace constants: pkg.Field → JS literal
8
+ const NS_CONSTANTS = {
9
+ math: {
10
+ Pi: "Math.PI",
11
+ E: "Math.E",
12
+ MaxFloat64: "Number.MAX_VALUE",
13
+ SmallestNonzeroFloat64: "Number.MIN_VALUE",
14
+ MaxInt: "Number.MAX_SAFE_INTEGER",
15
+ MinInt: "Number.MIN_SAFE_INTEGER",
16
+ },
17
+ io: {
18
+ EOF: '"EOF"',
19
+ Discard:
20
+ "{ WriteString(s) { return s.length; }, Write(b) { return b.length; } }",
21
+ },
22
+ os: { Args: "process.argv" },
23
+ time: {
24
+ Millisecond: "1000000",
25
+ Second: "1000000000",
26
+ Minute: "60000000000",
27
+ Hour: "3600000000000",
28
+ RFC3339: '"2006-01-02T15:04:05Z07:00"',
29
+ RFC3339Nano: '"2006-01-02T15:04:05.999999999Z07:00"',
30
+ DateOnly: '"2006-01-02"',
31
+ TimeOnly: '"15:04:05"',
32
+ DateTime: '"2006-01-02 15:04:05"',
33
+ UTC: "null",
34
+ Local: "null",
35
+ January: "1",
36
+ February: "2",
37
+ March: "3",
38
+ April: "4",
39
+ May: "5",
40
+ June: "6",
41
+ July: "7",
42
+ August: "8",
43
+ September: "9",
44
+ October: "10",
45
+ November: "11",
46
+ December: "12",
47
+ Sunday: "0",
48
+ Monday: "1",
49
+ Tuesday: "2",
50
+ Wednesday: "3",
51
+ Thursday: "4",
52
+ Friday: "5",
53
+ Saturday: "6",
54
+ },
55
+ utf8: { RuneError: "0xFFFD", MaxRune: "0x10FFFF", UTFMax: "4" },
56
+ };
57
+
5
58
  const INT_TYPE_NAMES = new Set([
6
59
  "int",
7
60
  "uint",
@@ -18,553 +71,326 @@ const INT_TYPE_NAMES = new Set([
18
71
  "rune",
19
72
  ]);
20
73
 
74
+ // Zero-value JS literals for basic Go types.
75
+ const ZERO_FOR_BASIC = {
76
+ int: "0",
77
+ uint: "0",
78
+ int8: "0",
79
+ int16: "0",
80
+ int32: "0",
81
+ int64: "0",
82
+ uint8: "0",
83
+ uint16: "0",
84
+ uint32: "0",
85
+ uint64: "0",
86
+ uintptr: "0",
87
+ float32: "0",
88
+ float64: "0",
89
+ byte: "0",
90
+ rune: "0",
91
+ complex64: "{ re: 0, im: 0 }",
92
+ complex128: "{ re: 0, im: 0 }",
93
+ string: '""',
94
+ bool: "false",
95
+ };
96
+
97
+ // Set of all numeric type names (for type-assertion codegen).
98
+ const TYPEOF_NUMBER_NAMES = new Set([...INT_TYPE_NAMES, "float32", "float64"]);
99
+
100
+ // Builtin function codegen — keyed by builtin name, value is (self, expr) => string.
101
+ const BUILTIN_GEN = {
102
+ append: (s, e) => s.genAppend(e),
103
+ len: (s, e) => s._genBuiltinLen(e),
104
+ cap: (s, e) => `${s.genExpr(e.args[0])}.length`,
105
+ make: (s, e) => s.genMake(e),
106
+ delete: (s, e) => {
107
+ const [m, k] = e.args.map((a) => s.genExpr(a));
108
+ return `(delete ${m}[${k}])`;
109
+ },
110
+ copy: (s, e) => {
111
+ const [dst, src] = e.args.map((a) => s.genExpr(a));
112
+ return `((__cd,__cs)=>{const n=Math.min(__cd.length,__cs.length);__cd.splice(0,n,...__cs.slice(0,n));return n;})(${dst},${src})`;
113
+ },
114
+ new: (s, e) => `{ value: ${s.zeroValueForExpr(e.args[0])} }`,
115
+ print: (s, e) => `console.log(${e.args.map((a) => s.genExpr(a)).join(", ")})`,
116
+ println: (s, e) =>
117
+ `console.log(${e.args.map((a) => s.genExpr(a)).join(", ")})`,
118
+ panic: (s, e) => `(() => { throw new Error(${s.genExpr(e.args[0])}); })()`,
119
+ recover: () =>
120
+ `(typeof __panic !== "undefined" && __panic !== null ? (() => { const __r = __panic.message ?? String(__panic); __panic = null; return __r; })() : null)`,
121
+ error: (s, e) => {
122
+ s._usesError = true;
123
+ return `__error(${s.genExpr(e.args[0])})`;
124
+ },
125
+ min: (s, e) => `Math.min(${e.args.map((a) => s.genExpr(a)).join(", ")})`,
126
+ max: (s, e) => `Math.max(${e.args.map((a) => s.genExpr(a)).join(", ")})`,
127
+ clear: (s, e) => s._genBuiltinClear(e),
128
+ complex: (s, e) =>
129
+ `{ re: ${s.genExpr(e.args[0])}, im: ${s.genExpr(e.args[1])} }`,
130
+ real: (s, e) => `(${s.genExpr(e.args[0])}).re`,
131
+ imag: (s, e) => `(${s.genExpr(e.args[0])}).im`,
132
+ };
133
+
134
+ // Method-name dispatch for genExpr — string values add no static call edges.
135
+ const EXPR_GEN_DELEGATE = {
136
+ UnaryExpr: "_genUnaryExpr",
137
+ BinaryExpr: "_genBinaryExpr",
138
+ CallExpr: "genCall",
139
+ SelectorExpr: "_genSelectorExpr",
140
+ IndexExpr: "_genIndexExpr",
141
+ SliceExpr: "_genSliceExpr",
142
+ CompositeLit: "genCompositeLit",
143
+ FuncLit: "_genFuncLit",
144
+ TypeConversion: "_genTypeConversion",
145
+ TypeAssertExpr: "_genTypeAssertExpr",
146
+ };
147
+
148
+ /** @type {ThisType<CodeGen>} */
21
149
  export const expressionGenMethods = {
22
150
  genExpr(expr) {
23
151
  switch (expr.kind) {
24
152
  case "BasicLit":
25
153
  if (expr.litKind === "STRING") return JSON.stringify(expr.value);
26
154
  return expr.value; // int, float, bool, nil(null)
27
-
28
155
  case "ImagLit":
29
156
  return `{ re: 0, im: ${expr.value} }`;
30
-
31
157
  case "Ident":
32
158
  if (this._boxedVars.has(expr.name) && !expr._isAddressOf)
33
159
  return `${expr.name}.value`;
34
160
  return expr.name;
35
-
36
- case "UnaryExpr": {
37
- const op = expr.op === "^" ? "~" : expr.op; // bitwise NOT
38
- // Address-of: &x
39
- if (op === "&") {
40
- if (expr.operand.kind === "Ident") {
41
- // Mark so Ident codegen does NOT append .value
42
- expr.operand._isAddressOf = true;
43
- // For boxed vars, &x returns the box itself
44
- if (this._boxedVars.has(expr.operand.name)) {
45
- return this.genExpr(expr.operand);
46
- }
47
- }
48
- // For non-boxed vars (structs, etc.), wrap in { value: x }
49
- return `{ value: ${this.genExpr(expr.operand)} }`;
50
- }
51
- // Dereference: *p → p.value
52
- if (op === "*") {
53
- return `${this.genExpr(expr.operand)}.value`;
54
- }
55
- // Unary minus/plus on complex
56
- if ((op === "-" || op === "+") && isComplex(expr.operand._type)) {
57
- const inner = this.genExpr(expr.operand);
58
- if (op === "-") return `{ re: -${inner}.re, im: -${inner}.im }`;
59
- return inner;
60
- }
61
- return `${op}${this.genExpr(expr.operand)}`;
62
- }
63
-
64
- case "BinaryExpr": {
65
- // Complex binary operations
66
- if (
67
- isComplex(expr._type) ||
68
- isComplex(expr.left._type) ||
69
- isComplex(expr.right._type)
70
- ) {
71
- const l = this._genComplexOperand(expr.left);
72
- const r = this._genComplexOperand(expr.right);
73
- switch (expr.op) {
74
- case "+":
75
- return `{ re: ${l}.re + ${r}.re, im: ${l}.im + ${r}.im }`;
76
- case "-":
77
- return `{ re: ${l}.re - ${r}.re, im: ${l}.im - ${r}.im }`;
78
- case "*":
79
- this._usesCmul = true;
80
- return `__cmul(${l}, ${r})`;
81
- case "/":
82
- this._usesCdiv = true;
83
- return `__cdiv(${l}, ${r})`;
84
- case "==":
85
- return `(${l}.re === ${r}.re && ${l}.im === ${r}.im)`;
86
- case "!=":
87
- return `(${l}.re !== ${r}.re || ${l}.im !== ${r}.im)`;
88
- }
89
- }
90
- const l = this.genExpr(expr.left);
91
- const r = this.genExpr(expr.right);
92
- // Integer division: only when both sides are int
93
- if (
94
- expr.op === "/" &&
95
- this.isIntType(expr.left._type) &&
96
- this.isIntType(expr.right._type)
97
- ) {
98
- return `Math.trunc(${l} / ${r})`;
99
- }
100
- // Go &^ (bit clear / AND NOT) → JS & ~
101
- if (expr.op === "&^") {
102
- return `${l} & ~${r}`;
103
- }
104
- // Struct/array equality: use __equal for value comparison
105
- if (expr.op === "==" || expr.op === "!=") {
106
- const lt = expr.left._type;
107
- if (this._isStructOrArrayType(lt)) {
108
- this._usesEqual = true;
109
- const cmp = `__equal(${l}, ${r})`;
110
- return expr.op === "==" ? cmp : `!${cmp}`;
111
- }
112
- }
113
- // Go == and != are strict — map to JS === / !==
114
- const op =
115
- expr.op === "==" ? "===" : expr.op === "!=" ? "!==" : expr.op;
116
- return `${l} ${op} ${r}`;
117
- }
118
-
119
- case "CallExpr":
120
- return this.genCall(expr);
121
-
161
+ case "AwaitExpr":
162
+ return `await ${this.genExpr(expr.expr)}`;
122
163
  case "InstantiationExpr":
123
- return this.genExpr(expr.expr); // type erasure
124
-
125
- case "SelectorExpr": {
126
- // Method expression: TypeName.MethodName → (recv, ...args) => recv.method(...args)
127
- if (expr._isMethodExpr) {
128
- const method = expr.field;
129
- return `((recv, ...args) => recv.${method}(...args))`;
130
- }
131
- // math constants
132
- if (expr.expr.kind === "Ident" && expr.expr.name === "math") {
133
- switch (expr.field) {
134
- case "Pi":
135
- return "Math.PI";
136
- case "E":
137
- return "Math.E";
138
- case "MaxFloat64":
139
- return "Number.MAX_VALUE";
140
- case "SmallestNonzeroFloat64":
141
- return "Number.MIN_VALUE";
142
- case "MaxInt":
143
- return "Number.MAX_SAFE_INTEGER";
144
- case "MinInt":
145
- return "Number.MIN_SAFE_INTEGER";
146
- }
147
- }
148
- // io constants
149
- if (expr.expr.kind === "Ident" && expr.expr.name === "io") {
150
- switch (expr.field) {
151
- case "EOF":
152
- return '"EOF"';
153
- case "Discard":
154
- return "{ WriteString(s) { return s.length; }, Write(b) { return b.length; } }";
155
- }
156
- }
157
- // os constants/vars
158
- if (expr.expr.kind === "Ident" && expr.expr.name === "os") {
159
- switch (expr.field) {
160
- case "Args":
161
- return "process.argv";
162
- }
163
- }
164
- // time constants (nanosecond durations and layout strings)
165
- if (expr.expr.kind === "Ident" && expr.expr.name === "time") {
166
- switch (expr.field) {
167
- case "Millisecond":
168
- return "1000000";
169
- case "Second":
170
- return "1000000000";
171
- case "Minute":
172
- return "60000000000";
173
- case "Hour":
174
- return "3600000000000";
175
- case "RFC3339":
176
- return '"2006-01-02T15:04:05Z07:00"';
177
- case "RFC3339Nano":
178
- return '"2006-01-02T15:04:05.999999999Z07:00"';
179
- case "DateOnly":
180
- return '"2006-01-02"';
181
- case "TimeOnly":
182
- return '"15:04:05"';
183
- case "DateTime":
184
- return '"2006-01-02 15:04:05"';
185
- case "UTC":
186
- case "Local":
187
- return "null";
188
- case "January":
189
- return "1";
190
- case "February":
191
- return "2";
192
- case "March":
193
- return "3";
194
- case "April":
195
- return "4";
196
- case "May":
197
- return "5";
198
- case "June":
199
- return "6";
200
- case "July":
201
- return "7";
202
- case "August":
203
- return "8";
204
- case "September":
205
- return "9";
206
- case "October":
207
- return "10";
208
- case "November":
209
- return "11";
210
- case "December":
211
- return "12";
212
- case "Sunday":
213
- return "0";
214
- case "Monday":
215
- return "1";
216
- case "Tuesday":
217
- return "2";
218
- case "Wednesday":
219
- return "3";
220
- case "Thursday":
221
- return "4";
222
- case "Friday":
223
- return "5";
224
- case "Saturday":
225
- return "6";
226
- }
227
- }
228
- // utf8 constants
229
- if (expr.expr.kind === "Ident" && expr.expr.name === "utf8") {
230
- switch (expr.field) {
231
- case "RuneError":
232
- return "0xFFFD";
233
- case "MaxRune":
234
- return "0x10FFFF";
235
- case "UTFMax":
236
- return "4";
237
- }
238
- }
239
- const base = this.genExpr(expr.expr);
240
- // Bundled GoFront packages are inlined — drop the qualifier.
241
- if (this.bundledPackages.has(base)) return expr.field;
242
- // Pointer types: route through .value
243
- if (expr.expr._type?.kind === "pointer" && expr.field !== "value") {
244
- const sel = `${base}.value.${expr.field}`;
245
- if (expr._isMethodValue && !expr._callee)
246
- return `${sel}.bind(${base}.value)`;
247
- return sel;
248
- }
249
- const sel = `${base}.${expr.field}`;
250
- // Method used as value (not called) — bind the receiver
251
- if (expr._isMethodValue && !expr._callee) return `${sel}.bind(${base})`;
252
- return sel;
253
- }
254
-
255
- case "IndexExpr": {
256
- const exprType = expr.expr._type;
257
- const wrapField = this._namedWrapperField(exprType, expr.expr);
258
- const base = wrapField
259
- ? `${this.genExpr(expr.expr)}.${wrapField}`
260
- : this.genExpr(expr.expr);
261
- const idx = this.genExpr(expr.index);
262
- if (expr._mapValueType && !expr._lvalue) {
263
- const zero = this.zeroValueForType(expr._mapValueType);
264
- // Use an IIFE to avoid double-evaluating base/key when either
265
- // could be a function call or other expression with side effects.
266
- if (this._hasCallExpr(expr.expr) || this._hasCallExpr(expr.index)) {
267
- return `((__m, __k) => __m[__k] ?? ${zero})(${base}, ${idx})`;
268
- }
269
- return `(${base}[${idx}] ?? ${zero})`;
270
- }
271
- // String indexing returns a byte (charCodeAt), not a JS character
272
- const isStr =
273
- (exprType?.kind === "basic" && exprType.name === "string") ||
274
- (exprType?.kind === "untyped" && exprType.base === "string");
275
- if (isStr && !expr._lvalue) {
276
- return `${base}.charCodeAt(${idx})`;
277
- }
278
- return `${base}[${idx}]`;
279
- }
280
-
281
- case "SliceExpr": {
282
- const base = this.genExpr(expr.expr);
283
- const lo = expr.low ? this.genExpr(expr.low) : "";
284
- const hi = expr.high ? this.genExpr(expr.high) : "";
285
- if (!lo && !hi) return `${base}.slice()`;
286
- if (!hi) return `${base}.slice(${lo})`;
287
- if (!lo) return `${base}.slice(0, ${hi})`;
288
- return `${base}.slice(${lo}, ${hi})`;
289
- }
290
-
291
- case "CompositeLit":
292
- return this.genCompositeLit(expr);
293
-
294
- case "FuncLit": {
295
- const params = expr.params.map((p) => p.name).join(", ");
296
- const asyncPrefix = expr.async ? "async " : "";
297
- const saved = this.out;
298
- const prevBoxed = this._boxedVars;
299
- this._boxedVars = new Set(prevBoxed); // inherit parent's boxed vars (closures)
300
- this._scanAddressTaken(expr.body);
301
- this.out = [];
302
- this.indented(() => this._genBody(expr.body));
303
- const body = this.out.join("\n");
304
- this.out = saved;
305
- this._boxedVars = prevBoxed;
306
- return `${asyncPrefix}function(${params}) {\n${body}\n${" ".repeat(this.indent)}}`;
164
+ case "RangeExpr":
165
+ return this.genExpr(expr.expr);
166
+ default: {
167
+ const m = EXPR_GEN_DELEGATE[expr.kind];
168
+ if (m) return this[m](expr);
169
+ throw new Error(`CodeGen: unhandled expression kind '${expr.kind}'`);
307
170
  }
171
+ }
172
+ },
308
173
 
309
- case "TypeConversion": {
310
- const inner = this.genExpr(expr.expr);
311
- const t = expr.targetType;
312
-
313
- // Complex type conversions
314
- if (t?.name === "complex128" || t?.name === "complex64") {
315
- const srcType = expr.expr._type;
316
- if (isComplex(srcType)) return inner; // complex→complex identity
317
- return `{ re: ${inner}, im: 0 }`; // numeric→complex
318
- }
174
+ _genUnaryExpr(expr) {
175
+ const op = expr.op === "^" ? "~" : expr.op; // bitwise NOT
176
+ // Address-of: &x
177
+ if (op === "&") {
178
+ if (expr.operand.kind === "Ident") {
179
+ // Mark so Ident codegen does NOT append .value
180
+ expr.operand._isAddressOf = true;
181
+ // For boxed vars, &x returns the box itself
182
+ if (this._boxedVars.has(expr.operand.name)) {
183
+ return this.genExpr(expr.operand);
184
+ }
185
+ }
186
+ // For non-boxed vars (structs, etc.), wrap in { value: x }
187
+ return `{ value: ${this.genExpr(expr.operand)} }`;
188
+ }
189
+ // Dereference: *p → p.value
190
+ if (op === "*") return `${this.genExpr(expr.operand)}.value`;
191
+ // Unary minus/plus on complex
192
+ if ((op === "-" || op === "+") && isComplex(expr.operand._type)) {
193
+ const inner = this.genExpr(expr.operand);
194
+ if (op === "-") return `{ re: -${inner}.re, im: -${inner}.im }`;
195
+ return inner;
196
+ }
197
+ return `${op}${this.genExpr(expr.operand)}`;
198
+ },
319
199
 
320
- // Array type conversion: [N]T(slice) → slice(0, N)
321
- if (t?.kind === "ArrayType") {
322
- const srcType = expr.expr._type;
323
- const srcResolved =
324
- srcType?.kind === "named" ? srcType.underlying : srcType;
325
- if (srcResolved?.kind === "slice") {
326
- const size =
327
- t.size?.value !== undefined ? Number(t.size.value) : t.size;
328
- return `${inner}.slice(0, ${size})`;
329
- }
330
- return inner;
331
- }
200
+ _genSelectorExpr(expr) {
201
+ if (expr._isMethodExpr) {
202
+ return `((recv, ...args) => recv.${expr.field}(...args))`;
203
+ }
204
+ if (expr.expr.kind === "Ident") {
205
+ const nsConst = NS_CONSTANTS[expr.expr.name]?.[expr.field];
206
+ if (nsConst !== undefined) return nsConst;
207
+ }
208
+ const base = this.genExpr(expr.expr);
209
+ if (this.bundledPackages.has(base)) return expr.field;
210
+ if (expr.expr._type?.kind === "pointer" && expr.field !== "value")
211
+ return this._genPointerSelectorField(expr, base);
212
+ const sel = `${base}.${expr.field}`;
213
+ if (expr._isMethodValue && !expr._callee) return `${sel}.bind(${base})`;
214
+ return sel;
215
+ },
332
216
 
333
- // Slice type conversions
334
- if (t?.kind === "SliceType") {
335
- const elem = t.elem?.name;
336
- // []byte(s) → UTF-8 byte array
337
- if (elem === "byte" || elem === "uint8") {
338
- return `Array.from(new TextEncoder().encode(${inner}))`;
339
- }
340
- // []rune(s) → Unicode code point array (only when source is a string)
341
- if (elem === "rune" || elem === "int32" || elem === "int") {
342
- const srcType = expr.expr._type;
343
- if (
344
- (srcType?.kind === "basic" && srcType?.name === "string") ||
345
- (srcType?.kind === "untyped" && srcType?.base === "string")
346
- ) {
347
- return `Array.from(${inner}, __c => __c.codePointAt(0))`;
348
- }
349
- return `Array.from(${inner})`;
350
- }
351
- return `Array.from(${inner})`;
352
- }
217
+ _genPointerSelectorField(expr, base) {
218
+ const sel = `${base}.value.${expr.field}`;
219
+ if (expr._isMethodValue && !expr._callee)
220
+ return `${sel}.bind(${base}.value)`;
221
+ return sel;
222
+ },
353
223
 
354
- const target = t?.name;
355
- // error("msg") → __error("msg")
356
- if (target === "error") {
357
- this._usesError = true;
358
- return `__error(${inner})`;
359
- }
360
- // Named wrapper type: NodeFunc(fn) → new NodeFunc(fn)
361
- if (target && this.namedWrapperNames.has(target)) {
362
- return `new ${target}(${inner})`;
363
- }
364
- switch (target) {
365
- case "string": {
366
- // Go string(65) → "A" (Unicode code point), not "65"
367
- const srcType = expr.expr._type;
368
- if (srcType && this.isIntType(srcType)) {
369
- return `String.fromCodePoint(${inner})`;
370
- }
371
- // string([]byte) → decode byte array to string
372
- if (
373
- srcType &&
374
- srcType.kind === "slice" &&
375
- srcType.elem?.name === "int"
376
- ) {
377
- return `${inner}.map(c => String.fromCharCode(c)).join("")`;
378
- }
379
- return `String(${inner})`;
380
- }
381
- case "int":
382
- case "byte":
383
- case "rune":
384
- return `Math.trunc(Number(${inner}))`;
385
- case "float64":
386
- return `Number(${inner})`;
387
- case "bool":
388
- return `Boolean(${inner})`;
389
- default:
390
- return inner;
391
- }
392
- }
224
+ _genIndexExpr(expr) {
225
+ const exprType = expr.expr._type;
226
+ const wrapField = this._namedWrapperField(exprType, expr.expr);
227
+ const base = wrapField
228
+ ? `${this.genExpr(expr.expr)}.${wrapField}`
229
+ : this.genExpr(expr.expr);
230
+ const idx = this.genExpr(expr.index);
231
+ if (expr._mapValueType && !expr._lvalue)
232
+ return this._genMapIndexExpr(expr, base, idx);
233
+ if (this._isStringExprType(exprType) && !expr._lvalue)
234
+ return `${base}.charCodeAt(${idx})`;
235
+ return `${base}[${idx}]`;
236
+ },
393
237
 
394
- case "TypeAssertExpr": {
395
- const val = this.genExpr(expr.expr);
396
- const check = this._typeCheckExpr(expr.type, val);
397
- if (!expr._commaOk) {
398
- // plain assertion: panic if check fails (matches Go behavior)
399
- if (check === "true") return val; // can't check at runtime — pass through
400
- return `(${check} ? ${val} : (() => { throw new Error("interface conversion: type assertion failed"); })())`;
401
- }
402
- // comma-ok: emit [value-or-zero, runtimeTypeCheck]
403
- const zero = this.zeroValueForTypeNode(expr.type);
404
- return `(${check} ? [${val}, true] : [${zero}, false])`;
405
- }
238
+ _genMapIndexExpr(expr, base, idx) {
239
+ const zero = this.zeroValueForType(expr._mapValueType);
240
+ if (this._hasCallExpr(expr.expr) || this._hasCallExpr(expr.index))
241
+ return `((__m, __k) => __m[__k] ?? ${zero})(${base}, ${idx})`;
242
+ return `(${base}[${idx}] ?? ${zero})`;
243
+ },
406
244
 
407
- case "AwaitExpr":
408
- return `await ${this.genExpr(expr.expr)}`;
245
+ _isStringExprType(exprType) {
246
+ return (
247
+ (exprType?.kind === "basic" && exprType.name === "string") ||
248
+ (exprType?.kind === "untyped" && exprType.base === "string")
249
+ );
250
+ },
409
251
 
410
- case "RangeExpr":
411
- return this.genExpr(expr.expr);
252
+ _genSliceExpr(expr) {
253
+ const base = this.genExpr(expr.expr);
254
+ const lo = expr.low ? this.genExpr(expr.low) : "";
255
+ const hi = expr.high ? this.genExpr(expr.high) : "";
256
+ if (!lo && !hi) return `${base}.slice()`;
257
+ if (!hi) return `${base}.slice(${lo})`;
258
+ if (!lo) return `${base}.slice(0, ${hi})`;
259
+ return `${base}.slice(${lo}, ${hi})`;
260
+ },
412
261
 
413
- default:
414
- throw new Error(`CodeGen: unhandled expression kind '${expr.kind}'`);
415
- }
262
+ _genFuncLit(expr) {
263
+ const params = expr.params.map((p) => p.name).join(", ");
264
+ const asyncPrefix = expr.async ? "async " : "";
265
+ const saved = this.out;
266
+ const prevBoxed = this._boxedVars;
267
+ this._boxedVars = new Set(prevBoxed); // inherit parent's boxed vars (closures)
268
+ this._scanAddressTaken(expr.body);
269
+ this.out = [];
270
+ this.indented(() => this._genBody(expr.body));
271
+ const body = this.out.join("\n");
272
+ this.out = saved;
273
+ this._boxedVars = prevBoxed;
274
+ return `${asyncPrefix}function(${params}) {\n${body}\n${" ".repeat(this.indent)}}`;
275
+ },
276
+
277
+ _genTypeAssertExpr(expr) {
278
+ const val = this.genExpr(expr.expr);
279
+ const check = this._typeCheckExpr(expr.type, val);
280
+ if (!expr._commaOk) {
281
+ // plain assertion: panic if check fails (matches Go behavior)
282
+ if (check === "true") return val; // can't check at runtime — pass through
283
+ return `(${check} ? ${val} : (() => { throw new Error("interface conversion: type assertion failed"); })())`;
284
+ }
285
+ // comma-ok: emit [value-or-zero, runtimeTypeCheck]
286
+ const zero = this.zeroValueForTypeNode(expr.type);
287
+ return `(${check} ? [${val}, true] : [${zero}, false])`;
288
+ },
289
+
290
+ _genTypeConversionCall(expr) {
291
+ const name = expr._conversionTargetType?.name;
292
+ const arg = this.genExpr(expr.args[0]);
293
+ if (name && this.namedWrapperNames.has(name)) return `new ${name}(${arg})`;
294
+ return arg;
416
295
  },
417
296
 
418
297
  genCall(expr) {
419
- // Named type constructor call: Greeter(fn), NodeFunc(fn) → new Greeter(fn)
420
- if (expr._isTypeConversion) {
421
- const name = expr._conversionTargetType?.name;
422
- const arg = this.genExpr(expr.args[0]);
423
- if (name && this.namedWrapperNames.has(name)) {
424
- return `new ${name}(${arg})`;
425
- }
426
- return arg; // non-wrapper named types: identity
427
- }
298
+ if (expr._isTypeConversion) return this._genTypeConversionCall(expr);
428
299
 
429
- // Unwrap InstantiationExpr for function calls: Foo[int](42) → Foo(42)
430
300
  const funcExpr =
431
301
  expr.func.kind === "InstantiationExpr" ? expr.func.expr : expr.func;
432
- // Handle built-ins that need special JS translation
433
302
  if (funcExpr.kind === "Ident") {
434
- switch (funcExpr.name) {
435
- case "append":
436
- return this.genAppend(expr);
437
- case "len": {
438
- if (expr._constLen != null) return String(expr._constLen);
439
- const arg = expr.args[0];
440
- const t = arg?._type;
441
- const wrapField = this._namedWrapperField(t, arg);
442
- if (wrapField) {
443
- return `${this.genExpr(arg)}.${wrapField}.length`;
444
- }
445
- const js = this.genExpr(arg);
446
- if (t?.kind === "map") return `Object.keys(${js}).length`;
447
- this._usesLen = true;
448
- return `__len(${js})`;
449
- }
450
- case "cap":
451
- return `${this.genExpr(expr.args[0])}.length`;
452
- case "make":
453
- return this.genMake(expr);
454
- case "delete": {
455
- const [m, k] = expr.args.map((a) => this.genExpr(a));
456
- return `(delete ${m}[${k}])`;
457
- }
458
- case "copy": {
459
- const [dst, src] = expr.args.map((a) => this.genExpr(a));
460
- return `((__cd,__cs)=>{const n=Math.min(__cd.length,__cs.length);__cd.splice(0,n,...__cs.slice(0,n));return n;})(${dst},${src})`;
461
- }
462
- case "new": {
463
- const zero = this.zeroValueForExpr(expr.args[0]);
464
- return `{ value: ${zero} }`;
465
- }
466
- case "print":
467
- case "println": {
468
- const args = expr.args.map((a) => this.genExpr(a)).join(", ");
469
- return `console.log(${args})`;
470
- }
471
- case "panic": {
472
- const arg = this.genExpr(expr.args[0]);
473
- return `(() => { throw new Error(${arg}); })()`;
474
- }
475
- case "recover":
476
- return `(typeof __panic !== "undefined" && __panic !== null ? (() => { const __r = __panic.message ?? String(__panic); __panic = null; return __r; })() : null)`;
477
- case "error": {
478
- this._usesError = true;
479
- const arg = this.genExpr(expr.args[0]);
480
- return `__error(${arg})`;
481
- }
482
- case "min": {
483
- const args = expr.args.map((a) => this.genExpr(a)).join(", ");
484
- return `Math.min(${args})`;
485
- }
486
- case "max": {
487
- const args = expr.args.map((a) => this.genExpr(a)).join(", ");
488
- return `Math.max(${args})`;
489
- }
490
- case "clear": {
491
- const arg = expr.args[0];
492
- const t = arg?._type;
493
- const js = this.genExpr(arg);
494
- if (
495
- t?.kind === "map" ||
496
- (t?.kind === "named" && t.underlying?.kind === "map")
497
- ) {
498
- return `((__m) => { for (const __k in __m) delete __m[__k]; })(${js})`;
499
- }
500
- return `(${js}).length = 0`;
501
- }
502
- case "complex": {
503
- const re = this.genExpr(expr.args[0]);
504
- const im = this.genExpr(expr.args[1]);
505
- return `{ re: ${re}, im: ${im} }`;
506
- }
507
- case "real":
508
- return `(${this.genExpr(expr.args[0])}).re`;
509
- case "imag":
510
- return `(${this.genExpr(expr.args[0])}).im`;
511
- }
512
- }
513
-
514
- // error.Error() → real method call
515
- if (
516
- expr.func.kind === "SelectorExpr" &&
517
- expr.func.field === "Error" &&
518
- expr.func.expr._type &&
519
- (expr.func.expr._type === ERROR ||
520
- expr.func.expr._type?.name === "error" ||
521
- (expr.func.expr._type?.kind === "named" &&
522
- expr.func.expr._type?.underlying?.kind === "interface"))
523
- ) {
524
- return `${this.genExpr(expr.func.expr)}.Error()`;
525
- }
526
-
527
- // fmt.Sprintf / fmt.Printf / fmt.Println / fmt.Print / fmt.Errorf
528
- // Standard library namespace dispatch — all follow the pattern:
529
- // pkg.Func(args) → inline JS
530
- if (expr.func.kind === "SelectorExpr" && expr.func.expr.kind === "Ident") {
531
- const ns = expr.func.expr.name;
532
- const fn = expr.func.field;
533
- const result = this._genStdlibCall(ns, fn, expr);
534
- if (result !== undefined) return result;
303
+ const builtin = this._genBuiltinIdent(funcExpr.name, expr);
304
+ if (builtin !== undefined) return builtin;
535
305
  }
536
306
 
537
- // strings.Builder / bytes.Buffer / regexp.Regexp method dispatch
538
307
  if (expr.func.kind === "SelectorExpr") {
539
- const recvType = expr.func.expr._type;
540
- const recvName = recvType?.name ?? recvType?.base?.name;
541
- if (recvName === "strings.Builder" || recvName === "bytes.Buffer") {
542
- const typeName = recvType?.name ?? recvType?.base?.name;
543
- return this._genBuilderCall(typeName, expr.func.field, expr);
544
- }
545
- if (recvName === "regexp.Regexp") {
546
- return this._genRegexpMethodCall(expr.func.field, expr);
547
- }
548
- if (recvName === "time.Time") {
549
- return this._genTimeMethodCall(expr.func.field, expr);
550
- }
308
+ const result = this._genSelectorCall(expr);
309
+ if (result !== undefined) return result;
551
310
  }
552
311
 
553
- // Mark the callee so SelectorExpr knows it's being called, not used as a value
554
312
  expr.func._callee = true;
555
313
  const rawFn = this.genExpr(expr.func);
556
- // Wrap function literals in parens so `function(){}()` → `(function(){})()`
557
314
  const fn = expr.func.kind === "FuncLit" ? `(${rawFn})` : rawFn;
558
- // Multi-value forwarding: f(g()) where g() returns multiple values → f(...g())
559
- if (expr._multiForward) {
560
- return `${fn}(...${this.genExpr(expr.args[0])})`;
561
- }
315
+ if (expr._multiForward) return `${fn}(...${this.genExpr(expr.args[0])})`;
562
316
  const args = expr.args
563
317
  .map((a) => (a._spread ? `...${this.genExpr(a)}` : this.genExpr(a)))
564
318
  .join(", ");
565
319
  return `${fn}(${args})`;
566
320
  },
567
321
 
322
+ _isErrorInterfaceType(t) {
323
+ return t?.kind === "named" && t?.underlying?.kind === "interface";
324
+ },
325
+
326
+ _isErrorType(t) {
327
+ return (
328
+ t && (t === ERROR || t?.name === "error" || this._isErrorInterfaceType(t))
329
+ );
330
+ },
331
+
332
+ _isErrorMethodCall(expr) {
333
+ if (expr.func.field !== "Error") return false;
334
+ return this._isErrorType(expr.func.expr._type);
335
+ },
336
+
337
+ _resolveRecvName(recvType) {
338
+ return recvType?.name ?? recvType?.base?.name;
339
+ },
340
+
341
+ _genReceiverTypeCall(recvName, expr) {
342
+ if (recvName === "strings.Builder" || recvName === "bytes.Buffer")
343
+ return this._genBuilderCall(recvName, expr.func.field, expr);
344
+ if (recvName === "regexp.Regexp")
345
+ return this._genRegexpMethodCall(expr.func.field, expr);
346
+ if (recvName === "time.Time")
347
+ return this._genTimeMethodCall(expr.func.field, expr);
348
+ return undefined;
349
+ },
350
+
351
+ _genSelectorCall(expr) {
352
+ if (this._isErrorMethodCall(expr))
353
+ return `${this.genExpr(expr.func.expr)}.Error()`;
354
+ if (expr.func.expr.kind === "Ident") {
355
+ const ns = expr.func.expr.name;
356
+ const fn = expr.func.field;
357
+ const result = this._genStdlibCall(ns, fn, expr);
358
+ if (result !== undefined) return result;
359
+ }
360
+ const recvType = expr.func.expr._type;
361
+ const recvName = this._resolveRecvName(recvType);
362
+ return this._genReceiverTypeCall(recvName, expr);
363
+ },
364
+
365
+ _genBuiltinLen(expr) {
366
+ if (expr._constLen != null) return String(expr._constLen);
367
+ const arg = expr.args[0];
368
+ const t = arg?._type;
369
+ const wrapField = this._namedWrapperField(t, arg);
370
+ if (wrapField) return `${this.genExpr(arg)}.${wrapField}.length`;
371
+ const js = this.genExpr(arg);
372
+ if (t?.kind === "map") return `Object.keys(${js}).length`;
373
+ this._usesLen = true;
374
+ return `__len(${js})`;
375
+ },
376
+
377
+ _genBuiltinClear(expr) {
378
+ const arg = expr.args[0];
379
+ const t = arg?._type;
380
+ const js = this.genExpr(arg);
381
+ if (
382
+ t?.kind === "map" ||
383
+ (t?.kind === "named" && t.underlying?.kind === "map")
384
+ )
385
+ return `((__m) => { for (const __k in __m) delete __m[__k]; })(${js})`;
386
+ return `(${js}).length = 0`;
387
+ },
388
+
389
+ _genBuiltinIdent(name, expr) {
390
+ const gen = BUILTIN_GEN[name];
391
+ return gen ? gen(this, expr) : undefined;
392
+ },
393
+
568
394
  genAppend(expr) {
569
395
  const retType = expr._type;
570
396
  const wrapField = this._namedWrapperField(retType, expr.args[0]);
@@ -631,74 +457,55 @@ export const expressionGenMethods = {
631
457
  .join(", ");
632
458
  },
633
459
 
634
- genCompositeLit(expr) {
635
- const t = expr.typeExpr;
636
-
637
- // Implicit composite literal: {X: 1} or {1, 2} inside a slice/map — type inferred from context
638
- if (t === null) {
639
- const typeName = expr._type?.name ?? expr._type?.underlying?.name;
640
- const hasPositional = expr.elems.some((e) => e._positionalField);
641
- const hasKeyed = expr.elems.some((e) => e.kind === "KeyValueExpr");
642
- if (hasPositional || hasKeyed) {
643
- if (typeName && this.structNames.has(typeName)) {
644
- return `new ${typeName}({ ${this._genStructFields(expr.elems)} })`;
645
- }
646
- const fields = expr.elems
647
- .map((e) => {
648
- if (e._positionalField)
649
- return `${e._positionalField}: ${this.genExpr(e)}`;
650
- return `${e.key.name ?? this.genExpr(e.key)}: ${this.genExpr(e.value)}`;
651
- })
652
- .join(", ");
653
- return `{ ${fields} }`;
654
- }
655
- return `[${expr.elems.map((e) => this.genExpr(e)).join(", ")}]`;
656
- }
460
+ _isSliceOrArrayType(t) {
461
+ return t?.kind === "SliceType" || t?.kind === "ArrayType";
462
+ },
657
463
 
658
- const typeName = this.getTypeName(t);
464
+ _isMapTypeNode(t) {
465
+ return t?.kind === "MapType";
466
+ },
659
467
 
660
- // Struct: new Foo({ X: 1, Y: 2 }) — keyed or positional
661
- if (typeName && this.structNames.has(typeName)) {
662
- return `new ${typeName}({ ${this._genStructFields(expr.elems)} })`;
663
- }
468
+ _genMapLitEntries(elems) {
469
+ return elems
470
+ .map((e) => {
471
+ if (e.kind === "KeyValueExpr") {
472
+ const k =
473
+ e.key.litKind === "STRING"
474
+ ? JSON.stringify(e.key.value)
475
+ : `[${this.genExpr(e.key)}]`;
476
+ return `${k}: ${this.genExpr(e.value)}`;
477
+ }
478
+ return this.genExpr(e);
479
+ })
480
+ .join(", ");
481
+ },
664
482
 
665
- // Named wrapper type: Group{a, b} → new Group([a, b])
666
- if (typeName && this.namedWrapperNames.has(typeName)) {
667
- const namedType = this.checker?.types.get(typeName);
668
- const u = namedType?.underlying;
669
- if (u?.kind === "map") {
670
- const entries = expr.elems
671
- .map((e) => {
672
- if (e.kind === "KeyValueExpr") {
673
- const k =
674
- e.key.litKind === "STRING"
675
- ? JSON.stringify(e.key.value)
676
- : `[${this.genExpr(e.key)}]`;
677
- return `${k}: ${this.genExpr(e.value)}`;
678
- }
679
- return this.genExpr(e);
680
- })
681
- .join(", ");
682
- return `new ${typeName}({ ${entries} })`;
683
- }
684
- // Default: slice
685
- const elems = expr.elems
686
- .map((e) =>
687
- e.kind === "KeyValueExpr" ? this.genExpr(e.value) : this.genExpr(e),
483
+ _genFallbackCompositeLit(expr) {
484
+ if (expr.elems.length > 0 && expr.elems[0]?.kind === "KeyValueExpr") {
485
+ const fields = expr.elems
486
+ .map(
487
+ (e) =>
488
+ `${e.key.name ?? this.genExpr(e.key)}: ${this.genExpr(e.value)}`,
688
489
  )
689
490
  .join(", ");
690
- return `new ${typeName}([${elems}])`;
491
+ return `{ ${fields} }`;
691
492
  }
493
+ return `[${expr.elems.map((e) => this.genExpr(e)).join(", ")}]`;
494
+ },
692
495
 
693
- // Fallback: named type that's not a struct but has positional elements → treat as struct
496
+ genCompositeLit(expr) {
497
+ const t = expr.typeExpr;
498
+ if (t === null) return this._genImplicitCompositeLit(expr);
499
+ const typeName = this.getTypeName(t);
500
+ if (typeName && this.structNames.has(typeName))
501
+ return `new ${typeName}({ ${this._genStructFields(expr.elems)} })`;
502
+ if (typeName && this.namedWrapperNames.has(typeName))
503
+ return this._genNamedWrapperLit(typeName, expr);
694
504
  if (expr.elems.some((e) => e._positionalField)) {
695
- if (typeName) {
505
+ if (typeName)
696
506
  return `new ${typeName}({ ${this._genStructFields(expr.elems)} })`;
697
- }
698
507
  }
699
-
700
- // Slice/array: [1, 2, 3]
701
- if (t?.kind === "SliceType" || t?.kind === "ArrayType") {
508
+ if (this._isSliceOrArrayType(t)) {
702
509
  const elems = expr.elems
703
510
  .map((e) =>
704
511
  e.kind === "KeyValueExpr" ? this.genExpr(e.value) : this.genExpr(e),
@@ -706,9 +513,35 @@ export const expressionGenMethods = {
706
513
  .join(", ");
707
514
  return `[${elems}]`;
708
515
  }
516
+ if (this._isMapTypeNode(t))
517
+ return `{ ${this._genMapLitEntries(expr.elems)} }`;
518
+ return this._genFallbackCompositeLit(expr);
519
+ },
520
+
521
+ _genImplicitCompositeLit(expr) {
522
+ const typeName = expr._type?.name ?? expr._type?.underlying?.name;
523
+ const hasPositional = expr.elems.some((e) => e._positionalField);
524
+ const hasKeyed = expr.elems.some((e) => e.kind === "KeyValueExpr");
525
+ if (hasPositional || hasKeyed) {
526
+ if (typeName && this.structNames.has(typeName)) {
527
+ return `new ${typeName}({ ${this._genStructFields(expr.elems)} })`;
528
+ }
529
+ const fields = expr.elems
530
+ .map((e) => {
531
+ if (e._positionalField)
532
+ return `${e._positionalField}: ${this.genExpr(e)}`;
533
+ return `${e.key.name ?? this.genExpr(e.key)}: ${this.genExpr(e.value)}`;
534
+ })
535
+ .join(", ");
536
+ return `{ ${fields} }`;
537
+ }
538
+ return `[${expr.elems.map((e) => this.genExpr(e)).join(", ")}]`;
539
+ },
709
540
 
710
- // Map: { key: value }
711
- if (t?.kind === "MapType") {
541
+ _genNamedWrapperLit(typeName, expr) {
542
+ const namedType = this.checker?.types.get(typeName);
543
+ const u = namedType?.underlying;
544
+ if (u?.kind === "map") {
712
545
  const entries = expr.elems
713
546
  .map((e) => {
714
547
  if (e.kind === "KeyValueExpr") {
@@ -721,20 +554,15 @@ export const expressionGenMethods = {
721
554
  return this.genExpr(e);
722
555
  })
723
556
  .join(", ");
724
- return `{ ${entries} }`;
725
- }
726
-
727
- // Fallback: key-value pairs → plain object, positional → array
728
- if (expr.elems.length > 0 && expr.elems[0]?.kind === "KeyValueExpr") {
729
- const fields = expr.elems
730
- .map(
731
- (e) =>
732
- `${e.key.name ?? this.genExpr(e.key)}: ${this.genExpr(e.value)}`,
733
- )
734
- .join(", ");
735
- return `{ ${fields} }`;
557
+ return `new ${typeName}({ ${entries} })`;
736
558
  }
737
- return `[${expr.elems.map((e) => this.genExpr(e)).join(", ")}]`;
559
+ // Default: slice
560
+ const elems = expr.elems
561
+ .map((e) =>
562
+ e.kind === "KeyValueExpr" ? this.genExpr(e.value) : this.genExpr(e),
563
+ )
564
+ .join(", ");
565
+ return `new ${typeName}([${elems}])`;
738
566
  },
739
567
 
740
568
  // ── Helpers ───────────────────────────────────────────────────
@@ -775,33 +603,14 @@ export const expressionGenMethods = {
775
603
 
776
604
  // Returns the JS zero-value literal for a basic type name, or null if not a basic type.
777
605
  _zeroForBasicName(name) {
778
- switch (name) {
779
- case "int":
780
- case "uint":
781
- case "int8":
782
- case "int16":
783
- case "int32":
784
- case "int64":
785
- case "uint8":
786
- case "uint16":
787
- case "uint32":
788
- case "uint64":
789
- case "uintptr":
790
- case "float32":
791
- case "float64":
792
- case "byte":
793
- case "rune":
794
- return "0";
795
- case "complex64":
796
- case "complex128":
797
- return "{ re: 0, im: 0 }";
798
- case "string":
799
- return '""';
800
- case "bool":
801
- return "false";
802
- default:
803
- return null;
804
- }
606
+ return Object.hasOwn(ZERO_FOR_BASIC, name) ? ZERO_FOR_BASIC[name] : null;
607
+ },
608
+
609
+ _zeroForNamedType(name) {
610
+ if (name === "strings.Builder") return '{ _buf: "" }';
611
+ if (name === "bytes.Buffer") return "{ _buf: [] }";
612
+ if (this.structNames.has(name)) return `new ${name}()`;
613
+ return "null";
805
614
  },
806
615
 
807
616
  zeroValueForTypeNode(typeNode) {
@@ -810,11 +619,7 @@ export const expressionGenMethods = {
810
619
  case "TypeName": {
811
620
  const basic = this._zeroForBasicName(typeNode.name);
812
621
  if (basic !== null) return basic;
813
- if (typeNode.name === "strings.Builder") return '{ _buf: "" }';
814
- if (typeNode.name === "bytes.Buffer") return "{ _buf: [] }";
815
- if (this.structNames.has(typeNode.name))
816
- return `new ${typeNode.name}()`;
817
- return "null";
622
+ return this._zeroForNamedType(typeNode.name);
818
623
  }
819
624
  case "SliceType":
820
625
  return "null";
@@ -856,10 +661,7 @@ export const expressionGenMethods = {
856
661
  return `{ ${fields} }`;
857
662
  }
858
663
  case "named":
859
- if (t.name === "strings.Builder") return '{ _buf: "" }';
860
- if (t.name === "bytes.Buffer") return "{ _buf: [] }";
861
- if (this.structNames.has(t.name)) return `new ${t.name}()`;
862
- return "null";
664
+ return this._zeroForNamedType(t.name);
863
665
  default:
864
666
  return "null";
865
667
  }
@@ -869,36 +671,15 @@ export const expressionGenMethods = {
869
671
  _typeCheckExpr(typeNode, val) {
870
672
  if (!typeNode) return "true";
871
673
  if (typeNode.kind === "TypeName") {
872
- switch (typeNode.name) {
873
- case "int":
874
- case "uint":
875
- case "int8":
876
- case "int16":
877
- case "int32":
878
- case "int64":
879
- case "uint8":
880
- case "uint16":
881
- case "uint32":
882
- case "uint64":
883
- case "uintptr":
884
- case "float32":
885
- case "float64":
886
- case "byte":
887
- case "rune":
888
- return `typeof ${val} === "number"`;
889
- case "string":
890
- return `typeof ${val} === "string"`;
891
- case "bool":
892
- return `typeof ${val} === "boolean"`;
893
- case "nil":
894
- return `${val} === null`;
895
- case "error":
896
- return `(typeof ${val} === "object" && ${val} !== null && typeof ${val}.Error === "function")`;
897
- default:
898
- if (this.structNames.has(typeNode.name))
899
- return `${val} instanceof ${typeNode.name}`;
900
- return "true"; // unknown type — can't check at runtime
901
- }
674
+ const name = typeNode.name;
675
+ if (TYPEOF_NUMBER_NAMES.has(name)) return `typeof ${val} === "number"`;
676
+ if (name === "string") return `typeof ${val} === "string"`;
677
+ if (name === "bool") return `typeof ${val} === "boolean"`;
678
+ if (name === "nil") return `${val} === null`;
679
+ if (name === "error")
680
+ return `(typeof ${val} === "object" && ${val} !== null && typeof ${val}.Error === "function")`;
681
+ if (this.structNames.has(name)) return `${val} instanceof ${name}`;
682
+ return "true"; // unknown type — can't check at runtime
902
683
  }
903
684
  return "true";
904
685
  },
@@ -928,1099 +709,124 @@ export const expressionGenMethods = {
928
709
  return base?.kind === "struct" || base?.kind === "array";
929
710
  },
930
711
 
931
- _genBuilderCall(typeName, method, expr) {
932
- // Resolve the receiver — may be a direct var or a &var (pointer)
933
- const recv = expr.func.expr;
934
- const isPtr = recv._type?.kind === "pointer";
935
- const base = isPtr ? `${this.genExpr(recv)}.value` : this.genExpr(recv);
936
- const args = expr.args.map((a) => this.genExpr(a));
937
-
938
- if (typeName === "strings.Builder") {
939
- switch (method) {
940
- case "WriteString":
941
- return `(${base}._buf += ${args[0]}, [${args[0]}.length, null])`;
942
- case "WriteByte":
943
- case "WriteRune":
944
- return `(${base}._buf += String.fromCodePoint(${args[0]}))`;
945
- case "Write":
946
- return `(${base}._buf += String.fromCharCode(...${args[0]}))`;
947
- case "String":
948
- return `${base}._buf`;
949
- case "Len":
950
- return `${base}._buf.length`;
951
- case "Reset":
952
- return `(${base}._buf = "")`;
953
- case "Grow":
954
- return "undefined";
955
- }
956
- }
957
-
958
- if (typeName === "bytes.Buffer") {
959
- switch (method) {
960
- case "WriteString":
961
- return `(${base}._buf.push(...new TextEncoder().encode(${args[0]})), [${args[0]}.length, null])`;
962
- case "WriteByte":
963
- return `${base}._buf.push(${args[0]})`;
964
- case "Write":
965
- return `(${base}._buf.push(...${args[0]}), [${args[0]}.length, null])`;
966
- case "String":
967
- return `new TextDecoder().decode(new Uint8Array(${base}._buf))`;
968
- case "Bytes":
969
- return `${base}._buf.slice()`;
970
- case "Len":
971
- return `${base}._buf.length`;
972
- case "Reset":
973
- return `(${base}._buf = [])`;
974
- case "Grow":
975
- return "undefined";
976
- }
977
- }
978
-
979
- return undefined;
980
- },
981
-
982
- _genRegexp(fn, a) {
983
- // Converts a Go regexp pattern string (which may contain inline flags like (?i))
984
- // into a JS RegExp, extracting the flags into the second constructor argument.
985
- const makeRegExp = (pat) =>
986
- `((p) => { const m = /^\\(\\?([gimsuy]+)\\)/.exec(p); return m ? new RegExp(p.slice(m[0].length), m[1]) : new RegExp(p); })(${pat})`;
987
- switch (fn) {
988
- case "MustCompile":
989
- return makeRegExp(a()[0]);
990
- case "Compile":
991
- return `[${makeRegExp(a()[0])}, null]`;
992
- case "MatchString": {
993
- const [pat, s] = a();
994
- return `[new RegExp(${pat}).test(${s}), null]`;
995
- }
996
- case "QuoteMeta":
997
- return `${a()[0]}.replace(/[.*+?^\${}()|[\\]\\\\]/g, '\\\\$&')`;
998
- }
999
- return undefined;
1000
- },
1001
-
1002
- _genRegexpMethodCall(method, expr) {
1003
- const recv = expr.func.expr;
1004
- // *Regexp is a plain JS RegExp — no .value boxing needed
1005
- const re = this.genExpr(recv);
1006
- const args = expr.args.map((a) => this.genExpr(a));
1007
-
1008
- switch (method) {
1009
- case "MatchString":
1010
- return `${re}.test(${args[0]})`;
1011
- case "FindString":
1012
- return `(${re}.exec(${args[0]})?.[0] ?? "")`;
1013
- case "FindStringIndex":
1014
- return `((m => m ? [m.index, m.index + m[0].length] : null)(${re}.exec(${args[0]})))`;
1015
- case "FindAllString":
1016
- return `[...${args[0]}.matchAll(new RegExp(${re}.source, ${re}.flags.includes("g") ? ${re}.flags : ${re}.flags + "g"))].slice(0, ${args[1]} < 0 ? undefined : ${args[1]}).map(m => m[0])`;
1017
- case "FindStringSubmatch":
1018
- return `[...(${re}.exec(${args[0]}) ?? [])]`;
1019
- case "FindAllStringSubmatch":
1020
- return `[...${args[0]}.matchAll(new RegExp(${re}.source, ${re}.flags.includes("g") ? ${re}.flags : ${re}.flags + "g"))].slice(0, ${args[1]} < 0 ? undefined : ${args[1]}).map(m => [...m])`;
1021
- case "ReplaceAllString":
1022
- return `${args[0]}.replaceAll(new RegExp(${re}.source, ${re}.flags.includes("g") ? ${re}.flags : ${re}.flags + "g"), ${args[1]})`;
1023
- case "ReplaceAllLiteralString":
1024
- return `${args[0]}.replaceAll(new RegExp(${re}.source, ${re}.flags.includes("g") ? ${re}.flags : ${re}.flags + "g"), ${args[1]}.replace(/\\$/g, '$$$$'))`;
1025
- case "Split":
1026
- return `${args[0]}.split(${re}).slice(0, ${args[1]} < 0 ? undefined : ${args[1]})`;
1027
- case "String":
1028
- return `${re}.source`;
1029
- }
1030
- return undefined;
1031
- },
1032
-
1033
- _genSlices(fn, a) {
1034
- switch (fn) {
1035
- case "Contains": {
1036
- const [s, v] = a();
1037
- return `${s}.includes(${v})`;
1038
- }
1039
- case "Index": {
1040
- const [s, v] = a();
1041
- return `${s}.indexOf(${v})`;
1042
- }
1043
- case "Equal": {
1044
- this._usesEqual = true;
1045
- const [a1, b1] = a();
1046
- return `__equal(${a1}, ${b1})`;
1047
- }
1048
- case "Compare": {
1049
- const [a1, b1] = a();
1050
- return `((a, b) => { for (let i = 0; i < a.length && i < b.length; i++) { if (a[i] < b[i]) return -1; if (a[i] > b[i]) return 1; } return a.length - b.length; })(${a1}, ${b1})`;
1051
- }
1052
- case "Sort": {
1053
- const [s] = a();
1054
- return `${s}.sort((a, b) => a < b ? -1 : a > b ? 1 : 0)`;
1055
- }
1056
- case "SortFunc": {
1057
- const [s, cmp] = a();
1058
- return `${s}.sort(${cmp})`;
1059
- }
1060
- case "SortStableFunc": {
1061
- const [s, cmp] = a();
1062
- return `${s}.sort(${cmp})`;
1063
- }
1064
- case "IsSorted": {
1065
- const [s] = a();
1066
- return `((s) => { for (let i = 1; i < s.length; i++) { if (s[i] < s[i-1]) return false; } return true; })(${s})`;
1067
- }
1068
- case "IsSortedFunc": {
1069
- const [s, cmp] = a();
1070
- return `((s, f) => { for (let i = 1; i < s.length; i++) { if (f(s[i], s[i-1]) < 0) return false; } return true; })(${s}, ${cmp})`;
1071
- }
1072
- case "Reverse": {
1073
- const [s] = a();
1074
- return `${s}.reverse()`;
1075
- }
1076
- case "Max": {
1077
- const [s] = a();
1078
- return `Math.max(...${s})`;
1079
- }
1080
- case "Min": {
1081
- const [s] = a();
1082
- return `Math.min(...${s})`;
1083
- }
1084
- case "MaxFunc": {
1085
- const [s, cmp] = a();
1086
- return `((s, f) => s.reduce((m, x) => f(x, m) > 0 ? x : m))(${s}, ${cmp})`;
1087
- }
1088
- case "MinFunc": {
1089
- const [s, cmp] = a();
1090
- return `((s, f) => s.reduce((m, x) => f(x, m) < 0 ? x : m))(${s}, ${cmp})`;
1091
- }
1092
- case "Clone": {
1093
- const [s] = a();
1094
- return `${s}.slice()`;
1095
- }
1096
- case "Compact": {
1097
- const [s] = a();
1098
- return `((s) => s.filter((v, i) => i === 0 || v !== s[i-1]))(${s})`;
1099
- }
1100
- case "CompactFunc": {
1101
- const [s, eq] = a();
1102
- return `((s, f) => s.filter((v, i) => i === 0 || !f(v, s[i-1])))(${s}, ${eq})`;
1103
- }
1104
- case "Concat": {
1105
- const args = a();
1106
- return `[].concat(${args.join(", ")})`;
1107
- }
1108
- case "Delete": {
1109
- const [s, i, j] = a();
1110
- return `[...${s}.slice(0, ${i}), ...${s}.slice(${j})]`;
1111
- }
1112
- case "DeleteFunc": {
1113
- const [s, fn2] = a();
1114
- return `${s}.filter((v) => !${fn2}(v))`;
1115
- }
1116
- case "Insert": {
1117
- const args = a();
1118
- const s = args[0];
1119
- const i = args[1];
1120
- const vs = args.slice(2);
1121
- return `[...${s}.slice(0, ${i}), ${vs.join(", ")}, ...${s}.slice(${i})]`;
1122
- }
1123
- case "Replace": {
1124
- const args = a();
1125
- const s = args[0];
1126
- const i = args[1];
1127
- const j = args[2];
1128
- const vs = args.slice(3);
1129
- return `[...${s}.slice(0, ${i}), ${vs.join(", ")}, ...${s}.slice(${j})]`;
1130
- }
1131
- case "Grow":
1132
- case "Clip": {
1133
- const [s] = a();
1134
- return `${s}.slice()`;
1135
- }
1136
- }
1137
- return undefined;
1138
- },
1139
-
1140
- _genIo(fn, _a, expr) {
1141
- switch (fn) {
1142
- case "WriteString": {
1143
- const writerArg = expr.args[0];
1144
- const writerType = writerArg._type;
1145
- const typeName =
1146
- writerType?.name ??
1147
- (writerType?.kind === "pointer" ? writerType.base?.name : null);
1148
- const w = this.genExpr(writerArg);
1149
- const s = this.genExpr(expr.args[1]);
1150
- const isPtr = writerType?.kind === "pointer";
1151
- const base = isPtr ? `${w}.value` : w;
1152
- if (typeName === "strings.Builder") {
1153
- return `((b,s) => { b._buf += s; return [s.length, null]; })(${base}, ${s})`;
1154
- }
1155
- if (typeName === "bytes.Buffer") {
1156
- return `((b,s) => { b._buf.push(...new TextEncoder().encode(s)); return [s.length, null]; })(${base}, ${s})`;
1157
- }
1158
- // Generic: auto-dereference pointer, then dispatch on concrete writer type.
1159
- // strings.Builder has { _buf: string }, bytes.Buffer has { _buf: [] }.
1160
- return `((b,s) => { const w = b?.value ?? b; if (typeof w.WriteString === "function") return [w.WriteString(s), null]; if (typeof w._buf === "string") { w._buf += s; return [s.length, null]; } if (Array.isArray(w._buf)) { w._buf.push(...new TextEncoder().encode(s)); return [s.length, null]; } return [0, "io.WriteString: unsupported writer"]; })(${w}, ${s})`;
1161
- }
1162
- case "ReadAll": {
1163
- const r = this.genExpr(expr.args[0]);
1164
- return `((r) => { const w = r?.value ?? r; if (typeof w._src === "string") { const s = w._src.slice(w._pos); w._pos = w._src.length; return [[...s].map(c => c.charCodeAt(0)), null]; } if (Array.isArray(w._src)) { const b = w._src.slice(w._pos); w._pos = w._src.length; return [b, null]; } const chunks = []; const buf = new Array(4096); let err = null; while (!err) { const [n, e] = w.Read(buf); if (n > 0) chunks.push(...buf.slice(0, n)); err = e; } return [chunks, err === "EOF" ? null : err]; })(${r})`;
1165
- }
1166
- }
1167
- return undefined;
1168
- },
1169
-
1170
- _genHtml(fn, a) {
1171
- switch (fn) {
1172
- case "EscapeString": {
1173
- const [s] = a();
1174
- return `${s}.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&#34;").replace(/'/g,"&#39;")`;
1175
- }
1176
- case "UnescapeString": {
1177
- const [s] = a();
1178
- return `${s}.replace(/&#39;/g,"'").replace(/&#34;/g,'"').replace(/&gt;/g,">").replace(/&lt;/g,"<").replace(/&amp;/g,"&")`;
1179
- }
1180
- }
1181
- return undefined;
1182
- },
1183
-
1184
- _genMaps(fn, a) {
1185
- switch (fn) {
1186
- case "Keys": {
1187
- const [m] = a();
1188
- return `Object.keys(${m})`;
1189
- }
1190
- case "Values": {
1191
- const [m] = a();
1192
- return `Object.values(${m})`;
1193
- }
1194
- case "Clone": {
1195
- const [m] = a();
1196
- return `({...${m}})`;
1197
- }
1198
- case "Copy": {
1199
- const [dst, src] = a();
1200
- return `Object.assign(${dst}, ${src})`;
1201
- }
1202
- case "Equal": {
712
+ _genComplexBinary(expr) {
713
+ const l = this._genComplexOperand(expr.left);
714
+ const r = this._genComplexOperand(expr.right);
715
+ switch (expr.op) {
716
+ case "+":
717
+ return `{ re: ${l}.re + ${r}.re, im: ${l}.im + ${r}.im }`;
718
+ case "-":
719
+ return `{ re: ${l}.re - ${r}.re, im: ${l}.im - ${r}.im }`;
720
+ case "*":
721
+ this._usesCmul = true;
722
+ return `__cmul(${l}, ${r})`;
723
+ case "/":
724
+ this._usesCdiv = true;
725
+ return `__cdiv(${l}, ${r})`;
726
+ case "==":
727
+ return `(${l}.re === ${r}.re && ${l}.im === ${r}.im)`;
728
+ case "!=":
729
+ return `(${l}.re !== ${r}.re || ${l}.im !== ${r}.im)`;
730
+ }
731
+ },
732
+
733
+ _genBinaryExpr(expr) {
734
+ if (
735
+ isComplex(expr._type) ||
736
+ isComplex(expr.left._type) ||
737
+ isComplex(expr.right._type)
738
+ )
739
+ return this._genComplexBinary(expr);
740
+ const l = this.genExpr(expr.left);
741
+ const r = this.genExpr(expr.right);
742
+ if (
743
+ expr.op === "/" &&
744
+ this.isIntType(expr.left._type) &&
745
+ this.isIntType(expr.right._type)
746
+ )
747
+ return `Math.trunc(${l} / ${r})`;
748
+ if (expr.op === "&^") return `${l} & ~${r}`;
749
+ if (expr.op === "==" || expr.op === "!=") {
750
+ if (this._isStructOrArrayType(expr.left._type)) {
1203
751
  this._usesEqual = true;
1204
- const [a1, b1] = a();
1205
- return `__equal(${a1}, ${b1})`;
1206
- }
1207
- case "EqualFunc": {
1208
- const [m1, m2, eq] = a();
1209
- return `((a, b, f) => { const ka = Object.keys(a); if (ka.length !== Object.keys(b).length) return false; return ka.every(k => k in b && f(a[k], b[k])); })(${m1}, ${m2}, ${eq})`;
1210
- }
1211
- case "Delete": {
1212
- const [m, k] = a();
1213
- return `(delete ${m}[${k}], undefined)`;
1214
- }
1215
- case "DeleteFunc": {
1216
- const [m, fn2] = a();
1217
- return `Object.keys(${m}).forEach(k => { if (${fn2}(k, ${m}[k])) delete ${m}[k]; })`;
752
+ const cmp = `__equal(${l}, ${r})`;
753
+ return expr.op === "==" ? cmp : `!${cmp}`;
1218
754
  }
1219
755
  }
1220
- return undefined;
756
+ const op = expr.op === "==" ? "===" : expr.op === "!=" ? "!==" : expr.op;
757
+ return `${l} ${op} ${r}`;
1221
758
  },
1222
759
 
1223
- typeComment(typeNode) {
1224
- if (!typeNode) return "unknown";
1225
- switch (typeNode.kind) {
1226
- case "TypeName":
1227
- return typeNode.name;
1228
- case "SliceType":
1229
- return `[]${this.typeComment(typeNode.elem)}`;
1230
- case "MapType":
1231
- return `map[${this.typeComment(typeNode.key)}]${this.typeComment(typeNode.value)}`;
1232
- default:
1233
- return typeNode.kind;
760
+ _genTypeConversion(expr) {
761
+ const inner = this.genExpr(expr.expr);
762
+ const t = expr.targetType;
763
+ if (t?.name === "complex128" || t?.name === "complex64") {
764
+ return isComplex(expr.expr._type) ? inner : `{ re: ${inner}, im: 0 }`;
1234
765
  }
1235
- },
1236
-
1237
- // Dispatch table for stdlib namespace calls (pkg.Func → inline JS).
1238
- // Returns the generated JS string, or undefined if not handled.
1239
- _genStdlibCall(ns, fn, expr) {
1240
- const a = () => expr.args.map((e) => this.genExpr(e));
1241
- switch (ns) {
1242
- case "fmt":
1243
- return this._genFmt(fn, a, expr);
1244
- case "strings":
1245
- return this._genStrings(fn, a);
1246
- case "bytes":
1247
- return this._genBytes(fn, a);
1248
- case "strconv":
1249
- return this._genStrconv(fn, a);
1250
- case "sort":
1251
- return this._genSort(fn, a);
1252
- case "math":
1253
- return this._genMath(fn, a);
1254
- case "unicode":
1255
- return this._genUnicode(fn, a);
1256
- case "os":
1257
- return this._genOs(fn, a);
1258
- case "errors":
1259
- return this._genErrors(fn, a);
1260
- case "time":
1261
- return this._genTime(fn, a);
1262
- case "regexp":
1263
- return this._genRegexp(fn, a);
1264
- case "slices":
1265
- return this._genSlices(fn, a);
1266
- case "maps":
1267
- return this._genMaps(fn, a);
1268
- case "html":
1269
- return this._genHtml(fn, a);
1270
- case "io":
1271
- return this._genIo(fn, a, expr);
1272
- case "rand":
1273
- return this._genRand(fn, a);
1274
- case "utf8":
1275
- return this._genUtf8(fn, a);
1276
- case "path":
1277
- return this._genPath(fn, a, expr);
1278
- case "gom":
1279
- return this._genGom(fn, expr);
1280
- default:
1281
- return undefined;
766
+ if (t?.kind === "ArrayType")
767
+ return this._genArrayTypeConversion(expr, inner, t);
768
+ if (t?.kind === "SliceType")
769
+ return this._genSliceTypeConversion(expr, inner, t);
770
+ const target = t?.name;
771
+ if (target === "error") {
772
+ this._usesError = true;
773
+ return `__error(${inner})`;
1282
774
  }
775
+ if (target && this.namedWrapperNames.has(target))
776
+ return `new ${target}(${inner})`;
777
+ return this._genPrimitiveConversion(expr, inner, target);
1283
778
  },
1284
779
 
1285
- _genGom(fn, expr) {
1286
- const a = () =>
1287
- expr.args.map((e) =>
1288
- e._spread ? `...${this.genExpr(e)}` : this.genExpr(e),
1289
- );
1290
- const elementTags = {
1291
- Div: "div",
1292
- Section: "section",
1293
- Article: "article",
1294
- Aside: "aside",
1295
- Header: "header",
1296
- Footer: "footer",
1297
- Main: "main",
1298
- Nav: "nav",
1299
- Figure: "figure",
1300
- H1: "h1",
1301
- H2: "h2",
1302
- H3: "h3",
1303
- H4: "h4",
1304
- H5: "h5",
1305
- H6: "h6",
1306
- Span: "span",
1307
- A: "a",
1308
- Strong: "strong",
1309
- Em: "em",
1310
- Code: "code",
1311
- Pre: "pre",
1312
- Small: "small",
1313
- Mark: "mark",
1314
- P: "p",
1315
- Br: "br",
1316
- Hr: "hr",
1317
- Ul: "ul",
1318
- Ol: "ol",
1319
- Li: "li",
1320
- Dl: "dl",
1321
- Dt: "dt",
1322
- Dd: "dd",
1323
- Form: "form",
1324
- Input: "input",
1325
- Button: "button",
1326
- Textarea: "textarea",
1327
- Select: "select",
1328
- Option: "option",
1329
- Label: "label",
1330
- Fieldset: "fieldset",
1331
- Legend: "legend",
1332
- Img: "img",
1333
- Video: "video",
1334
- Audio: "audio",
1335
- Canvas: "canvas",
1336
- Table: "table",
1337
- Thead: "thead",
1338
- Tbody: "tbody",
1339
- Tfoot: "tfoot",
1340
- Tr: "tr",
1341
- Th: "th",
1342
- Td: "td",
1343
- };
1344
- if (elementTags[fn]) {
1345
- const tag = elementTags[fn];
1346
- const args = a();
1347
- if (args.length === 0)
1348
- return `(()=>({Mount(p){const e=document.createElement("${tag}");p.appendChild(e);}}))()`;
1349
- return (
1350
- `((...c)=>({Mount(p){const e=document.createElement("${tag}");c.forEach(n=>n?.Mount?.(e));p.appendChild(e);}}))` +
1351
- `(${args.join(",")})`
1352
- );
780
+ _genArrayTypeConversion(expr, inner, t) {
781
+ const srcResolved =
782
+ expr.expr._type?.kind === "named"
783
+ ? expr.expr._type.underlying
784
+ : expr.expr._type;
785
+ if (srcResolved?.kind === "slice") {
786
+ const size = t.size?.value !== undefined ? Number(t.size.value) : t.size;
787
+ return `${inner}.slice(0, ${size})`;
1353
788
  }
1354
- const attrHelpers = {
1355
- For: "for",
1356
- Name: "name",
1357
- Value: "value",
1358
- Target: "target",
1359
- Rel: "rel",
1360
- Alt: "alt",
1361
- Title: "title",
1362
- Lang: "lang",
1363
- Action: "action",
1364
- Method: "method",
1365
- AutoComplete: "autocomplete",
1366
- Draggable: "draggable",
1367
- Role: "role",
1368
- StyleAttr: "style",
1369
- };
1370
- if (attrHelpers[fn]) {
1371
- const [v] = a();
1372
- return `((v)=>({Mount(e){e.setAttribute("${attrHelpers[fn]}",v)}}))(${v})`;
1373
- }
1374
- if (fn === "AriaLabel") {
1375
- const [v] = a();
1376
- return `((v)=>({Mount(e){e.setAttribute("aria-label",v)}}))(${v})`;
1377
- }
1378
- const boolAttrs = {
1379
- Disabled: "disabled",
1380
- Checked: "checked",
1381
- Selected: "selected",
1382
- Readonly: "readonly",
1383
- };
1384
- if (boolAttrs[fn])
1385
- return `({Mount(e){e.setAttribute("${boolAttrs[fn]}","")}})`;
1386
-
1387
- const args = a();
1388
- switch (fn) {
1389
- case "El": {
1390
- const [tag, ...children] = args;
1391
- if (children.length === 0)
1392
- return `((t)=>({Mount(p){const e=document.createElement(t);p.appendChild(e);}})) (${tag})`;
1393
- return (
1394
- `((t,...c)=>({Mount(p){const e=document.createElement(t);c.forEach(n=>n?.Mount?.(e));p.appendChild(e);}}))` +
1395
- `(${tag},${children.join(",")})`
1396
- );
1397
- }
1398
- case "Text": {
1399
- const [s] = args;
1400
- return `((s)=>({Mount(p){p.appendChild(document.createTextNode(s))}}))(${s})`;
1401
- }
1402
- case "Attr": {
1403
- const [name, value] = args;
1404
- return `((n,v)=>({Mount(e){e.setAttribute(n,v)}}))(${name},${value})`;
1405
- }
1406
- case "Class":
1407
- return `((v)=>({Mount(e){e.className=v}}))(${args[0]})`;
1408
- case "Type":
1409
- return `((v)=>({Mount(e){e.type=v}}))(${args[0]})`;
1410
- case "Href":
1411
- return `((v)=>({Mount(e){e.href=v}}))(${args[0]})`;
1412
- case "Src":
1413
- return `((v)=>({Mount(e){e.src=v}}))(${args[0]})`;
1414
- case "Placeholder":
1415
- return `((v)=>({Mount(e){e.placeholder=v}}))(${args[0]})`;
1416
- case "DataAttr":
1417
- return `((k,v)=>({Mount(e){e.setAttribute("data-"+k,v)}}))(${args[0]},${args[1]})`;
1418
- case "If":
1419
- return `((c,n)=>c?n:{Mount(){}})(${args[0]},${args[1]})`;
1420
- case "Map":
1421
- return `((s,f)=>{const c=s.map(f);return{_items:c,Mount(p){c.forEach(n=>n.Mount(p))}}})(${args[0]},${args[1]})`;
1422
- case "Style":
1423
- return `((s)=>({Mount(p){const e=document.createElement("style");e.textContent=s;p.appendChild(e);}})) (${args[0]})`;
1424
- case "Mount":
1425
- return `((sel,n)=>{const e=document.querySelector(sel);e.innerHTML="";n.Mount(e)})(${args[0]},${args[1]})`;
1426
- case "MountTo":
1427
- return `((sel,n)=>{const e=document.querySelector(sel);n.Mount(e)})(${args[0]},${args[1]})`;
1428
- }
1429
- return undefined;
789
+ return inner;
1430
790
  },
1431
791
 
1432
- _genFmt(fn, _a, expr) {
1433
- const fmtArgs = expr.args.map((e) => this.genExpr(e)).join(", ");
1434
- switch (fn) {
1435
- case "Sprintf":
1436
- this._usesSprintf = true;
1437
- return `__sprintf(${fmtArgs})`;
1438
- case "Errorf": {
1439
- this._usesSprintf = true;
1440
- this._usesError = true;
1441
- const fmtStr = expr.args[0];
1442
- if (fmtStr?.kind === "BasicLit" && fmtStr.value?.includes("%w")) {
1443
- const lastArg = this.genExpr(expr.args[expr.args.length - 1]);
1444
- return `__error(__sprintf(${fmtArgs}), ${lastArg})`;
1445
- }
1446
- return `__error(__sprintf(${fmtArgs}))`;
1447
- }
1448
- case "Printf":
1449
- case "Print":
1450
- this._usesSprintf = true;
1451
- return `process?.stdout?.write(__sprintf(${fmtArgs}))`;
1452
- case "Println":
1453
- this._usesSprintf = true;
1454
- return `console.log(__sprintf(${fmtArgs}))`;
1455
- case "Fprintf":
1456
- case "Fprintln":
1457
- case "Fprint": {
1458
- this._usesSprintf = true;
1459
- const writerArg = expr.args[0];
1460
- const rest = expr.args.slice(1);
1461
- const restJs = rest.map((e) => this.genExpr(e)).join(", ");
1462
- // Resolve the writer to its underlying buffer expression
1463
- const writerType = writerArg._type;
1464
- const targetTypeName =
1465
- writerType?.name ??
1466
- (writerType?.kind === "pointer" ? writerType.base?.name : null);
1467
- let buf;
1468
- if (targetTypeName === "strings.Builder") {
1469
- const w = this.genExpr(writerArg);
1470
- buf =
1471
- writerType?.kind === "pointer" ? `${w}.value._buf` : `${w}._buf`;
1472
- } else if (targetTypeName === "bytes.Buffer") {
1473
- const w = this.genExpr(writerArg);
1474
- const base = writerType?.kind === "pointer" ? `${w}.value` : w;
1475
- buf = null;
1476
- if (fn === "Fprintf") {
1477
- return `((__b,__s)=>{ __b._buf.push(...new TextEncoder().encode(__s)); })(${base}, __sprintf(${restJs}))`;
1478
- }
1479
- const valJs =
1480
- fn === "Fprintln"
1481
- ? `__sprintf("%v\\n", ${restJs})`
1482
- : `__sprintf("%v", ${restJs})`;
1483
- return `((__b,__s)=>{ __b._buf.push(...new TextEncoder().encode(__s)); })(${base}, ${valJs})`;
1484
- } else {
1485
- // Generic io.Writer fallback — call .WriteString if available
1486
- const w = this.genExpr(writerArg);
1487
- const formatted =
1488
- fn === "Fprintf"
1489
- ? `__sprintf(${restJs})`
1490
- : fn === "Fprintln"
1491
- ? `__sprintf("%v\\n", ${restJs})`
1492
- : `__sprintf("%v", ${restJs})`;
1493
- return `${w}.WriteString(${formatted})`;
1494
- }
1495
- // strings.Builder path
1496
- if (fn === "Fprintf") return `(${buf} += __sprintf(${restJs}))`;
1497
- if (fn === "Fprintln")
1498
- return `(${buf} += __sprintf("%v\\n", ${restJs}))`;
1499
- return `(${buf} += __sprintf("%v", ${restJs}))`;
1500
- }
1501
- case "Sscan": {
1502
- const strArg = this.genExpr(expr.args[0]);
1503
- const restArgs = expr.args
1504
- .slice(1)
1505
- .map((e) => this.genExpr(e))
1506
- .join(", ");
1507
- return `((str, ...ptrs) => { const tokens = str.trim().split(/\\s+/).filter(Boolean); let n = 0; for (let i = 0; i < ptrs.length && i < tokens.length; i++) { const t = tokens[i]; const p = ptrs[i]; if (typeof p.value === "number") p.value = Number(t); else if (typeof p.value === "boolean") p.value = t === "true"; else p.value = t; n++; } return [n, n < ptrs.length ? "unexpected EOF" : null]; })(${strArg}, ${restArgs})`;
1508
- }
1509
- case "Sscanln": {
1510
- const strArg = this.genExpr(expr.args[0]);
1511
- const restArgs = expr.args
1512
- .slice(1)
1513
- .map((e) => this.genExpr(e))
1514
- .join(", ");
1515
- return `((str, ...ptrs) => { const line = str.split("\\n")[0]; const tokens = line.trim().split(/\\s+/).filter(Boolean); let n = 0; for (let i = 0; i < ptrs.length && i < tokens.length; i++) { const t = tokens[i]; const p = ptrs[i]; if (typeof p.value === "number") p.value = Number(t); else if (typeof p.value === "boolean") p.value = t === "true"; else p.value = t; n++; } return [n, n < ptrs.length ? "unexpected EOF" : null]; })(${strArg}, ${restArgs})`;
1516
- }
1517
- case "Sscanf": {
1518
- const strArg = this.genExpr(expr.args[0]);
1519
- const fmtArg = this.genExpr(expr.args[1]);
1520
- const restArgs = expr.args
1521
- .slice(2)
1522
- .map((e) => this.genExpr(e))
1523
- .join(", ");
1524
- return `((str, _fmt, ...ptrs) => { const tokens = str.trim().split(/\\s+/).filter(Boolean); let n = 0; for (let i = 0; i < ptrs.length && i < tokens.length; i++) { const t = tokens[i]; const p = ptrs[i]; if (typeof p.value === "number") p.value = Number(t); else if (typeof p.value === "boolean") p.value = t === "true"; else p.value = t; n++; } return [n, n < ptrs.length ? "input does not match format" : null]; })(${strArg}, ${fmtArg}, ${restArgs})`;
1525
- }
1526
- default:
1527
- return undefined;
1528
- }
792
+ _isStringSource(srcType) {
793
+ return (
794
+ (srcType?.kind === "basic" && srcType?.name === "string") ||
795
+ (srcType?.kind === "untyped" && srcType?.base === "string")
796
+ );
1529
797
  },
1530
798
 
1531
- _genStrings(fn, a) {
1532
- const args = a();
1533
- switch (fn) {
1534
- case "Contains":
1535
- return `${args[0]}.includes(${args[1]})`;
1536
- case "HasPrefix":
1537
- return `${args[0]}.startsWith(${args[1]})`;
1538
- case "HasSuffix":
1539
- return `${args[0]}.endsWith(${args[1]})`;
1540
- case "Index":
1541
- return `${args[0]}.indexOf(${args[1]})`;
1542
- case "LastIndex":
1543
- return `${args[0]}.lastIndexOf(${args[1]})`;
1544
- case "Count":
1545
- return `((s, sep) => sep === "" ? s.length + 1 : s.split(sep).length - 1)(${args[0]}, ${args[1]})`;
1546
- case "Repeat":
1547
- return `${args[0]}.repeat(${args[1]})`;
1548
- case "Replace":
1549
- return `${args[0]}.replace(${args[1]}, ${args[2]})`;
1550
- case "ReplaceAll":
1551
- return `${args[0]}.replaceAll(${args[1]}, ${args[2]})`;
1552
- case "ToUpper":
1553
- return `${args[0]}.toUpperCase()`;
1554
- case "ToLower":
1555
- return `${args[0]}.toLowerCase()`;
1556
- case "TrimSpace":
1557
- return `${args[0]}.trim()`;
1558
- case "Trim":
1559
- return `${args[0]}.replace(new RegExp(\`^[\${${args[1]}}]+|[\${${args[1]}}]+$\`, "g"), "")`;
1560
- case "TrimPrefix":
1561
- return `((s, pre) => s.startsWith(pre) ? s.slice(pre.length) : s)(${args[0]}, ${args[1]})`;
1562
- case "TrimSuffix":
1563
- return `((s, suf) => !suf.length || !s.endsWith(suf) ? s : s.slice(0, -suf.length))(${args[0]}, ${args[1]})`;
1564
- case "TrimLeft":
1565
- return `${args[0]}.replace(new RegExp(\`^[\${${args[1]}}]+\`), "")`;
1566
- case "TrimRight":
1567
- return `${args[0]}.replace(new RegExp(\`[\${${args[1]}}]+$\`), "")`;
1568
- case "Split":
1569
- return `${args[0]}.split(${args[1]})`;
1570
- case "Join":
1571
- return `${args[0]}.join(${args[1]})`;
1572
- case "EqualFold":
1573
- return `${args[0]}.toLowerCase() === ${args[1]}.toLowerCase()`;
1574
- case "Fields":
1575
- return `(${args[0]}).trim() === '' ? [] : (${args[0]}).trim().split(/\\s+/)`;
1576
- case "Cut":
1577
- return `((s, sep) => { const i = s.indexOf(sep); return i < 0 ? [s, "", false] : [s.slice(0, i), s.slice(i + sep.length), true]; })(${args[0]}, ${args[1]})`;
1578
- case "CutPrefix":
1579
- return `(${args[0]}).startsWith(${args[1]}) ? [(${args[0]}).slice((${args[1]}).length), true] : [${args[0]}, false]`;
1580
- case "CutSuffix":
1581
- return `(${args[0]}).endsWith(${args[1]}) ? [(${args[0]}).slice(0, -(${args[1]}).length), true] : [${args[0]}, false]`;
1582
- case "SplitN":
1583
- return `((s, sep, n) => { if (n === 0) return []; if (n < 0) return s.split(sep); const r = []; let cur = s; for (let i = 1; i < n && cur.length; i++) { const j = cur.indexOf(sep); if (j < 0) break; r.push(cur.slice(0, j)); cur = cur.slice(j + sep.length); } r.push(cur); return r; })(${args[0]}, ${args[1]}, ${args[2]})`;
1584
- case "SplitAfter":
1585
- return `((s, sep) => { if (sep === "") return [...s]; const r = []; let cur = s; while (cur.length) { const j = cur.indexOf(sep); if (j < 0) { r.push(cur); break; } r.push(cur.slice(0, j + sep.length)); cur = cur.slice(j + sep.length); } return r; })(${args[0]}, ${args[1]})`;
1586
- case "SplitAfterN":
1587
- return `((s, sep, n) => { if (n === 0) return []; if (n < 0 || sep === "") return ((s, sep) => { if (sep === "") return [...s]; const r = []; let cur = s; while (cur.length) { const j = cur.indexOf(sep); if (j < 0) { r.push(cur); break; } r.push(cur.slice(0, j + sep.length)); cur = cur.slice(j + sep.length); } return r; })(s, sep); const r = []; let cur = s; for (let i = 1; i < n && cur.length; i++) { const j = cur.indexOf(sep); if (j < 0) break; r.push(cur.slice(0, j + sep.length)); cur = cur.slice(j + sep.length); } r.push(cur); return r; })(${args[0]}, ${args[1]}, ${args[2]})`;
1588
- case "IndexAny":
1589
- return `((s, chars) => { let m = -1; for (const c of chars) { const i = s.indexOf(c); if (i >= 0 && (m < 0 || i < m)) m = i; } return m; })(${args[0]}, ${args[1]})`;
1590
- case "LastIndexAny":
1591
- return `((s, chars) => { let m = -1; for (const c of chars) { const i = s.lastIndexOf(c); if (i > m) m = i; } return m; })(${args[0]}, ${args[1]})`;
1592
- case "ContainsAny":
1593
- return `[...(${args[1]})].some(c => (${args[0]}).includes(c))`;
1594
- case "ContainsRune":
1595
- return `(${args[0]}).includes(String.fromCodePoint(${args[1]}))`;
1596
- case "IndexRune":
1597
- return `(${args[0]}).indexOf(String.fromCodePoint(${args[1]}))`;
1598
- case "IndexByte":
1599
- return `(${args[0]}).indexOf(String.fromCharCode(${args[1]}))`;
1600
- case "LastIndexByte":
1601
- return `(${args[0]}).lastIndexOf(String.fromCharCode(${args[1]}))`;
1602
- case "Map":
1603
- return `[...(${args[1]})].map(c => String.fromCodePoint((${args[0]})(c.codePointAt(0)))).join("")`;
1604
- case "Title":
1605
- return `(${args[0]}).replace(/\\b\\w/g, c => c.toUpperCase())`;
1606
- case "ToTitle":
1607
- return `(${args[0]}).toUpperCase()`;
1608
- case "TrimFunc":
1609
- return `((s, f) => { let l = 0, r = s.length; while (l < r && f(s.codePointAt(l))) l++; while (r > l && f(s.codePointAt(r - 1))) r--; return s.slice(l, r); })(${args[0]}, ${args[1]})`;
1610
- case "TrimLeftFunc":
1611
- return `((s, f) => { let l = 0; while (l < s.length && f(s.codePointAt(l))) l++; return s.slice(l); })(${args[0]}, ${args[1]})`;
1612
- case "TrimRightFunc":
1613
- return `((s, f) => { let r = s.length; while (r > 0 && f(s.codePointAt(r - 1))) r--; return s.slice(0, r); })(${args[0]}, ${args[1]})`;
1614
- case "IndexFunc":
1615
- return `((s, f) => { for (let i = 0; i < s.length; i++) { const cp = s.codePointAt(i); if (f(cp)) return i; if (cp > 0xFFFF) i++; } return -1; })(${args[0]}, ${args[1]})`;
1616
- case "LastIndexFunc":
1617
- return `((s, f) => { for (let i = s.length - 1; i >= 0; i--) { const cp = s.codePointAt(i); if (f(cp)) return i; } return -1; })(${args[0]}, ${args[1]})`;
1618
- case "NewReader":
1619
- return `{_src: ${args[0]}, _pos: 0, Read(p) { const n = Math.min(p.length, this._src.length - this._pos); for (let i = 0; i < n; i++) p[i] = this._src.charCodeAt(this._pos + i); this._pos += n; return [n, n === 0 ? "EOF" : null]; }, Len() { return this._src.length - this._pos; }, Reset(s) { this._src = s; this._pos = 0; }}`;
1620
- case "NewReplacer": {
1621
- const allArgs = args.join(", ");
1622
- return `((...pairs) => { const p = []; for (let i = 0; i < pairs.length; i += 2) p.push([pairs[i], pairs[i+1]]); return { _p: p, Replace(s) { let r = s; for (const [o, n] of this._p) r = r.split(o).join(n); return r; } }; })(${allArgs})`;
1623
- }
1624
- default:
1625
- return undefined;
799
+ _genSliceTypeConversion(expr, inner, t) {
800
+ const elem = t.elem?.name;
801
+ if (elem === "byte" || elem === "uint8")
802
+ return `Array.from(new TextEncoder().encode(${inner}))`;
803
+ if (elem === "rune" || elem === "int32" || elem === "int") {
804
+ if (this._isStringSource(expr.expr._type))
805
+ return `Array.from(${inner}, __c => __c.codePointAt(0))`;
1626
806
  }
807
+ return `Array.from(${inner})`;
1627
808
  },
1628
809
 
1629
- _genBytes(fn, a) {
1630
- const __bs = `(b => String.fromCharCode(...b))`;
1631
- const __sb = `(s => [...s].map(c => c.charCodeAt(0)))`;
1632
- const args = a();
1633
- switch (fn) {
1634
- case "Contains":
1635
- return `${__bs}(${args[0]}).includes(${__bs}(${args[1]}))`;
1636
- case "HasPrefix":
1637
- 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]})`;
1638
- case "HasSuffix":
1639
- 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]})`;
1640
- case "Index":
1641
- return `${__bs}(${args[0]}).indexOf(${__bs}(${args[1]}))`;
1642
- case "Count":
1643
- return `((b, sep) => sep.length === 0 ? b.length + 1 : ${__bs}(b).split(${__bs}(sep)).length - 1)(${args[0]}, ${args[1]})`;
1644
- case "Repeat":
1645
- return `${__sb}(${__bs}(${args[0]}).repeat(${args[1]}))`;
1646
- case "Replace":
1647
- 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]})`;
1648
- case "ToUpper":
1649
- return `${__sb}(${__bs}(${args[0]}).toUpperCase())`;
1650
- case "ToLower":
1651
- return `${__sb}(${__bs}(${args[0]}).toLowerCase())`;
1652
- case "TrimSpace":
1653
- return `${__sb}(${__bs}(${args[0]}).trim())`;
1654
- case "Trim":
1655
- return `${__sb}(${__bs}(${args[0]}).replace(new RegExp(\`^[\${${args[1]}}]+|[\${${args[1]}}]+$\`, "g"), ""))`;
1656
- case "Equal":
1657
- 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]})`;
1658
- case "Split":
1659
- return `${__bs}(${args[0]}).split(${__bs}(${args[1]})).map(p => ${__sb}(p))`;
1660
- case "Join":
1661
- return `${__sb}(${args[0]}.map(p => ${__bs}(p)).join(${__bs}(${args[1]})))`;
1662
- case "ReplaceAll":
1663
- return `((b, o, n) => { const r = []; let i = 0; while (i <= b.length - o.length) { if (o.every((v, j) => b[i + j] === v)) { r.push(...n); i += o.length; } else r.push(b[i++]); } return r.concat(b.slice(i)); })(${args[0]}, ${args[1]}, ${args[2]})`;
1664
- case "TrimPrefix":
1665
- return `((b, p) => p.every((v, i) => b[i] === v) ? b.slice(p.length) : b.slice())(${args[0]}, ${args[1]})`;
1666
- case "TrimSuffix":
1667
- return `((b, s) => s.length && s.every((v, i) => b[b.length - s.length + i] === v) ? b.slice(0, -s.length) : b.slice())(${args[0]}, ${args[1]})`;
1668
- case "TrimLeft":
1669
- return `((b, c) => { let i = 0; while (i < b.length && c.includes(String.fromCharCode(b[i]))) i++; return b.slice(i); })(${args[0]}, ${args[1]})`;
1670
- case "TrimRight":
1671
- return `((b, c) => { let i = b.length; while (i > 0 && c.includes(String.fromCharCode(b[i - 1]))) i--; return b.slice(0, i); })(${args[0]}, ${args[1]})`;
1672
- case "TrimFunc":
1673
- return `((b, f) => { let l = 0, r = b.length; while (l < r && f(b[l])) l++; while (r > l && f(b[r - 1])) r--; return b.slice(l, r); })(${args[0]}, ${args[1]})`;
1674
- case "IndexByte":
1675
- return `(${args[0]}).indexOf(${args[1]})`;
1676
- case "LastIndex":
1677
- return `((b, s) => { for (let i = b.length - s.length; i >= 0; i--) if (s.every((v, j) => b[i + j] === v)) return i; return -1; })(${args[0]}, ${args[1]})`;
1678
- case "LastIndexByte":
1679
- return `(${args[0]}).lastIndexOf(${args[1]})`;
1680
- case "Fields":
1681
- return `((b) => { const s = String.fromCharCode(...b).trim(); return s === '' ? [] : s.split(/\\s+/).map(w => [...w].map(c => c.charCodeAt(0))); })(${args[0]})`;
1682
- case "Cut":
1683
- return `((b, sep) => { for (let i = 0; i <= b.length - sep.length; i++) if (sep.every((v, j) => b[i + j] === v)) return [b.slice(0, i), b.slice(i + sep.length), true]; return [b.slice(), [], false]; })(${args[0]}, ${args[1]})`;
1684
- case "ContainsAny":
1685
- return `[...(${args[1]})].some(c => (${args[0]}).includes(c.charCodeAt(0)))`;
1686
- case "ContainsRune":
1687
- return `(${args[0]}).includes(${args[1]})`;
1688
- case "Map":
1689
- return `(${args[1]}).map(v => (${args[0]})(v))`;
1690
- case "SplitN":
1691
- return `((b, sep, n) => { if (n === 0) return []; if (n < 0) return ((b, sep) => { const r = []; let i = 0; while (i <= b.length) { const j = b.findIndex((_, k) => k >= i && sep.every((v, l) => b[k + l] === sep[l])); if (j < 0 || j >= b.length) break; r.push(b.slice(i, j)); i = j + sep.length; } r.push(b.slice(i)); return r; })(b, sep); const r = []; let i = 0; for (let c = 1; c < n; c++) { const j = b.findIndex((_, k) => k >= i && sep.every((v, l) => b[k + l] === sep[l])); if (j < 0) break; r.push(b.slice(i, j)); i = j + sep.length; } r.push(b.slice(i)); return r; })(${args[0]}, ${args[1]}, ${args[2]})`;
1692
- case "NewReader":
1693
- return `{_src: ${args[0]}, _pos: 0, Read(p) { const n = Math.min(p.length, this._src.length - this._pos); for (let i = 0; i < n; i++) p[i] = this._src[this._pos + i]; this._pos += n; return [n, n === 0 ? "EOF" : null]; }, Len() { return this._src.length - this._pos; }, Reset(b) { this._src = b; this._pos = 0; }}`;
1694
- default:
1695
- return undefined;
1696
- }
1697
- },
1698
-
1699
- _genStrconv(fn, a) {
1700
- const args = a();
1701
- switch (fn) {
1702
- case "Itoa":
1703
- return `String(${args[0]})`;
1704
- case "Atoi":
1705
- return `(Number.isNaN(Number(${args[0]})) ? [0, "invalid syntax"] : [Number(${args[0]}) | 0, null])`;
1706
- case "FormatBool":
1707
- return `String(${args[0]})`;
1708
- case "FormatInt":
1709
- return `(${args[0]}).toString(${args[1]})`;
1710
- case "FormatFloat":
1711
- return `String(${args[0]})`;
1712
- case "ParseFloat":
1713
- return `(Number.isNaN(Number(${args[0]})) ? [0, "invalid syntax"] : [Number(${args[0]}), null])`;
1714
- case "ParseInt":
1715
- return `(Number.isNaN(parseInt(${args[0]}, ${args[1]} || 10)) ? [0, "invalid syntax"] : [parseInt(${args[0]}, ${args[1]} || 10), null])`;
1716
- case "ParseBool":
1717
- return `(${args[0]} === "true" || ${args[0]} === "1" ? [true, null] : ${args[0]} === "false" || ${args[0]} === "0" ? [false, null] : [false, "invalid syntax"])`;
1718
- case "Quote":
1719
- return `JSON.stringify(${args[0]})`;
1720
- case "Unquote":
1721
- return `((s) => { try { const v = JSON.parse(s); return [v, null]; } catch(e) { return ["", "invalid syntax"]; } })(${args[0]})`;
1722
- case "AppendInt":
1723
- return `[...(${args[0]}), ...new TextEncoder().encode((${args[1]}).toString(${args[2]}))]`;
1724
- case "AppendFloat":
1725
- return `[...(${args[0]}), ...new TextEncoder().encode(${args[1]}.toFixed(${args[3]} < 0 ? 6 : ${args[3]}))]`;
1726
- default:
1727
- return undefined;
1728
- }
1729
- },
1730
-
1731
- _genSort(fn, a) {
1732
- const args = a();
1733
- switch (fn) {
1734
- case "Ints":
1735
- case "Float64s":
1736
- return `${args[0]}.sort((a, b) => a - b)`;
1737
- case "Strings":
1738
- return `${args[0]}.sort()`;
1739
- case "Slice":
1740
- case "SliceStable":
1741
- return `${args[0]}.sort((a, b) => ${args[1]}(a, b) ? -1 : ${args[1]}(b, a) ? 1 : 0)`;
1742
- case "SliceIsSorted":
1743
- return `${args[0]}.every((v, i, a) => i === 0 || ${args[1]}(a[i - 1], v))`;
1744
- case "Search":
1745
- return `((n, f) => { let lo = 0, hi = n; while (lo < hi) { const mid = (lo + hi) >>> 1; if (f(mid)) hi = mid; else lo = mid + 1; } return lo; })(${args[0]}, ${args[1]})`;
1746
- case "IntsAreSorted":
1747
- case "Float64sAreSorted":
1748
- case "StringsAreSorted":
1749
- return `(${args[0]}).every((v, i, a) => i === 0 || a[i - 1] <= v)`;
1750
- default:
1751
- return undefined;
1752
- }
1753
- },
1754
-
1755
- _genMath(fn, a) {
1756
- const args = a();
1757
- switch (fn) {
1758
- case "Abs":
1759
- return `Math.abs(${args[0]})`;
1760
- case "Floor":
1761
- return `Math.floor(${args[0]})`;
1762
- case "Ceil":
1763
- return `Math.ceil(${args[0]})`;
1764
- case "Round":
1765
- return `Math.round(${args[0]})`;
1766
- case "Sqrt":
1767
- return `Math.sqrt(${args[0]})`;
1768
- case "Cbrt":
1769
- return `Math.cbrt(${args[0]})`;
1770
- case "Pow":
1771
- return `Math.pow(${args[0]}, ${args[1]})`;
1772
- case "Log":
1773
- return `Math.log(${args[0]})`;
1774
- case "Log2":
1775
- return `Math.log2(${args[0]})`;
1776
- case "Log10":
1777
- return `Math.log10(${args[0]})`;
1778
- case "Sin":
1779
- return `Math.sin(${args[0]})`;
1780
- case "Cos":
1781
- return `Math.cos(${args[0]})`;
1782
- case "Tan":
1783
- return `Math.tan(${args[0]})`;
1784
- case "Min":
1785
- return `Math.min(${args[0]}, ${args[1]})`;
1786
- case "Max":
1787
- return `Math.max(${args[0]}, ${args[1]})`;
1788
- case "Mod":
1789
- return `${args[0]} % ${args[1]}`;
1790
- case "Inf":
1791
- return `(${args[0]} >= 0 ? Infinity : -Infinity)`;
1792
- case "IsNaN":
1793
- return `Number.isNaN(${args[0]})`;
1794
- case "IsInf":
1795
- return `(${args[1]} > 0 ? ${args[0]} === Infinity : ${args[1]} < 0 ? ${args[0]} === -Infinity : !Number.isFinite(${args[0]}))`;
1796
- case "NaN":
1797
- return "NaN";
1798
- case "Atan":
1799
- return `Math.atan(${args[0]})`;
1800
- case "Atan2":
1801
- return `Math.atan2(${args[0]}, ${args[1]})`;
1802
- case "Asin":
1803
- return `Math.asin(${args[0]})`;
1804
- case "Acos":
1805
- return `Math.acos(${args[0]})`;
1806
- case "Exp":
1807
- return `Math.exp(${args[0]})`;
1808
- case "Exp2":
1809
- return `Math.pow(2, ${args[0]})`;
1810
- case "Trunc":
1811
- return `Math.trunc(${args[0]})`;
1812
- case "Hypot":
1813
- return `Math.hypot(${args[0]}, ${args[1]})`;
1814
- case "Signbit":
1815
- return `(${args[0]} < 0 || Object.is(${args[0]}, -0))`;
1816
- case "Copysign":
1817
- return `(Math.abs(${args[0]}) * (${args[1]} < 0 || Object.is(${args[1]}, -0) ? -1 : 1))`;
1818
- case "Dim":
1819
- return `Math.max(${args[0]} - ${args[1]}, 0)`;
1820
- case "Remainder":
1821
- return `(${args[0]} - Math.round(${args[0]} / ${args[1]}) * ${args[1]})`;
1822
- default:
1823
- return undefined;
1824
- }
1825
- },
1826
-
1827
- _genUnicode(fn, a) {
1828
- const args = a();
1829
- const cp = `String.fromCodePoint(${args[0]})`;
1830
- switch (fn) {
1831
- case "IsLetter":
1832
- return `/\\p{L}/u.test(${cp})`;
1833
- case "IsDigit":
1834
- return `/\\p{Nd}/u.test(${cp})`;
1835
- case "IsSpace":
1836
- return `/\\s/.test(${cp})`;
1837
- case "IsUpper":
1838
- return `((__c) => __c === __c.toUpperCase() && /\\p{L}/u.test(__c))(${cp})`;
1839
- case "IsLower":
1840
- return `((__c) => __c === __c.toLowerCase() && /\\p{L}/u.test(__c))(${cp})`;
1841
- case "IsPunct":
1842
- return `/\\p{P}/u.test(${cp})`;
1843
- case "IsControl":
1844
- return `/\\p{Cc}/u.test(${cp})`;
1845
- case "IsPrint":
1846
- return `!/\\p{Cc}/u.test(${cp})`;
1847
- case "IsGraphic":
1848
- return `!/\\p{Cc}/u.test(${cp})`;
1849
- case "ToUpper":
1850
- return `${cp}.toUpperCase().codePointAt(0)`;
1851
- case "ToLower":
1852
- return `${cp}.toLowerCase().codePointAt(0)`;
1853
- default:
1854
- return undefined;
1855
- }
1856
- },
1857
-
1858
- _genOs(fn, a) {
1859
- const args = a();
1860
- switch (fn) {
1861
- case "Exit":
1862
- return `process.exit(${args[0]})`;
1863
- case "Getenv":
1864
- return `(process.env[${args[0]}] ?? "")`;
1865
- default:
1866
- return undefined;
1867
- }
1868
- },
1869
-
1870
- _genErrors(fn, a) {
1871
- const args = a();
1872
- switch (fn) {
1873
- case "New":
1874
- this._usesError = true;
1875
- return `__error(${args[0]})`;
1876
- case "Is":
1877
- this._usesErrorIs = true;
1878
- return `__errorIs(${args[0]}, ${args[1]})`;
1879
- case "Unwrap":
1880
- return `(${args[0]}?._cause ?? null)`;
1881
- default:
1882
- return undefined;
1883
- }
1884
- },
1885
-
1886
- _genTimeMethodCall(method, expr) {
1887
- const recv = expr.func.expr;
1888
- const recvJs = this.genExpr(recv);
1889
- const args = expr.args.map((a) => this.genExpr(a));
1890
- switch (method) {
1891
- case "Format":
1892
- this._usesTimeFmt = true;
1893
- return `__timeFmt(${recvJs}._d, ${args[0]})`;
1894
- case "String":
1895
- this._usesTimeFmt = true;
1896
- return `__timeFmt(${recvJs}._d, "2006-01-02T15:04:05Z07:00")`;
1897
- case "Year":
1898
- return `${recvJs}._d.getFullYear()`;
1899
- case "Month":
1900
- return `${recvJs}._d.getMonth() + 1`;
1901
- case "Day":
1902
- return `${recvJs}._d.getDate()`;
1903
- case "Hour":
1904
- return `${recvJs}._d.getHours()`;
1905
- case "Minute":
1906
- return `${recvJs}._d.getMinutes()`;
1907
- case "Second":
1908
- return `${recvJs}._d.getSeconds()`;
1909
- case "Weekday":
1910
- return `${recvJs}._d.getDay()`;
1911
- case "Unix":
1912
- return `Math.floor(${recvJs}._d.getTime() / 1000)`;
1913
- case "UnixMilli":
1914
- return `${recvJs}._d.getTime()`;
1915
- case "Add":
1916
- return `{_d: new Date(${recvJs}._d.getTime() + (${args[0]}))}`;
1917
- case "Sub":
1918
- return `${recvJs}._d.getTime() - (${args[0]})._d.getTime()`;
1919
- case "Before":
1920
- return `${recvJs}._d < (${args[0]})._d`;
1921
- case "After":
1922
- return `${recvJs}._d > (${args[0]})._d`;
1923
- case "Equal":
1924
- return `${recvJs}._d.getTime() === (${args[0]})._d.getTime()`;
1925
- default:
1926
- return undefined;
1927
- }
1928
- },
1929
-
1930
- _genTime(fn, a) {
1931
- const args = a();
1932
- switch (fn) {
1933
- case "Now":
1934
- return "{_d: new Date()}";
1935
- case "Since":
1936
- return `(Date.now() - (${args[0]})._d.getTime())`;
1937
- case "Sleep":
1938
- return `await new Promise(r => setTimeout(r, ${args[0]} / 1000000))`;
1939
- case "Parse":
1940
- this._usesTimeParse = true;
1941
- return `__timeParse(${args[0]}, ${args[1]})`;
1942
- case "Unix":
1943
- return `{_d: new Date((${args[0]}) * 1000)}`;
1944
- case "Date":
1945
- return `{_d: new Date(${args[0]}, ${args[1]} - 1, ${args[2]}, ${args[3]}, ${args[4]}, ${args[5]})}`;
1946
- default:
1947
- return undefined;
1948
- }
1949
- },
1950
-
1951
- _genRand(fn, a) {
1952
- const args = a();
1953
- switch (fn) {
1954
- case "Intn":
1955
- case "Int63n":
1956
- case "Int31n":
1957
- return `Math.floor(Math.random() * ${args[0]})`;
1958
- case "Float64":
1959
- case "Float32":
1960
- return "Math.random()";
1961
- case "Int":
1962
- case "Int31":
1963
- return "Math.floor(Math.random() * 2147483647)";
1964
- case "Int63":
1965
- return "Math.floor(Math.random() * Number.MAX_SAFE_INTEGER)";
1966
- case "Seed":
1967
- return "(void 0)";
1968
- case "Shuffle":
1969
- return `((n, f) => { for (let i = n - 1; i > 0; i--) { const j = Math.floor(Math.random() * (i + 1)); f(i, j); } })(${args[0]}, ${args[1]})`;
1970
- case "Perm":
1971
- return `((n) => { const a = Array.from({length: n}, (_, i) => i); for (let i = n - 1; i > 0; i--) { const j = Math.floor(Math.random() * (i + 1)); [a[i], a[j]] = [a[j], a[i]]; } return a; })(${args[0]})`;
1972
- default:
1973
- return undefined;
1974
- }
1975
- },
1976
-
1977
- _genUtf8(fn, a) {
1978
- const args = a();
1979
- switch (fn) {
1980
- case "RuneCountInString":
1981
- return `[...(${args[0]})].length`;
1982
- case "RuneLen":
1983
- return `((r) => r < 0 ? -1 : r <= 0x7F ? 1 : r <= 0x7FF ? 2 : r <= 0xFFFF ? (r >= 0xD800 && r <= 0xDFFF ? -1 : 3) : r <= 0x10FFFF ? 4 : -1)(${args[0]})`;
1984
- case "ValidString":
1985
- return `/[\\uD800-\\uDBFF](?![\\uDC00-\\uDFFF])|(?<![\\uD800-\\uDBFF])[\\uDC00-\\uDFFF]/.test(${args[0]}) === false`;
1986
- case "ValidRune":
1987
- return `(${args[0]}) >= 0 && (${args[0]}) <= 0x10FFFF && !((${args[0]}) >= 0xD800 && (${args[0]}) <= 0xDFFF)`;
1988
- case "DecodeRuneInString":
1989
- return `((s) => { if (!s) return [0xFFFD, 0]; const cp = s.codePointAt(0); return [cp, cp > 0xFFFF ? 2 : 1]; })(${args[0]})`;
1990
- case "DecodeLastRuneInString":
1991
- return `((s) => { if (!s) return [0xFFFD, 0]; const i = s.length > 1 && s.charCodeAt(s.length - 1) >= 0xDC00 && s.charCodeAt(s.length - 1) <= 0xDFFF ? s.length - 2 : s.length - 1; const cp = s.codePointAt(i); return [cp, cp > 0xFFFF ? 2 : 1]; })(${args[0]})`;
1992
- case "FullRuneInString":
1993
- return `(${args[0]}).length > 0`;
1994
- default:
1995
- return undefined;
1996
- }
1997
- },
1998
-
1999
- _genPath(fn, a, expr) {
2000
- const args = a();
2001
- switch (fn) {
2002
- case "Base":
2003
- return `((p) => { if (!p) return "."; const stripped = p.replace(/\\/+$/, ""); if (!stripped) return "/"; const i = stripped.lastIndexOf("/"); return i < 0 ? stripped : stripped.slice(i + 1) || "/"; })(${args[0]})`;
2004
- case "Dir":
2005
- return `((p) => { const i = p.lastIndexOf("/"); if (i < 0) return "."; if (i === 0) return "/"; return p.slice(0, i); })(${args[0]})`;
2006
- case "Ext":
2007
- return `((p) => { const b = p.slice(p.lastIndexOf("/") + 1); const i = b.lastIndexOf("."); return i <= 0 ? "" : b.slice(i); })(${args[0]})`;
2008
- case "Join": {
2009
- this._usesPathClean = true;
2010
- const allArgs = expr.args.map((e) => this.genExpr(e)).join(", ");
2011
- return `__pathClean([${allArgs}].filter(x => x !== "").join("/"))`;
810
+ _genPrimitiveConversion(expr, inner, target) {
811
+ switch (target) {
812
+ case "string": {
813
+ const srcType = expr.expr._type;
814
+ if (srcType && this.isIntType(srcType))
815
+ return `String.fromCodePoint(${inner})`;
816
+ if (srcType?.kind === "slice" && srcType.elem?.name === "int")
817
+ return `${inner}.map(c => String.fromCharCode(c)).join("")`;
818
+ return `String(${inner})`;
2012
819
  }
2013
- case "Clean":
2014
- this._usesPathClean = true;
2015
- return `__pathClean(${args[0]})`;
2016
- case "IsAbs":
2017
- return `(${args[0]}).startsWith("/")`;
2018
- case "Split":
2019
- return `((p) => { const i = p.lastIndexOf("/"); return i < 0 ? ["", p] : [p.slice(0, i + 1), p.slice(i + 1)]; })(${args[0]})`;
2020
- case "Match":
2021
- return `((pat, name) => { try { const sp=/[.+^$()|[\\]\\\\]/g; const re = new RegExp("^" + pat.replace(sp, "\\\\$&").replace(/\\*/g, "[^/]*").replace(/\\?/g, "[^/]") + "$"); return [re.test(name), null]; } catch(e) { return [false, "syntax error in pattern"]; } })(${args[0]}, ${args[1]})`;
820
+ case "int":
821
+ case "byte":
822
+ case "rune":
823
+ return `Math.trunc(Number(${inner}))`;
824
+ case "float64":
825
+ return `Number(${inner})`;
826
+ case "bool":
827
+ return `Boolean(${inner})`;
2022
828
  default:
2023
- return undefined;
829
+ return inner;
2024
830
  }
2025
831
  },
2026
832
  };