gofront 0.0.9 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. package/README.md +6 -6
  2. package/package.json +7 -4
  3. package/src/codegen/expressions.js +520 -1717
  4. package/src/codegen/index.js +576 -0
  5. package/src/codegen/runtime.js +151 -0
  6. package/src/codegen/statements.js +305 -296
  7. package/src/codegen/stdlib/builder.js +36 -0
  8. package/src/codegen/stdlib/bytes.js +62 -0
  9. package/src/codegen/stdlib/errors.js +17 -0
  10. package/src/codegen/stdlib/fmt.js +101 -0
  11. package/src/codegen/stdlib/gom.js +152 -0
  12. package/src/codegen/stdlib/html.js +17 -0
  13. package/src/codegen/stdlib/index.js +95 -0
  14. package/src/codegen/stdlib/io.js +33 -0
  15. package/src/codegen/stdlib/maps.js +42 -0
  16. package/src/codegen/stdlib/math.js +53 -0
  17. package/src/codegen/stdlib/os.js +10 -0
  18. package/src/codegen/stdlib/path.js +31 -0
  19. package/src/codegen/stdlib/rand.js +24 -0
  20. package/src/codegen/stdlib/regexp.js +51 -0
  21. package/src/codegen/stdlib/slices.js +48 -0
  22. package/src/codegen/stdlib/sort.js +28 -0
  23. package/src/codegen/stdlib/strconv.js +30 -0
  24. package/src/codegen/stdlib/strings.js +83 -0
  25. package/src/codegen/stdlib/time.js +54 -0
  26. package/src/codegen/stdlib/unicode.js +22 -0
  27. package/src/codegen/stdlib/utf8.js +23 -0
  28. package/src/codegen/templ.js +281 -0
  29. package/src/compiler.js +80 -74
  30. package/src/dts-parser.js +355 -371
  31. package/src/index.js +13 -49
  32. package/src/lexer.js +186 -272
  33. package/src/minifier.js +305 -318
  34. package/src/parser/expressions.js +51 -34
  35. package/src/{parser.js → parser/index.js} +4 -4
  36. package/src/parser/statements.js +24 -34
  37. package/src/parser/types.js +31 -29
  38. package/src/templ-parser.js +2 -2
  39. package/src/tokens.js +109 -0
  40. package/src/typechecker/assignability.js +258 -0
  41. package/src/typechecker/expressions.js +559 -549
  42. package/src/typechecker/index.js +555 -0
  43. package/src/typechecker/resolve.js +421 -0
  44. package/src/typechecker/statements.js +267 -280
  45. package/src/typechecker/stdlib/core.js +592 -0
  46. package/src/typechecker/stdlib/extended.js +332 -0
  47. package/src/typechecker/stdlib.js +9 -1060
  48. package/src/typechecker/termination.js +83 -0
  49. package/src/codegen.js +0 -882
  50. package/src/typechecker.js +0 -1299
@@ -2,6 +2,57 @@
2
2
 
3
3
  import { ERROR, isComplex } from "../typechecker/types.js";
4
4
 
