gofront 0.0.1 → 0.0.3
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 +357 -33
- package/package.json +7 -3
- package/src/codegen/expressions.js +804 -0
- package/src/codegen/source-map.js +45 -0
- package/src/codegen/statements.js +406 -0
- package/src/codegen.js +50 -851
- package/src/compiler.js +44 -23
- package/src/dts-parser.js +0 -4
- package/src/index.js +11 -20
- package/src/lexer.js +170 -12
- package/src/parser/expressions.js +389 -0
- package/src/parser/statements.js +355 -0
- package/src/parser/types.js +140 -0
- package/src/parser.js +57 -745
- package/src/resolver.js +1 -1
- package/src/typechecker/expressions.js +359 -0
- package/src/typechecker/statements.js +250 -0
- package/src/typechecker/types.js +226 -0
- package/src/typechecker.js +390 -647
|
@@ -0,0 +1,804 @@
|
|
|
1
|
+
// CodeGen expression methods and helpers — installed as a mixin on CodeGen.prototype.
|
|
2
|
+
|
|
3
|
+
const INT_TYPE_NAMES = new Set([
|
|
4
|
+
"int",
|
|
5
|
+
"uint",
|
|
6
|
+
"int8",
|
|
7
|
+
"int16",
|
|
8
|
+
"int32",
|
|
9
|
+
"int64",
|
|
10
|
+
"uint8",
|
|
11
|
+
"uint16",
|
|
12
|
+
"uint32",
|
|
13
|
+
"uint64",
|
|
14
|
+
"uintptr",
|
|
15
|
+
"byte",
|
|
16
|
+
"rune",
|
|
17
|
+
]);
|
|
18
|
+
|
|
19
|
+
export const expressionGenMethods = {
|
|
20
|
+
genExpr(expr) {
|
|
21
|
+
switch (expr.kind) {
|
|
22
|
+
case "BasicLit":
|
|
23
|
+
if (expr.litKind === "STRING") return JSON.stringify(expr.value);
|
|
24
|
+
return expr.value; // int, float, bool, nil(null)
|
|
25
|
+
|
|
26
|
+
case "Ident":
|
|
27
|
+
return expr.name;
|
|
28
|
+
|
|
29
|
+
case "UnaryExpr": {
|
|
30
|
+
const op = expr.op === "^" ? "~" : expr.op; // bitwise NOT
|
|
31
|
+
// Dereference/address-of — transparent in JS
|
|
32
|
+
if (op === "*" || op === "&") return this.genExpr(expr.operand);
|
|
33
|
+
return `${op}${this.genExpr(expr.operand)}`;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
case "BinaryExpr": {
|
|
37
|
+
const l = this.genExpr(expr.left);
|
|
38
|
+
const r = this.genExpr(expr.right);
|
|
39
|
+
// Integer division: only when both sides are int
|
|
40
|
+
if (
|
|
41
|
+
expr.op === "/" &&
|
|
42
|
+
this.isIntType(expr.left._type) &&
|
|
43
|
+
this.isIntType(expr.right._type)
|
|
44
|
+
) {
|
|
45
|
+
return `Math.trunc(${l} / ${r})`;
|
|
46
|
+
}
|
|
47
|
+
// Go &^ (bit clear / AND NOT) → JS & ~
|
|
48
|
+
if (expr.op === "&^") {
|
|
49
|
+
return `${l} & ~${r}`;
|
|
50
|
+
}
|
|
51
|
+
// Go == and != are strict — map to JS === / !==
|
|
52
|
+
const op =
|
|
53
|
+
expr.op === "==" ? "===" : expr.op === "!=" ? "!==" : expr.op;
|
|
54
|
+
return `${l} ${op} ${r}`;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
case "CallExpr":
|
|
58
|
+
return this.genCall(expr);
|
|
59
|
+
|
|
60
|
+
case "SelectorExpr": {
|
|
61
|
+
// math constants
|
|
62
|
+
if (expr.expr.kind === "Ident" && expr.expr.name === "math") {
|
|
63
|
+
switch (expr.field) {
|
|
64
|
+
case "Pi":
|
|
65
|
+
return "Math.PI";
|
|
66
|
+
case "E":
|
|
67
|
+
return "Math.E";
|
|
68
|
+
case "MaxFloat64":
|
|
69
|
+
return "Number.MAX_VALUE";
|
|
70
|
+
case "SmallestNonzeroFloat64":
|
|
71
|
+
return "Number.MIN_VALUE";
|
|
72
|
+
case "MaxInt":
|
|
73
|
+
return "Number.MAX_SAFE_INTEGER";
|
|
74
|
+
case "MinInt":
|
|
75
|
+
return "Number.MIN_SAFE_INTEGER";
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
// time constants (nanosecond durations)
|
|
79
|
+
if (expr.expr.kind === "Ident" && expr.expr.name === "time") {
|
|
80
|
+
switch (expr.field) {
|
|
81
|
+
case "Millisecond":
|
|
82
|
+
return "1000000";
|
|
83
|
+
case "Second":
|
|
84
|
+
return "1000000000";
|
|
85
|
+
case "Minute":
|
|
86
|
+
return "60000000000";
|
|
87
|
+
case "Hour":
|
|
88
|
+
return "3600000000000";
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
const base = this.genExpr(expr.expr);
|
|
92
|
+
// Bundled GoFront packages are inlined — drop the qualifier.
|
|
93
|
+
if (this.bundledPackages.has(base)) return expr.field;
|
|
94
|
+
// Pointer types are wrapped as { value: T }; route through .value
|
|
95
|
+
if (expr.expr._type?.kind === "pointer" && expr.field !== "value") {
|
|
96
|
+
return `${base}.value.${expr.field}`;
|
|
97
|
+
}
|
|
98
|
+
return `${base}.${expr.field}`;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
case "IndexExpr": {
|
|
102
|
+
const base = this.genExpr(expr.expr);
|
|
103
|
+
const idx = this.genExpr(expr.index);
|
|
104
|
+
if (expr._mapValueType && !expr._lvalue) {
|
|
105
|
+
const zero = this.zeroValueForType(expr._mapValueType);
|
|
106
|
+
// Use an IIFE to avoid double-evaluating base/key when either
|
|
107
|
+
// could be a function call or other expression with side effects.
|
|
108
|
+
if (this._hasCallExpr(expr.expr) || this._hasCallExpr(expr.index)) {
|
|
109
|
+
return `((__m, __k) => __m[__k] ?? ${zero})(${base}, ${idx})`;
|
|
110
|
+
}
|
|
111
|
+
return `(${base}[${idx}] ?? ${zero})`;
|
|
112
|
+
}
|
|
113
|
+
return `${base}[${idx}]`;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
case "SliceExpr": {
|
|
117
|
+
const base = this.genExpr(expr.expr);
|
|
118
|
+
const lo = expr.low ? this.genExpr(expr.low) : "";
|
|
119
|
+
const hi = expr.high ? this.genExpr(expr.high) : "";
|
|
120
|
+
if (!lo && !hi) return `${base}.slice()`;
|
|
121
|
+
if (!hi) return `${base}.slice(${lo})`;
|
|
122
|
+
return `${base}.slice(${lo}, ${hi})`;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
case "CompositeLit":
|
|
126
|
+
return this.genCompositeLit(expr);
|
|
127
|
+
|
|
128
|
+
case "FuncLit": {
|
|
129
|
+
const params = expr.params.map((p) => p.name).join(", ");
|
|
130
|
+
const asyncPrefix = expr.async ? "async " : "";
|
|
131
|
+
const saved = this.out;
|
|
132
|
+
this.out = [];
|
|
133
|
+
this.indented(() => this._genBody(expr.body));
|
|
134
|
+
const body = this.out.join("\n");
|
|
135
|
+
this.out = saved;
|
|
136
|
+
return `${asyncPrefix}function(${params}) {\n${body}\n${" ".repeat(this.indent)}}`;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
case "TypeConversion": {
|
|
140
|
+
const inner = this.genExpr(expr.expr);
|
|
141
|
+
const t = expr.targetType;
|
|
142
|
+
|
|
143
|
+
// Slice type conversions
|
|
144
|
+
if (t?.kind === "SliceType") {
|
|
145
|
+
const elem = t.elem?.name;
|
|
146
|
+
// []byte(s) → UTF-8 byte array
|
|
147
|
+
if (elem === "byte" || elem === "uint8") {
|
|
148
|
+
return `Array.from(new TextEncoder().encode(${inner}))`;
|
|
149
|
+
}
|
|
150
|
+
// []rune(s) → Unicode code point array (only when source is a string)
|
|
151
|
+
if (elem === "rune" || elem === "int32" || elem === "int") {
|
|
152
|
+
const srcType = expr.expr._type;
|
|
153
|
+
if (
|
|
154
|
+
(srcType?.kind === "basic" && srcType?.name === "string") ||
|
|
155
|
+
(srcType?.kind === "untyped" && srcType?.base === "string")
|
|
156
|
+
) {
|
|
157
|
+
return `Array.from(${inner}, __c => __c.codePointAt(0))`;
|
|
158
|
+
}
|
|
159
|
+
return `Array.from(${inner})`;
|
|
160
|
+
}
|
|
161
|
+
return `Array.from(${inner})`;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
const target = t?.name;
|
|
165
|
+
switch (target) {
|
|
166
|
+
case "string": {
|
|
167
|
+
// Go string(65) → "A" (Unicode code point), not "65"
|
|
168
|
+
const srcType = expr.expr._type;
|
|
169
|
+
if (srcType && this.isIntType(srcType)) {
|
|
170
|
+
return `String.fromCodePoint(${inner})`;
|
|
171
|
+
}
|
|
172
|
+
return `String(${inner})`;
|
|
173
|
+
}
|
|
174
|
+
case "int":
|
|
175
|
+
case "byte":
|
|
176
|
+
case "rune":
|
|
177
|
+
return `Math.trunc(Number(${inner}))`;
|
|
178
|
+
case "float64":
|
|
179
|
+
return `Number(${inner})`;
|
|
180
|
+
case "bool":
|
|
181
|
+
return `Boolean(${inner})`;
|
|
182
|
+
default:
|
|
183
|
+
return inner;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
case "TypeAssertExpr": {
|
|
188
|
+
const val = this.genExpr(expr.expr);
|
|
189
|
+
const check = this._typeCheckExpr(expr.type, val);
|
|
190
|
+
if (!expr._commaOk) {
|
|
191
|
+
// plain assertion: panic if check fails (matches Go behavior)
|
|
192
|
+
if (check === "true") return val; // can't check at runtime — pass through
|
|
193
|
+
return `(${check} ? ${val} : (() => { throw new Error("interface conversion: type assertion failed"); })())`;
|
|
194
|
+
}
|
|
195
|
+
// comma-ok: emit [value-or-zero, runtimeTypeCheck]
|
|
196
|
+
const zero = this.zeroValueForTypeNode(expr.type);
|
|
197
|
+
return `(${check} ? [${val}, true] : [${zero}, false])`;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
case "AwaitExpr":
|
|
201
|
+
return `await ${this.genExpr(expr.expr)}`;
|
|
202
|
+
|
|
203
|
+
case "RangeExpr":
|
|
204
|
+
return this.genExpr(expr.expr);
|
|
205
|
+
|
|
206
|
+
default:
|
|
207
|
+
throw new Error(`CodeGen: unhandled expression kind '${expr.kind}'`);
|
|
208
|
+
}
|
|
209
|
+
},
|
|
210
|
+
|
|
211
|
+
genCall(expr) {
|
|
212
|
+
// Handle built-ins that need special JS translation
|
|
213
|
+
if (expr.func.kind === "Ident") {
|
|
214
|
+
switch (expr.func.name) {
|
|
215
|
+
case "append":
|
|
216
|
+
return this.genAppend(expr);
|
|
217
|
+
case "len": {
|
|
218
|
+
const arg = expr.args[0];
|
|
219
|
+
const t = arg?._type;
|
|
220
|
+
const js = this.genExpr(arg);
|
|
221
|
+
if (t?.kind === "map") return `Object.keys(${js}).length`;
|
|
222
|
+
this._usesLen = true;
|
|
223
|
+
return `__len(${js})`;
|
|
224
|
+
}
|
|
225
|
+
case "cap":
|
|
226
|
+
return `${this.genExpr(expr.args[0])}.length`;
|
|
227
|
+
case "make":
|
|
228
|
+
return this.genMake(expr);
|
|
229
|
+
case "delete": {
|
|
230
|
+
const [m, k] = expr.args.map((a) => this.genExpr(a));
|
|
231
|
+
return `(delete ${m}[${k}])`;
|
|
232
|
+
}
|
|
233
|
+
case "copy": {
|
|
234
|
+
const [dst, src] = expr.args.map((a) => this.genExpr(a));
|
|
235
|
+
return `((__cd,__cs)=>{const n=Math.min(__cd.length,__cs.length);__cd.splice(0,n,...__cs.slice(0,n));return n;})(${dst},${src})`;
|
|
236
|
+
}
|
|
237
|
+
case "new": {
|
|
238
|
+
const zero = this.zeroValueForExpr(expr.args[0]);
|
|
239
|
+
return `{ value: ${zero} }`;
|
|
240
|
+
}
|
|
241
|
+
case "print":
|
|
242
|
+
case "println": {
|
|
243
|
+
const args = expr.args.map((a) => this.genExpr(a)).join(", ");
|
|
244
|
+
return `console.log(${args})`;
|
|
245
|
+
}
|
|
246
|
+
case "panic": {
|
|
247
|
+
const arg = this.genExpr(expr.args[0]);
|
|
248
|
+
return `(() => { throw new Error(${arg}); })()`;
|
|
249
|
+
}
|
|
250
|
+
case "recover":
|
|
251
|
+
return `(typeof __panic !== "undefined" && __panic !== null ? (() => { const __r = __panic.message ?? String(__panic); __panic = null; return __r; })() : null)`;
|
|
252
|
+
case "error": {
|
|
253
|
+
// errors are plain strings; nil (null) means no error
|
|
254
|
+
const arg = this.genExpr(expr.args[0]);
|
|
255
|
+
return arg;
|
|
256
|
+
}
|
|
257
|
+
case "min": {
|
|
258
|
+
const args = expr.args.map((a) => this.genExpr(a)).join(", ");
|
|
259
|
+
return `Math.min(${args})`;
|
|
260
|
+
}
|
|
261
|
+
case "max": {
|
|
262
|
+
const args = expr.args.map((a) => this.genExpr(a)).join(", ");
|
|
263
|
+
return `Math.max(${args})`;
|
|
264
|
+
}
|
|
265
|
+
case "clear": {
|
|
266
|
+
const arg = expr.args[0];
|
|
267
|
+
const t = arg?._type;
|
|
268
|
+
const js = this.genExpr(arg);
|
|
269
|
+
if (
|
|
270
|
+
t?.kind === "map" ||
|
|
271
|
+
(t?.kind === "named" && t.underlying?.kind === "map")
|
|
272
|
+
) {
|
|
273
|
+
return `((__m) => { for (const __k in __m) delete __m[__k]; })(${js})`;
|
|
274
|
+
}
|
|
275
|
+
return `(${js}).length = 0`;
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
// error.Error() → the error string itself (errors are plain strings at runtime)
|
|
281
|
+
if (
|
|
282
|
+
expr.func.kind === "SelectorExpr" &&
|
|
283
|
+
expr.func.field === "Error" &&
|
|
284
|
+
expr.func.expr._type?.name === "error"
|
|
285
|
+
) {
|
|
286
|
+
return this.genExpr(expr.func.expr);
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
// fmt.Sprintf / fmt.Printf / fmt.Println / fmt.Print / fmt.Errorf
|
|
290
|
+
if (
|
|
291
|
+
expr.func.kind === "SelectorExpr" &&
|
|
292
|
+
expr.func.expr.kind === "Ident" &&
|
|
293
|
+
expr.func.expr.name === "fmt"
|
|
294
|
+
) {
|
|
295
|
+
const fmtArgs = expr.args.map((a) => this.genExpr(a)).join(", ");
|
|
296
|
+
switch (expr.func.field) {
|
|
297
|
+
case "Sprintf":
|
|
298
|
+
this._usesSprintf = true;
|
|
299
|
+
return `__sprintf(${fmtArgs})`;
|
|
300
|
+
case "Errorf":
|
|
301
|
+
this._usesSprintf = true;
|
|
302
|
+
return `__sprintf(${fmtArgs})`;
|
|
303
|
+
case "Printf":
|
|
304
|
+
this._usesSprintf = true;
|
|
305
|
+
return `process?.stdout?.write(__sprintf(${fmtArgs}))`;
|
|
306
|
+
case "Println":
|
|
307
|
+
this._usesSprintf = true;
|
|
308
|
+
return `console.log(__sprintf(${fmtArgs}))`;
|
|
309
|
+
case "Print":
|
|
310
|
+
this._usesSprintf = true;
|
|
311
|
+
return `process?.stdout?.write(__sprintf(${fmtArgs}))`;
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
// strings.* — direct JS string method mappings
|
|
316
|
+
if (
|
|
317
|
+
expr.func.kind === "SelectorExpr" &&
|
|
318
|
+
expr.func.expr.kind === "Ident" &&
|
|
319
|
+
expr.func.expr.name === "strings"
|
|
320
|
+
) {
|
|
321
|
+
const a = expr.args.map((e) => this.genExpr(e));
|
|
322
|
+
switch (expr.func.field) {
|
|
323
|
+
case "Contains":
|
|
324
|
+
return `${a[0]}.includes(${a[1]})`;
|
|
325
|
+
case "HasPrefix":
|
|
326
|
+
return `${a[0]}.startsWith(${a[1]})`;
|
|
327
|
+
case "HasSuffix":
|
|
328
|
+
return `${a[0]}.endsWith(${a[1]})`;
|
|
329
|
+
case "Index":
|
|
330
|
+
return `${a[0]}.indexOf(${a[1]})`;
|
|
331
|
+
case "LastIndex":
|
|
332
|
+
return `${a[0]}.lastIndexOf(${a[1]})`;
|
|
333
|
+
case "Count":
|
|
334
|
+
return `${a[0]}.split(${a[1]}).length - 1`;
|
|
335
|
+
case "Repeat":
|
|
336
|
+
return `${a[0]}.repeat(${a[1]})`;
|
|
337
|
+
case "Replace":
|
|
338
|
+
return `${a[0]}.replace(${a[1]}, ${a[2]})`;
|
|
339
|
+
case "ReplaceAll":
|
|
340
|
+
return `${a[0]}.replaceAll(${a[1]}, ${a[2]})`;
|
|
341
|
+
case "ToUpper":
|
|
342
|
+
return `${a[0]}.toUpperCase()`;
|
|
343
|
+
case "ToLower":
|
|
344
|
+
return `${a[0]}.toLowerCase()`;
|
|
345
|
+
case "TrimSpace":
|
|
346
|
+
return `${a[0]}.trim()`;
|
|
347
|
+
case "Trim":
|
|
348
|
+
return `${a[0]}.replace(new RegExp(\`^[\${${a[1]}}]+|[\${${a[1]}}]+$\`, "g"), "")`;
|
|
349
|
+
case "TrimPrefix":
|
|
350
|
+
return `(${a[0]}.startsWith(${a[1]}) ? ${a[0]}.slice(${a[1]}.length) : ${a[0]})`;
|
|
351
|
+
case "TrimSuffix":
|
|
352
|
+
return `(${a[0]}.endsWith(${a[1]}) ? ${a[0]}.slice(0, -${a[1]}.length) : ${a[0]})`;
|
|
353
|
+
case "TrimLeft":
|
|
354
|
+
return `${a[0]}.replace(new RegExp(\`^[\${${a[1]}}]+\`), "")`;
|
|
355
|
+
case "TrimRight":
|
|
356
|
+
return `${a[0]}.replace(new RegExp(\`[\${${a[1]}}]+$\`), "")`;
|
|
357
|
+
case "Split":
|
|
358
|
+
return `${a[0]}.split(${a[1]})`;
|
|
359
|
+
case "Join":
|
|
360
|
+
return `${a[0]}.join(${a[1]})`;
|
|
361
|
+
case "EqualFold":
|
|
362
|
+
return `${a[0]}.toLowerCase() === ${a[1]}.toLowerCase()`;
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
// strconv.* — number/string conversion
|
|
367
|
+
if (
|
|
368
|
+
expr.func.kind === "SelectorExpr" &&
|
|
369
|
+
expr.func.expr.kind === "Ident" &&
|
|
370
|
+
expr.func.expr.name === "strconv"
|
|
371
|
+
) {
|
|
372
|
+
const a = expr.args.map((e) => this.genExpr(e));
|
|
373
|
+
switch (expr.func.field) {
|
|
374
|
+
case "Itoa":
|
|
375
|
+
return `String(${a[0]})`;
|
|
376
|
+
case "Atoi":
|
|
377
|
+
return `(Number.isNaN(Number(${a[0]})) ? [0, "invalid syntax"] : [Number(${a[0]}) | 0, null])`;
|
|
378
|
+
case "FormatBool":
|
|
379
|
+
return `String(${a[0]})`;
|
|
380
|
+
case "FormatInt":
|
|
381
|
+
return `(${a[0]}).toString(${a[1]})`;
|
|
382
|
+
case "FormatFloat":
|
|
383
|
+
return `String(${a[0]})`;
|
|
384
|
+
case "ParseFloat":
|
|
385
|
+
return `(Number.isNaN(Number(${a[0]})) ? [0, "invalid syntax"] : [Number(${a[0]}), null])`;
|
|
386
|
+
case "ParseInt":
|
|
387
|
+
return `(Number.isNaN(parseInt(${a[0]}, ${a[1]} || 10)) ? [0, "invalid syntax"] : [parseInt(${a[0]}, ${a[1]} || 10), null])`;
|
|
388
|
+
case "ParseBool":
|
|
389
|
+
return `(${a[0]} === "true" || ${a[0]} === "1" ? [true, null] : ${a[0]} === "false" || ${a[0]} === "0" ? [false, null] : [false, "invalid syntax"])`;
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
// sort.* — in-place sorting
|
|
394
|
+
if (
|
|
395
|
+
expr.func.kind === "SelectorExpr" &&
|
|
396
|
+
expr.func.expr.kind === "Ident" &&
|
|
397
|
+
expr.func.expr.name === "sort"
|
|
398
|
+
) {
|
|
399
|
+
const a = expr.args.map((e) => this.genExpr(e));
|
|
400
|
+
switch (expr.func.field) {
|
|
401
|
+
case "Ints":
|
|
402
|
+
case "Float64s":
|
|
403
|
+
return `${a[0]}.sort((a, b) => a - b)`;
|
|
404
|
+
case "Strings":
|
|
405
|
+
return `${a[0]}.sort()`;
|
|
406
|
+
case "Slice":
|
|
407
|
+
return `${a[0]}.sort((a, b) => ${a[1]}(a, b) ? -1 : ${a[1]}(b, a) ? 1 : 0)`;
|
|
408
|
+
case "SliceStable":
|
|
409
|
+
return `${a[0]}.sort((a, b) => ${a[1]}(a, b) ? -1 : ${a[1]}(b, a) ? 1 : 0)`;
|
|
410
|
+
case "SliceIsSorted":
|
|
411
|
+
return `${a[0]}.every((v, i, a) => i === 0 || ${a[1]}(a[i - 1], v))`;
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
// math.* — math functions
|
|
416
|
+
if (
|
|
417
|
+
expr.func.kind === "SelectorExpr" &&
|
|
418
|
+
expr.func.expr.kind === "Ident" &&
|
|
419
|
+
expr.func.expr.name === "math"
|
|
420
|
+
) {
|
|
421
|
+
const a = expr.args.map((e) => this.genExpr(e));
|
|
422
|
+
switch (expr.func.field) {
|
|
423
|
+
case "Abs":
|
|
424
|
+
return `Math.abs(${a[0]})`;
|
|
425
|
+
case "Floor":
|
|
426
|
+
return `Math.floor(${a[0]})`;
|
|
427
|
+
case "Ceil":
|
|
428
|
+
return `Math.ceil(${a[0]})`;
|
|
429
|
+
case "Round":
|
|
430
|
+
return `Math.round(${a[0]})`;
|
|
431
|
+
case "Sqrt":
|
|
432
|
+
return `Math.sqrt(${a[0]})`;
|
|
433
|
+
case "Cbrt":
|
|
434
|
+
return `Math.cbrt(${a[0]})`;
|
|
435
|
+
case "Pow":
|
|
436
|
+
return `Math.pow(${a[0]}, ${a[1]})`;
|
|
437
|
+
case "Log":
|
|
438
|
+
return `Math.log(${a[0]})`;
|
|
439
|
+
case "Log2":
|
|
440
|
+
return `Math.log2(${a[0]})`;
|
|
441
|
+
case "Log10":
|
|
442
|
+
return `Math.log10(${a[0]})`;
|
|
443
|
+
case "Sin":
|
|
444
|
+
return `Math.sin(${a[0]})`;
|
|
445
|
+
case "Cos":
|
|
446
|
+
return `Math.cos(${a[0]})`;
|
|
447
|
+
case "Tan":
|
|
448
|
+
return `Math.tan(${a[0]})`;
|
|
449
|
+
case "Min":
|
|
450
|
+
return `Math.min(${a[0]}, ${a[1]})`;
|
|
451
|
+
case "Max":
|
|
452
|
+
return `Math.max(${a[0]}, ${a[1]})`;
|
|
453
|
+
case "Mod":
|
|
454
|
+
return `${a[0]} % ${a[1]}`;
|
|
455
|
+
case "Inf":
|
|
456
|
+
return `(${a[0]} >= 0 ? Infinity : -Infinity)`;
|
|
457
|
+
case "IsNaN":
|
|
458
|
+
return `Number.isNaN(${a[0]})`;
|
|
459
|
+
case "IsInf":
|
|
460
|
+
return `(${a[1]} > 0 ? ${a[0]} === Infinity : ${a[1]} < 0 ? ${a[0]} === -Infinity : !Number.isFinite(${a[0]}))`;
|
|
461
|
+
case "NaN":
|
|
462
|
+
return "NaN";
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
// errors.New — identity (errors are plain strings)
|
|
467
|
+
if (
|
|
468
|
+
expr.func.kind === "SelectorExpr" &&
|
|
469
|
+
expr.func.expr.kind === "Ident" &&
|
|
470
|
+
expr.func.expr.name === "errors"
|
|
471
|
+
) {
|
|
472
|
+
const a = expr.args.map((e) => this.genExpr(e));
|
|
473
|
+
if (expr.func.field === "New") return a[0];
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
// time.* — partial JS-friendly time support
|
|
477
|
+
if (
|
|
478
|
+
expr.func.kind === "SelectorExpr" &&
|
|
479
|
+
expr.func.expr.kind === "Ident" &&
|
|
480
|
+
expr.func.expr.name === "time"
|
|
481
|
+
) {
|
|
482
|
+
const a = expr.args.map((e) => this.genExpr(e));
|
|
483
|
+
switch (expr.func.field) {
|
|
484
|
+
case "Now":
|
|
485
|
+
return "Date.now()";
|
|
486
|
+
case "Since":
|
|
487
|
+
return `(Date.now() - ${a[0]})`;
|
|
488
|
+
case "Sleep":
|
|
489
|
+
return `await new Promise(r => setTimeout(r, ${a[0]} / 1000000))`;
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
const rawFn = this.genExpr(expr.func);
|
|
494
|
+
// Wrap function literals in parens so `function(){}()` → `(function(){})()`
|
|
495
|
+
const fn = expr.func.kind === "FuncLit" ? `(${rawFn})` : rawFn;
|
|
496
|
+
const args = expr.args
|
|
497
|
+
.map((a) => (a._spread ? `...${this.genExpr(a)}` : this.genExpr(a)))
|
|
498
|
+
.join(", ");
|
|
499
|
+
return `${fn}(${args})`;
|
|
500
|
+
},
|
|
501
|
+
|
|
502
|
+
genAppend(expr) {
|
|
503
|
+
const slice = this.genExpr(expr.args[0]);
|
|
504
|
+
const elems = expr.args
|
|
505
|
+
.slice(1)
|
|
506
|
+
.map((a) => (a._spread ? `...${this.genExpr(a)}` : this.genExpr(a)));
|
|
507
|
+
if (elems.length === 0) return slice;
|
|
508
|
+
this._usesAppend = true;
|
|
509
|
+
return `__append(${slice}, ${elems.join(", ")})`;
|
|
510
|
+
},
|
|
511
|
+
|
|
512
|
+
genMake(expr) {
|
|
513
|
+
// make([]T, n) or make([]T, n, cap) → new Array(n).fill(zero)
|
|
514
|
+
// make(map[K]V) → {}
|
|
515
|
+
const typeArg = expr.args[0];
|
|
516
|
+
const typeNode = typeArg.kind === "TypeExpr" ? typeArg.type : typeArg;
|
|
517
|
+
const resolvedKind = typeArg._type?.kind;
|
|
518
|
+
if (typeNode.kind === "SliceType" || resolvedKind === "slice") {
|
|
519
|
+
const n = expr.args[1] ? this.genExpr(expr.args[1]) : "0";
|
|
520
|
+
// Use the proper zero value for the element type (e.g. new Point() not 0)
|
|
521
|
+
const elemNode = typeNode.kind === "SliceType" ? typeNode.elem : null;
|
|
522
|
+
const elemResolved =
|
|
523
|
+
typeArg._type?.kind === "slice" ? typeArg._type.elem : null;
|
|
524
|
+
let zero = "null";
|
|
525
|
+
if (elemResolved) {
|
|
526
|
+
zero = this.zeroValueForType(elemResolved);
|
|
527
|
+
} else if (elemNode) {
|
|
528
|
+
zero = this.zeroValueForTypeNode(elemNode);
|
|
529
|
+
}
|
|
530
|
+
// If zero value is a constructor call, use a factory function so each element is distinct
|
|
531
|
+
if (zero.startsWith("new ")) {
|
|
532
|
+
return `Array.from({length: ${n}}, () => ${zero})`;
|
|
533
|
+
}
|
|
534
|
+
return `new Array(${n}).fill(${zero})`;
|
|
535
|
+
}
|
|
536
|
+
// map or fallback
|
|
537
|
+
return "{}";
|
|
538
|
+
},
|
|
539
|
+
|
|
540
|
+
// Render KeyValueExpr elements as a JS field list string.
|
|
541
|
+
// Handles embedded-spread (_isEmbedInit) and plain key: value pairs.
|
|
542
|
+
_genStructFields(elems) {
|
|
543
|
+
return elems
|
|
544
|
+
.filter((e) => e.kind === "KeyValueExpr")
|
|
545
|
+
.map((e) =>
|
|
546
|
+
e._isEmbedInit
|
|
547
|
+
? `...${this.genExpr(e.value)}`
|
|
548
|
+
: `${e.key.name ?? this.genExpr(e.key)}: ${this.genExpr(e.value)}`,
|
|
549
|
+
)
|
|
550
|
+
.join(", ");
|
|
551
|
+
},
|
|
552
|
+
|
|
553
|
+
genCompositeLit(expr) {
|
|
554
|
+
const t = expr.typeExpr;
|
|
555
|
+
|
|
556
|
+
// Implicit composite literal: {X: 1} inside a slice/map — type inferred from context
|
|
557
|
+
if (t === null) {
|
|
558
|
+
if (expr.elems.length > 0 && expr.elems[0]?.kind === "KeyValueExpr") {
|
|
559
|
+
const typeName = expr._type?.name ?? expr._type?.underlying?.name;
|
|
560
|
+
if (typeName && this.structNames.has(typeName)) {
|
|
561
|
+
return `new ${typeName}({ ${this._genStructFields(expr.elems)} })`;
|
|
562
|
+
}
|
|
563
|
+
const fields = expr.elems
|
|
564
|
+
.map(
|
|
565
|
+
(e) =>
|
|
566
|
+
`${e.key.name ?? this.genExpr(e.key)}: ${this.genExpr(e.value)}`,
|
|
567
|
+
)
|
|
568
|
+
.join(", ");
|
|
569
|
+
return `{ ${fields} }`;
|
|
570
|
+
}
|
|
571
|
+
return `[${expr.elems.map((e) => this.genExpr(e)).join(", ")}]`;
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
const typeName = this.getTypeName(t);
|
|
575
|
+
|
|
576
|
+
// Struct: new Foo({ X: 1, Y: 2 })
|
|
577
|
+
if (typeName && this.structNames.has(typeName)) {
|
|
578
|
+
return `new ${typeName}({ ${this._genStructFields(expr.elems)} })`;
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
// Slice/array: [1, 2, 3]
|
|
582
|
+
if (t?.kind === "SliceType" || t?.kind === "ArrayType") {
|
|
583
|
+
const elems = expr.elems
|
|
584
|
+
.map((e) =>
|
|
585
|
+
e.kind === "KeyValueExpr" ? this.genExpr(e.value) : this.genExpr(e),
|
|
586
|
+
)
|
|
587
|
+
.join(", ");
|
|
588
|
+
return `[${elems}]`;
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
// Map: { key: value }
|
|
592
|
+
if (t?.kind === "MapType") {
|
|
593
|
+
const entries = expr.elems
|
|
594
|
+
.map((e) => {
|
|
595
|
+
if (e.kind === "KeyValueExpr") {
|
|
596
|
+
const k =
|
|
597
|
+
e.key.litKind === "STRING"
|
|
598
|
+
? JSON.stringify(e.key.value)
|
|
599
|
+
: `[${this.genExpr(e.key)}]`;
|
|
600
|
+
return `${k}: ${this.genExpr(e.value)}`;
|
|
601
|
+
}
|
|
602
|
+
return this.genExpr(e);
|
|
603
|
+
})
|
|
604
|
+
.join(", ");
|
|
605
|
+
return `{ ${entries} }`;
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
// Fallback: key-value pairs → plain object, positional → array
|
|
609
|
+
if (expr.elems.length > 0 && expr.elems[0]?.kind === "KeyValueExpr") {
|
|
610
|
+
const fields = expr.elems
|
|
611
|
+
.map(
|
|
612
|
+
(e) =>
|
|
613
|
+
`${e.key.name ?? this.genExpr(e.key)}: ${this.genExpr(e.value)}`,
|
|
614
|
+
)
|
|
615
|
+
.join(", ");
|
|
616
|
+
return `{ ${fields} }`;
|
|
617
|
+
}
|
|
618
|
+
return `[${expr.elems.map((e) => this.genExpr(e)).join(", ")}]`;
|
|
619
|
+
},
|
|
620
|
+
|
|
621
|
+
// ── Helpers ───────────────────────────────────────────────────
|
|
622
|
+
|
|
623
|
+
getTypeName(typeNode) {
|
|
624
|
+
if (!typeNode) return null;
|
|
625
|
+
if (typeNode.kind === "TypeName") return typeNode.name;
|
|
626
|
+
if (typeNode.kind === "Ident") return typeNode.name;
|
|
627
|
+
if (typeNode.kind === "SelectorExpr") return typeNode.field;
|
|
628
|
+
return null;
|
|
629
|
+
},
|
|
630
|
+
|
|
631
|
+
isIntType(t) {
|
|
632
|
+
if (!t) return false;
|
|
633
|
+
if (t.kind === "untyped") return t.base === "int";
|
|
634
|
+
const base = t.kind === "named" ? t.underlying : t;
|
|
635
|
+
return base?.kind === "basic" && INT_TYPE_NAMES.has(base.name);
|
|
636
|
+
},
|
|
637
|
+
|
|
638
|
+
// Returns true if the AST node contains a function call (side-effect risk).
|
|
639
|
+
_hasCallExpr(node) {
|
|
640
|
+
if (!node || typeof node !== "object") return false;
|
|
641
|
+
if (node.kind === "CallExpr") return true;
|
|
642
|
+
for (const v of Object.values(node)) {
|
|
643
|
+
if (v && typeof v === "object" && this._hasCallExpr(v)) return true;
|
|
644
|
+
}
|
|
645
|
+
return false;
|
|
646
|
+
},
|
|
647
|
+
|
|
648
|
+
// Returns the JS zero-value literal for a basic type name, or null if not a basic type.
|
|
649
|
+
_zeroForBasicName(name) {
|
|
650
|
+
switch (name) {
|
|
651
|
+
case "int":
|
|
652
|
+
case "uint":
|
|
653
|
+
case "int8":
|
|
654
|
+
case "int16":
|
|
655
|
+
case "int32":
|
|
656
|
+
case "int64":
|
|
657
|
+
case "uint8":
|
|
658
|
+
case "uint16":
|
|
659
|
+
case "uint32":
|
|
660
|
+
case "uint64":
|
|
661
|
+
case "uintptr":
|
|
662
|
+
case "float32":
|
|
663
|
+
case "float64":
|
|
664
|
+
case "byte":
|
|
665
|
+
case "rune":
|
|
666
|
+
return "0";
|
|
667
|
+
case "string":
|
|
668
|
+
return '""';
|
|
669
|
+
case "bool":
|
|
670
|
+
return "false";
|
|
671
|
+
default:
|
|
672
|
+
return null;
|
|
673
|
+
}
|
|
674
|
+
},
|
|
675
|
+
|
|
676
|
+
zeroValueForTypeNode(typeNode) {
|
|
677
|
+
if (!typeNode) return "null";
|
|
678
|
+
switch (typeNode.kind) {
|
|
679
|
+
case "TypeName": {
|
|
680
|
+
const basic = this._zeroForBasicName(typeNode.name);
|
|
681
|
+
if (basic !== null) return basic;
|
|
682
|
+
if (this.structNames.has(typeNode.name))
|
|
683
|
+
return `new ${typeNode.name}()`;
|
|
684
|
+
return "null";
|
|
685
|
+
}
|
|
686
|
+
case "SliceType":
|
|
687
|
+
return "null";
|
|
688
|
+
case "ArrayType":
|
|
689
|
+
return "[]";
|
|
690
|
+
case "MapType":
|
|
691
|
+
return "{}";
|
|
692
|
+
case "PointerType":
|
|
693
|
+
return "null";
|
|
694
|
+
case "StructType": {
|
|
695
|
+
const fields = typeNode.fields
|
|
696
|
+
.filter((f) => !f.embedded && f.names.length > 0)
|
|
697
|
+
.map(
|
|
698
|
+
(f) =>
|
|
699
|
+
`${f.names.map((n) => `${n}: ${this.zeroValueForTypeNode(f.type)}`).join(", ")}`,
|
|
700
|
+
)
|
|
701
|
+
.join(", ");
|
|
702
|
+
return `{ ${fields} }`;
|
|
703
|
+
}
|
|
704
|
+
default:
|
|
705
|
+
return "null";
|
|
706
|
+
}
|
|
707
|
+
},
|
|
708
|
+
|
|
709
|
+
// zeroValueForType operates on typechecker type objects (not AST type nodes).
|
|
710
|
+
zeroValueForType(t) {
|
|
711
|
+
if (!t) return "null";
|
|
712
|
+
switch (t.kind) {
|
|
713
|
+
case "basic": {
|
|
714
|
+
const basic = this._zeroForBasicName(t.name);
|
|
715
|
+
return basic !== null ? basic : "null";
|
|
716
|
+
}
|
|
717
|
+
case "slice":
|
|
718
|
+
return "null";
|
|
719
|
+
case "map":
|
|
720
|
+
return "{}";
|
|
721
|
+
case "struct": {
|
|
722
|
+
const fields = [...t.fields.entries()]
|
|
723
|
+
.map(([name, ft]) => `${name}: ${this.zeroValueForType(ft)}`)
|
|
724
|
+
.join(", ");
|
|
725
|
+
return `{ ${fields} }`;
|
|
726
|
+
}
|
|
727
|
+
case "named":
|
|
728
|
+
if (this.structNames.has(t.name)) return `new ${t.name}()`;
|
|
729
|
+
return "null";
|
|
730
|
+
default:
|
|
731
|
+
return "null";
|
|
732
|
+
}
|
|
733
|
+
},
|
|
734
|
+
|
|
735
|
+
// Emit a JS boolean expression that checks whether `val` matches type node `t`.
|
|
736
|
+
_typeCheckExpr(typeNode, val) {
|
|
737
|
+
if (!typeNode) return "true";
|
|
738
|
+
if (typeNode.kind === "TypeName") {
|
|
739
|
+
switch (typeNode.name) {
|
|
740
|
+
case "int":
|
|
741
|
+
case "uint":
|
|
742
|
+
case "int8":
|
|
743
|
+
case "int16":
|
|
744
|
+
case "int32":
|
|
745
|
+
case "int64":
|
|
746
|
+
case "uint8":
|
|
747
|
+
case "uint16":
|
|
748
|
+
case "uint32":
|
|
749
|
+
case "uint64":
|
|
750
|
+
case "uintptr":
|
|
751
|
+
case "float32":
|
|
752
|
+
case "float64":
|
|
753
|
+
case "byte":
|
|
754
|
+
case "rune":
|
|
755
|
+
return `typeof ${val} === "number"`;
|
|
756
|
+
case "string":
|
|
757
|
+
return `typeof ${val} === "string"`;
|
|
758
|
+
case "bool":
|
|
759
|
+
return `typeof ${val} === "boolean"`;
|
|
760
|
+
case "nil":
|
|
761
|
+
return `${val} === null`;
|
|
762
|
+
case "error":
|
|
763
|
+
return `typeof ${val} === "string"`;
|
|
764
|
+
default:
|
|
765
|
+
if (this.structNames.has(typeNode.name))
|
|
766
|
+
return `${val} instanceof ${typeNode.name}`;
|
|
767
|
+
return "true"; // unknown type — can't check at runtime
|
|
768
|
+
}
|
|
769
|
+
}
|
|
770
|
+
return "true";
|
|
771
|
+
},
|
|
772
|
+
|
|
773
|
+
zeroValueForExpr(expr) {
|
|
774
|
+
// For new(T) calls
|
|
775
|
+
if (expr.kind === "Ident") {
|
|
776
|
+
switch (expr.name) {
|
|
777
|
+
case "int":
|
|
778
|
+
case "float64":
|
|
779
|
+
return "0";
|
|
780
|
+
case "string":
|
|
781
|
+
return '""';
|
|
782
|
+
case "bool":
|
|
783
|
+
return "false";
|
|
784
|
+
default:
|
|
785
|
+
if (this.structNames.has(expr.name)) return `new ${expr.name}()`;
|
|
786
|
+
}
|
|
787
|
+
}
|
|
788
|
+
return "null";
|
|
789
|
+
},
|
|
790
|
+
|
|
791
|
+
typeComment(typeNode) {
|
|
792
|
+
if (!typeNode) return "unknown";
|
|
793
|
+
switch (typeNode.kind) {
|
|
794
|
+
case "TypeName":
|
|
795
|
+
return typeNode.name;
|
|
796
|
+
case "SliceType":
|
|
797
|
+
return `[]${this.typeComment(typeNode.elem)}`;
|
|
798
|
+
case "MapType":
|
|
799
|
+
return `map[${this.typeComment(typeNode.key)}]${this.typeComment(typeNode.value)}`;
|
|
800
|
+
default:
|
|
801
|
+
return typeNode.kind;
|
|
802
|
+
}
|
|
803
|
+
},
|
|
804
|
+
};
|