gofront 0.0.4 → 0.0.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +123 -139
- package/package.json +2 -3
- package/src/codegen/expressions.js +778 -253
- package/src/codegen/source-map.js +13 -5
- package/src/codegen/statements.js +193 -6
- package/src/codegen.js +115 -15
- package/src/compiler.js +10 -3
- package/src/dev-server.js +83 -0
- package/src/dts-parser.js +1 -1
- package/src/index.js +47 -16
- package/src/lexer.js +24 -1
- package/src/minifier.js +698 -0
- package/src/parser/expressions.js +115 -15
- package/src/parser/statements.js +4 -4
- package/src/parser/types.js +108 -3
- package/src/parser.js +57 -4
- package/src/typechecker/expressions.js +279 -10
- package/src/typechecker/statements.js +64 -3
- package/src/typechecker/types.js +96 -10
- package/src/typechecker.js +698 -16
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
// CodeGen expression methods and helpers — installed as a mixin on CodeGen.prototype.
|
|
2
2
|
|
|
3
|
+
import { ERROR, isComplex } from "../typechecker/types.js";
|
|
4
|
+
|
|
5
|
+
const globalError = ERROR;
|
|
6
|
+
|
|
3
7
|
const INT_TYPE_NAMES = new Set([
|
|
4
8
|
"int",
|
|
5
9
|
"uint",
|
|
@@ -23,17 +27,68 @@ export const expressionGenMethods = {
|
|
|
23
27
|
if (expr.litKind === "STRING") return JSON.stringify(expr.value);
|
|
24
28
|
return expr.value; // int, float, bool, nil(null)
|
|
25
29
|
|
|
30
|
+
case "ImagLit":
|
|
31
|
+
return `{ re: 0, im: ${expr.value} }`;
|
|
32
|
+
|
|
26
33
|
case "Ident":
|
|
34
|
+
if (this._boxedVars.has(expr.name) && !expr._isAddressOf)
|
|
35
|
+
return `${expr.name}.value`;
|
|
27
36
|
return expr.name;
|
|
28
37
|
|
|
29
38
|
case "UnaryExpr": {
|
|
30
39
|
const op = expr.op === "^" ? "~" : expr.op; // bitwise NOT
|
|
31
|
-
//
|
|
32
|
-
if (op === "
|
|
40
|
+
// Address-of: &x
|
|
41
|
+
if (op === "&") {
|
|
42
|
+
if (expr.operand.kind === "Ident") {
|
|
43
|
+
// Mark so Ident codegen does NOT append .value
|
|
44
|
+
expr.operand._isAddressOf = true;
|
|
45
|
+
// For boxed vars, &x returns the box itself
|
|
46
|
+
if (this._boxedVars.has(expr.operand.name)) {
|
|
47
|
+
return this.genExpr(expr.operand);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
// For non-boxed vars (structs, etc.), wrap in { value: x }
|
|
51
|
+
return `{ value: ${this.genExpr(expr.operand)} }`;
|
|
52
|
+
}
|
|
53
|
+
// Dereference: *p → p.value
|
|
54
|
+
if (op === "*") {
|
|
55
|
+
return `${this.genExpr(expr.operand)}.value`;
|
|
56
|
+
}
|
|
57
|
+
// Unary minus/plus on complex
|
|
58
|
+
if ((op === "-" || op === "+") && isComplex(expr.operand._type)) {
|
|
59
|
+
const inner = this.genExpr(expr.operand);
|
|
60
|
+
if (op === "-") return `{ re: -${inner}.re, im: -${inner}.im }`;
|
|
61
|
+
return inner;
|
|
62
|
+
}
|
|
33
63
|
return `${op}${this.genExpr(expr.operand)}`;
|
|
34
64
|
}
|
|
35
65
|
|
|
36
66
|
case "BinaryExpr": {
|
|
67
|
+
// Complex binary operations
|
|
68
|
+
if (
|
|
69
|
+
isComplex(expr._type) ||
|
|
70
|
+
isComplex(expr.left._type) ||
|
|
71
|
+
isComplex(expr.right._type)
|
|
72
|
+
) {
|
|
73
|
+
const l = this._genComplexOperand(expr.left);
|
|
74
|
+
const r = this._genComplexOperand(expr.right);
|
|
75
|
+
switch (expr.op) {
|
|
76
|
+
case "+":
|
|
77
|
+
return `{ re: ${l}.re + ${r}.re, im: ${l}.im + ${r}.im }`;
|
|
78
|
+
case "-":
|
|
79
|
+
return `{ re: ${l}.re - ${r}.re, im: ${l}.im - ${r}.im }`;
|
|
80
|
+
case "*":
|
|
81
|
+
this._usesCmul = true;
|
|
82
|
+
return `__cmul(${l}, ${r})`;
|
|
83
|
+
case "/":
|
|
84
|
+
this._usesCdiv = true;
|
|
85
|
+
return `__cdiv(${l}, ${r})`;
|
|
86
|
+
case "==":
|
|
87
|
+
return `(${l}.re === ${r}.re && ${l}.im === ${r}.im)`;
|
|
88
|
+
case "!=":
|
|
89
|
+
return `(${l}.re !== ${r}.re || ${l}.im !== ${r}.im)`;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
37
92
|
const l = this.genExpr(expr.left);
|
|
38
93
|
const r = this.genExpr(expr.right);
|
|
39
94
|
// Integer division: only when both sides are int
|
|
@@ -66,6 +121,9 @@ export const expressionGenMethods = {
|
|
|
66
121
|
case "CallExpr":
|
|
67
122
|
return this.genCall(expr);
|
|
68
123
|
|
|
124
|
+
case "InstantiationExpr":
|
|
125
|
+
return this.genExpr(expr.expr); // type erasure
|
|
126
|
+
|
|
69
127
|
case "SelectorExpr": {
|
|
70
128
|
// Method expression: TypeName.MethodName → (recv, ...args) => recv.method(...args)
|
|
71
129
|
if (expr._isMethodExpr) {
|
|
@@ -112,7 +170,7 @@ export const expressionGenMethods = {
|
|
|
112
170
|
const base = this.genExpr(expr.expr);
|
|
113
171
|
// Bundled GoFront packages are inlined — drop the qualifier.
|
|
114
172
|
if (this.bundledPackages.has(base)) return expr.field;
|
|
115
|
-
// Pointer types
|
|
173
|
+
// Pointer types: route through .value
|
|
116
174
|
if (expr.expr._type?.kind === "pointer" && expr.field !== "value") {
|
|
117
175
|
const sel = `${base}.value.${expr.field}`;
|
|
118
176
|
if (expr._isMethodValue && !expr._callee)
|
|
@@ -164,10 +222,14 @@ export const expressionGenMethods = {
|
|
|
164
222
|
const params = expr.params.map((p) => p.name).join(", ");
|
|
165
223
|
const asyncPrefix = expr.async ? "async " : "";
|
|
166
224
|
const saved = this.out;
|
|
225
|
+
const prevBoxed = this._boxedVars;
|
|
226
|
+
this._boxedVars = new Set(prevBoxed); // inherit parent's boxed vars (closures)
|
|
227
|
+
this._scanAddressTaken(expr.body);
|
|
167
228
|
this.out = [];
|
|
168
229
|
this.indented(() => this._genBody(expr.body));
|
|
169
230
|
const body = this.out.join("\n");
|
|
170
231
|
this.out = saved;
|
|
232
|
+
this._boxedVars = prevBoxed;
|
|
171
233
|
return `${asyncPrefix}function(${params}) {\n${body}\n${" ".repeat(this.indent)}}`;
|
|
172
234
|
}
|
|
173
235
|
|
|
@@ -175,6 +237,26 @@ export const expressionGenMethods = {
|
|
|
175
237
|
const inner = this.genExpr(expr.expr);
|
|
176
238
|
const t = expr.targetType;
|
|
177
239
|
|
|
240
|
+
// Complex type conversions
|
|
241
|
+
if (t?.name === "complex128" || t?.name === "complex64") {
|
|
242
|
+
const srcType = expr.expr._type;
|
|
243
|
+
if (isComplex(srcType)) return inner; // complex→complex identity
|
|
244
|
+
return `{ re: ${inner}, im: 0 }`; // numeric→complex
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// Array type conversion: [N]T(slice) → slice(0, N)
|
|
248
|
+
if (t?.kind === "ArrayType") {
|
|
249
|
+
const srcType = expr.expr._type;
|
|
250
|
+
const srcResolved =
|
|
251
|
+
srcType?.kind === "named" ? srcType.underlying : srcType;
|
|
252
|
+
if (srcResolved?.kind === "slice") {
|
|
253
|
+
const size =
|
|
254
|
+
t.size?.value !== undefined ? Number(t.size.value) : t.size;
|
|
255
|
+
return `${inner}.slice(0, ${size})`;
|
|
256
|
+
}
|
|
257
|
+
return inner;
|
|
258
|
+
}
|
|
259
|
+
|
|
178
260
|
// Slice type conversions
|
|
179
261
|
if (t?.kind === "SliceType") {
|
|
180
262
|
const elem = t.elem?.name;
|
|
@@ -197,6 +279,11 @@ export const expressionGenMethods = {
|
|
|
197
279
|
}
|
|
198
280
|
|
|
199
281
|
const target = t?.name;
|
|
282
|
+
// error("msg") → __error("msg")
|
|
283
|
+
if (target === "error") {
|
|
284
|
+
this._usesError = true;
|
|
285
|
+
return `__error(${inner})`;
|
|
286
|
+
}
|
|
200
287
|
switch (target) {
|
|
201
288
|
case "string": {
|
|
202
289
|
// Go string(65) → "A" (Unicode code point), not "65"
|
|
@@ -204,6 +291,14 @@ export const expressionGenMethods = {
|
|
|
204
291
|
if (srcType && this.isIntType(srcType)) {
|
|
205
292
|
return `String.fromCodePoint(${inner})`;
|
|
206
293
|
}
|
|
294
|
+
// string([]byte) → decode byte array to string
|
|
295
|
+
if (
|
|
296
|
+
srcType &&
|
|
297
|
+
srcType.kind === "slice" &&
|
|
298
|
+
srcType.elem?.name === "int"
|
|
299
|
+
) {
|
|
300
|
+
return `${inner}.map(c => String.fromCharCode(c)).join("")`;
|
|
301
|
+
}
|
|
207
302
|
return `String(${inner})`;
|
|
208
303
|
}
|
|
209
304
|
case "int":
|
|
@@ -244,12 +339,16 @@ export const expressionGenMethods = {
|
|
|
244
339
|
},
|
|
245
340
|
|
|
246
341
|
genCall(expr) {
|
|
342
|
+
// Unwrap InstantiationExpr for function calls: Foo[int](42) → Foo(42)
|
|
343
|
+
const funcExpr =
|
|
344
|
+
expr.func.kind === "InstantiationExpr" ? expr.func.expr : expr.func;
|
|
247
345
|
// Handle built-ins that need special JS translation
|
|
248
|
-
if (
|
|
249
|
-
switch (
|
|
346
|
+
if (funcExpr.kind === "Ident") {
|
|
347
|
+
switch (funcExpr.name) {
|
|
250
348
|
case "append":
|
|
251
349
|
return this.genAppend(expr);
|
|
252
350
|
case "len": {
|
|
351
|
+
if (expr._constLen != null) return String(expr._constLen);
|
|
253
352
|
const arg = expr.args[0];
|
|
254
353
|
const t = arg?._type;
|
|
255
354
|
const js = this.genExpr(arg);
|
|
@@ -285,9 +384,9 @@ export const expressionGenMethods = {
|
|
|
285
384
|
case "recover":
|
|
286
385
|
return `(typeof __panic !== "undefined" && __panic !== null ? (() => { const __r = __panic.message ?? String(__panic); __panic = null; return __r; })() : null)`;
|
|
287
386
|
case "error": {
|
|
288
|
-
|
|
387
|
+
this._usesError = true;
|
|
289
388
|
const arg = this.genExpr(expr.args[0]);
|
|
290
|
-
return arg
|
|
389
|
+
return `__error(${arg})`;
|
|
291
390
|
}
|
|
292
391
|
case "min": {
|
|
293
392
|
const args = expr.args.map((a) => this.genExpr(a)).join(", ");
|
|
@@ -309,263 +408,51 @@ export const expressionGenMethods = {
|
|
|
309
408
|
}
|
|
310
409
|
return `(${js}).length = 0`;
|
|
311
410
|
}
|
|
411
|
+
case "complex": {
|
|
412
|
+
const re = this.genExpr(expr.args[0]);
|
|
413
|
+
const im = this.genExpr(expr.args[1]);
|
|
414
|
+
return `{ re: ${re}, im: ${im} }`;
|
|
415
|
+
}
|
|
416
|
+
case "real":
|
|
417
|
+
return `(${this.genExpr(expr.args[0])}).re`;
|
|
418
|
+
case "imag":
|
|
419
|
+
return `(${this.genExpr(expr.args[0])}).im`;
|
|
312
420
|
}
|
|
313
421
|
}
|
|
314
422
|
|
|
315
|
-
// error.Error() →
|
|
423
|
+
// error.Error() → real method call
|
|
316
424
|
if (
|
|
317
425
|
expr.func.kind === "SelectorExpr" &&
|
|
318
426
|
expr.func.field === "Error" &&
|
|
319
|
-
expr.func.expr._type
|
|
427
|
+
expr.func.expr._type &&
|
|
428
|
+
(expr.func.expr._type === globalError ||
|
|
429
|
+
expr.func.expr._type?.name === "error" ||
|
|
430
|
+
(expr.func.expr._type?.kind === "named" &&
|
|
431
|
+
expr.func.expr._type?.underlying?.kind === "interface"))
|
|
320
432
|
) {
|
|
321
|
-
return this.genExpr(expr.func.expr)
|
|
433
|
+
return `${this.genExpr(expr.func.expr)}.Error()`;
|
|
322
434
|
}
|
|
323
435
|
|
|
324
436
|
// fmt.Sprintf / fmt.Printf / fmt.Println / fmt.Print / fmt.Errorf
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
expr.func.expr.name
|
|
329
|
-
|
|
330
|
-
const
|
|
331
|
-
|
|
332
|
-
case "Sprintf":
|
|
333
|
-
case "Errorf":
|
|
334
|
-
this._usesSprintf = true;
|
|
335
|
-
return `__sprintf(${fmtArgs})`;
|
|
336
|
-
case "Printf":
|
|
337
|
-
case "Print":
|
|
338
|
-
this._usesSprintf = true;
|
|
339
|
-
return `process?.stdout?.write(__sprintf(${fmtArgs}))`;
|
|
340
|
-
case "Println":
|
|
341
|
-
this._usesSprintf = true;
|
|
342
|
-
return `console.log(__sprintf(${fmtArgs}))`;
|
|
343
|
-
}
|
|
344
|
-
}
|
|
345
|
-
|
|
346
|
-
// strings.* — direct JS string method mappings
|
|
347
|
-
if (
|
|
348
|
-
expr.func.kind === "SelectorExpr" &&
|
|
349
|
-
expr.func.expr.kind === "Ident" &&
|
|
350
|
-
expr.func.expr.name === "strings"
|
|
351
|
-
) {
|
|
352
|
-
const a = expr.args.map((e) => this.genExpr(e));
|
|
353
|
-
switch (expr.func.field) {
|
|
354
|
-
case "Contains":
|
|
355
|
-
return `${a[0]}.includes(${a[1]})`;
|
|
356
|
-
case "HasPrefix":
|
|
357
|
-
return `${a[0]}.startsWith(${a[1]})`;
|
|
358
|
-
case "HasSuffix":
|
|
359
|
-
return `${a[0]}.endsWith(${a[1]})`;
|
|
360
|
-
case "Index":
|
|
361
|
-
return `${a[0]}.indexOf(${a[1]})`;
|
|
362
|
-
case "LastIndex":
|
|
363
|
-
return `${a[0]}.lastIndexOf(${a[1]})`;
|
|
364
|
-
case "Count":
|
|
365
|
-
return `${a[0]}.split(${a[1]}).length - 1`;
|
|
366
|
-
case "Repeat":
|
|
367
|
-
return `${a[0]}.repeat(${a[1]})`;
|
|
368
|
-
case "Replace":
|
|
369
|
-
return `${a[0]}.replace(${a[1]}, ${a[2]})`;
|
|
370
|
-
case "ReplaceAll":
|
|
371
|
-
return `${a[0]}.replaceAll(${a[1]}, ${a[2]})`;
|
|
372
|
-
case "ToUpper":
|
|
373
|
-
return `${a[0]}.toUpperCase()`;
|
|
374
|
-
case "ToLower":
|
|
375
|
-
return `${a[0]}.toLowerCase()`;
|
|
376
|
-
case "TrimSpace":
|
|
377
|
-
return `${a[0]}.trim()`;
|
|
378
|
-
case "Trim":
|
|
379
|
-
return `${a[0]}.replace(new RegExp(\`^[\${${a[1]}}]+|[\${${a[1]}}]+$\`, "g"), "")`;
|
|
380
|
-
case "TrimPrefix":
|
|
381
|
-
return `(${a[0]}.startsWith(${a[1]}) ? ${a[0]}.slice(${a[1]}.length) : ${a[0]})`;
|
|
382
|
-
case "TrimSuffix":
|
|
383
|
-
return `(${a[0]}.endsWith(${a[1]}) ? ${a[0]}.slice(0, -${a[1]}.length) : ${a[0]})`;
|
|
384
|
-
case "TrimLeft":
|
|
385
|
-
return `${a[0]}.replace(new RegExp(\`^[\${${a[1]}}]+\`), "")`;
|
|
386
|
-
case "TrimRight":
|
|
387
|
-
return `${a[0]}.replace(new RegExp(\`[\${${a[1]}}]+$\`), "")`;
|
|
388
|
-
case "Split":
|
|
389
|
-
return `${a[0]}.split(${a[1]})`;
|
|
390
|
-
case "Join":
|
|
391
|
-
return `${a[0]}.join(${a[1]})`;
|
|
392
|
-
case "EqualFold":
|
|
393
|
-
return `${a[0]}.toLowerCase() === ${a[1]}.toLowerCase()`;
|
|
394
|
-
}
|
|
395
|
-
}
|
|
396
|
-
|
|
397
|
-
// strconv.* — number/string conversion
|
|
398
|
-
if (
|
|
399
|
-
expr.func.kind === "SelectorExpr" &&
|
|
400
|
-
expr.func.expr.kind === "Ident" &&
|
|
401
|
-
expr.func.expr.name === "strconv"
|
|
402
|
-
) {
|
|
403
|
-
const a = expr.args.map((e) => this.genExpr(e));
|
|
404
|
-
switch (expr.func.field) {
|
|
405
|
-
case "Itoa":
|
|
406
|
-
return `String(${a[0]})`;
|
|
407
|
-
case "Atoi":
|
|
408
|
-
return `(Number.isNaN(Number(${a[0]})) ? [0, "invalid syntax"] : [Number(${a[0]}) | 0, null])`;
|
|
409
|
-
case "FormatBool":
|
|
410
|
-
return `String(${a[0]})`;
|
|
411
|
-
case "FormatInt":
|
|
412
|
-
return `(${a[0]}).toString(${a[1]})`;
|
|
413
|
-
case "FormatFloat":
|
|
414
|
-
return `String(${a[0]})`;
|
|
415
|
-
case "ParseFloat":
|
|
416
|
-
return `(Number.isNaN(Number(${a[0]})) ? [0, "invalid syntax"] : [Number(${a[0]}), null])`;
|
|
417
|
-
case "ParseInt":
|
|
418
|
-
return `(Number.isNaN(parseInt(${a[0]}, ${a[1]} || 10)) ? [0, "invalid syntax"] : [parseInt(${a[0]}, ${a[1]} || 10), null])`;
|
|
419
|
-
case "ParseBool":
|
|
420
|
-
return `(${a[0]} === "true" || ${a[0]} === "1" ? [true, null] : ${a[0]} === "false" || ${a[0]} === "0" ? [false, null] : [false, "invalid syntax"])`;
|
|
421
|
-
}
|
|
422
|
-
}
|
|
423
|
-
|
|
424
|
-
// sort.* — in-place sorting
|
|
425
|
-
if (
|
|
426
|
-
expr.func.kind === "SelectorExpr" &&
|
|
427
|
-
expr.func.expr.kind === "Ident" &&
|
|
428
|
-
expr.func.expr.name === "sort"
|
|
429
|
-
) {
|
|
430
|
-
const a = expr.args.map((e) => this.genExpr(e));
|
|
431
|
-
switch (expr.func.field) {
|
|
432
|
-
case "Ints":
|
|
433
|
-
case "Float64s":
|
|
434
|
-
return `${a[0]}.sort((a, b) => a - b)`;
|
|
435
|
-
case "Strings":
|
|
436
|
-
return `${a[0]}.sort()`;
|
|
437
|
-
case "Slice":
|
|
438
|
-
return `${a[0]}.sort((a, b) => ${a[1]}(a, b) ? -1 : ${a[1]}(b, a) ? 1 : 0)`;
|
|
439
|
-
case "SliceStable":
|
|
440
|
-
return `${a[0]}.sort((a, b) => ${a[1]}(a, b) ? -1 : ${a[1]}(b, a) ? 1 : 0)`;
|
|
441
|
-
case "SliceIsSorted":
|
|
442
|
-
return `${a[0]}.every((v, i, a) => i === 0 || ${a[1]}(a[i - 1], v))`;
|
|
443
|
-
}
|
|
444
|
-
}
|
|
445
|
-
|
|
446
|
-
// math.* — math functions
|
|
447
|
-
if (
|
|
448
|
-
expr.func.kind === "SelectorExpr" &&
|
|
449
|
-
expr.func.expr.kind === "Ident" &&
|
|
450
|
-
expr.func.expr.name === "math"
|
|
451
|
-
) {
|
|
452
|
-
const a = expr.args.map((e) => this.genExpr(e));
|
|
453
|
-
switch (expr.func.field) {
|
|
454
|
-
case "Abs":
|
|
455
|
-
return `Math.abs(${a[0]})`;
|
|
456
|
-
case "Floor":
|
|
457
|
-
return `Math.floor(${a[0]})`;
|
|
458
|
-
case "Ceil":
|
|
459
|
-
return `Math.ceil(${a[0]})`;
|
|
460
|
-
case "Round":
|
|
461
|
-
return `Math.round(${a[0]})`;
|
|
462
|
-
case "Sqrt":
|
|
463
|
-
return `Math.sqrt(${a[0]})`;
|
|
464
|
-
case "Cbrt":
|
|
465
|
-
return `Math.cbrt(${a[0]})`;
|
|
466
|
-
case "Pow":
|
|
467
|
-
return `Math.pow(${a[0]}, ${a[1]})`;
|
|
468
|
-
case "Log":
|
|
469
|
-
return `Math.log(${a[0]})`;
|
|
470
|
-
case "Log2":
|
|
471
|
-
return `Math.log2(${a[0]})`;
|
|
472
|
-
case "Log10":
|
|
473
|
-
return `Math.log10(${a[0]})`;
|
|
474
|
-
case "Sin":
|
|
475
|
-
return `Math.sin(${a[0]})`;
|
|
476
|
-
case "Cos":
|
|
477
|
-
return `Math.cos(${a[0]})`;
|
|
478
|
-
case "Tan":
|
|
479
|
-
return `Math.tan(${a[0]})`;
|
|
480
|
-
case "Min":
|
|
481
|
-
return `Math.min(${a[0]}, ${a[1]})`;
|
|
482
|
-
case "Max":
|
|
483
|
-
return `Math.max(${a[0]}, ${a[1]})`;
|
|
484
|
-
case "Mod":
|
|
485
|
-
return `${a[0]} % ${a[1]}`;
|
|
486
|
-
case "Inf":
|
|
487
|
-
return `(${a[0]} >= 0 ? Infinity : -Infinity)`;
|
|
488
|
-
case "IsNaN":
|
|
489
|
-
return `Number.isNaN(${a[0]})`;
|
|
490
|
-
case "IsInf":
|
|
491
|
-
return `(${a[1]} > 0 ? ${a[0]} === Infinity : ${a[1]} < 0 ? ${a[0]} === -Infinity : !Number.isFinite(${a[0]}))`;
|
|
492
|
-
case "NaN":
|
|
493
|
-
return "NaN";
|
|
494
|
-
}
|
|
495
|
-
}
|
|
496
|
-
|
|
497
|
-
// unicode.* — rune/character classification
|
|
498
|
-
if (
|
|
499
|
-
expr.func.kind === "SelectorExpr" &&
|
|
500
|
-
expr.func.expr.kind === "Ident" &&
|
|
501
|
-
expr.func.expr.name === "unicode"
|
|
502
|
-
) {
|
|
503
|
-
const a = expr.args.map((e) => this.genExpr(e));
|
|
504
|
-
switch (expr.func.field) {
|
|
505
|
-
case "IsLetter":
|
|
506
|
-
return `/\\p{L}/u.test(String.fromCodePoint(${a[0]}))`;
|
|
507
|
-
case "IsDigit":
|
|
508
|
-
return `/\\p{Nd}/u.test(String.fromCodePoint(${a[0]}))`;
|
|
509
|
-
case "IsSpace":
|
|
510
|
-
return `/\\s/.test(String.fromCodePoint(${a[0]}))`;
|
|
511
|
-
case "IsUpper":
|
|
512
|
-
return `((__c) => __c === __c.toUpperCase() && /\\p{L}/u.test(__c))(String.fromCodePoint(${a[0]}))`;
|
|
513
|
-
case "IsLower":
|
|
514
|
-
return `((__c) => __c === __c.toLowerCase() && /\\p{L}/u.test(__c))(String.fromCodePoint(${a[0]}))`;
|
|
515
|
-
case "IsPunct":
|
|
516
|
-
return `/\\p{P}/u.test(String.fromCodePoint(${a[0]}))`;
|
|
517
|
-
case "IsControl":
|
|
518
|
-
return `/\\p{Cc}/u.test(String.fromCodePoint(${a[0]}))`;
|
|
519
|
-
case "IsPrint":
|
|
520
|
-
return `!/\\p{Cc}/u.test(String.fromCodePoint(${a[0]}))`;
|
|
521
|
-
case "IsGraphic":
|
|
522
|
-
return `!/\\p{Cc}/u.test(String.fromCodePoint(${a[0]}))`;
|
|
523
|
-
case "ToUpper":
|
|
524
|
-
return `String.fromCodePoint(${a[0]}).toUpperCase().codePointAt(0)`;
|
|
525
|
-
case "ToLower":
|
|
526
|
-
return `String.fromCodePoint(${a[0]}).toLowerCase().codePointAt(0)`;
|
|
527
|
-
}
|
|
528
|
-
}
|
|
529
|
-
|
|
530
|
-
// os.* — process/environment access
|
|
531
|
-
if (
|
|
532
|
-
expr.func.kind === "SelectorExpr" &&
|
|
533
|
-
expr.func.expr.kind === "Ident" &&
|
|
534
|
-
expr.func.expr.name === "os"
|
|
535
|
-
) {
|
|
536
|
-
const a = expr.args.map((e) => this.genExpr(e));
|
|
537
|
-
switch (expr.func.field) {
|
|
538
|
-
case "Exit":
|
|
539
|
-
return `process.exit(${a[0]})`;
|
|
540
|
-
case "Getenv":
|
|
541
|
-
return `(process.env[${a[0]}] ?? "")`;
|
|
542
|
-
}
|
|
437
|
+
// Standard library namespace dispatch — all follow the pattern:
|
|
438
|
+
// pkg.Func(args) → inline JS
|
|
439
|
+
if (expr.func.kind === "SelectorExpr" && expr.func.expr.kind === "Ident") {
|
|
440
|
+
const ns = expr.func.expr.name;
|
|
441
|
+
const fn = expr.func.field;
|
|
442
|
+
const result = this._genStdlibCall(ns, fn, expr);
|
|
443
|
+
if (result !== undefined) return result;
|
|
543
444
|
}
|
|
544
445
|
|
|
545
|
-
//
|
|
546
|
-
if (
|
|
547
|
-
expr.func.
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
// time.* — partial JS-friendly time support
|
|
556
|
-
if (
|
|
557
|
-
expr.func.kind === "SelectorExpr" &&
|
|
558
|
-
expr.func.expr.kind === "Ident" &&
|
|
559
|
-
expr.func.expr.name === "time"
|
|
560
|
-
) {
|
|
561
|
-
const a = expr.args.map((e) => this.genExpr(e));
|
|
562
|
-
switch (expr.func.field) {
|
|
563
|
-
case "Now":
|
|
564
|
-
return "Date.now()";
|
|
565
|
-
case "Since":
|
|
566
|
-
return `(Date.now() - ${a[0]})`;
|
|
567
|
-
case "Sleep":
|
|
568
|
-
return `await new Promise(r => setTimeout(r, ${a[0]} / 1000000))`;
|
|
446
|
+
// strings.Builder / bytes.Buffer / regexp.Regexp method dispatch
|
|
447
|
+
if (expr.func.kind === "SelectorExpr") {
|
|
448
|
+
const recvType = expr.func.expr._type;
|
|
449
|
+
const recvName = recvType?.name ?? recvType?.base?.name;
|
|
450
|
+
if (recvName === "strings.Builder" || recvName === "bytes.Buffer") {
|
|
451
|
+
const typeName = recvType?.name ?? recvType?.base?.name;
|
|
452
|
+
return this._genBuilderCall(typeName, expr.func.field, expr);
|
|
453
|
+
}
|
|
454
|
+
if (recvName === "regexp.Regexp") {
|
|
455
|
+
return this._genRegexpMethodCall(expr.func.field, expr);
|
|
569
456
|
}
|
|
570
457
|
}
|
|
571
458
|
|
|
@@ -718,10 +605,18 @@ export const expressionGenMethods = {
|
|
|
718
605
|
|
|
719
606
|
// ── Helpers ───────────────────────────────────────────────────
|
|
720
607
|
|
|
608
|
+
_genComplexOperand(expr) {
|
|
609
|
+
if (isComplex(expr._type)) return this.genExpr(expr);
|
|
610
|
+
return `{ re: ${this.genExpr(expr)}, im: 0 }`;
|
|
611
|
+
},
|
|
612
|
+
|
|
721
613
|
getTypeName(typeNode) {
|
|
722
614
|
if (!typeNode) return null;
|
|
723
615
|
if (typeNode.kind === "TypeName") return typeNode.name;
|
|
616
|
+
if (typeNode.kind === "GenericTypeName") return typeNode.name;
|
|
724
617
|
if (typeNode.kind === "Ident") return typeNode.name;
|
|
618
|
+
if (typeNode.kind === "InstantiationExpr")
|
|
619
|
+
return this.getTypeName(typeNode.expr);
|
|
725
620
|
if (typeNode.kind === "SelectorExpr") return typeNode.field;
|
|
726
621
|
return null;
|
|
727
622
|
},
|
|
@@ -762,6 +657,9 @@ export const expressionGenMethods = {
|
|
|
762
657
|
case "byte":
|
|
763
658
|
case "rune":
|
|
764
659
|
return "0";
|
|
660
|
+
case "complex64":
|
|
661
|
+
case "complex128":
|
|
662
|
+
return "{ re: 0, im: 0 }";
|
|
765
663
|
case "string":
|
|
766
664
|
return '""';
|
|
767
665
|
case "bool":
|
|
@@ -777,6 +675,8 @@ export const expressionGenMethods = {
|
|
|
777
675
|
case "TypeName": {
|
|
778
676
|
const basic = this._zeroForBasicName(typeNode.name);
|
|
779
677
|
if (basic !== null) return basic;
|
|
678
|
+
if (typeNode.name === "strings.Builder") return '{ _buf: "" }';
|
|
679
|
+
if (typeNode.name === "bytes.Buffer") return "{ _buf: [] }";
|
|
780
680
|
if (this.structNames.has(typeNode.name))
|
|
781
681
|
return `new ${typeNode.name}()`;
|
|
782
682
|
return "null";
|
|
@@ -821,6 +721,8 @@ export const expressionGenMethods = {
|
|
|
821
721
|
return `{ ${fields} }`;
|
|
822
722
|
}
|
|
823
723
|
case "named":
|
|
724
|
+
if (t.name === "strings.Builder") return '{ _buf: "" }';
|
|
725
|
+
if (t.name === "bytes.Buffer") return "{ _buf: [] }";
|
|
824
726
|
if (this.structNames.has(t.name)) return `new ${t.name}()`;
|
|
825
727
|
return "null";
|
|
826
728
|
default:
|
|
@@ -856,7 +758,7 @@ export const expressionGenMethods = {
|
|
|
856
758
|
case "nil":
|
|
857
759
|
return `${val} === null`;
|
|
858
760
|
case "error":
|
|
859
|
-
return `typeof ${val} === "
|
|
761
|
+
return `(typeof ${val} === "object" && ${val} !== null && typeof ${val}.Error === "function")`;
|
|
860
762
|
default:
|
|
861
763
|
if (this.structNames.has(typeNode.name))
|
|
862
764
|
return `${val} instanceof ${typeNode.name}`;
|
|
@@ -891,6 +793,268 @@ export const expressionGenMethods = {
|
|
|
891
793
|
return base?.kind === "struct" || base?.kind === "array";
|
|
892
794
|
},
|
|
893
795
|
|
|
796
|
+
_genBuilderCall(typeName, method, expr) {
|
|
797
|
+
// Resolve the receiver — may be a direct var or a &var (pointer)
|
|
798
|
+
const recv = expr.func.expr;
|
|
799
|
+
const isPtr = recv._type?.kind === "pointer";
|
|
800
|
+
const base = isPtr ? `${this.genExpr(recv)}.value` : this.genExpr(recv);
|
|
801
|
+
const args = expr.args.map((a) => this.genExpr(a));
|
|
802
|
+
|
|
803
|
+
if (typeName === "strings.Builder") {
|
|
804
|
+
switch (method) {
|
|
805
|
+
case "WriteString":
|
|
806
|
+
return `(${base}._buf += ${args[0]}, [${args[0]}.length, null])`;
|
|
807
|
+
case "WriteByte":
|
|
808
|
+
case "WriteRune":
|
|
809
|
+
return `(${base}._buf += String.fromCodePoint(${args[0]}))`;
|
|
810
|
+
case "Write":
|
|
811
|
+
return `(${base}._buf += String.fromCharCode(...${args[0]}))`;
|
|
812
|
+
case "String":
|
|
813
|
+
return `${base}._buf`;
|
|
814
|
+
case "Len":
|
|
815
|
+
return `${base}._buf.length`;
|
|
816
|
+
case "Reset":
|
|
817
|
+
return `(${base}._buf = "")`;
|
|
818
|
+
case "Grow":
|
|
819
|
+
return "undefined";
|
|
820
|
+
}
|
|
821
|
+
}
|
|
822
|
+
|
|
823
|
+
if (typeName === "bytes.Buffer") {
|
|
824
|
+
switch (method) {
|
|
825
|
+
case "WriteString":
|
|
826
|
+
return `(${base}._buf.push(...new TextEncoder().encode(${args[0]})), [${args[0]}.length, null])`;
|
|
827
|
+
case "WriteByte":
|
|
828
|
+
return `${base}._buf.push(${args[0]})`;
|
|
829
|
+
case "Write":
|
|
830
|
+
return `(${base}._buf.push(...${args[0]}), [${args[0]}.length, null])`;
|
|
831
|
+
case "String":
|
|
832
|
+
return `new TextDecoder().decode(new Uint8Array(${base}._buf))`;
|
|
833
|
+
case "Bytes":
|
|
834
|
+
return `${base}._buf.slice()`;
|
|
835
|
+
case "Len":
|
|
836
|
+
return `${base}._buf.length`;
|
|
837
|
+
case "Reset":
|
|
838
|
+
return `(${base}._buf = [])`;
|
|
839
|
+
case "Grow":
|
|
840
|
+
return "undefined";
|
|
841
|
+
}
|
|
842
|
+
}
|
|
843
|
+
|
|
844
|
+
return undefined;
|
|
845
|
+
},
|
|
846
|
+
|
|
847
|
+
_genRegexp(fn, a) {
|
|
848
|
+
// Converts a Go regexp pattern string (which may contain inline flags like (?i))
|
|
849
|
+
// into a JS RegExp, extracting the flags into the second constructor argument.
|
|
850
|
+
const makeRegExp = (pat) =>
|
|
851
|
+
`((p) => { const m = /^\\(\\?([gimsuy]+)\\)/.exec(p); return m ? new RegExp(p.slice(m[0].length), m[1]) : new RegExp(p); })(${pat})`;
|
|
852
|
+
switch (fn) {
|
|
853
|
+
case "MustCompile":
|
|
854
|
+
return makeRegExp(a()[0]);
|
|
855
|
+
case "Compile":
|
|
856
|
+
return `[${makeRegExp(a()[0])}, null]`;
|
|
857
|
+
case "MatchString": {
|
|
858
|
+
const [pat, s] = a();
|
|
859
|
+
return `[new RegExp(${pat}).test(${s}), null]`;
|
|
860
|
+
}
|
|
861
|
+
case "QuoteMeta":
|
|
862
|
+
return `${a()[0]}.replace(/[.*+?^\${}()|[\\]\\\\]/g, '\\\\$&')`;
|
|
863
|
+
}
|
|
864
|
+
return undefined;
|
|
865
|
+
},
|
|
866
|
+
|
|
867
|
+
_genRegexpMethodCall(method, expr) {
|
|
868
|
+
const recv = expr.func.expr;
|
|
869
|
+
// *Regexp is a plain JS RegExp — no .value boxing needed
|
|
870
|
+
const re = this.genExpr(recv);
|
|
871
|
+
const args = expr.args.map((a) => this.genExpr(a));
|
|
872
|
+
|
|
873
|
+
switch (method) {
|
|
874
|
+
case "MatchString":
|
|
875
|
+
return `${re}.test(${args[0]})`;
|
|
876
|
+
case "FindString":
|
|
877
|
+
return `(${re}.exec(${args[0]})?.[0] ?? "")`;
|
|
878
|
+
case "FindStringIndex":
|
|
879
|
+
return `((m => m ? [m.index, m.index + m[0].length] : null)(${re}.exec(${args[0]})))`;
|
|
880
|
+
case "FindAllString":
|
|
881
|
+
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])`;
|
|
882
|
+
case "FindStringSubmatch":
|
|
883
|
+
return `[...(${re}.exec(${args[0]}) ?? [])]`;
|
|
884
|
+
case "FindAllStringSubmatch":
|
|
885
|
+
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])`;
|
|
886
|
+
case "ReplaceAllString":
|
|
887
|
+
return `${args[0]}.replaceAll(new RegExp(${re}.source, ${re}.flags.includes("g") ? ${re}.flags : ${re}.flags + "g"), ${args[1]})`;
|
|
888
|
+
case "ReplaceAllLiteralString":
|
|
889
|
+
return `${args[0]}.replaceAll(new RegExp(${re}.source, ${re}.flags.includes("g") ? ${re}.flags : ${re}.flags + "g"), ${args[1]}.replace(/\\$/g, '$$$$'))`;
|
|
890
|
+
case "Split":
|
|
891
|
+
return `${args[0]}.split(${re}).slice(0, ${args[1]} < 0 ? undefined : ${args[1]})`;
|
|
892
|
+
case "String":
|
|
893
|
+
return `${re}.source`;
|
|
894
|
+
}
|
|
895
|
+
return undefined;
|
|
896
|
+
},
|
|
897
|
+
|
|
898
|
+
_genSlices(fn, a) {
|
|
899
|
+
switch (fn) {
|
|
900
|
+
case "Contains": {
|
|
901
|
+
const [s, v] = a();
|
|
902
|
+
return `${s}.includes(${v})`;
|
|
903
|
+
}
|
|
904
|
+
case "Index": {
|
|
905
|
+
const [s, v] = a();
|
|
906
|
+
return `${s}.indexOf(${v})`;
|
|
907
|
+
}
|
|
908
|
+
case "Equal": {
|
|
909
|
+
this._usesEqual = true;
|
|
910
|
+
const [a1, b1] = a();
|
|
911
|
+
return `__equal(${a1}, ${b1})`;
|
|
912
|
+
}
|
|
913
|
+
case "Compare": {
|
|
914
|
+
const [a1, b1] = a();
|
|
915
|
+
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})`;
|
|
916
|
+
}
|
|
917
|
+
case "Sort": {
|
|
918
|
+
const [s] = a();
|
|
919
|
+
return `${s}.sort((a, b) => a < b ? -1 : a > b ? 1 : 0)`;
|
|
920
|
+
}
|
|
921
|
+
case "SortFunc": {
|
|
922
|
+
const [s, cmp] = a();
|
|
923
|
+
return `${s}.sort(${cmp})`;
|
|
924
|
+
}
|
|
925
|
+
case "SortStableFunc": {
|
|
926
|
+
const [s, cmp] = a();
|
|
927
|
+
return `${s}.sort(${cmp})`;
|
|
928
|
+
}
|
|
929
|
+
case "IsSorted": {
|
|
930
|
+
const [s] = a();
|
|
931
|
+
return `((s) => { for (let i = 1; i < s.length; i++) { if (s[i] < s[i-1]) return false; } return true; })(${s})`;
|
|
932
|
+
}
|
|
933
|
+
case "IsSortedFunc": {
|
|
934
|
+
const [s, cmp] = a();
|
|
935
|
+
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})`;
|
|
936
|
+
}
|
|
937
|
+
case "Reverse": {
|
|
938
|
+
const [s] = a();
|
|
939
|
+
return `${s}.reverse()`;
|
|
940
|
+
}
|
|
941
|
+
case "Max": {
|
|
942
|
+
const [s] = a();
|
|
943
|
+
return `Math.max(...${s})`;
|
|
944
|
+
}
|
|
945
|
+
case "Min": {
|
|
946
|
+
const [s] = a();
|
|
947
|
+
return `Math.min(...${s})`;
|
|
948
|
+
}
|
|
949
|
+
case "MaxFunc": {
|
|
950
|
+
const [s, cmp] = a();
|
|
951
|
+
return `((s, f) => s.reduce((m, x) => f(x, m) > 0 ? x : m))(${s}, ${cmp})`;
|
|
952
|
+
}
|
|
953
|
+
case "MinFunc": {
|
|
954
|
+
const [s, cmp] = a();
|
|
955
|
+
return `((s, f) => s.reduce((m, x) => f(x, m) < 0 ? x : m))(${s}, ${cmp})`;
|
|
956
|
+
}
|
|
957
|
+
case "Clone": {
|
|
958
|
+
const [s] = a();
|
|
959
|
+
return `${s}.slice()`;
|
|
960
|
+
}
|
|
961
|
+
case "Compact": {
|
|
962
|
+
const [s] = a();
|
|
963
|
+
return `((s) => s.filter((v, i) => i === 0 || v !== s[i-1]))(${s})`;
|
|
964
|
+
}
|
|
965
|
+
case "CompactFunc": {
|
|
966
|
+
const [s, eq] = a();
|
|
967
|
+
return `((s, f) => s.filter((v, i) => i === 0 || !f(v, s[i-1])))(${s}, ${eq})`;
|
|
968
|
+
}
|
|
969
|
+
case "Concat": {
|
|
970
|
+
const args = a();
|
|
971
|
+
return `[].concat(${args.join(", ")})`;
|
|
972
|
+
}
|
|
973
|
+
case "Delete": {
|
|
974
|
+
const [s, i, j] = a();
|
|
975
|
+
return `[...${s}.slice(0, ${i}), ...${s}.slice(${j})]`;
|
|
976
|
+
}
|
|
977
|
+
case "DeleteFunc": {
|
|
978
|
+
const [s, fn2] = a();
|
|
979
|
+
return `${s}.filter((v) => !${fn2}(v))`;
|
|
980
|
+
}
|
|
981
|
+
case "Insert": {
|
|
982
|
+
const args = a();
|
|
983
|
+
const s = args[0];
|
|
984
|
+
const i = args[1];
|
|
985
|
+
const vs = args.slice(2);
|
|
986
|
+
return `[...${s}.slice(0, ${i}), ${vs.join(", ")}, ...${s}.slice(${i})]`;
|
|
987
|
+
}
|
|
988
|
+
case "Replace": {
|
|
989
|
+
const args = a();
|
|
990
|
+
const s = args[0];
|
|
991
|
+
const i = args[1];
|
|
992
|
+
const j = args[2];
|
|
993
|
+
const vs = args.slice(3);
|
|
994
|
+
return `[...${s}.slice(0, ${i}), ${vs.join(", ")}, ...${s}.slice(${j})]`;
|
|
995
|
+
}
|
|
996
|
+
case "Grow":
|
|
997
|
+
case "Clip": {
|
|
998
|
+
const [s] = a();
|
|
999
|
+
return `${s}.slice()`;
|
|
1000
|
+
}
|
|
1001
|
+
}
|
|
1002
|
+
return undefined;
|
|
1003
|
+
},
|
|
1004
|
+
|
|
1005
|
+
_genHtml(fn, a) {
|
|
1006
|
+
switch (fn) {
|
|
1007
|
+
case "EscapeString": {
|
|
1008
|
+
const [s] = a();
|
|
1009
|
+
return `${s}.replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""").replace(/'/g,"'")`;
|
|
1010
|
+
}
|
|
1011
|
+
case "UnescapeString": {
|
|
1012
|
+
const [s] = a();
|
|
1013
|
+
return `${s}.replace(/'/g,"'").replace(/"/g,'"').replace(/>/g,">").replace(/</g,"<").replace(/&/g,"&")`;
|
|
1014
|
+
}
|
|
1015
|
+
}
|
|
1016
|
+
return undefined;
|
|
1017
|
+
},
|
|
1018
|
+
|
|
1019
|
+
_genMaps(fn, a) {
|
|
1020
|
+
switch (fn) {
|
|
1021
|
+
case "Keys": {
|
|
1022
|
+
const [m] = a();
|
|
1023
|
+
return `Object.keys(${m})`;
|
|
1024
|
+
}
|
|
1025
|
+
case "Values": {
|
|
1026
|
+
const [m] = a();
|
|
1027
|
+
return `Object.values(${m})`;
|
|
1028
|
+
}
|
|
1029
|
+
case "Clone": {
|
|
1030
|
+
const [m] = a();
|
|
1031
|
+
return `({...${m}})`;
|
|
1032
|
+
}
|
|
1033
|
+
case "Copy": {
|
|
1034
|
+
const [dst, src] = a();
|
|
1035
|
+
return `Object.assign(${dst}, ${src})`;
|
|
1036
|
+
}
|
|
1037
|
+
case "Equal": {
|
|
1038
|
+
this._usesEqual = true;
|
|
1039
|
+
const [a1, b1] = a();
|
|
1040
|
+
return `__equal(${a1}, ${b1})`;
|
|
1041
|
+
}
|
|
1042
|
+
case "EqualFunc": {
|
|
1043
|
+
const [m1, m2, eq] = a();
|
|
1044
|
+
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})`;
|
|
1045
|
+
}
|
|
1046
|
+
case "Delete": {
|
|
1047
|
+
const [m, k] = a();
|
|
1048
|
+
return `(delete ${m}[${k}], undefined)`;
|
|
1049
|
+
}
|
|
1050
|
+
case "DeleteFunc": {
|
|
1051
|
+
const [m, fn2] = a();
|
|
1052
|
+
return `Object.keys(${m}).forEach(k => { if (${fn2}(k, ${m}[k])) delete ${m}[k]; })`;
|
|
1053
|
+
}
|
|
1054
|
+
}
|
|
1055
|
+
return undefined;
|
|
1056
|
+
},
|
|
1057
|
+
|
|
894
1058
|
typeComment(typeNode) {
|
|
895
1059
|
if (!typeNode) return "unknown";
|
|
896
1060
|
switch (typeNode.kind) {
|
|
@@ -904,4 +1068,365 @@ export const expressionGenMethods = {
|
|
|
904
1068
|
return typeNode.kind;
|
|
905
1069
|
}
|
|
906
1070
|
},
|
|
1071
|
+
|
|
1072
|
+
// Dispatch table for stdlib namespace calls (pkg.Func → inline JS).
|
|
1073
|
+
// Returns the generated JS string, or undefined if not handled.
|
|
1074
|
+
_genStdlibCall(ns, fn, expr) {
|
|
1075
|
+
const a = () => expr.args.map((e) => this.genExpr(e));
|
|
1076
|
+
switch (ns) {
|
|
1077
|
+
case "fmt":
|
|
1078
|
+
return this._genFmt(fn, a, expr);
|
|
1079
|
+
case "strings":
|
|
1080
|
+
return this._genStrings(fn, a);
|
|
1081
|
+
case "bytes":
|
|
1082
|
+
return this._genBytes(fn, a);
|
|
1083
|
+
case "strconv":
|
|
1084
|
+
return this._genStrconv(fn, a);
|
|
1085
|
+
case "sort":
|
|
1086
|
+
return this._genSort(fn, a);
|
|
1087
|
+
case "math":
|
|
1088
|
+
return this._genMath(fn, a);
|
|
1089
|
+
case "unicode":
|
|
1090
|
+
return this._genUnicode(fn, a);
|
|
1091
|
+
case "os":
|
|
1092
|
+
return this._genOs(fn, a);
|
|
1093
|
+
case "errors":
|
|
1094
|
+
return this._genErrors(fn, a);
|
|
1095
|
+
case "time":
|
|
1096
|
+
return this._genTime(fn, a);
|
|
1097
|
+
case "regexp":
|
|
1098
|
+
return this._genRegexp(fn, a);
|
|
1099
|
+
case "slices":
|
|
1100
|
+
return this._genSlices(fn, a);
|
|
1101
|
+
case "maps":
|
|
1102
|
+
return this._genMaps(fn, a);
|
|
1103
|
+
case "html":
|
|
1104
|
+
return this._genHtml(fn, a);
|
|
1105
|
+
default:
|
|
1106
|
+
return undefined;
|
|
1107
|
+
}
|
|
1108
|
+
},
|
|
1109
|
+
|
|
1110
|
+
_genFmt(fn, _a, expr) {
|
|
1111
|
+
const fmtArgs = expr.args.map((e) => this.genExpr(e)).join(", ");
|
|
1112
|
+
switch (fn) {
|
|
1113
|
+
case "Sprintf":
|
|
1114
|
+
this._usesSprintf = true;
|
|
1115
|
+
return `__sprintf(${fmtArgs})`;
|
|
1116
|
+
case "Errorf": {
|
|
1117
|
+
this._usesSprintf = true;
|
|
1118
|
+
this._usesError = true;
|
|
1119
|
+
const fmtStr = expr.args[0];
|
|
1120
|
+
if (fmtStr?.kind === "BasicLit" && fmtStr.value?.includes("%w")) {
|
|
1121
|
+
const lastArg = this.genExpr(expr.args[expr.args.length - 1]);
|
|
1122
|
+
return `__error(__sprintf(${fmtArgs}), ${lastArg})`;
|
|
1123
|
+
}
|
|
1124
|
+
return `__error(__sprintf(${fmtArgs}))`;
|
|
1125
|
+
}
|
|
1126
|
+
case "Printf":
|
|
1127
|
+
case "Print":
|
|
1128
|
+
this._usesSprintf = true;
|
|
1129
|
+
return `process?.stdout?.write(__sprintf(${fmtArgs}))`;
|
|
1130
|
+
case "Println":
|
|
1131
|
+
this._usesSprintf = true;
|
|
1132
|
+
return `console.log(__sprintf(${fmtArgs}))`;
|
|
1133
|
+
case "Fprintf":
|
|
1134
|
+
case "Fprintln":
|
|
1135
|
+
case "Fprint": {
|
|
1136
|
+
this._usesSprintf = true;
|
|
1137
|
+
const writerArg = expr.args[0];
|
|
1138
|
+
const rest = expr.args.slice(1);
|
|
1139
|
+
const restJs = rest.map((e) => this.genExpr(e)).join(", ");
|
|
1140
|
+
// Resolve the writer to its underlying buffer expression
|
|
1141
|
+
const writerType = writerArg._type;
|
|
1142
|
+
const targetTypeName =
|
|
1143
|
+
writerType?.name ??
|
|
1144
|
+
(writerType?.kind === "pointer" ? writerType.base?.name : null);
|
|
1145
|
+
let buf;
|
|
1146
|
+
if (targetTypeName === "strings.Builder") {
|
|
1147
|
+
const w = this.genExpr(writerArg);
|
|
1148
|
+
buf =
|
|
1149
|
+
writerType?.kind === "pointer" ? `${w}.value._buf` : `${w}._buf`;
|
|
1150
|
+
} else if (targetTypeName === "bytes.Buffer") {
|
|
1151
|
+
const w = this.genExpr(writerArg);
|
|
1152
|
+
const base = writerType?.kind === "pointer" ? `${w}.value` : w;
|
|
1153
|
+
buf = null;
|
|
1154
|
+
if (fn === "Fprintf") {
|
|
1155
|
+
return `((__b,__s)=>{ __b._buf.push(...new TextEncoder().encode(__s)); })(${base}, __sprintf(${restJs}))`;
|
|
1156
|
+
}
|
|
1157
|
+
const valJs =
|
|
1158
|
+
fn === "Fprintln"
|
|
1159
|
+
? `__sprintf("%v\\n", ${restJs})`
|
|
1160
|
+
: `__sprintf("%v", ${restJs})`;
|
|
1161
|
+
return `((__b,__s)=>{ __b._buf.push(...new TextEncoder().encode(__s)); })(${base}, ${valJs})`;
|
|
1162
|
+
} else {
|
|
1163
|
+
// Generic io.Writer fallback — call .WriteString if available
|
|
1164
|
+
const w = this.genExpr(writerArg);
|
|
1165
|
+
const formatted =
|
|
1166
|
+
fn === "Fprintf"
|
|
1167
|
+
? `__sprintf(${restJs})`
|
|
1168
|
+
: fn === "Fprintln"
|
|
1169
|
+
? `__sprintf("%v\\n", ${restJs})`
|
|
1170
|
+
: `__sprintf("%v", ${restJs})`;
|
|
1171
|
+
return `${w}.WriteString(${formatted})`;
|
|
1172
|
+
}
|
|
1173
|
+
// strings.Builder path
|
|
1174
|
+
if (fn === "Fprintf") return `(${buf} += __sprintf(${restJs}))`;
|
|
1175
|
+
if (fn === "Fprintln")
|
|
1176
|
+
return `(${buf} += __sprintf("%v\\n", ${restJs}))`;
|
|
1177
|
+
return `(${buf} += __sprintf("%v", ${restJs}))`;
|
|
1178
|
+
}
|
|
1179
|
+
default:
|
|
1180
|
+
return undefined;
|
|
1181
|
+
}
|
|
1182
|
+
},
|
|
1183
|
+
|
|
1184
|
+
_genStrings(fn, a) {
|
|
1185
|
+
const args = a();
|
|
1186
|
+
switch (fn) {
|
|
1187
|
+
case "Contains":
|
|
1188
|
+
return `${args[0]}.includes(${args[1]})`;
|
|
1189
|
+
case "HasPrefix":
|
|
1190
|
+
return `${args[0]}.startsWith(${args[1]})`;
|
|
1191
|
+
case "HasSuffix":
|
|
1192
|
+
return `${args[0]}.endsWith(${args[1]})`;
|
|
1193
|
+
case "Index":
|
|
1194
|
+
return `${args[0]}.indexOf(${args[1]})`;
|
|
1195
|
+
case "LastIndex":
|
|
1196
|
+
return `${args[0]}.lastIndexOf(${args[1]})`;
|
|
1197
|
+
case "Count":
|
|
1198
|
+
return `((s, sep) => sep === "" ? s.length + 1 : s.split(sep).length - 1)(${args[0]}, ${args[1]})`;
|
|
1199
|
+
case "Repeat":
|
|
1200
|
+
return `${args[0]}.repeat(${args[1]})`;
|
|
1201
|
+
case "Replace":
|
|
1202
|
+
return `${args[0]}.replace(${args[1]}, ${args[2]})`;
|
|
1203
|
+
case "ReplaceAll":
|
|
1204
|
+
return `${args[0]}.replaceAll(${args[1]}, ${args[2]})`;
|
|
1205
|
+
case "ToUpper":
|
|
1206
|
+
return `${args[0]}.toUpperCase()`;
|
|
1207
|
+
case "ToLower":
|
|
1208
|
+
return `${args[0]}.toLowerCase()`;
|
|
1209
|
+
case "TrimSpace":
|
|
1210
|
+
return `${args[0]}.trim()`;
|
|
1211
|
+
case "Trim":
|
|
1212
|
+
return `${args[0]}.replace(new RegExp(\`^[\${${args[1]}}]+|[\${${args[1]}}]+$\`, "g"), "")`;
|
|
1213
|
+
case "TrimPrefix":
|
|
1214
|
+
return `((s, pre) => s.startsWith(pre) ? s.slice(pre.length) : s)(${args[0]}, ${args[1]})`;
|
|
1215
|
+
case "TrimSuffix":
|
|
1216
|
+
return `((s, suf) => !suf.length || !s.endsWith(suf) ? s : s.slice(0, -suf.length))(${args[0]}, ${args[1]})`;
|
|
1217
|
+
case "TrimLeft":
|
|
1218
|
+
return `${args[0]}.replace(new RegExp(\`^[\${${args[1]}}]+\`), "")`;
|
|
1219
|
+
case "TrimRight":
|
|
1220
|
+
return `${args[0]}.replace(new RegExp(\`[\${${args[1]}}]+$\`), "")`;
|
|
1221
|
+
case "Split":
|
|
1222
|
+
return `${args[0]}.split(${args[1]})`;
|
|
1223
|
+
case "Join":
|
|
1224
|
+
return `${args[0]}.join(${args[1]})`;
|
|
1225
|
+
case "EqualFold":
|
|
1226
|
+
return `${args[0]}.toLowerCase() === ${args[1]}.toLowerCase()`;
|
|
1227
|
+
default:
|
|
1228
|
+
return undefined;
|
|
1229
|
+
}
|
|
1230
|
+
},
|
|
1231
|
+
|
|
1232
|
+
_genBytes(fn, a) {
|
|
1233
|
+
const __bs = `(b => String.fromCharCode(...b))`;
|
|
1234
|
+
const __sb = `(s => [...s].map(c => c.charCodeAt(0)))`;
|
|
1235
|
+
const args = a();
|
|
1236
|
+
switch (fn) {
|
|
1237
|
+
case "Contains":
|
|
1238
|
+
return `${__bs}(${args[0]}).includes(${__bs}(${args[1]}))`;
|
|
1239
|
+
case "HasPrefix":
|
|
1240
|
+
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]})`;
|
|
1241
|
+
case "HasSuffix":
|
|
1242
|
+
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]})`;
|
|
1243
|
+
case "Index":
|
|
1244
|
+
return `${__bs}(${args[0]}).indexOf(${__bs}(${args[1]}))`;
|
|
1245
|
+
case "Count":
|
|
1246
|
+
return `((b, sep) => sep.length === 0 ? b.length + 1 : ${__bs}(b).split(${__bs}(sep)).length - 1)(${args[0]}, ${args[1]})`;
|
|
1247
|
+
case "Repeat":
|
|
1248
|
+
return `${__sb}(${__bs}(${args[0]}).repeat(${args[1]}))`;
|
|
1249
|
+
case "Replace":
|
|
1250
|
+
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]})`;
|
|
1251
|
+
case "ToUpper":
|
|
1252
|
+
return `${__sb}(${__bs}(${args[0]}).toUpperCase())`;
|
|
1253
|
+
case "ToLower":
|
|
1254
|
+
return `${__sb}(${__bs}(${args[0]}).toLowerCase())`;
|
|
1255
|
+
case "TrimSpace":
|
|
1256
|
+
return `${__sb}(${__bs}(${args[0]}).trim())`;
|
|
1257
|
+
case "Trim":
|
|
1258
|
+
return `${__sb}(${__bs}(${args[0]}).replace(new RegExp(\`^[\${${args[1]}}]+|[\${${args[1]}}]+$\`, "g"), ""))`;
|
|
1259
|
+
case "Equal":
|
|
1260
|
+
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]})`;
|
|
1261
|
+
case "Split":
|
|
1262
|
+
return `${__bs}(${args[0]}).split(${__bs}(${args[1]})).map(p => ${__sb}(p))`;
|
|
1263
|
+
case "Join":
|
|
1264
|
+
return `${__sb}(${args[0]}.map(p => ${__bs}(p)).join(${__bs}(${args[1]})))`;
|
|
1265
|
+
default:
|
|
1266
|
+
return undefined;
|
|
1267
|
+
}
|
|
1268
|
+
},
|
|
1269
|
+
|
|
1270
|
+
_genStrconv(fn, a) {
|
|
1271
|
+
const args = a();
|
|
1272
|
+
switch (fn) {
|
|
1273
|
+
case "Itoa":
|
|
1274
|
+
return `String(${args[0]})`;
|
|
1275
|
+
case "Atoi":
|
|
1276
|
+
return `(Number.isNaN(Number(${args[0]})) ? [0, "invalid syntax"] : [Number(${args[0]}) | 0, null])`;
|
|
1277
|
+
case "FormatBool":
|
|
1278
|
+
return `String(${args[0]})`;
|
|
1279
|
+
case "FormatInt":
|
|
1280
|
+
return `(${args[0]}).toString(${args[1]})`;
|
|
1281
|
+
case "FormatFloat":
|
|
1282
|
+
return `String(${args[0]})`;
|
|
1283
|
+
case "ParseFloat":
|
|
1284
|
+
return `(Number.isNaN(Number(${args[0]})) ? [0, "invalid syntax"] : [Number(${args[0]}), null])`;
|
|
1285
|
+
case "ParseInt":
|
|
1286
|
+
return `(Number.isNaN(parseInt(${args[0]}, ${args[1]} || 10)) ? [0, "invalid syntax"] : [parseInt(${args[0]}, ${args[1]} || 10), null])`;
|
|
1287
|
+
case "ParseBool":
|
|
1288
|
+
return `(${args[0]} === "true" || ${args[0]} === "1" ? [true, null] : ${args[0]} === "false" || ${args[0]} === "0" ? [false, null] : [false, "invalid syntax"])`;
|
|
1289
|
+
default:
|
|
1290
|
+
return undefined;
|
|
1291
|
+
}
|
|
1292
|
+
},
|
|
1293
|
+
|
|
1294
|
+
_genSort(fn, a) {
|
|
1295
|
+
const args = a();
|
|
1296
|
+
switch (fn) {
|
|
1297
|
+
case "Ints":
|
|
1298
|
+
case "Float64s":
|
|
1299
|
+
return `${args[0]}.sort((a, b) => a - b)`;
|
|
1300
|
+
case "Strings":
|
|
1301
|
+
return `${args[0]}.sort()`;
|
|
1302
|
+
case "Slice":
|
|
1303
|
+
case "SliceStable":
|
|
1304
|
+
return `${args[0]}.sort((a, b) => ${args[1]}(a, b) ? -1 : ${args[1]}(b, a) ? 1 : 0)`;
|
|
1305
|
+
case "SliceIsSorted":
|
|
1306
|
+
return `${args[0]}.every((v, i, a) => i === 0 || ${args[1]}(a[i - 1], v))`;
|
|
1307
|
+
default:
|
|
1308
|
+
return undefined;
|
|
1309
|
+
}
|
|
1310
|
+
},
|
|
1311
|
+
|
|
1312
|
+
_genMath(fn, a) {
|
|
1313
|
+
const args = a();
|
|
1314
|
+
switch (fn) {
|
|
1315
|
+
case "Abs":
|
|
1316
|
+
return `Math.abs(${args[0]})`;
|
|
1317
|
+
case "Floor":
|
|
1318
|
+
return `Math.floor(${args[0]})`;
|
|
1319
|
+
case "Ceil":
|
|
1320
|
+
return `Math.ceil(${args[0]})`;
|
|
1321
|
+
case "Round":
|
|
1322
|
+
return `Math.round(${args[0]})`;
|
|
1323
|
+
case "Sqrt":
|
|
1324
|
+
return `Math.sqrt(${args[0]})`;
|
|
1325
|
+
case "Cbrt":
|
|
1326
|
+
return `Math.cbrt(${args[0]})`;
|
|
1327
|
+
case "Pow":
|
|
1328
|
+
return `Math.pow(${args[0]}, ${args[1]})`;
|
|
1329
|
+
case "Log":
|
|
1330
|
+
return `Math.log(${args[0]})`;
|
|
1331
|
+
case "Log2":
|
|
1332
|
+
return `Math.log2(${args[0]})`;
|
|
1333
|
+
case "Log10":
|
|
1334
|
+
return `Math.log10(${args[0]})`;
|
|
1335
|
+
case "Sin":
|
|
1336
|
+
return `Math.sin(${args[0]})`;
|
|
1337
|
+
case "Cos":
|
|
1338
|
+
return `Math.cos(${args[0]})`;
|
|
1339
|
+
case "Tan":
|
|
1340
|
+
return `Math.tan(${args[0]})`;
|
|
1341
|
+
case "Min":
|
|
1342
|
+
return `Math.min(${args[0]}, ${args[1]})`;
|
|
1343
|
+
case "Max":
|
|
1344
|
+
return `Math.max(${args[0]}, ${args[1]})`;
|
|
1345
|
+
case "Mod":
|
|
1346
|
+
return `${args[0]} % ${args[1]}`;
|
|
1347
|
+
case "Inf":
|
|
1348
|
+
return `(${args[0]} >= 0 ? Infinity : -Infinity)`;
|
|
1349
|
+
case "IsNaN":
|
|
1350
|
+
return `Number.isNaN(${args[0]})`;
|
|
1351
|
+
case "IsInf":
|
|
1352
|
+
return `(${args[1]} > 0 ? ${args[0]} === Infinity : ${args[1]} < 0 ? ${args[0]} === -Infinity : !Number.isFinite(${args[0]}))`;
|
|
1353
|
+
case "NaN":
|
|
1354
|
+
return "NaN";
|
|
1355
|
+
default:
|
|
1356
|
+
return undefined;
|
|
1357
|
+
}
|
|
1358
|
+
},
|
|
1359
|
+
|
|
1360
|
+
_genUnicode(fn, a) {
|
|
1361
|
+
const args = a();
|
|
1362
|
+
const cp = `String.fromCodePoint(${args[0]})`;
|
|
1363
|
+
switch (fn) {
|
|
1364
|
+
case "IsLetter":
|
|
1365
|
+
return `/\\p{L}/u.test(${cp})`;
|
|
1366
|
+
case "IsDigit":
|
|
1367
|
+
return `/\\p{Nd}/u.test(${cp})`;
|
|
1368
|
+
case "IsSpace":
|
|
1369
|
+
return `/\\s/.test(${cp})`;
|
|
1370
|
+
case "IsUpper":
|
|
1371
|
+
return `((__c) => __c === __c.toUpperCase() && /\\p{L}/u.test(__c))(${cp})`;
|
|
1372
|
+
case "IsLower":
|
|
1373
|
+
return `((__c) => __c === __c.toLowerCase() && /\\p{L}/u.test(__c))(${cp})`;
|
|
1374
|
+
case "IsPunct":
|
|
1375
|
+
return `/\\p{P}/u.test(${cp})`;
|
|
1376
|
+
case "IsControl":
|
|
1377
|
+
return `/\\p{Cc}/u.test(${cp})`;
|
|
1378
|
+
case "IsPrint":
|
|
1379
|
+
return `!/\\p{Cc}/u.test(${cp})`;
|
|
1380
|
+
case "IsGraphic":
|
|
1381
|
+
return `!/\\p{Cc}/u.test(${cp})`;
|
|
1382
|
+
case "ToUpper":
|
|
1383
|
+
return `${cp}.toUpperCase().codePointAt(0)`;
|
|
1384
|
+
case "ToLower":
|
|
1385
|
+
return `${cp}.toLowerCase().codePointAt(0)`;
|
|
1386
|
+
default:
|
|
1387
|
+
return undefined;
|
|
1388
|
+
}
|
|
1389
|
+
},
|
|
1390
|
+
|
|
1391
|
+
_genOs(fn, a) {
|
|
1392
|
+
const args = a();
|
|
1393
|
+
switch (fn) {
|
|
1394
|
+
case "Exit":
|
|
1395
|
+
return `process.exit(${args[0]})`;
|
|
1396
|
+
case "Getenv":
|
|
1397
|
+
return `(process.env[${args[0]}] ?? "")`;
|
|
1398
|
+
default:
|
|
1399
|
+
return undefined;
|
|
1400
|
+
}
|
|
1401
|
+
},
|
|
1402
|
+
|
|
1403
|
+
_genErrors(fn, a) {
|
|
1404
|
+
const args = a();
|
|
1405
|
+
switch (fn) {
|
|
1406
|
+
case "New":
|
|
1407
|
+
this._usesError = true;
|
|
1408
|
+
return `__error(${args[0]})`;
|
|
1409
|
+
case "Is":
|
|
1410
|
+
this._usesErrorIs = true;
|
|
1411
|
+
return `__errorIs(${args[0]}, ${args[1]})`;
|
|
1412
|
+
case "Unwrap":
|
|
1413
|
+
return `(${args[0]}?._cause ?? null)`;
|
|
1414
|
+
default:
|
|
1415
|
+
return undefined;
|
|
1416
|
+
}
|
|
1417
|
+
},
|
|
1418
|
+
|
|
1419
|
+
_genTime(fn, a) {
|
|
1420
|
+
const args = a();
|
|
1421
|
+
switch (fn) {
|
|
1422
|
+
case "Now":
|
|
1423
|
+
return "Date.now()";
|
|
1424
|
+
case "Since":
|
|
1425
|
+
return `(Date.now() - ${args[0]})`;
|
|
1426
|
+
case "Sleep":
|
|
1427
|
+
return `await new Promise(r => setTimeout(r, ${args[0]} / 1000000))`;
|
|
1428
|
+
default:
|
|
1429
|
+
return undefined;
|
|
1430
|
+
}
|
|
1431
|
+
},
|
|
907
1432
|
};
|