5
+ // Namespace constants: pkg.Field → JS literal
6
+ const NS_CONSTANTS = {
7
+ math: {
8
+ Pi: "Math.PI",
9
+ E: "Math.E",
10
+ MaxFloat64: "Number.MAX_VALUE",
11
+ SmallestNonzeroFloat64: "Number.MIN_VALUE",
12
+ MaxInt: "Number.MAX_SAFE_INTEGER",
13
+ MinInt: "Number.MIN_SAFE_INTEGER",
14
+ },
15
+ io: {
16
+ EOF: '"EOF"',
17
+ Discard:
18
+ "{ WriteString(s) { return s.length; }, Write(b) { return b.length; } }",
19
+ },
20
+ os: { Args: "process.argv" },
21
+ time: {
22
+ Millisecond: "1000000",
23
+ Second: "1000000000",
24
+ Minute: "60000000000",
25
+ Hour: "3600000000000",
26
+ RFC3339: '"2006-01-02T15:04:05Z07:00"',
27
+ RFC3339Nano: '"2006-01-02T15:04:05.999999999Z07:00"',
28
+ DateOnly: '"2006-01-02"',
29
+ TimeOnly: '"15:04:05"',
30
+ DateTime: '"2006-01-02 15:04:05"',
31
+ UTC: "null",
32
+ Local: "null",
33
+ January: "1",
34
+ February: "2",
35
+ March: "3",
36
+ April: "4",
37
+ May: "5",
38
+ June: "6",
39
+ July: "7",
40
+ August: "8",
41
+ September: "9",
42
+ October: "10",
43
+ November: "11",
44
+ December: "12",
45
+ Sunday: "0",
46
+ Monday: "1",
47
+ Tuesday: "2",
48
+ Wednesday: "3",
49
+ Thursday: "4",
50
+ Friday: "5",
51
+ Saturday: "6",
52
+ },
53
+ utf8: { RuneError: "0xFFFD", MaxRune: "0x10FFFF", UTFMax: "4" },
54
+ };
55
+
5
56
  const INT_TYPE_NAMES = new Set([
6
57
  "int",
7
58
  "uint",
@@ -18,553 +69,325 @@ const INT_TYPE_NAMES = new Set([
18
69
  "rune",
19
70
  ]);
20
71
 
72
+ // Zero-value JS literals for basic Go types.
73
+ const ZERO_FOR_BASIC = {
74
+ int: "0",
75
+ uint: "0",
76
+ int8: "0",
77
+ int16: "0",
78
+ int32: "0",
79
+ int64: "0",
80
+ uint8: "0",
81
+ uint16: "0",
82
+ uint32: "0",
83
+ uint64: "0",
84
+ uintptr: "0",
85
+ float32: "0",
86
+ float64: "0",
87
+ byte: "0",
88
+ rune: "0",
89
+ complex64: "{ re: 0, im: 0 }",
90
+ complex128: "{ re: 0, im: 0 }",
91
+ string: '""',
92
+ bool: "false",
93
+ };
94
+
95
+ // Set of all numeric type names (for type-assertion codegen).
96
+ const TYPEOF_NUMBER_NAMES = new Set([...INT_TYPE_NAMES, "float32", "float64"]);
97
+
98
+ // Builtin function codegen — keyed by builtin name, value is (self, expr) => string.
99
+ const BUILTIN_GEN = {
100
+ append: (s, e) => s.genAppend(e),
101
+ len: (s, e) => s._genBuiltinLen(e),
102
+ cap: (s, e) => `${s.genExpr(e.args[0])}.length`,
103
+ make: (s, e) => s.genMake(e),
104
+ delete: (s, e) => {
105
+ const [m, k] = e.args.map((a) => s.genExpr(a));
106
+ return `(delete ${m}[${k}])`;
107
+ },
108
+ copy: (s, e) => {
109
+ const [dst, src] = e.args.map((a) => s.genExpr(a));
110
+ return `((__cd,__cs)=>{const n=Math.min(__cd.length,__cs.length);__cd.splice(0,n,...__cs.slice(0,n));return n;})(${dst},${src})`;
111
+ },
112
+ new: (s, e) => `{ value: ${s.zeroValueForExpr(e.args[0])} }`,
113
+ print: (s, e) => `console.log(${e.args.map((a) => s.genExpr(a)).join(", ")})`,
114
+ println: (s, e) =>
115
+ `console.log(${e.args.map((a) => s.genExpr(a)).join(", ")})`,
116
+ panic: (s, e) => `(() => { throw new Error(${s.genExpr(e.args[0])}); })()`,
117
+ recover: () =>
118
+ `(typeof __panic !== "undefined" && __panic !== null ? (() => { const __r = __panic.message ?? String(__panic); __panic = null; return __r; })() : null)`,
119
+ error: (s, e) => {
120
+ s._usesError = true;
121
+ return `__error(${s.genExpr(e.args[0])})`;
122
+ },
123
+ min: (s, e) => `Math.min(${e.args.map((a) => s.genExpr(a)).join(", ")})`,
124
+ max: (s, e) => `Math.max(${e.args.map((a) => s.genExpr(a)).join(", ")})`,
125
+ clear: (s, e) => s._genBuiltinClear(e),
126
+ complex: (s, e) =>
127
+ `{ re: ${s.genExpr(e.args[0])}, im: ${s.genExpr(e.args[1])} }`,
128
+ real: (s, e) => `(${s.genExpr(e.args[0])}).re`,
129
+ imag: (s, e) => `(${s.genExpr(e.args[0])}).im`,
130
+ };
131
+
132
+ // Method-name dispatch for genExpr — string values add no static call edges.
133
+ const EXPR_GEN_DELEGATE = {
134
+ UnaryExpr: "_genUnaryExpr",
135
+ BinaryExpr: "_genBinaryExpr",
136
+ CallExpr: "genCall",
137
+ SelectorExpr: "_genSelectorExpr",
138
+ IndexExpr: "_genIndexExpr",
139
+ SliceExpr: "_genSliceExpr",
140
+ CompositeLit: "genCompositeLit",
141
+ FuncLit: "_genFuncLit",
142
+ TypeConversion: "_genTypeConversion",
143
+ TypeAssertExpr: "_genTypeAssertExpr",
144
+ };
145
+
21
146
  export const expressionGenMethods = {
22
147
  genExpr(expr) {
23
148
  switch (expr.kind) {
24
149
  case "BasicLit":
25
150
  if (expr.litKind === "STRING") return JSON.stringify(expr.value);
26
151
  return expr.value; // int, float, bool, nil(null)
27
-
28
152
  case "ImagLit":
29
153
  return `{ re: 0, im: ${expr.value} }`;
30
-
31
154
  case "Ident":
32
155
  if (this._boxedVars.has(expr.name) && !expr._isAddressOf)
33
156
  return `${expr.name}.value`;
34
157
  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
-
158
+ case "AwaitExpr":
159
+ return `await ${this.genExpr(expr.expr)}`;
122
160
  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)}}`;
161
+ case "RangeExpr":
162
+ return this.genExpr(expr.expr);
163
+ default: {
164
+ const m = EXPR_GEN_DELEGATE[expr.kind];
165
+ if (m) return this[m](expr);
166
+ throw new Error(`CodeGen: unhandled expression kind '${expr.kind}'`);
307
167
  }
168
+ }
169
+ },
308
170
 
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
- }
171
+ _genUnaryExpr(expr) {
172
+ const op = expr.op === "^" ? "~" : expr.op; // bitwise NOT
173
+ // Address-of: &x
174
+ if (op === "&") {
175
+ if (expr.operand.kind === "Ident") {
176
+ // Mark so Ident codegen does NOT append .value
177
+ expr.operand._isAddressOf = true;
178
+ // For boxed vars, &x returns the box itself
179
+ if (this._boxedVars.has(expr.operand.name)) {
180
+ return this.genExpr(expr.operand);
181
+ }
182
+ }
183
+ // For non-boxed vars (structs, etc.), wrap in { value: x }
184
+ return `{ value: ${this.genExpr(expr.operand)} }`;
185
+ }
186
+ // Dereference: *p → p.value
187
+ if (op === "*") return `${this.genExpr(expr.operand)}.value`;
188
+ // Unary minus/plus on complex
189
+ if ((op === "-" || op === "+") && isComplex(expr.operand._type)) {
190
+ const inner = this.genExpr(expr.operand);
191
+ if (op === "-") return `{ re: -${inner}.re, im: -${inner}.im }`;
192
+ return inner;
193
+ }
194
+ return `${op}${this.genExpr(expr.operand)}`;
195
+ },
319
196
 
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
- }
197
+ _genSelectorExpr(expr) {
198
+ if (expr._isMethodExpr) {
199
+ return `((recv, ...args) => recv.${expr.field}(...args))`;
200
+ }
201
+ if (expr.expr.kind === "Ident") {
202
+ const nsConst = NS_CONSTANTS[expr.expr.name]?.[expr.field];
203
+ if (nsConst !== undefined) return nsConst;
204
+ }
205
+ const base = this.genExpr(expr.expr);
206
+ if (this.bundledPackages.has(base)) return expr.field;
207
+ if (expr.expr._type?.kind === "pointer" && expr.field !== "value")
208
+ return this._genPointerSelectorField(expr, base);
209
+ const sel = `${base}.${expr.field}`;
210
+ if (expr._isMethodValue && !expr._callee) return `${sel}.bind(${base})`;
211
+ return sel;
212
+ },
332
213
 
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
- }
214
+ _genPointerSelectorField(expr, base) {
215
+ const sel = `${base}.value.${expr.field}`;
216
+ if (expr._isMethodValue && !expr._callee)
217
+ return `${sel}.bind(${base}.value)`;
218
+ return sel;
219
+ },
353
220
 
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
- }
221
+ _genIndexExpr(expr) {
222
+ const exprType = expr.expr._type;
223
+ const wrapField = this._namedWrapperField(exprType, expr.expr);
224
+ const base = wrapField
225
+ ? `${this.genExpr(expr.expr)}.${wrapField}`
226
+ : this.genExpr(expr.expr);
227
+ const idx = this.genExpr(expr.index);
228
+ if (expr._mapValueType && !expr._lvalue)
229
+ return this._genMapIndexExpr(expr, base, idx);
230
+ if (this._isStringExprType(exprType) && !expr._lvalue)
231
+ return `${base}.charCodeAt(${idx})`;
232
+ return `${base}[${idx}]`;
233
+ },
393
234
 
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
- }
235
+ _genMapIndexExpr(expr, base, idx) {
236
+ const zero = this.zeroValueForType(expr._mapValueType);
237
+ if (this._hasCallExpr(expr.expr) || this._hasCallExpr(expr.index))
238
+ return `((__m, __k) => __m[__k] ?? ${zero})(${base}, ${idx})`;
239
+ return `(${base}[${idx}] ?? ${zero})`;
240
+ },
406
241
 
407
- case "AwaitExpr":
408
- return `await ${this.genExpr(expr.expr)}`;
242
+ _isStringExprType(exprType) {
243
+ return (
244
+ (exprType?.kind === "basic" && exprType.name === "string") ||
245
+ (exprType?.kind === "untyped" && exprType.base === "string")
246
+ );
247
+ },
409
248
 
410
- case "RangeExpr":
411
- return this.genExpr(expr.expr);
249
+ _genSliceExpr(expr) {
250
+ const base = this.genExpr(expr.expr);
251
+ const lo = expr.low ? this.genExpr(expr.low) : "";
252
+ const hi = expr.high ? this.genExpr(expr.high) : "";
253
+ if (!lo && !hi) return `${base}.slice()`;
254
+ if (!hi) return `${base}.slice(${lo})`;
255
+ if (!lo) return `${base}.slice(0, ${hi})`;
256
+ return `${base}.slice(${lo}, ${hi})`;
257
+ },
412
258
 
413
- default:
414
- throw new Error(`CodeGen: unhandled expression kind '${expr.kind}'`);
415
- }
259
+ _genFuncLit(expr) {
260
+ const params = expr.params.map((p) => p.name).join(", ");
261
+ const asyncPrefix = expr.async ? "async " : "";
262
+ const saved = this.out;
263
+ const prevBoxed = this._boxedVars;
264
+ this._boxedVars = new Set(prevBoxed); // inherit parent's boxed vars (closures)
265
+ this._scanAddressTaken(expr.body);
266
+ this.out = [];
267
+ this.indented(() => this._genBody(expr.body));
268
+ const body = this.out.join("\n");
269
+ this.out = saved;
270
+ this._boxedVars = prevBoxed;
271
+ return `${asyncPrefix}function(${params}) {\n${body}\n${" ".repeat(this.indent)}}`;
272
+ },
273
+
274
+ _genTypeAssertExpr(expr) {
275
+ const val = this.genExpr(expr.expr);
276
+ const check = this._typeCheckExpr(expr.type, val);
277
+ if (!expr._commaOk) {
278
+ // plain assertion: panic if check fails (matches Go behavior)
279
+ if (check === "true") return val; // can't check at runtime — pass through
280
+ return `(${check} ? ${val} : (() => { throw new Error("interface conversion: type assertion failed"); })())`;
281
+ }
282
+ // comma-ok: emit [value-or-zero, runtimeTypeCheck]
283
+ const zero = this.zeroValueForTypeNode(expr.type);
284
+ return `(${check} ? [${val}, true] : [${zero}, false])`;
285
+ },
286
+
287
+ _genTypeConversionCall(expr) {
288
+ const name = expr._conversionTargetType?.name;
289
+ const arg = this.genExpr(expr.args[0]);
290
+ if (name && this.namedWrapperNames.has(name)) return `new ${name}(${arg})`;
291
+ return arg;
416
292
  },
417
293
 
418
294
  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
- }
295
+ if (expr._isTypeConversion) return this._genTypeConversionCall(expr);
428
296
 
429
- // Unwrap InstantiationExpr for function calls: Foo[int](42) → Foo(42)
430
297
  const funcExpr =
431
298
  expr.func.kind === "InstantiationExpr" ? expr.func.expr : expr.func;
432
- // Handle built-ins that need special JS translation
433
299
  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;
300
+ const builtin = this._genBuiltinIdent(funcExpr.name, expr);
301
+ if (builtin !== undefined) return builtin;
535
302
  }
536
303
 
537
- // strings.Builder / bytes.Buffer / regexp.Regexp method dispatch
538
304
  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
- }
305
+ const result = this._genSelectorCall(expr);
306
+ if (result !== undefined) return result;
551
307
  }
552
308
 
553
- // Mark the callee so SelectorExpr knows it's being called, not used as a value
554
309
  expr.func._callee = true;
555
310
  const rawFn = this.genExpr(expr.func);
556
- // Wrap function literals in parens so `function(){}()` → `(function(){})()`
557
311
  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
- }
312
+ if (expr._multiForward) return `${fn}(...${this.genExpr(expr.args[0])})`;
562
313
  const args = expr.args
563
314
  .map((a) => (a._spread ? `...${this.genExpr(a)}` : this.genExpr(a)))
564
315
  .join(", ");
565
316
  return `${fn}(${args})`;
566
317
  },
567
318
 
319
+ _isErrorInterfaceType(t) {
320
+ return t?.kind === "named" && t?.underlying?.kind === "interface";
321
+ },
322
+
323
+ _isErrorType(t) {
324
+ return (
325
+ t && (t === ERROR || t?.name === "error" || this._isErrorInterfaceType(t))
326
+ );
327
+ },
328
+
329
+ _isErrorMethodCall(expr) {
330
+ if (expr.func.field !== "Error") return false;
331
+ return this._isErrorType(expr.func.expr._type);
332
+ },
333
+
334
+ _resolveRecvName(recvType) {
335
+ return recvType?.name ?? recvType?.base?.name;
336
+ },
337
+
338
+ _genReceiverTypeCall(recvName, expr) {
339
+ if (recvName === "strings.Builder" || recvName === "bytes.Buffer")
340
+ return this._genBuilderCall(recvName, expr.func.field, expr);
341
+ if (recvName === "regexp.Regexp")
342
+ return this._genRegexpMethodCall(expr.func.field, expr);
343
+ if (recvName === "time.Time")
344
+ return this._genTimeMethodCall(expr.func.field, expr);
345
+ return undefined;
346
+ },
347
+
348
+ _genSelectorCall(expr) {
349
+ if (this._isErrorMethodCall(expr))
350
+ return `${this.genExpr(expr.func.expr)}.Error()`;
351
+ if (expr.func.expr.kind === "Ident") {
352
+ const ns = expr.func.expr.name;
353
+ const fn = expr.func.field;
354
+ const result = this._genStdlibCall(ns, fn, expr);
355
+ if (result !== undefined) return result;
356
+ }
357
+ const recvType = expr.func.expr._type;
358
+ const recvName = this._resolveRecvName(recvType);
359
+ return this._genReceiverTypeCall(recvName, expr);
360
+ },
361
+
362
+ _genBuiltinLen(expr) {
363
+ if (expr._constLen != null) return String(expr._constLen);
364
+ const arg = expr.args[0];
365
+ const t = arg?._type;
366
+ const wrapField = this._namedWrapperField(t, arg);
367
+ if (wrapField) return `${this.genExpr(arg)}.${wrapField}.length`;
368
+ const js = this.genExpr(arg);
369
+ if (t?.kind === "map") return `Object.keys(${js}).length`;
370
+ this._usesLen = true;
371
+ return `__len(${js})`;
372
+ },
373
+
374
+ _genBuiltinClear(expr) {
375
+ const arg = expr.args[0];
376
+ const t = arg?._type;
377
+ const js = this.genExpr(arg);
378
+ if (
379
+ t?.kind === "map" ||
380
+ (t?.kind === "named" && t.underlying?.kind === "map")
381
+ )
382
+ return `((__m) => { for (const __k in __m) delete __m[__k]; })(${js})`;
383
+ return `(${js}).length = 0`;
384
+ },
385
+
386
+ _genBuiltinIdent(name, expr) {
387
+ const gen = BUILTIN_GEN[name];
388
+ return gen ? gen(this, expr) : undefined;
389
+ },
390
+
568
391
  genAppend(expr) {
569
392
  const retType = expr._type;
570
393
  const wrapField = this._namedWrapperField(retType, expr.args[0]);
@@ -631,74 +454,55 @@ export const expressionGenMethods = {
631
454
  .join(", ");
632
455
  },
633
456
 
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
- }
457
+ _isSliceOrArrayType(t) {
458
+ return t?.kind === "SliceType" || t?.kind === "ArrayType";
459
+ },
657
460
 
658
- const typeName = this.getTypeName(t);
461
+ _isMapTypeNode(t) {
462
+ return t?.kind === "MapType";
463
+ },
659
464
 
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
- }
465
+ _genMapLitEntries(elems) {
466
+ return elems
467
+ .map((e) => {
468
+ if (e.kind === "KeyValueExpr") {
469
+ const k =
470
+ e.key.litKind === "STRING"
471
+ ? JSON.stringify(e.key.value)
472
+ : `[${this.genExpr(e.key)}]`;
473
+ return `${k}: ${this.genExpr(e.value)}`;
474
+ }
475
+ return this.genExpr(e);
476
+ })
477
+ .join(", ");
478
+ },
664
479
 
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),
480
+ _genFallbackCompositeLit(expr) {
481
+ if (expr.elems.length > 0 && expr.elems[0]?.kind === "KeyValueExpr") {
482
+ const fields = expr.elems
483
+ .map(
484
+ (e) =>
485
+ `${e.key.name ?? this.genExpr(e.key)}: ${this.genExpr(e.value)}`,
688
486
  )
689
487
  .join(", ");
690
- return `new ${typeName}([${elems}])`;
488
+ return `{ ${fields} }`;
691
489
  }
490
+ return `[${expr.elems.map((e) => this.genExpr(e)).join(", ")}]`;
491
+ },
692
492
 
693
- // Fallback: named type that's not a struct but has positional elements → treat as struct
493
+ genCompositeLit(expr) {
494
+ const t = expr.typeExpr;
495
+ if (t === null) return this._genImplicitCompositeLit(expr);
496
+ const typeName = this.getTypeName(t);
497
+ if (typeName && this.structNames.has(typeName))
498
+ return `new ${typeName}({ ${this._genStructFields(expr.elems)} })`;
499
+ if (typeName && this.namedWrapperNames.has(typeName))
500
+ return this._genNamedWrapperLit(typeName, expr);
694
501
  if (expr.elems.some((e) => e._positionalField)) {
695
- if (typeName) {
502
+ if (typeName)
696
503
  return `new ${typeName}({ ${this._genStructFields(expr.elems)} })`;
697
- }
698
504
  }
699
-
700
- // Slice/array: [1, 2, 3]
701
- if (t?.kind === "SliceType" || t?.kind === "ArrayType") {
505
+ if (this._isSliceOrArrayType(t)) {
702
506
  const elems = expr.elems
703
507
  .map((e) =>
704
508
  e.kind === "KeyValueExpr" ? this.genExpr(e.value) : this.genExpr(e),
@@ -706,9 +510,35 @@ export const expressionGenMethods = {
706
510
  .join(", ");
707
511
  return `[${elems}]`;
708
512
  }
513
+ if (this._isMapTypeNode(t))
514
+ return `{ ${this._genMapLitEntries(expr.elems)} }`;
515
+ return this._genFallbackCompositeLit(expr);
516
+ },
517
+
518
+ _genImplicitCompositeLit(expr) {
519
+ const typeName = expr._type?.name ?? expr._type?.underlying?.name;
520
+ const hasPositional = expr.elems.some((e) => e._positionalField);
521
+ const hasKeyed = expr.elems.some((e) => e.kind === "KeyValueExpr");
522
+ if (hasPositional || hasKeyed) {
523
+ if (typeName && this.structNames.has(typeName)) {
524
+ return `new ${typeName}({ ${this._genStructFields(expr.elems)} })`;
525
+ }
526
+ const fields = expr.elems
527
+ .map((e) => {
528
+ if (e._positionalField)
529
+ return `${e._positionalField}: ${this.genExpr(e)}`;
530
+ return `${e.key.name ?? this.genExpr(e.key)}: ${this.genExpr(e.value)}`;
531
+ })
532
+ .join(", ");
533
+ return `{ ${fields} }`;
534
+ }
535
+ return `[${expr.elems.map((e) => this.genExpr(e)).join(", ")}]`;
536
+ },
709
537
 
710
- // Map: { key: value }
711
- if (t?.kind === "MapType") {
538
+ _genNamedWrapperLit(typeName, expr) {
539
+ const namedType = this.checker?.types.get(typeName);
540
+ const u = namedType?.underlying;
541
+ if (u?.kind === "map") {
712
542
  const entries = expr.elems
713
543
  .map((e) => {
714
544
  if (e.kind === "KeyValueExpr") {
@@ -721,20 +551,15 @@ export const expressionGenMethods = {
721
551
  return this.genExpr(e);
722
552
  })
723
553
  .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} }`;
554
+ return `new ${typeName}({ ${entries} })`;
736
555
  }
737
- return `[${expr.elems.map((e) => this.genExpr(e)).join(", ")}]`;
556
+ // Default: slice
557
+ const elems = expr.elems
558
+ .map((e) =>
559
+ e.kind === "KeyValueExpr" ? this.genExpr(e.value) : this.genExpr(e),
560
+ )
561
+ .join(", ");
562
+ return `new ${typeName}([${elems}])`;
738
563
  },
