gofront 1.4.0 → 1.6.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.
- package/README.md +174 -403
- package/package.json +16 -19
- package/src/{codegen → backend/js}/expressions.js +265 -77
- package/src/{codegen → backend/js}/index.js +120 -89
- package/src/{codegen → backend/js}/runtime.js +102 -0
- package/src/{codegen → backend/js}/source-map.js +32 -0
- package/src/{codegen → backend/js}/statements.js +139 -64
- package/src/backend/js/stdlib/bits.js +32 -0
- package/src/{codegen → backend/js}/stdlib/errors.js +2 -2
- package/src/{codegen → backend/js}/stdlib/fmt.js +26 -8
- package/src/{codegen → backend/js}/stdlib/index.js +6 -0
- package/src/{codegen → backend/js}/stdlib/maps.js +1 -1
- package/src/{codegen → backend/js}/stdlib/path.js +2 -2
- package/src/backend/js/stdlib/shared.js +17 -0
- package/src/{codegen → backend/js}/stdlib/slices.js +1 -1
- package/src/{codegen → backend/js}/stdlib/sort.js +1 -1
- package/src/backend/js/stdlib/strconv.js +58 -0
- package/src/{codegen → backend/js}/stdlib/strings.js +4 -1
- package/src/{codegen → backend/js}/stdlib/testing.js +2 -2
- package/src/{codegen → backend/js}/stdlib/time.js +3 -3
- package/src/{codegen → backend/js}/templ.js +3 -3
- package/src/backend/wasm/boundary.js +1798 -0
- package/src/backend/wasm/emit-builtins.js +1103 -0
- package/src/backend/wasm/emit-exprs.js +1908 -0
- package/src/backend/wasm/emit-maps.js +1863 -0
- package/src/backend/wasm/emit-shared.js +519 -0
- package/src/backend/wasm/emit-stdlib.js +3006 -0
- package/src/backend/wasm/emit-stmts.js +2472 -0
- package/src/backend/wasm/emit.js +1376 -0
- package/src/backend/wasm/encode.js +937 -0
- package/src/backend/wasm/glue.js +144 -0
- package/src/backend/wasm/index.js +2790 -0
- package/src/backend/wasm/monomorph.js +711 -0
- package/src/backend/wasm/optimize.js +227 -0
- package/src/backend/wasm/types.js +544 -0
- package/src/backend/wasm/wat.js +302 -0
- package/src/cli-core.js +323 -75
- package/src/compiler.js +447 -14
- package/src/dev-server.js +56 -15
- package/src/index.js +99 -120
- package/src/lexer.js +18 -0
- package/src/lower/boxing.js +35 -0
- package/src/lower/captures.js +172 -0
- package/src/lower/embedding.js +30 -0
- package/src/lower/escape.js +164 -0
- package/src/lower/functions.js +302 -0
- package/src/lower/globals.js +316 -0
- package/src/lower/index.js +118 -0
- package/src/lower/ownership.js +137 -0
- package/src/lower/range.js +67 -0
- package/src/lower/walk.js +25 -0
- package/src/minifier.js +2 -0
- package/src/parser/index.js +8 -3
- package/src/resolver.js +3 -1
- package/src/test-runner.js +94 -20
- package/src/typechecker/assignability.js +42 -1
- package/src/typechecker/expressions.js +182 -3
- package/src/typechecker/index.js +261 -5
- package/src/typechecker/resolve.js +35 -6
- package/src/typechecker/stdlib/core.js +34 -19
- package/src/typechecker/stdlib/extended.js +18 -3
- package/src/typechecker/stdlib/shared.js +53 -0
- package/src/typechecker/stdlib.js +2 -0
- package/src/typechecker/types.js +259 -6
- package/src/vendor.js +2 -2
- package/src/codegen/stdlib/strconv.js +0 -33
- /package/src/{codegen → backend/js}/stdlib/builder.js +0 -0
- /package/src/{codegen → backend/js}/stdlib/bytes.js +0 -0
- /package/src/{codegen → backend/js}/stdlib/gom.js +0 -0
- /package/src/{codegen → backend/js}/stdlib/html.js +0 -0
- /package/src/{codegen → backend/js}/stdlib/io.js +0 -0
- /package/src/{codegen → backend/js}/stdlib/math.js +0 -0
- /package/src/{codegen → backend/js}/stdlib/os.js +0 -0
- /package/src/{codegen → backend/js}/stdlib/rand.js +0 -0
- /package/src/{codegen → backend/js}/stdlib/regexp.js +0 -0
- /package/src/{codegen → backend/js}/stdlib/unicode.js +0 -0
- /package/src/{codegen → backend/js}/stdlib/utf8.js +0 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gofront",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.6.0",
|
|
4
4
|
"author": "Luuk van Venrooij",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -11,12 +11,15 @@
|
|
|
11
11
|
"url": "https://github.com/seriva/gofront/issues"
|
|
12
12
|
},
|
|
13
13
|
"homepage": "https://github.com/seriva/gofront#readme",
|
|
14
|
-
"description": "A Go-inspired language that
|
|
14
|
+
"description": "A Go-inspired language that compiles to JavaScript and WebAssembly",
|
|
15
15
|
"keywords": [
|
|
16
16
|
"go",
|
|
17
17
|
"compiler",
|
|
18
18
|
"transpiler",
|
|
19
19
|
"javascript",
|
|
20
|
+
"webassembly",
|
|
21
|
+
"wasm",
|
|
22
|
+
"wasmgc",
|
|
20
23
|
"frontend",
|
|
21
24
|
"type-safe"
|
|
22
25
|
],
|
|
@@ -31,30 +34,24 @@
|
|
|
31
34
|
"gofront": "./src/index.js"
|
|
32
35
|
},
|
|
33
36
|
"scripts": {
|
|
34
|
-
"build
|
|
35
|
-
"
|
|
36
|
-
"
|
|
37
|
-
"build:templ": "node src/index.js example/templ/src -o example/templ/app.js",
|
|
38
|
-
"build:webgl": "node src/index.js example/webgl/src -o example/webgl/app.js",
|
|
39
|
-
"build:all": "npm run build:simple && npm run build:reactive && npm run build:gom && npm run build:templ && npm run build:webgl",
|
|
40
|
-
"test": "npm run test:unit",
|
|
41
|
-
"test:all": "npm run test:unit && npm run test:perf && npm run test:examples && npm run test:examples:dom && npm run test:e2e",
|
|
42
|
-
"test:unit": "node test/unit/run.js",
|
|
43
|
-
"test:perf": "node test/e2e/perf/zero-alloc.js",
|
|
44
|
-
"test:examples": "node src/index.js test example/simple/src && node src/index.js test example/simple/src/utils && node src/index.js test example/reactive/src && node src/index.js test example/reactive/src/utils && node src/index.js test example/gom/src && node src/index.js test example/gom/src/utils && node src/index.js test example/templ/src && node src/index.js test example/templ/src/utils",
|
|
45
|
-
"test:examples:dom": "node src/index.js test --dom example/simple/src && node src/index.js test --dom example/gom/src && node src/index.js test --dom example/templ/src",
|
|
37
|
+
"build": "node src/index.js example/simple/src -o example/simple/app.js && node src/index.js example/reactive/src -o example/reactive/app.js && node src/index.js example/gom/src -o example/gom/app.js && node src/index.js example/templ/src -o example/templ/app.js && node src/index.js example/webgl/src -o example/webgl/app.js && node src/index.js example/hybrid/src -o example/hybrid/app.js",
|
|
38
|
+
"test": "node test/unit/run.js && node test/e2e/perf/zero-alloc.js && node src/index.js test --dom example/...",
|
|
39
|
+
"test:e2e": "playwright test",
|
|
46
40
|
"test:coverage": "c8 --include='src/**' --reporter=text --reporter=lcov node test/unit/run.js",
|
|
47
|
-
"
|
|
41
|
+
"bench": "node --expose-gc test/e2e/perf/raycast-bench.js",
|
|
48
42
|
"format": "biome format --write .",
|
|
49
|
-
"check": "biome check . && sentrux check && sentrux gate && node src/index.js
|
|
43
|
+
"check": "biome check . && sentrux check && sentrux gate && node src/index.js check example/...",
|
|
50
44
|
"prepare": "lefthook install"
|
|
51
45
|
},
|
|
52
46
|
"devDependencies": {
|
|
53
|
-
"@biomejs/biome": "^2.5.
|
|
47
|
+
"@biomejs/biome": "^2.5.15",
|
|
54
48
|
"@playwright/test": "^1.63.0",
|
|
55
49
|
"c8": "^12.0.0",
|
|
56
|
-
"jsdom": "^30.1.
|
|
57
|
-
"lefthook": "^2.1.
|
|
50
|
+
"jsdom": "^30.1.2",
|
|
51
|
+
"lefthook": "^2.1.17",
|
|
58
52
|
"serve": "^14.2.6"
|
|
53
|
+
},
|
|
54
|
+
"optionalDependencies": {
|
|
55
|
+
"binaryen": "^132.0.0"
|
|
59
56
|
}
|
|
60
57
|
}
|
|
@@ -1,12 +1,78 @@
|
|
|
1
|
+
import {
|
|
2
|
+
closureMutates,
|
|
3
|
+
fnMutates,
|
|
4
|
+
nodeMutatesVar,
|
|
5
|
+
nodeWritesVar,
|
|
6
|
+
rootIdentName,
|
|
7
|
+
} from "../../lower/index.js";
|
|
1
8
|
import {
|
|
2
9
|
ERROR,
|
|
3
10
|
isComplex,
|
|
4
11
|
isTypedArraySlice,
|
|
5
12
|
typedArrayConstructorForElem,
|
|
6
|
-
} from "
|
|
13
|
+
} from "../../typechecker/types.js";
|
|
7
14
|
|
|
8
15
|
/** @typedef {import('./index.js').CodeGen} CodeGen */
|
|
9
16
|
|
|
17
|
+
const STRICT_NUMERIC_TYPES = new Set([
|
|
18
|
+
"float32",
|
|
19
|
+
"int8",
|
|
20
|
+
"int16",
|
|
21
|
+
"int32",
|
|
22
|
+
"rune",
|
|
23
|
+
"uint8",
|
|
24
|
+
"byte",
|
|
25
|
+
"uint16",
|
|
26
|
+
"uint32",
|
|
27
|
+
]);
|
|
28
|
+
|
|
29
|
+
const STRICT_OPS = new Set([
|
|
30
|
+
"+",
|
|
31
|
+
"-",
|
|
32
|
+
"*",
|
|
33
|
+
"/",
|
|
34
|
+
"%",
|
|
35
|
+
"&",
|
|
36
|
+
"|",
|
|
37
|
+
"^",
|
|
38
|
+
"&^",
|
|
39
|
+
"<<",
|
|
40
|
+
">>",
|
|
41
|
+
]);
|
|
42
|
+
|
|
43
|
+
// Splits a generated JS lvalue at its outermost trailing access:
|
|
44
|
+
// `a.b[i]` → { obj: "a.b", key: "i" }, `a.b.c` → { obj: "a.b", prop: "c" }.
|
|
45
|
+
function splitLastAccess(js) {
|
|
46
|
+
if (js.endsWith("]")) {
|
|
47
|
+
let depth = 0;
|
|
48
|
+
let quote = null;
|
|
49
|
+
for (let i = js.length - 1; i >= 0; i--) {
|
|
50
|
+
const ch = js[i];
|
|
51
|
+
if (quote) {
|
|
52
|
+
if (ch === quote && js[i - 1] !== "\\") quote = null;
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
if (ch === '"' || ch === "'" || ch === "`") quote = ch;
|
|
56
|
+
else if (ch === "]" || ch === ")") depth++;
|
|
57
|
+
else if (ch === "[" || ch === "(") {
|
|
58
|
+
depth--;
|
|
59
|
+
if (depth === 0 && ch === "[" && i > 0)
|
|
60
|
+
return { obj: js.slice(0, i), key: js.slice(i + 1, -1) };
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
const m = /^(.*[^.])\.([A-Za-z_$][\w$]*)$/s.exec(js);
|
|
66
|
+
if (!m) return null;
|
|
67
|
+
// Only split when the dot is at nesting depth 0, e.g. not inside `f(a.b)`.
|
|
68
|
+
let depth = 0;
|
|
69
|
+
for (const ch of m[1]) {
|
|
70
|
+
if (ch === "(" || ch === "[") depth++;
|
|
71
|
+
else if (ch === ")" || ch === "]") depth--;
|
|
72
|
+
}
|
|
73
|
+
return depth === 0 ? { obj: m[1], prop: m[2] } : null;
|
|
74
|
+
}
|
|
75
|
+
|
|
10
76
|
// Namespace constants: pkg.Field → JS literal
|
|
11
77
|
const NS_CONSTANTS = {
|
|
12
78
|
math: {
|
|
@@ -144,7 +210,7 @@ const TYPEOF_NUMBER_NAMES = new Set([...INT_TYPE_NAMES, "float32", "float64"]);
|
|
|
144
210
|
const BUILTIN_GEN = {
|
|
145
211
|
append: (s, e) => s.genAppend(e),
|
|
146
212
|
len: (s, e) => s._genBuiltinLen(e),
|
|
147
|
-
cap: (s, e) =>
|
|
213
|
+
cap: (s, e) => `(${s.genExpr(e.args[0])}?.length ?? 0)`,
|
|
148
214
|
make: (s, e) => s.genMake(e),
|
|
149
215
|
delete: (s, e) => {
|
|
150
216
|
const [m, k] = e.args.map((a) => s.genExpr(a));
|
|
@@ -167,14 +233,19 @@ const BUILTIN_GEN = {
|
|
|
167
233
|
}
|
|
168
234
|
return `{ value: ${s.zeroValueForExpr(arg)} }`;
|
|
169
235
|
},
|
|
170
|
-
print
|
|
236
|
+
// `print` writes without a trailing newline (Go semantics); the wasm glue
|
|
237
|
+
// buffers the same way, so output parity holds across backends.
|
|
238
|
+
print: (s, e) =>
|
|
239
|
+
`((__s) => (typeof process !== "undefined" && process?.stdout?.write ? process.stdout.write(__s) : console.log(__s)))([${e.args.map((a) => s.genExpr(a)).join(", ")}].map(String).join(" "))`,
|
|
171
240
|
println: (s, e) =>
|
|
172
241
|
`console.log(${e.args.map((a) => s.genExpr(a)).join(", ")})`,
|
|
173
242
|
panic: (s, e) => `(() => { throw new Error(${s.genExpr(e.args[0])}); })()`,
|
|
174
|
-
recover: () =>
|
|
175
|
-
|
|
243
|
+
recover: (s) => {
|
|
244
|
+
s.useHelper("panicRt");
|
|
245
|
+
return `__recover(${s._recoverOkInScope ? "__recoverOk" : "false"})`;
|
|
246
|
+
},
|
|
176
247
|
error: (s, e) => {
|
|
177
|
-
s.
|
|
248
|
+
s.useHelper("error");
|
|
178
249
|
return `__error(${s.genExpr(e.args[0])})`;
|
|
179
250
|
},
|
|
180
251
|
min: (s, e) => `Math.min(${e.args.map((a) => s.genExpr(a)).join(", ")})`,
|
|
@@ -206,7 +277,7 @@ export const expressionGenMethods = {
|
|
|
206
277
|
if (expr._ifaceBox && !expr._ifaceBoxing) {
|
|
207
278
|
expr._ifaceBoxing = true;
|
|
208
279
|
try {
|
|
209
|
-
this.
|
|
280
|
+
this.useHelper("ifaceBox");
|
|
210
281
|
const fn = expr._ifaceBox === "ptr" ? "__ifp" : "__ifv";
|
|
211
282
|
return `${fn}(${this.genExpr(expr)})`;
|
|
212
283
|
} finally {
|
|
@@ -299,7 +370,7 @@ export const expressionGenMethods = {
|
|
|
299
370
|
if (base.kind === "struct") {
|
|
300
371
|
if (this._structClassName(t) || this._structClassName(base))
|
|
301
372
|
return `${js}.__clone()`;
|
|
302
|
-
this.
|
|
373
|
+
this.useHelper("sclone");
|
|
303
374
|
return `__sclone(${js})`;
|
|
304
375
|
}
|
|
305
376
|
const elemClone = this._cloneJs(base.elem, "__e");
|
|
@@ -335,60 +406,21 @@ export const expressionGenMethods = {
|
|
|
335
406
|
},
|
|
336
407
|
|
|
337
408
|
_rootIdentName(e) {
|
|
338
|
-
|
|
339
|
-
if (e.kind === "Ident") return e.name;
|
|
340
|
-
if (e.kind === "SelectorExpr" || e.kind === "IndexExpr") e = e.expr;
|
|
341
|
-
else if (e.kind === "UnaryExpr" && e.op === "*") e = e.operand;
|
|
342
|
-
else return null;
|
|
343
|
-
}
|
|
344
|
-
return null;
|
|
409
|
+
return rootIdentName(e);
|
|
345
410
|
},
|
|
346
411
|
|
|
347
412
|
// True when `node` itself (not its children) writes to variable `name`.
|
|
348
413
|
_nodeWritesVar(node, name) {
|
|
349
|
-
|
|
350
|
-
switch (node.kind) {
|
|
351
|
-
case "AssignStmt":
|
|
352
|
-
return node.lhs.some(root);
|
|
353
|
-
case "DefineStmt":
|
|
354
|
-
return node.lhs.some((e) => e._redecl && e.name === name);
|
|
355
|
-
case "IncDecStmt":
|
|
356
|
-
return root(node.expr);
|
|
357
|
-
case "SelectorExpr":
|
|
358
|
-
return Boolean(
|
|
359
|
-
node._isMethodValue && node._type?._ptrRecv && root(node.expr),
|
|
360
|
-
);
|
|
361
|
-
default:
|
|
362
|
-
return false;
|
|
363
|
-
}
|
|
414
|
+
return nodeWritesVar(node, name);
|
|
364
415
|
},
|
|
365
416
|
|
|
366
417
|
// True when `node` may modify (or take the address of) the value held by variable `name`.
|
|
367
418
|
_nodeMutatesVar(node, name, addrOnly = false) {
|
|
368
|
-
|
|
369
|
-
if (Array.isArray(node))
|
|
370
|
-
return node.some((n) => this._nodeMutatesVar(n, name, addrOnly));
|
|
371
|
-
if (
|
|
372
|
-
node.kind === "UnaryExpr" &&
|
|
373
|
-
node.op === "&" &&
|
|
374
|
-
this._rootIdentName(node.operand) === name
|
|
375
|
-
)
|
|
376
|
-
return true;
|
|
377
|
-
if (!addrOnly && this._nodeWritesVar(node, name)) return true;
|
|
378
|
-
for (const key of Object.keys(node)) {
|
|
379
|
-
if (key.startsWith("_")) continue;
|
|
380
|
-
if (this._nodeMutatesVar(node[key], name, addrOnly)) return true;
|
|
381
|
-
}
|
|
382
|
-
return false;
|
|
419
|
+
return nodeMutatesVar(node, name, addrOnly);
|
|
383
420
|
},
|
|
384
421
|
|
|
385
422
|
_fnMutates(name, addrOnly = false) {
|
|
386
|
-
|
|
387
|
-
if (!ctx) return true;
|
|
388
|
-
const key = `${addrOnly ? "&" : ""}${name}`;
|
|
389
|
-
if (!ctx.mut.has(key))
|
|
390
|
-
ctx.mut.set(key, this._nodeMutatesVar(ctx.body, name, addrOnly));
|
|
391
|
-
return ctx.mut.get(key);
|
|
423
|
+
return fnMutates(this._fnCtx?.body, name, addrOnly, this._fnCtx?.mut);
|
|
392
424
|
},
|
|
393
425
|
|
|
394
426
|
_withFnCtx(body, fn) {
|
|
@@ -447,21 +479,7 @@ export const expressionGenMethods = {
|
|
|
447
479
|
},
|
|
448
480
|
|
|
449
481
|
_closureMutates(name) {
|
|
450
|
-
|
|
451
|
-
const key = `λ${name}`;
|
|
452
|
-
if (!ctx.mut.has(key)) {
|
|
453
|
-
const walk = (node) => {
|
|
454
|
-
if (!node || typeof node !== "object") return false;
|
|
455
|
-
if (Array.isArray(node)) return node.some(walk);
|
|
456
|
-
if (node.kind === "FuncLit")
|
|
457
|
-
return this._nodeMutatesVar(node.body, name);
|
|
458
|
-
return Object.keys(node).some(
|
|
459
|
-
(k) => !k.startsWith("_") && walk(node[k]),
|
|
460
|
-
);
|
|
461
|
-
};
|
|
462
|
-
ctx.mut.set(key, walk(ctx.body));
|
|
463
|
-
}
|
|
464
|
-
return ctx.mut.get(key);
|
|
482
|
+
return closureMutates(this._fnCtx?.body, name, this._fnCtx?.mut);
|
|
465
483
|
},
|
|
466
484
|
|
|
467
485
|
_genUnaryExpr(expr) {
|
|
@@ -600,7 +618,7 @@ export const expressionGenMethods = {
|
|
|
600
618
|
this._withFnCtx(expr.body, () =>
|
|
601
619
|
this.indented(() => {
|
|
602
620
|
this._emitParamCopies(expr.params);
|
|
603
|
-
this._genBody(expr.body);
|
|
621
|
+
this._genBody(expr.body, expr);
|
|
604
622
|
}),
|
|
605
623
|
);
|
|
606
624
|
const body = this.out.join("\n");
|
|
@@ -689,12 +707,20 @@ export const expressionGenMethods = {
|
|
|
689
707
|
return this._genTimeMethodCall(expr.func.field, expr);
|
|
690
708
|
if (recvName === "testing.T")
|
|
691
709
|
return this._genTestingMethodCall(expr.func.field, expr);
|
|
710
|
+
if (recvName?.startsWith("shared.") && expr.func.field === "Subarray") {
|
|
711
|
+
const args = expr.args.map((a) => this.genExpr(a)).join(", ");
|
|
712
|
+
return `${this.genExpr(expr.func.expr)}.subarray(${args})`;
|
|
713
|
+
}
|
|
692
714
|
return undefined;
|
|
693
715
|
},
|
|
694
716
|
|
|
695
717
|
_genSelectorCall(expr) {
|
|
696
|
-
if (this._isErrorMethodCall(expr))
|
|
697
|
-
|
|
718
|
+
if (this._isErrorMethodCall(expr)) {
|
|
719
|
+
const recv = this.genExpr(expr.func.expr);
|
|
720
|
+
if (!this.strict) return `${recv}.Error()`;
|
|
721
|
+
this.useHelper("nilCall");
|
|
722
|
+
return `__nilcall(${recv}).Error()`;
|
|
723
|
+
}
|
|
698
724
|
if (expr.func.expr.kind === "Ident") {
|
|
699
725
|
const ns = expr.func.expr.name;
|
|
700
726
|
const fn = expr.func.field;
|
|
@@ -703,7 +729,25 @@ export const expressionGenMethods = {
|
|
|
703
729
|
}
|
|
704
730
|
const recvType = expr.func.expr._type;
|
|
705
731
|
const recvName = this._resolveRecvName(recvType);
|
|
706
|
-
|
|
732
|
+
const typed = this._genReceiverTypeCall(recvName, expr);
|
|
733
|
+
if (typed !== undefined) return typed;
|
|
734
|
+
if (this.strict && this._isInterfaceType(recvType)) {
|
|
735
|
+
// Go panics with a nil-dereference runtime error; JS would throw a
|
|
736
|
+
// TypeError with a different message.
|
|
737
|
+
this.useHelper("nilCall");
|
|
738
|
+
const recv = this.genExpr(expr.func.expr);
|
|
739
|
+
const args = expr.args
|
|
740
|
+
.map((a) => (a._spread ? `...${this.genExpr(a)}` : this.genExpr(a)))
|
|
741
|
+
.join(", ");
|
|
742
|
+
return `__nilcall(${recv}).${expr.func.field}(${args})`;
|
|
743
|
+
}
|
|
744
|
+
return undefined;
|
|
745
|
+
},
|
|
746
|
+
|
|
747
|
+
_isInterfaceType(t) {
|
|
748
|
+
if (!t) return false;
|
|
749
|
+
if (t.kind === "interface") return true;
|
|
750
|
+
return t.kind === "named" && t.underlying?.kind === "interface";
|
|
707
751
|
},
|
|
708
752
|
|
|
709
753
|
_genBuiltinLen(expr) {
|
|
@@ -714,7 +758,7 @@ export const expressionGenMethods = {
|
|
|
714
758
|
if (wrapField) return `${this.genExpr(arg)}.${wrapField}.length`;
|
|
715
759
|
const js = this.genExpr(arg);
|
|
716
760
|
if (t?.kind === "map") return `Object.keys(${js}).length`;
|
|
717
|
-
this.
|
|
761
|
+
this.useHelper("len");
|
|
718
762
|
return `__len(${js})`;
|
|
719
763
|
},
|
|
720
764
|
|
|
@@ -747,7 +791,7 @@ export const expressionGenMethods = {
|
|
|
747
791
|
a._spread ? `...${this.genExpr(a)}` : this.genValueExpr(a),
|
|
748
792
|
);
|
|
749
793
|
if (elems.length === 0) return this.genExpr(expr.args[0]);
|
|
750
|
-
this.
|
|
794
|
+
this.useHelper("append");
|
|
751
795
|
return `new ${retType.name}(__append(${sliceJS}, ${elems.join(", ")}))`;
|
|
752
796
|
}
|
|
753
797
|
const slice = this.genExpr(expr.args[0]);
|
|
@@ -755,7 +799,7 @@ export const expressionGenMethods = {
|
|
|
755
799
|
.slice(1)
|
|
756
800
|
.map((a) => (a._spread ? `...${this.genExpr(a)}` : this.genValueExpr(a)));
|
|
757
801
|
if (elems.length === 0) return slice;
|
|
758
|
-
this.
|
|
802
|
+
this.useHelper("append");
|
|
759
803
|
const sliceType = expr._type ?? expr.args[0]._type;
|
|
760
804
|
const typedCtor = isTypedArraySlice(sliceType)
|
|
761
805
|
? typedArrayConstructorForElem(
|
|
@@ -1227,10 +1271,10 @@ export const expressionGenMethods = {
|
|
|
1227
1271
|
case "-":
|
|
1228
1272
|
return `{ re: ${l}.re - ${r}.re, im: ${l}.im - ${r}.im }`;
|
|
1229
1273
|
case "*":
|
|
1230
|
-
this.
|
|
1274
|
+
this.useHelper("cmul");
|
|
1231
1275
|
return `__cmul(${l}, ${r})`;
|
|
1232
1276
|
case "/":
|
|
1233
|
-
this.
|
|
1277
|
+
this.useHelper("cdiv");
|
|
1234
1278
|
return `__cdiv(${l}, ${r})`;
|
|
1235
1279
|
case "==":
|
|
1236
1280
|
return `(${l}.re === ${r}.re && ${l}.im === ${r}.im)`;
|
|
@@ -1248,11 +1292,17 @@ export const expressionGenMethods = {
|
|
|
1248
1292
|
return this._genComplexBinary(expr);
|
|
1249
1293
|
|
|
1250
1294
|
if (this._isStructEquality(expr)) {
|
|
1251
|
-
this.
|
|
1295
|
+
this.useHelper("equal");
|
|
1252
1296
|
const cmp = `__equal(${this.genExpr(expr.left)}, ${this.genExpr(expr.right)})`;
|
|
1253
1297
|
return expr.op === "==" ? cmp : `!${cmp}`;
|
|
1254
1298
|
}
|
|
1255
1299
|
|
|
1300
|
+
if (this.strict && this._needsStrictOp(expr.op, expr._type)) {
|
|
1301
|
+
const l = this.genExpr(expr.left);
|
|
1302
|
+
const r = this.genExpr(expr.right);
|
|
1303
|
+
return this._genStrictBinary(expr.op, l, r, expr._type);
|
|
1304
|
+
}
|
|
1305
|
+
|
|
1256
1306
|
const jsOp = this._emittedBinaryOp(expr) ?? "/";
|
|
1257
1307
|
const operand = (child, isRight) =>
|
|
1258
1308
|
wrapForJsOp(
|
|
@@ -1327,7 +1377,7 @@ export const expressionGenMethods = {
|
|
|
1327
1377
|
return this._genSliceTypeConversion(expr, inner, t);
|
|
1328
1378
|
const target = t?.name;
|
|
1329
1379
|
if (target === "error") {
|
|
1330
|
-
this.
|
|
1380
|
+
this.useHelper("error");
|
|
1331
1381
|
return `__error(${inner})`;
|
|
1332
1382
|
}
|
|
1333
1383
|
if (target && this.namedWrapperNames.has(target))
|
|
@@ -1422,4 +1472,142 @@ export const expressionGenMethods = {
|
|
|
1422
1472
|
return inner;
|
|
1423
1473
|
}
|
|
1424
1474
|
},
|
|
1475
|
+
|
|
1476
|
+
_typeName(t) {
|
|
1477
|
+
if (!t) return null;
|
|
1478
|
+
if (t.kind === "untyped") return t.base;
|
|
1479
|
+
if (t.kind === "named") return this._typeName(t.underlying);
|
|
1480
|
+
if (t.kind === "basic") return t.name;
|
|
1481
|
+
return null;
|
|
1482
|
+
},
|
|
1483
|
+
|
|
1484
|
+
_isStrictAtomicOp(_op, typeName) {
|
|
1485
|
+
return STRICT_NUMERIC_TYPES.has(typeName);
|
|
1486
|
+
},
|
|
1487
|
+
|
|
1488
|
+
_needsStrictOp(op, type) {
|
|
1489
|
+
if (!STRICT_OPS.has(op)) return false;
|
|
1490
|
+
if (this._isStrictAtomicOp(op, this._typeName(type))) return true;
|
|
1491
|
+
return this.isIntType(type) && (op === "/" || op === "%");
|
|
1492
|
+
},
|
|
1493
|
+
|
|
1494
|
+
// Emits `lhs = strict(lhs op rhs)` for any lvalue shape; returns false when not applicable.
|
|
1495
|
+
// Indexed / nested targets are split into object + key temps so sub-expressions run once.
|
|
1496
|
+
_genStrictCompound(lhsNode, op, rhsJs, srcLine = null, lhsJs = null) {
|
|
1497
|
+
const type = lhsNode._type;
|
|
1498
|
+
if (!this._needsStrictOp(op, type)) return false;
|
|
1499
|
+
if (lhsNode.kind === "IndexExpr" && lhsNode.expr?._type?.kind === "map")
|
|
1500
|
+
return false;
|
|
1501
|
+
const l = lhsJs ?? this._genAssignLhsExpr(lhsNode);
|
|
1502
|
+
if (/^[A-Za-z_$][\w$]*(\.[A-Za-z_$][\w$]*)*$/.test(l)) {
|
|
1503
|
+
this.line(
|
|
1504
|
+
`${l} = ${this._genStrictBinary(op, l, rhsJs, type)};`,
|
|
1505
|
+
srcLine,
|
|
1506
|
+
);
|
|
1507
|
+
return true;
|
|
1508
|
+
}
|
|
1509
|
+
const split = splitLastAccess(l);
|
|
1510
|
+
if (!split) return false;
|
|
1511
|
+
this._tmpCounter = (this._tmpCounter ?? 0) + 1;
|
|
1512
|
+
const o = `__o${this._tmpCounter}`;
|
|
1513
|
+
if (split.key != null) {
|
|
1514
|
+
const k = `__k${this._tmpCounter}`;
|
|
1515
|
+
const target = `${o}[${k}]`;
|
|
1516
|
+
this.line(
|
|
1517
|
+
`{ const ${o} = ${split.obj}, ${k} = ${split.key}; ${target} = ${this._genStrictBinary(op, target, rhsJs, type)}; }`,
|
|
1518
|
+
srcLine,
|
|
1519
|
+
);
|
|
1520
|
+
} else {
|
|
1521
|
+
const target = `${o}.${split.prop}`;
|
|
1522
|
+
this.line(
|
|
1523
|
+
`{ const ${o} = ${split.obj}; ${target} = ${this._genStrictBinary(op, target, rhsJs, type)}; }`,
|
|
1524
|
+
srcLine,
|
|
1525
|
+
);
|
|
1526
|
+
}
|
|
1527
|
+
return true;
|
|
1528
|
+
},
|
|
1529
|
+
|
|
1530
|
+
_genStrictBinary(op, l, r, type) {
|
|
1531
|
+
const tName = this._typeName(type);
|
|
1532
|
+
if (tName === "float32") {
|
|
1533
|
+
return `Math.fround(${l} ${op} ${r})`;
|
|
1534
|
+
}
|
|
1535
|
+
if (tName === "int32" || tName === "rune") {
|
|
1536
|
+
if (op === "*") return `Math.imul(${l}, ${r})`;
|
|
1537
|
+
if (op === "/")
|
|
1538
|
+
return `((${r}) === 0 ? (() => { throw new Error("runtime error: integer divide by zero"); })() : (Math.trunc((${l}) / (${r})) | 0))`;
|
|
1539
|
+
if (op === "%")
|
|
1540
|
+
return `((${r}) === 0 ? (() => { throw new Error("runtime error: integer divide by zero"); })() : (((${l}) % (${r})) | 0))`;
|
|
1541
|
+
if (op === "<<" || op === ">>") {
|
|
1542
|
+
return `((${r}) >= 32 ? (${op === ">>" ? `((${l}) < 0 ? -1 : 0)` : "0"}) : ((${l}) ${op} (${r}))) | 0`;
|
|
1543
|
+
}
|
|
1544
|
+
if (op === "&^") return `((${l}) & ~(${r})) | 0`;
|
|
1545
|
+
return `((${l} ${op} ${r}) | 0)`;
|
|
1546
|
+
}
|
|
1547
|
+
if (tName === "uint32") {
|
|
1548
|
+
if (op === "/")
|
|
1549
|
+
return `((${r}) === 0 ? (() => { throw new Error("runtime error: integer divide by zero"); })() : (Math.trunc((${l}) / (${r})) >>> 0))`;
|
|
1550
|
+
if (op === "%")
|
|
1551
|
+
return `((${r}) === 0 ? (() => { throw new Error("runtime error: integer divide by zero"); })() : (((${l}) % (${r})) >>> 0))`;
|
|
1552
|
+
if (op === "<<" || op === ">>") {
|
|
1553
|
+
const jsOp = op === ">>" ? ">>>" : "<<";
|
|
1554
|
+
return `((${r}) >= 32 ? 0 : ((${l}) ${jsOp} (${r}))) >>> 0`;
|
|
1555
|
+
}
|
|
1556
|
+
if (op === "&^") return `((${l}) & ~(${r})) >>> 0`;
|
|
1557
|
+
return `((${l} ${op} ${r}) >>> 0)`;
|
|
1558
|
+
}
|
|
1559
|
+
if (tName === "int8") {
|
|
1560
|
+
if (op === "/")
|
|
1561
|
+
return `((${r}) === 0 ? (() => { throw new Error("runtime error: integer divide by zero"); })() : ((Math.trunc((${l}) / (${r})) << 24) >> 24))`;
|
|
1562
|
+
if (op === "%")
|
|
1563
|
+
return `((${r}) === 0 ? (() => { throw new Error("runtime error: integer divide by zero"); })() : (((((${l}) % (${r})) << 24) >> 24)))`;
|
|
1564
|
+
if (op === "<<" || op === ">>") {
|
|
1565
|
+
return `((${r}) >= 8 ? (${op === ">>" ? `((${l}) < 0 ? -1 : 0)` : "0"}) : (((${l}) ${op} (${r})) << 24 >> 24))`;
|
|
1566
|
+
}
|
|
1567
|
+
if (op === "&^") return `((((${l}) & ~(${r})) << 24) >> 24)`;
|
|
1568
|
+
return `((((${l} ${op} ${r})) << 24) >> 24)`;
|
|
1569
|
+
}
|
|
1570
|
+
if (tName === "int16") {
|
|
1571
|
+
if (op === "/")
|
|
1572
|
+
return `((${r}) === 0 ? (() => { throw new Error("runtime error: integer divide by zero"); })() : ((Math.trunc((${l}) / (${r})) << 16) >> 16))`;
|
|
1573
|
+
if (op === "%")
|
|
1574
|
+
return `((${r}) === 0 ? (() => { throw new Error("runtime error: integer divide by zero"); })() : (((((${l}) % (${r})) << 16) >> 16)))`;
|
|
1575
|
+
if (op === "<<" || op === ">>") {
|
|
1576
|
+
return `((${r}) >= 16 ? (${op === ">>" ? `((${l}) < 0 ? -1 : 0)` : "0"}) : (((${l}) ${op} (${r})) << 16 >> 16))`;
|
|
1577
|
+
}
|
|
1578
|
+
if (op === "&^") return `((((${l}) & ~(${r})) << 16) >> 16)`;
|
|
1579
|
+
return `((((${l} ${op} ${r})) << 16) >> 16)`;
|
|
1580
|
+
}
|
|
1581
|
+
if (tName === "uint8" || tName === "byte") {
|
|
1582
|
+
if (op === "/")
|
|
1583
|
+
return `((${r}) === 0 ? (() => { throw new Error("runtime error: integer divide by zero"); })() : (Math.trunc((${l}) / (${r})) & 0xFF))`;
|
|
1584
|
+
if (op === "%")
|
|
1585
|
+
return `((${r}) === 0 ? (() => { throw new Error("runtime error: integer divide by zero"); })() : (((${l}) % (${r})) & 0xFF))`;
|
|
1586
|
+
if (op === "<<" || op === ">>") {
|
|
1587
|
+
const jsOp = op === ">>" ? ">>>" : "<<";
|
|
1588
|
+
return `((${r}) >= 8 ? 0 : (((${l}) ${jsOp} (${r})) & 0xFF))`;
|
|
1589
|
+
}
|
|
1590
|
+
if (op === "&^") return `(((${l}) & ~(${r})) & 0xFF)`;
|
|
1591
|
+
return `((${l} ${op} ${r}) & 0xFF)`;
|
|
1592
|
+
}
|
|
1593
|
+
if (tName === "uint16") {
|
|
1594
|
+
if (op === "/")
|
|
1595
|
+
return `((${r}) === 0 ? (() => { throw new Error("runtime error: integer divide by zero"); })() : (Math.trunc((${l}) / (${r})) & 0xFFFF))`;
|
|
1596
|
+
if (op === "%")
|
|
1597
|
+
return `((${r}) === 0 ? (() => { throw new Error("runtime error: integer divide by zero"); })() : (((${l}) % (${r})) & 0xFFFF))`;
|
|
1598
|
+
if (op === "<<" || op === ">>") {
|
|
1599
|
+
const jsOp = op === ">>" ? ">>>" : "<<";
|
|
1600
|
+
return `((${r}) >= 16 ? 0 : (((${l}) ${jsOp} (${r})) & 0xFFFF))`;
|
|
1601
|
+
}
|
|
1602
|
+
if (op === "&^") return `(((${l}) & ~(${r})) & 0xFFFF)`;
|
|
1603
|
+
return `((${l} ${op} ${r}) & 0xFFFF)`;
|
|
1604
|
+
}
|
|
1605
|
+
if (this.isIntType(type)) {
|
|
1606
|
+
if (op === "/")
|
|
1607
|
+
return `((${r}) === 0 ? (() => { throw new Error("runtime error: integer divide by zero"); })() : Math.trunc((${l}) / (${r})))`;
|
|
1608
|
+
if (op === "%")
|
|
1609
|
+
return `((${r}) === 0 ? (() => { throw new Error("runtime error: integer divide by zero"); })() : ((${l}) % (${r})))`;
|
|
1610
|
+
}
|
|
1611
|
+
return `${l} ${op} ${r}`;
|
|
1612
|
+
},
|
|
1425
1613
|
};
|