gofront 1.5.1 → 1.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +174 -466
- package/package.json +16 -20
- package/src/backend/js/expressions.js +288 -128
- package/src/backend/js/index.js +138 -56
- package/src/backend/js/runtime.js +1055 -53
- package/src/backend/js/source-map.js +32 -0
- package/src/backend/js/statements.js +151 -83
- package/src/backend/js/stdlib/base64.js +12 -0
- package/src/backend/js/stdlib/binary.js +102 -0
- package/src/backend/js/stdlib/bits.js +15 -1
- package/src/backend/js/stdlib/builder.js +28 -9
- package/src/backend/js/stdlib/bytes.js +75 -18
- package/src/backend/js/stdlib/errors.js +22 -3
- package/src/backend/js/stdlib/fmt.js +95 -20
- package/src/backend/js/stdlib/hex.js +17 -0
- package/src/backend/js/stdlib/html.js +1 -1
- package/src/backend/js/stdlib/index.js +19 -0
- package/src/backend/js/stdlib/io.js +105 -3
- package/src/backend/js/stdlib/maps.js +1 -1
- package/src/backend/js/stdlib/math.js +13 -1
- package/src/backend/js/stdlib/path.js +20 -6
- package/src/backend/js/stdlib/rand.js +10 -5
- package/src/backend/js/stdlib/sha256.js +12 -0
- package/src/backend/js/stdlib/shared.js +17 -0
- package/src/backend/js/stdlib/slices.js +53 -27
- package/src/backend/js/stdlib/sort.js +1 -1
- package/src/backend/js/stdlib/strconv.js +52 -11
- package/src/backend/js/stdlib/strings.js +152 -16
- package/src/backend/js/stdlib/sync.js +13 -0
- package/src/backend/js/stdlib/testing.js +2 -2
- package/src/backend/js/stdlib/time.js +93 -22
- package/src/backend/js/stdlib/unicode.js +2 -2
- package/src/backend/js/templ.js +1 -1
- package/src/backend/wasm/boundary.js +1677 -58
- package/src/backend/wasm/const-fold.js +205 -0
- package/src/backend/wasm/emit-builtins.js +1230 -0
- package/src/backend/wasm/emit-exprs.js +2096 -0
- package/src/backend/wasm/emit-maps.js +1999 -0
- package/src/backend/wasm/emit-shared.js +519 -0
- package/src/backend/wasm/emit-stdlib.js +13 -0
- package/src/backend/wasm/emit-stmts.js +2466 -0
- package/src/backend/wasm/emit.js +656 -5031
- package/src/backend/wasm/encode.js +259 -85
- package/src/backend/wasm/glue.js +19 -5
- package/src/backend/wasm/index.js +2432 -86
- package/src/backend/wasm/monomorph.js +711 -0
- package/src/backend/wasm/optimize.js +236 -0
- package/src/backend/wasm/stdlib/bytes.js +5039 -0
- package/src/backend/wasm/stdlib/core.js +8903 -0
- package/src/backend/wasm/stdlib/index.js +40 -0
- package/src/backend/wasm/stdlib/io.js +2161 -0
- package/src/backend/wasm/stdlib/packages.js +2400 -0
- package/src/backend/wasm/stdlib/pure.js +720 -0
- package/src/backend/wasm/stdlib/time.js +1200 -0
- package/src/backend/wasm/types.js +312 -36
- package/src/backend/wasm/wat.js +49 -2
- package/src/cli-core.js +276 -73
- package/src/compiler.js +162 -25
- package/src/dts-parser.js +79 -126
- package/src/index.js +88 -121
- package/src/lexer.js +80 -29
- package/src/lower/boxing.js +4 -6
- package/src/lower/captures.js +12 -23
- package/src/lower/escape.js +4 -9
- package/src/lower/functions.js +277 -5
- package/src/lower/globals.js +7 -15
- package/src/lower/index.js +5 -2
- package/src/lower/ownership.js +35 -19
- package/src/lower/range.js +2 -5
- package/src/lower/walk.js +25 -0
- package/src/minifier.js +20 -68
- package/src/parser/declarations.js +3 -6
- package/src/parser/expressions.js +3 -8
- package/src/parser/index.js +18 -3
- package/src/parser/statements.js +25 -38
- package/src/parser/types.js +1 -2
- package/src/resolver.js +3 -27
- package/src/templ-lexer.js +29 -63
- package/src/templ-parser.js +22 -45
- package/src/test-runner.js +20 -0
- package/src/typechecker/assignability.js +123 -14
- package/src/typechecker/expressions.js +244 -4
- package/src/typechecker/index.js +27 -1
- package/src/typechecker/resolve.js +71 -12
- package/src/typechecker/statements.js +0 -7
- package/src/typechecker/stdlib/core.js +292 -103
- package/src/typechecker/stdlib/extended.js +471 -4
- package/src/typechecker/stdlib/shared.js +53 -0
- package/src/typechecker/stdlib.js +2 -0
- package/src/typechecker/types.js +545 -8
- package/src/vendor.js +2 -2
package/src/lower/functions.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
// src/lower/functions.js
|
|
2
2
|
// Analyzes function signatures, named returns and defer structures.
|
|
3
3
|
|
|
4
|
+
import { forEachChild, someChild } from "./walk.js";
|
|
5
|
+
|
|
4
6
|
export function extractNamedReturns(decl) {
|
|
5
7
|
const named = decl?.returnType?._namedReturns;
|
|
6
8
|
if (!named || named.length === 0) return null;
|
|
@@ -19,12 +21,282 @@ export function hasDefer(body) {
|
|
|
19
21
|
if (Array.isArray(node)) return node.some(walk);
|
|
20
22
|
if (node.kind === "DeferStmt") return true;
|
|
21
23
|
if (node.kind === "FuncLit") return false; // separate function boundary
|
|
22
|
-
|
|
23
|
-
if (key.startsWith("_")) continue;
|
|
24
|
-
if (walk(node[key])) return true;
|
|
25
|
-
}
|
|
26
|
-
return false;
|
|
24
|
+
return someChild(node, walk);
|
|
27
25
|
};
|
|
28
26
|
|
|
29
27
|
return walk(body);
|
|
30
28
|
}
|
|
29
|
+
|
|
30
|
+
let defargCounter = 0;
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* True when `body` contains a direct `recover()` call (not nested inside a
|
|
34
|
+
* FuncLit). Used to decide which functions may legitimately observe an
|
|
35
|
+
* in-flight panic: Go only honours recover() when it is called directly by
|
|
36
|
+
* a deferred function.
|
|
37
|
+
*/
|
|
38
|
+
export function hasDirectRecover(body) {
|
|
39
|
+
if (!body) return false;
|
|
40
|
+
if (body._hasDirectRecover != null) return Boolean(body._hasDirectRecover);
|
|
41
|
+
const walk = (node) => {
|
|
42
|
+
if (!node || typeof node !== "object") return false;
|
|
43
|
+
if (Array.isArray(node)) return node.some(walk);
|
|
44
|
+
if (node.kind === "FuncLit") return false;
|
|
45
|
+
if (
|
|
46
|
+
node.kind === "CallExpr" &&
|
|
47
|
+
node.func?.kind === "Ident" &&
|
|
48
|
+
node.func.name === "recover"
|
|
49
|
+
)
|
|
50
|
+
return true;
|
|
51
|
+
return someChild(node, walk);
|
|
52
|
+
};
|
|
53
|
+
const result = walk(body);
|
|
54
|
+
body._hasDirectRecover = result;
|
|
55
|
+
return result;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const BUILTIN_FUNC_NAMES = new Set([
|
|
59
|
+
"println",
|
|
60
|
+
"print",
|
|
61
|
+
"panic",
|
|
62
|
+
"recover",
|
|
63
|
+
"make",
|
|
64
|
+
"new",
|
|
65
|
+
"len",
|
|
66
|
+
"cap",
|
|
67
|
+
"append",
|
|
68
|
+
"copy",
|
|
69
|
+
"delete",
|
|
70
|
+
"close",
|
|
71
|
+
"clear",
|
|
72
|
+
"min",
|
|
73
|
+
"max",
|
|
74
|
+
]);
|
|
75
|
+
|
|
76
|
+
export function normalizeDefers(programOrPrograms) {
|
|
77
|
+
defargCounter = 0;
|
|
78
|
+
const programs = Array.isArray(programOrPrograms)
|
|
79
|
+
? programOrPrograms
|
|
80
|
+
: [programOrPrograms];
|
|
81
|
+
|
|
82
|
+
const pkgFuncNames = new Set(BUILTIN_FUNC_NAMES);
|
|
83
|
+
// Package-level funcs/methods whose body calls recover() directly. A
|
|
84
|
+
// `defer f()` whose callee is one of these must keep recover armed when the
|
|
85
|
+
// synthesized wrapper forwards the call.
|
|
86
|
+
const recoverFuncs = new Set();
|
|
87
|
+
for (const program of programs) {
|
|
88
|
+
for (const d of program?.decls ?? []) {
|
|
89
|
+
if (d.kind === "FuncDecl") pkgFuncNames.add(d.name);
|
|
90
|
+
if (
|
|
91
|
+
(d.kind === "FuncDecl" || d.kind === "MethodDecl") &&
|
|
92
|
+
hasDirectRecover(d.body)
|
|
93
|
+
)
|
|
94
|
+
recoverFuncs.add(d.name);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
const ctx = { pkgFuncNames, recoverFuncs };
|
|
98
|
+
|
|
99
|
+
for (const program of programs) {
|
|
100
|
+
if (!program?.decls) continue;
|
|
101
|
+
for (const d of program.decls) {
|
|
102
|
+
if (d.body) normalizeBlockDefers(d.body, ctx);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function normalizeBlockDefers(node, ctx) {
|
|
108
|
+
if (!node || typeof node !== "object") return;
|
|
109
|
+
if (Array.isArray(node)) {
|
|
110
|
+
for (let i = 0; i < node.length; i++) {
|
|
111
|
+
const item = node[i];
|
|
112
|
+
if (item && item.kind === "DeferStmt") {
|
|
113
|
+
const inserted = normalizeDeferStmt(item, node, i, ctx);
|
|
114
|
+
i += inserted;
|
|
115
|
+
} else {
|
|
116
|
+
normalizeBlockDefers(item, ctx);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
if (node.kind === "FuncLit") {
|
|
123
|
+
normalizeBlockDefers(node.body, ctx);
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
forEachChild(node, (child) => normalizeBlockDefers(child, ctx));
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// Does the deferred callee possibly call recover() directly? Known package
|
|
131
|
+
// funcs/methods are looked up; unknown callees (closure variables, func
|
|
132
|
+
// values) are assumed to, so recover stays armed through the wrapper.
|
|
133
|
+
function deferCalleeMayRecover(funcExpr, ctx) {
|
|
134
|
+
if (!ctx) return false;
|
|
135
|
+
if (funcExpr.kind === "Ident") {
|
|
136
|
+
if (BUILTIN_FUNC_NAMES.has(funcExpr.name)) return false;
|
|
137
|
+
if (ctx.pkgFuncNames.has(funcExpr.name))
|
|
138
|
+
return ctx.recoverFuncs.has(funcExpr.name);
|
|
139
|
+
return true;
|
|
140
|
+
}
|
|
141
|
+
if (funcExpr.kind === "SelectorExpr") {
|
|
142
|
+
if (funcExpr.expr?._type?.kind === "namespace") return false;
|
|
143
|
+
return ctx.recoverFuncs.has(funcExpr.field);
|
|
144
|
+
}
|
|
145
|
+
if (funcExpr.kind === "FuncLit") return hasDirectRecover(funcExpr.body);
|
|
146
|
+
return true;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function normalizeDeferStmt(stmt, parentList = null, index = -1, ctx = null) {
|
|
150
|
+
if (stmt.call?.kind !== "CallExpr") return 0;
|
|
151
|
+
const call = stmt.call;
|
|
152
|
+
const isZeroArgFuncLit =
|
|
153
|
+
call.func?.kind === "FuncLit" && (!call.args || call.args.length === 0);
|
|
154
|
+
if (isZeroArgFuncLit) {
|
|
155
|
+
// The FuncLit itself is the deferred function: recover() inside it is
|
|
156
|
+
// honoured.
|
|
157
|
+
call.func._isDeferTarget = true;
|
|
158
|
+
normalizeBlockDefers(call.func.body, ctx);
|
|
159
|
+
return 0;
|
|
160
|
+
}
|
|
161
|
+
const pkgFuncNames = ctx?.pkgFuncNames ?? null;
|
|
162
|
+
|
|
163
|
+
const tempStmts = [];
|
|
164
|
+
let funcExpr = call.func;
|
|
165
|
+
|
|
166
|
+
// Hoist method receiver if call.func is a method call (e.g. defer r.Close()).
|
|
167
|
+
// Skipped for pointer-receiver methods on an addressable (non-pointer)
|
|
168
|
+
// operand: Go evaluates `&recv` at defer time, so the deferred call must
|
|
169
|
+
// observe later mutations of the original variable — copying the struct
|
|
170
|
+
// into a temp would break that. The receiver expression itself is then
|
|
171
|
+
// re-evaluated at run time, which matches Go for the common `defer x.M()`
|
|
172
|
+
// / `defer s.f.M()` cases.
|
|
173
|
+
const recvIsAddressableValue =
|
|
174
|
+
call.func?._type?._ptrRecv === true &&
|
|
175
|
+
call.func.expr?._type?.kind !== "pointer";
|
|
176
|
+
if (
|
|
177
|
+
parentList &&
|
|
178
|
+
index >= 0 &&
|
|
179
|
+
call.func?.kind === "SelectorExpr" &&
|
|
180
|
+
call.func.expr?._type?.kind !== "namespace" &&
|
|
181
|
+
!recvIsAddressableValue
|
|
182
|
+
) {
|
|
183
|
+
const recv = call.func.expr;
|
|
184
|
+
defargCounter++;
|
|
185
|
+
const recvName = `__defrecv$${defargCounter}`;
|
|
186
|
+
const recvIdent = {
|
|
187
|
+
kind: "Ident",
|
|
188
|
+
name: recvName,
|
|
189
|
+
_type: recv._type,
|
|
190
|
+
};
|
|
191
|
+
tempStmts.push({
|
|
192
|
+
kind: "DefineStmt",
|
|
193
|
+
lhs: [{ kind: "Ident", name: recvName, _type: recv._type }],
|
|
194
|
+
rhs: [recv],
|
|
195
|
+
});
|
|
196
|
+
funcExpr = {
|
|
197
|
+
...call.func,
|
|
198
|
+
expr: recvIdent,
|
|
199
|
+
};
|
|
200
|
+
} else if (
|
|
201
|
+
parentList &&
|
|
202
|
+
index >= 0 &&
|
|
203
|
+
call.func?.kind === "Ident" &&
|
|
204
|
+
pkgFuncNames &&
|
|
205
|
+
!pkgFuncNames.has(call.func.name)
|
|
206
|
+
) {
|
|
207
|
+
// Hoist local function variable (e.g. fn := f1; defer fn(); fn = f2)
|
|
208
|
+
defargCounter++;
|
|
209
|
+
const fnName = `__deffn$${defargCounter}`;
|
|
210
|
+
const fnIdent = {
|
|
211
|
+
kind: "Ident",
|
|
212
|
+
name: fnName,
|
|
213
|
+
_type: call.func._type,
|
|
214
|
+
};
|
|
215
|
+
tempStmts.push({
|
|
216
|
+
kind: "DefineStmt",
|
|
217
|
+
lhs: [{ kind: "Ident", name: fnName, _type: call.func._type }],
|
|
218
|
+
rhs: [call.func],
|
|
219
|
+
});
|
|
220
|
+
funcExpr = fnIdent;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
const args = call.args ?? [];
|
|
224
|
+
const tempIdents = [];
|
|
225
|
+
|
|
226
|
+
if (parentList && index >= 0 && args.length > 0) {
|
|
227
|
+
for (let a = 0; a < args.length; a++) {
|
|
228
|
+
const arg = args[a];
|
|
229
|
+
if (arg._type?.kind === "tuple" && Array.isArray(arg._type.types)) {
|
|
230
|
+
const tupleTypes = arg._type.types;
|
|
231
|
+
const tupleLhs = [];
|
|
232
|
+
for (let t = 0; t < tupleTypes.length; t++) {
|
|
233
|
+
defargCounter++;
|
|
234
|
+
const tempName = `__defarg$${defargCounter}`;
|
|
235
|
+
const ident = {
|
|
236
|
+
kind: "Ident",
|
|
237
|
+
name: tempName,
|
|
238
|
+
_type: tupleTypes[t],
|
|
239
|
+
};
|
|
240
|
+
tempIdents.push(ident);
|
|
241
|
+
tupleLhs.push(ident);
|
|
242
|
+
}
|
|
243
|
+
tempStmts.push({
|
|
244
|
+
kind: "DefineStmt",
|
|
245
|
+
lhs: tupleLhs,
|
|
246
|
+
rhs: [arg],
|
|
247
|
+
});
|
|
248
|
+
} else {
|
|
249
|
+
defargCounter++;
|
|
250
|
+
const tempName = `__defarg$${defargCounter}`;
|
|
251
|
+
tempIdents.push({
|
|
252
|
+
kind: "Ident",
|
|
253
|
+
name: tempName,
|
|
254
|
+
_type: arg._type,
|
|
255
|
+
});
|
|
256
|
+
tempStmts.push({
|
|
257
|
+
kind: "DefineStmt",
|
|
258
|
+
lhs: [{ kind: "Ident", name: tempName, _type: arg._type }],
|
|
259
|
+
rhs: [arg],
|
|
260
|
+
});
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
if (parentList && index >= 0 && tempStmts.length > 0) {
|
|
266
|
+
parentList.splice(index, 0, ...tempStmts);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
const callArgs = tempIdents.length > 0 ? tempIdents : args;
|
|
270
|
+
const innerCall = {
|
|
271
|
+
...call,
|
|
272
|
+
func: funcExpr,
|
|
273
|
+
args: callArgs,
|
|
274
|
+
_multiForward: tempIdents.length > 0 ? false : call._multiForward,
|
|
275
|
+
};
|
|
276
|
+
const synthFuncLit = {
|
|
277
|
+
kind: "FuncLit",
|
|
278
|
+
params: [],
|
|
279
|
+
returnType: null,
|
|
280
|
+
body: {
|
|
281
|
+
kind: "Block",
|
|
282
|
+
stmts: [{ kind: "ExprStmt", expr: innerCall }],
|
|
283
|
+
list: [{ kind: "ExprStmt", expr: innerCall }],
|
|
284
|
+
_hasDefer: false,
|
|
285
|
+
},
|
|
286
|
+
_type: { kind: "FuncType", params: [], returnType: null },
|
|
287
|
+
_isDeferTarget: true,
|
|
288
|
+
// The wrapper only forwards to the real deferred callee; when that
|
|
289
|
+
// callee may call recover() directly, recover must stay armed across
|
|
290
|
+
// the wrapper frame.
|
|
291
|
+
_deferForward: deferCalleeMayRecover(call.func, ctx),
|
|
292
|
+
};
|
|
293
|
+
if (call.func?.kind === "FuncLit") normalizeBlockDefers(call.func.body, ctx);
|
|
294
|
+
stmt.call = {
|
|
295
|
+
kind: "CallExpr",
|
|
296
|
+
func: synthFuncLit,
|
|
297
|
+
args: [],
|
|
298
|
+
_type: { kind: "basic", name: "void" },
|
|
299
|
+
};
|
|
300
|
+
|
|
301
|
+
return tempStmts.length;
|
|
302
|
+
}
|
package/src/lower/globals.js
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
// and computes functions that participate in the package initialization call path.
|
|
4
4
|
|
|
5
5
|
import { isReferenceType } from "./boxing.js";
|
|
6
|
+
import { forEachChild, someChild } from "./walk.js";
|
|
6
7
|
|
|
7
8
|
/**
|
|
8
9
|
* Computes:
|
|
@@ -117,11 +118,9 @@ function scanReassignments(node, packageGlobals, reassigned, insideInit) {
|
|
|
117
118
|
}
|
|
118
119
|
}
|
|
119
120
|
checkAddressTaken(node, packageGlobals, reassigned);
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
}
|
|
124
|
-
}
|
|
121
|
+
forEachChild(node, (child) =>
|
|
122
|
+
scanReassignments(child, packageGlobals, reassigned, insideInit),
|
|
123
|
+
);
|
|
125
124
|
}
|
|
126
125
|
|
|
127
126
|
function addCallTarget(call, called) {
|
|
@@ -145,9 +144,7 @@ function findCalledFunctions(node, called = new Set()) {
|
|
|
145
144
|
return called;
|
|
146
145
|
}
|
|
147
146
|
if (node.kind === "CallExpr") addCallTarget(node, called);
|
|
148
|
-
|
|
149
|
-
if (!k.startsWith("_")) findCalledFunctions(node[k], called);
|
|
150
|
-
}
|
|
147
|
+
forEachChild(node, (child) => findCalledFunctions(child, called));
|
|
151
148
|
return called;
|
|
152
149
|
}
|
|
153
150
|
|
|
@@ -244,9 +241,7 @@ function scanReadIdents(node, readSet) {
|
|
|
244
241
|
readSet.add(node.name);
|
|
245
242
|
return;
|
|
246
243
|
}
|
|
247
|
-
|
|
248
|
-
if (!k.startsWith("_")) scanReadIdents(node[k], readSet);
|
|
249
|
-
}
|
|
244
|
+
forEachChild(node, (child) => scanReadIdents(child, readSet));
|
|
250
245
|
}
|
|
251
246
|
|
|
252
247
|
function isLocallyDeclared(node, name) {
|
|
@@ -265,10 +260,7 @@ function isLocallyDeclared(node, name) {
|
|
|
265
260
|
return true;
|
|
266
261
|
}
|
|
267
262
|
}
|
|
268
|
-
|
|
269
|
-
if (!k.startsWith("_") && isLocallyDeclared(node[k], name)) return true;
|
|
270
|
-
}
|
|
271
|
-
return false;
|
|
263
|
+
return someChild(node, (child) => isLocallyDeclared(child, name));
|
|
272
264
|
}
|
|
273
265
|
|
|
274
266
|
function getGlobalsToCache(fn, neverAssignedGlobals, initPathFunctions) {
|
package/src/lower/index.js
CHANGED
|
@@ -9,16 +9,17 @@ export * from "./functions.js";
|
|
|
9
9
|
export * from "./globals.js";
|
|
10
10
|
export * from "./ownership.js";
|
|
11
11
|
export * from "./range.js";
|
|
12
|
+
export * from "./walk.js";
|
|
12
13
|
|
|
13
14
|
import { scanAddressTaken } from "./boxing.js";
|
|
14
15
|
import { analyzeCaptures } from "./captures.js";
|
|
15
16
|
import { computeEmbeddedStubs } from "./embedding.js";
|
|
16
17
|
import { paramEscapes } from "./escape.js";
|
|
17
|
-
import { extractNamedReturns, hasDefer } from "./functions.js";
|
|
18
|
+
import { extractNamedReturns, hasDefer, normalizeDefers } from "./functions.js";
|
|
18
19
|
import { computeGlobalAnalysis } from "./globals.js";
|
|
19
20
|
import { OwnershipContext } from "./ownership.js";
|
|
20
21
|
|
|
21
|
-
|
|
22
|
+
class LowerResult {
|
|
22
23
|
constructor() {
|
|
23
24
|
// Map<ASTNode (FuncDecl, MethodDecl, FuncLit), Set<string>>
|
|
24
25
|
this.boxedVars = new Map();
|
|
@@ -53,6 +54,8 @@ export function lower(programOrPrograms, checker = null) {
|
|
|
53
54
|
? programOrPrograms
|
|
54
55
|
: [programOrPrograms];
|
|
55
56
|
|
|
57
|
+
normalizeDefers(programs);
|
|
58
|
+
|
|
56
59
|
// 1. Aggregate methods across all files in the package
|
|
57
60
|
const methodMap = new Map();
|
|
58
61
|
for (const program of programs) {
|
package/src/lower/ownership.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
// src/lower/ownership.js
|
|
2
2
|
// Tracks variable ownership, mutation and clone elision for Go value semantics.
|
|
3
3
|
|
|
4
|
+
import { forEachChild, someChild } from "./walk.js";
|
|
5
|
+
|
|
4
6
|
export function rootIdentName(e) {
|
|
5
7
|
while (e) {
|
|
6
8
|
if (e.kind === "Ident") return e.name;
|
|
@@ -42,11 +44,7 @@ export function nodeMutatesVar(node, name, addrOnly = false) {
|
|
|
42
44
|
)
|
|
43
45
|
return true;
|
|
44
46
|
if (!addrOnly && nodeWritesVar(node, name)) return true;
|
|
45
|
-
|
|
46
|
-
if (key.startsWith("_")) continue;
|
|
47
|
-
if (nodeMutatesVar(node[key], name, addrOnly)) return true;
|
|
48
|
-
}
|
|
49
|
-
return false;
|
|
47
|
+
return someChild(node, (child) => nodeMutatesVar(child, name, addrOnly));
|
|
50
48
|
}
|
|
51
49
|
|
|
52
50
|
// Single-pass collector of all mutated or address-taken variables in an AST node.
|
|
@@ -85,38 +83,56 @@ export function collectMutatedVars(
|
|
|
85
83
|
}
|
|
86
84
|
}
|
|
87
85
|
|
|
88
|
-
|
|
89
|
-
if (key.startsWith("_")) continue;
|
|
90
|
-
collectMutatedVars(node[key], mutated, addrOnly);
|
|
91
|
-
}
|
|
86
|
+
forEachChild(node, (child) => collectMutatedVars(child, mutated, addrOnly));
|
|
92
87
|
return mutated;
|
|
93
88
|
}
|
|
94
89
|
|
|
95
90
|
export function fnMutates(body, name, addrOnly = false, cache = null) {
|
|
96
91
|
if (!body) return true;
|
|
97
|
-
const key = `${addrOnly ? "&" : ""}${name}`;
|
|
98
92
|
if (cache) {
|
|
99
|
-
|
|
100
|
-
|
|
93
|
+
const setKey = addrOnly ? "&*" : "*";
|
|
94
|
+
let set = cache.get(setKey);
|
|
95
|
+
if (!set) {
|
|
96
|
+
set = collectMutatedVars(body, new Set(), addrOnly);
|
|
97
|
+
cache.set(setKey, set);
|
|
101
98
|
}
|
|
102
|
-
return
|
|
99
|
+
return set.has(name);
|
|
103
100
|
}
|
|
104
101
|
return nodeMutatesVar(body, name, addrOnly);
|
|
105
102
|
}
|
|
106
103
|
|
|
104
|
+
function collectClosureMutatedVars(node, mutated = new Set()) {
|
|
105
|
+
if (!node || typeof node !== "object") return mutated;
|
|
106
|
+
if (Array.isArray(node)) {
|
|
107
|
+
for (const item of node) collectClosureMutatedVars(item, mutated);
|
|
108
|
+
return mutated;
|
|
109
|
+
}
|
|
110
|
+
if (node.kind === "FuncLit") {
|
|
111
|
+
collectMutatedVars(node.body, mutated, false);
|
|
112
|
+
return mutated;
|
|
113
|
+
}
|
|
114
|
+
forEachChild(node, (child) => collectClosureMutatedVars(child, mutated));
|
|
115
|
+
return mutated;
|
|
116
|
+
}
|
|
117
|
+
|
|
107
118
|
export function closureMutates(body, name, cache = null) {
|
|
108
|
-
|
|
109
|
-
if (cache
|
|
119
|
+
if (!body) return false;
|
|
120
|
+
if (cache) {
|
|
121
|
+
let set = cache.get("λ*");
|
|
122
|
+
if (!set) {
|
|
123
|
+
set = collectClosureMutatedVars(body, new Set());
|
|
124
|
+
cache.set("λ*", set);
|
|
125
|
+
}
|
|
126
|
+
return set.has(name);
|
|
127
|
+
}
|
|
110
128
|
|
|
111
129
|
const walk = (node) => {
|
|
112
130
|
if (!node || typeof node !== "object") return false;
|
|
113
131
|
if (Array.isArray(node)) return node.some(walk);
|
|
114
132
|
if (node.kind === "FuncLit") return nodeMutatesVar(node.body, name);
|
|
115
|
-
return
|
|
133
|
+
return someChild(node, walk);
|
|
116
134
|
};
|
|
117
|
-
|
|
118
|
-
if (cache) cache.set(key, res);
|
|
119
|
-
return res;
|
|
135
|
+
return walk(body);
|
|
120
136
|
}
|
|
121
137
|
|
|
122
138
|
export class OwnershipContext {
|
package/src/lower/range.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
// Classifies for-range loop shapes and variable scoping/redelcarations.
|
|
3
3
|
|
|
4
4
|
import { isNumeric } from "../typechecker/types.js";
|
|
5
|
+
import { someChild } from "./walk.js";
|
|
5
6
|
|
|
6
7
|
export function isRangeFor(stmt) {
|
|
7
8
|
if (!stmt?.init) return false;
|
|
@@ -40,11 +41,7 @@ export function nodeAssigns(node, name) {
|
|
|
40
41
|
node.expr.name === name)
|
|
41
42
|
)
|
|
42
43
|
return true;
|
|
43
|
-
|
|
44
|
-
if (key.startsWith("_")) continue;
|
|
45
|
-
if (nodeAssigns(node[key], name)) return true;
|
|
46
|
-
}
|
|
47
|
-
return false;
|
|
44
|
+
return someChild(node, (child) => nodeAssigns(child, name));
|
|
48
45
|
}
|
|
49
46
|
|
|
50
47
|
// True when the body re-declares one of `names` at its top level (legal Go shadowing).
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
// src/lower/walk.js
|
|
2
|
+
// Shared AST child traversal for the lowering passes. Side-table fields
|
|
3
|
+
// (`_type`, `_boxed`, …) start with "_" and are never AST children.
|
|
4
|
+
|
|
5
|
+
/** True for a side-table key that traversal must skip. */
|
|
6
|
+
function isSideTableKey(key) {
|
|
7
|
+
return key.charCodeAt(0) === 95; // "_"
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/** Calls `fn(child)` for every non-side-table property of `node`. */
|
|
11
|
+
export function forEachChild(node, fn) {
|
|
12
|
+
for (const key of Object.keys(node)) {
|
|
13
|
+
if (isSideTableKey(key)) continue;
|
|
14
|
+
fn(node[key]);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/** True when `pred(child)` holds for some non-side-table property of `node`. */
|
|
19
|
+
export function someChild(node, pred) {
|
|
20
|
+
for (const key of Object.keys(node)) {
|
|
21
|
+
if (isSideTableKey(key)) continue;
|
|
22
|
+
if (pred(node[key])) return true;
|
|
23
|
+
}
|
|
24
|
+
return false;
|
|
25
|
+
}
|
package/src/minifier.js
CHANGED
|
@@ -121,7 +121,7 @@ const REGEX_CONTEXT_VALUES = new Set([
|
|
|
121
121
|
const DIVISION_AFTER_OPS = new Set([")", "]", "++", "--"]);
|
|
122
122
|
|
|
123
123
|
function _isRegexContext(tokens) {
|
|
124
|
-
const prev =
|
|
124
|
+
const prev = findPrevNonWs(tokens);
|
|
125
125
|
if (!prev) return true;
|
|
126
126
|
if (prev.type === "op") return !DIVISION_AFTER_OPS.has(prev.value);
|
|
127
127
|
return REGEX_CONTEXT_VALUES.has(prev.value);
|
|
@@ -323,14 +323,6 @@ function tokenize(code) {
|
|
|
323
323
|
return tokens;
|
|
324
324
|
}
|
|
325
325
|
|
|
326
|
-
function lastNonWhitespaceToken(tokens) {
|
|
327
|
-
for (let i = tokens.length - 1; i >= 0; i--) {
|
|
328
|
-
if (tokens[i].type !== "ws" && tokens[i].type !== "comment")
|
|
329
|
-
return tokens[i];
|
|
330
|
-
}
|
|
331
|
-
return null;
|
|
332
|
-
}
|
|
333
|
-
|
|
334
326
|
// ── Stage 1 & 2: Compress ────────────────────────────────────
|
|
335
327
|
|
|
336
328
|
function isIdentLike(type) {
|
|
@@ -348,15 +340,6 @@ const SPACE_AFTER_KW_RIGHT_TYPES = new Set([
|
|
|
348
340
|
]);
|
|
349
341
|
// Operator values that do NOT need a space after a keyword
|
|
350
342
|
const NO_SPACE_AFTER_KW_OPS = new Set(["(", "[", "!", "-", "+"]);
|
|
351
|
-
// Adjacent op pairs that would accidentally merge into a different operator
|
|
352
|
-
const _INCREMENT_MERGE_PAIRS = new Set([
|
|
353
|
-
"+|+",
|
|
354
|
-
"-|-",
|
|
355
|
-
"+|++",
|
|
356
|
-
"-|--",
|
|
357
|
-
"++|+",
|
|
358
|
-
"--|−-",
|
|
359
|
-
]);
|
|
360
343
|
|
|
361
344
|
function _wouldMergeUnaryOps(left, right) {
|
|
362
345
|
return (
|
|
@@ -436,11 +419,7 @@ function compressTokens(tokens) {
|
|
|
436
419
|
return out;
|
|
437
420
|
}
|
|
438
421
|
|
|
439
|
-
function
|
|
440
|
-
return compressTokens(tokenize(code));
|
|
441
|
-
}
|
|
442
|
-
|
|
443
|
-
function findPrevNonWs(tokens, index) {
|
|
422
|
+
function findPrevNonWs(tokens, index = tokens.length) {
|
|
444
423
|
for (let i = index - 1; i >= 0; i--) {
|
|
445
424
|
if (tokens[i].type !== "ws" && tokens[i].type !== "comment")
|
|
446
425
|
return tokens[i];
|
|
@@ -477,7 +456,7 @@ function _matchingParen(tokens, openIdx) {
|
|
|
477
456
|
// A `(` opens a parameter list only when its `)` is followed by a body `{` or `=>`;
|
|
478
457
|
// otherwise `name(...)` is a call and its arguments may be free globals.
|
|
479
458
|
function _collectParamDecls(tokens, i, declaredLocals) {
|
|
480
|
-
const prev =
|
|
459
|
+
const prev = findPrevNonWs(tokens, i);
|
|
481
460
|
if (
|
|
482
461
|
!prev ||
|
|
483
462
|
(prev.value !== "function" && prev.type !== "ident" && prev.value !== ")")
|
|
@@ -583,22 +562,7 @@ function _filterRenameCandidates(declaredLocals) {
|
|
|
583
562
|
}
|
|
584
563
|
}
|
|
585
564
|
|
|
586
|
-
function
|
|
587
|
-
let out = "";
|
|
588
|
-
for (let i = 0; i < tokens.length; i++) {
|
|
589
|
-
const tok = tokens[i];
|
|
590
|
-
if (tok.type === "ident" && renameMap.has(tok.value)) {
|
|
591
|
-
const prev = findPrevNonWsToken(tokens, i);
|
|
592
|
-
out += prev && prev.value === "." ? tok.value : renameMap.get(tok.value);
|
|
593
|
-
} else {
|
|
594
|
-
out += tok.value;
|
|
595
|
-
}
|
|
596
|
-
}
|
|
597
|
-
return out;
|
|
598
|
-
}
|
|
599
|
-
|
|
600
|
-
function mangle(code) {
|
|
601
|
-
const tokens = tokenize(code);
|
|
565
|
+
function mangleTokens(tokens) {
|
|
602
566
|
const { declaredLocals } = _collectLocalDecls(tokens);
|
|
603
567
|
_filterRenameCandidates(declaredLocals);
|
|
604
568
|
|
|
@@ -612,7 +576,15 @@ function mangle(code) {
|
|
|
612
576
|
const nameGen = shortNameGeneratorFn(occupied);
|
|
613
577
|
const renameMap = new Map();
|
|
614
578
|
for (const name of declaredLocals) renameMap.set(name, nameGen());
|
|
615
|
-
return
|
|
579
|
+
return tokens.map((tok, i) => {
|
|
580
|
+
if (tok.type === "ident" && renameMap.has(tok.value)) {
|
|
581
|
+
const prev = findPrevNonWs(tokens, i);
|
|
582
|
+
if (prev?.value !== ".") {
|
|
583
|
+
return { type: "ident", value: renameMap.get(tok.value) };
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
return tok;
|
|
587
|
+
});
|
|
616
588
|
}
|
|
617
589
|
|
|
618
590
|
function nextNonWs(tokens, index) {
|
|
@@ -624,14 +596,6 @@ function nextNonWs(tokens, index) {
|
|
|
624
596
|
return null;
|
|
625
597
|
}
|
|
626
598
|
|
|
627
|
-
function findPrevNonWsToken(tokens, index) {
|
|
628
|
-
for (let i = index - 1; i >= 0; i--) {
|
|
629
|
-
if (tokens[i].type !== "ws" && tokens[i].type !== "comment")
|
|
630
|
-
return tokens[i];
|
|
631
|
-
}
|
|
632
|
-
return null;
|
|
633
|
-
}
|
|
634
|
-
|
|
635
599
|
function collectDestructured(tokens, startIdx, locals) {
|
|
636
600
|
const open = tokens[startIdx].value;
|
|
637
601
|
const close = open === "[" ? "]" : "}";
|
|
@@ -647,7 +611,7 @@ function collectDestructured(tokens, startIdx, locals) {
|
|
|
647
611
|
}
|
|
648
612
|
|
|
649
613
|
function _isParamStart(tokens, i) {
|
|
650
|
-
const prev =
|
|
614
|
+
const prev = findPrevNonWs(tokens, i);
|
|
651
615
|
return (
|
|
652
616
|
!prev ||
|
|
653
617
|
prev.value === "(" ||
|
|
@@ -839,28 +803,16 @@ function foldTokens(tokens) {
|
|
|
839
803
|
return current;
|
|
840
804
|
}
|
|
841
805
|
|
|
842
|
-
function foldLiterals(code) {
|
|
843
|
-
const tokens = tokenize(code);
|
|
844
|
-
return compressTokens(foldTokens(tokens));
|
|
845
|
-
}
|
|
846
|
-
|
|
847
806
|
// ── Public API ───────────────────────────────────────────────
|
|
848
807
|
|
|
849
808
|
export function minify(code, options = {}) {
|
|
850
809
|
const { mangle: doMangle = false } = options;
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
// Stage 4: literal folding
|
|
856
|
-
result = foldLiterals(result);
|
|
857
|
-
|
|
858
|
-
// Stage 3: mangling (optional)
|
|
810
|
+
let tokens = tokenize(code).filter(
|
|
811
|
+
(t) => t.type !== "ws" && t.type !== "comment",
|
|
812
|
+
);
|
|
813
|
+
tokens = foldTokens(tokens);
|
|
859
814
|
if (doMangle) {
|
|
860
|
-
|
|
861
|
-
// Re-compress after mangling to clean up any leftover whitespace
|
|
862
|
-
result = compress(result);
|
|
815
|
+
tokens = mangleTokens(tokens);
|
|
863
816
|
}
|
|
864
|
-
|
|
865
|
-
return result;
|
|
817
|
+
return compressTokens(tokens);
|
|
866
818
|
}
|