739
564
 
740
565
  // ── Helpers ───────────────────────────────────────────────────
@@ -775,33 +600,14 @@ export const expressionGenMethods = {
775
600
 
776
601
  // Returns the JS zero-value literal for a basic type name, or null if not a basic type.
777
602
  _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
- }
603
+ return Object.hasOwn(ZERO_FOR_BASIC, name) ? ZERO_FOR_BASIC[name] : null;
604
+ },
605
+
606
+ _zeroForNamedType(name) {
607
+ if (name === "strings.Builder") return '{ _buf: "" }';
608
+ if (name === "bytes.Buffer") return "{ _buf: [] }";
609
+ if (this.structNames.has(name)) return `new ${name}()`;
610
+ return "null";
805
611
  },
806
612
 
807
613
  zeroValueForTypeNode(typeNode) {
@@ -810,11 +616,7 @@ export const expressionGenMethods = {
810
616
  case "TypeName": {
811
617
  const basic = this._zeroForBasicName(typeNode.name);
812
618
  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";
619
+ return this._zeroForNamedType(typeNode.name);
818
620
  }
819
621
  case "SliceType":
820
622
  return "null";
@@ -856,10 +658,7 @@ export const expressionGenMethods = {
856
658
  return `{ ${fields} }`;
857
659
  }
858
660
  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";
661
+ return this._zeroForNamedType(t.name);
863
662
  default:
864
663
  return "null";
865
664
  }
@@ -869,36 +668,15 @@ export const expressionGenMethods = {
869
668
  _typeCheckExpr(typeNode, val) {
870
669
  if (!typeNode) return "true";
871
670
  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
- }
671
+ const name = typeNode.name;
672
+ if (TYPEOF_NUMBER_NAMES.has(name)) return `typeof ${val} === "number"`;
673
+ if (name === "string") return `typeof ${val} === "string"`;
674
+ if (name === "bool") return `typeof ${val} === "boolean"`;
675
+ if (name === "nil") return `${val} === null`;
676
+ if (name === "error")
677
+ return `(typeof ${val} === "object" && ${val} !== null && typeof ${val}.Error === "function")`;
678
+ if (this.structNames.has(name)) return `${val} instanceof ${name}`;
679
+ return "true"; // unknown type — can't check at runtime
902
680
  }
903
681
  return "true";
904
682
  },
@@ -928,1099 +706,124 @@ export const expressionGenMethods = {
928
706
  return base?.kind === "struct" || base?.kind === "array";
929
707
  },
930
708
 
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": {
709
+ _genComplexBinary(expr) {
710
+ const l = this._genComplexOperand(expr.left);
711
+ const r = this._genComplexOperand(expr.right);
712
+ switch (expr.op) {
713
+ case "+":
714
+ return `{ re: ${l}.re + ${r}.re, im: ${l}.im + ${r}.im }`;
715
+ case "-":
716
+ return `{ re: ${l}.re - ${r}.re, im: ${l}.im - ${r}.im }`;
717
+ case "*":
718
+ this._usesCmul = true;
719
+ return `__cmul(${l}, ${r})`;
720
+ case "/":
721
+ this._usesCdiv = true;
722
+ return `__cdiv(${l}, ${r})`;
723
+ case "==":
724
+ return `(${l}.re === ${r}.re && ${l}.im === ${r}.im)`;
725
+ case "!=":
726
+ return `(${l}.re !== ${r}.re || ${l}.im !== ${r}.im)`;
727
+ }
728
+ },
729
+
730
+ _genBinaryExpr(expr) {
731
+ if (
732
+ isComplex(expr._type) ||
733
+ isComplex(expr.left._type) ||
734
+ isComplex(expr.right._type)
735
+ )
736
+ return this._genComplexBinary(expr);
737
+ const l = this.genExpr(expr.left);
738
+ const r = this.genExpr(expr.right);
739
+ if (
740
+ expr.op === "/" &&
741
+ this.isIntType(expr.left._type) &&
742
+ this.isIntType(expr.right._type)
743
+ )
744
+ return `Math.trunc(${l} / ${r})`;
745
+ if (expr.op === "&^") return `${l} & ~${r}`;
746
+ if (expr.op === "==" || expr.op === "!=") {
747
+ if (this._isStructOrArrayType(expr.left._type)) {
1203
748
  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]; })`;
749
+ const cmp = `__equal(${l}, ${r})`;
750
+ return expr.op === "==" ? cmp : `!${cmp}`;
1218
751
  }
1219
752
  }
1220
- return undefined;
753
+ const op = expr.op === "==" ? "===" : expr.op === "!=" ? "!==" : expr.op;
754
+ return `${l} ${op} ${r}`;
1221
755
  },
1222
756
 
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;
757
+ _genTypeConversion(expr) {
758
+ const inner = this.genExpr(expr.expr);
759
+ const t = expr.targetType;
760
+ if (t?.name === "complex128" || t?.name === "complex64") {
761
+ return isComplex(expr.expr._type) ? inner : `{ re: ${inner}, im: 0 }`;
1234
762
  }
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;
763
+ if (t?.kind === "ArrayType")
764
+ return this._genArrayTypeConversion(expr, inner, t);
765
+ if (t?.kind === "SliceType")
766
+ return this._genSliceTypeConversion(expr, inner, t);
767
+ const target = t?.name;
768
+ if (target === "error") {
769
+ this._usesError = true;
770
+ return `__error(${inner})`;
1282
771
  }
772
+ if (target && this.namedWrapperNames.has(target))
773
+ return `new ${target}(${inner})`;
774
+ return this._genPrimitiveConversion(expr, inner, target);
1283
775
  },
1284
776
 
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
- );
777
+ _genArrayTypeConversion(expr, inner, t) {
778
+ const srcResolved =
779
+ expr.expr._type?.kind === "named"
780
+ ? expr.expr._type.underlying
781
+ : expr.expr._type;
782
+ if (srcResolved?.kind === "slice") {
783
+ const size = t.size?.value !== undefined ? Number(t.size.value) : t.size;
784
+ return `${inner}.slice(0, ${size})`;
1353
785
  }
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;
786
+ return inner;
1430
787
  },
1431
788
 
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
- }
789
+ _isStringSource(srcType) {
790
+ return (
791
+ (srcType?.kind === "basic" && srcType?.name === "string") ||
792
+ (srcType?.kind === "untyped" && srcType?.base === "string")
793
+ );
1529
794
  },
1530
795
 
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;
796
+ _genSliceTypeConversion(expr, inner, t) {
797
+ const elem = t.elem?.name;
798
+ if (elem === "byte" || elem === "uint8")
799
+ return `Array.from(new TextEncoder().encode(${inner}))`;
800
+ if (elem === "rune" || elem === "int32" || elem === "int") {
801
+ if (this._isStringSource(expr.expr._type))
802
+ return `Array.from(${inner}, __c => __c.codePointAt(0))`;
1626
803
  }
804
+ return `Array.from(${inner})`;
1627
805
  },
1628
806
 
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("/"))`;
807
+ _genPrimitiveConversion(expr, inner, target) {
808
+ switch (target) {
809
+ case "string": {
810
+ const srcType = expr.expr._type;
811
+ if (srcType && this.isIntType(srcType))
812
+ return `String.fromCodePoint(${inner})`;
813
+ if (srcType?.kind === "slice" && srcType.elem?.name === "int")
814
+ return `${inner}.map(c => String.fromCharCode(c)).join("")`;
815
+ return `String(${inner})`;
2012
816
  }
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]})`;
817
+ case "int":
818
+ case "byte":
819
+ case "rune":
820
+ return `Math.trunc(Number(${inner}))`;
821
+ case "float64":
822
+ return `Number(${inner})`;
823
+ case "bool":
824
+ return `Boolean(${inner})`;
2022
825
  default:
2023
- return undefined;
826
+ return inner;
2024
827
  }
2025
828
  },
2026
829
  };