gofront 1.3.12 → 1.5.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 +90 -20
- package/package.json +2 -1
- package/src/{codegen → backend/js}/expressions.js +217 -60
- package/src/{codegen → backend/js}/index.js +47 -36
- package/src/{codegen → backend/js}/runtime.js +11 -0
- package/src/{codegen → backend/js}/statements.js +43 -48
- package/src/backend/js/stdlib/bits.js +32 -0
- package/src/{codegen → backend/js}/stdlib/index.js +3 -0
- package/src/{codegen → backend/js}/templ.js +31 -2
- package/src/backend/wasm/boundary.js +1345 -0
- package/src/backend/wasm/emit.js +5779 -0
- package/src/backend/wasm/encode.js +789 -0
- package/src/backend/wasm/glue.js +130 -0
- package/src/backend/wasm/index.js +1413 -0
- package/src/backend/wasm/types.js +420 -0
- package/src/backend/wasm/wat.js +255 -0
- package/src/cli-core.js +52 -7
- package/src/compiler.js +328 -12
- package/src/dev-server.js +56 -15
- package/src/index.js +17 -9
- package/src/lexer.js +18 -0
- package/src/lower/boxing.js +36 -0
- package/src/lower/captures.js +179 -0
- package/src/lower/embedding.js +30 -0
- package/src/lower/escape.js +169 -0
- package/src/lower/functions.js +30 -0
- package/src/lower/globals.js +324 -0
- package/src/lower/index.js +115 -0
- package/src/lower/ownership.js +142 -0
- package/src/lower/range.js +70 -0
- package/src/minifier.js +2 -0
- package/src/parser/index.js +8 -3
- package/src/resolver.js +4 -1
- package/src/templ-lexer.js +107 -3
- package/src/templ-parser.js +29 -0
- package/src/test-runner.js +74 -20
- package/src/typechecker/expressions.js +40 -1
- package/src/typechecker/index.js +301 -6
- package/src/typechecker/statements.js +7 -0
- package/src/typechecker/stdlib/core.js +17 -0
- package/src/typechecker/types.js +56 -2
- /package/src/{codegen → backend/js}/source-map.js +0 -0
- /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/errors.js +0 -0
- /package/src/{codegen → backend/js}/stdlib/fmt.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/maps.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/path.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/slices.js +0 -0
- /package/src/{codegen → backend/js}/stdlib/sort.js +0 -0
- /package/src/{codegen → backend/js}/stdlib/strconv.js +0 -0
- /package/src/{codegen → backend/js}/stdlib/strings.js +0 -0
- /package/src/{codegen → backend/js}/stdlib/testing.js +0 -0
- /package/src/{codegen → backend/js}/stdlib/time.js +0 -0
- /package/src/{codegen → backend/js}/stdlib/unicode.js +0 -0
- /package/src/{codegen → backend/js}/stdlib/utf8.js +0 -0
package/src/typechecker/index.js
CHANGED
|
@@ -25,9 +25,18 @@ import {
|
|
|
25
25
|
TAINTED_ANY,
|
|
26
26
|
TypeCheckError,
|
|
27
27
|
typeStr,
|
|
28
|
+
VALID_TARGETS,
|
|
28
29
|
VOID,
|
|
29
30
|
} from "./types.js";
|
|
30
31
|
|
|
32
|
+
function rootIdentName(e) {
|
|
33
|
+
if (!e) return null;
|
|
34
|
+
if (e.kind === "Ident") return e.name;
|
|
35
|
+
if (e.kind === "SelectorExpr" || e.kind === "IndexExpr")
|
|
36
|
+
return rootIdentName(e.expr);
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
|
|
31
40
|
// Re-export for consumers that import from typechecker.js
|
|
32
41
|
export { TypeCheckError, typeStr };
|
|
33
42
|
|
|
@@ -36,6 +45,10 @@ export class TypeChecker {
|
|
|
36
45
|
this.types = new Map(); // named types
|
|
37
46
|
this.globals = new Scope();
|
|
38
47
|
this.errors = [];
|
|
48
|
+
this.target = "js";
|
|
49
|
+
this.pkgName = null;
|
|
50
|
+
this.blockers = new Map();
|
|
51
|
+
this.summary = null;
|
|
39
52
|
this._currentFile = null; // filename of file currently being checked
|
|
40
53
|
this._currentSource = null;
|
|
41
54
|
this._loopDepth = 0; // for break/continue validation
|
|
@@ -117,12 +130,42 @@ export class TypeChecker {
|
|
|
117
130
|
return new Map(this.types);
|
|
118
131
|
}
|
|
119
132
|
|
|
120
|
-
|
|
133
|
+
recordBlocker(category, item = null) {
|
|
134
|
+
if (!this.blockers.has(category)) {
|
|
135
|
+
this.blockers.set(category, []);
|
|
136
|
+
}
|
|
137
|
+
this.blockers.get(category).push(item);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
getSummary() {
|
|
141
|
+
if (this.blockers.size === 0) return null;
|
|
142
|
+
let total = 0;
|
|
143
|
+
const parts = [];
|
|
144
|
+
for (const [category, items] of this.blockers) {
|
|
145
|
+
const uniqueItems = [
|
|
146
|
+
...new Set(items.filter((x) => x != null && x !== "")),
|
|
147
|
+
];
|
|
148
|
+
const count = uniqueItems.length > 0 ? uniqueItems.length : items.length;
|
|
149
|
+
total += count;
|
|
150
|
+
const itemsStr =
|
|
151
|
+
uniqueItems.length > 0 ? ` (${uniqueItems.join(", ")})` : "";
|
|
152
|
+
// "gom usage" is a mass noun; everything else pluralises on the last word.
|
|
153
|
+
const label =
|
|
154
|
+
count === 1 || category === "gom usage" ? category : `${category}s`;
|
|
155
|
+
parts.push(`${count} ${label}${itemsStr}`);
|
|
156
|
+
}
|
|
157
|
+
const noun = total === 1 ? "blocker" : "blockers";
|
|
158
|
+
const what = this.target === "both" ? "both" : "wasm";
|
|
159
|
+
return `package '${this.pkgName}' cannot be ${what}: ${total} ${noun} — ${parts.join(", ")}`;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
err(msg, node, hint = null) {
|
|
121
163
|
const e = new TypeCheckError(
|
|
122
164
|
msg,
|
|
123
165
|
node,
|
|
124
166
|
this._currentFile,
|
|
125
167
|
this._currentSource,
|
|
168
|
+
hint,
|
|
126
169
|
);
|
|
127
170
|
this.errors.push(e);
|
|
128
171
|
return TAINTED_ANY; // tainted recovery type — suppresses downstream cascade errors
|
|
@@ -163,18 +206,181 @@ export class TypeChecker {
|
|
|
163
206
|
}
|
|
164
207
|
}
|
|
165
208
|
|
|
166
|
-
// Like check() but operates over multiple programs (same-package multi-file).
|
|
167
|
-
// All passes run across all files before moving to the next pass,
|
|
168
|
-
// so every file sees every other file's declarations.
|
|
169
209
|
checkAll(programs) {
|
|
210
|
+
for (const p of programs) {
|
|
211
|
+
const d = p._targetDirective;
|
|
212
|
+
if (!d) continue;
|
|
213
|
+
this._setCurrentFile(p);
|
|
214
|
+
if (!VALID_TARGETS.has(d.value)) {
|
|
215
|
+
this.err(
|
|
216
|
+
`unknown //gofront:target '${d.value}' (expected js, wasm or both)`,
|
|
217
|
+
d,
|
|
218
|
+
);
|
|
219
|
+
p.target = null;
|
|
220
|
+
if (this.target === d.value) this.target = "js";
|
|
221
|
+
} else if (!d.beforePackage) {
|
|
222
|
+
this.err("//gofront:target must appear before the package clause", d);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
if (programs.length > 0) {
|
|
226
|
+
if (!this.target || this.target === "js") {
|
|
227
|
+
const pTarget = programs.find((p) => p.target)?.target;
|
|
228
|
+
if (pTarget) this.target = pTarget;
|
|
229
|
+
}
|
|
230
|
+
if (!this.pkgName) {
|
|
231
|
+
this.pkgName = programs[0].pkg?.name ?? null;
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
if (this.target === "wasm" || this.target === "both") {
|
|
236
|
+
for (const p of programs) {
|
|
237
|
+
if (p._filename?.endsWith(".templ")) {
|
|
238
|
+
this._setCurrentFile(p);
|
|
239
|
+
const fileName = p._filename.split("/").pop();
|
|
240
|
+
this.recordBlocker(".templ file", fileName);
|
|
241
|
+
this.err(
|
|
242
|
+
`.templ files are not allowed in wasm packages; move '${fileName}' to a js package`,
|
|
243
|
+
p.pkg,
|
|
244
|
+
);
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
170
249
|
this._collectTypesPass(programs);
|
|
171
250
|
this._collectFuncsPass(programs);
|
|
172
251
|
this._promoteEmbeddedMethods();
|
|
173
252
|
this._collectVarsConstsPass(programs);
|
|
174
253
|
this._checkTopDeclsPass(programs);
|
|
254
|
+
|
|
255
|
+
if (this.target === "both") {
|
|
256
|
+
this._checkMutablePackageVars(programs);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
if (this.blockers.size > 0) {
|
|
260
|
+
const summaryLine = this.getSummary();
|
|
261
|
+
this.summary = summaryLine;
|
|
262
|
+
const summaryErr = new Error(summaryLine);
|
|
263
|
+
summaryErr.isSummary = true;
|
|
264
|
+
this.errors.push(summaryErr);
|
|
265
|
+
}
|
|
266
|
+
|
|
175
267
|
return this.errors;
|
|
176
268
|
}
|
|
177
269
|
|
|
270
|
+
_checkMutablePackageVars(programs) {
|
|
271
|
+
const pkgVars = new Set();
|
|
272
|
+
for (const p of programs) {
|
|
273
|
+
for (const d of p.decls) {
|
|
274
|
+
if (d.kind === "VarDecl") {
|
|
275
|
+
for (const spec of d.decls) {
|
|
276
|
+
for (const name of spec.names) {
|
|
277
|
+
if (name !== "_") pkgVars.add(name);
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
if (pkgVars.size === 0) return;
|
|
284
|
+
|
|
285
|
+
const SCOPE_KINDS = new Set([
|
|
286
|
+
"Block",
|
|
287
|
+
"FuncLit",
|
|
288
|
+
"ForStmt",
|
|
289
|
+
"IfStmt",
|
|
290
|
+
"SwitchStmt",
|
|
291
|
+
"TypeSwitchStmt",
|
|
292
|
+
"CaseClause",
|
|
293
|
+
]);
|
|
294
|
+
|
|
295
|
+
const report = (root, node) => {
|
|
296
|
+
this.recordBlocker("mutable package variable", root);
|
|
297
|
+
this.err(
|
|
298
|
+
`package-level variable '${root}' is mutated; not allowed in 'both' packages (each target gets its own copy)`,
|
|
299
|
+
node,
|
|
300
|
+
);
|
|
301
|
+
};
|
|
302
|
+
|
|
303
|
+
const checkBody = (body, initialLocals) => {
|
|
304
|
+
const scopes = [new Set(initialLocals)];
|
|
305
|
+
const isLocal = (name) => scopes.some((s) => s.has(name));
|
|
306
|
+
const declare = (name) => {
|
|
307
|
+
if (name && name !== "_") scopes[scopes.length - 1].add(name);
|
|
308
|
+
};
|
|
309
|
+
const flag = (e, node) => {
|
|
310
|
+
const root = rootIdentName(e);
|
|
311
|
+
if (root && pkgVars.has(root) && !isLocal(root)) report(root, node);
|
|
312
|
+
};
|
|
313
|
+
|
|
314
|
+
const walk = (node) => {
|
|
315
|
+
if (!node || typeof node !== "object") return;
|
|
316
|
+
if (Array.isArray(node)) {
|
|
317
|
+
for (const item of node) walk(item);
|
|
318
|
+
return;
|
|
319
|
+
}
|
|
320
|
+
const opensScope = SCOPE_KINDS.has(node.kind);
|
|
321
|
+
if (opensScope) scopes.push(new Set());
|
|
322
|
+
switch (node.kind) {
|
|
323
|
+
case "FuncLit":
|
|
324
|
+
for (const p of node.params ?? []) declare(p.name);
|
|
325
|
+
for (const r of node.returnType?._namedReturns ?? [])
|
|
326
|
+
declare(r.name);
|
|
327
|
+
break;
|
|
328
|
+
case "VarDecl":
|
|
329
|
+
for (const spec of node.decls ?? [])
|
|
330
|
+
for (const name of spec.names ?? []) declare(name);
|
|
331
|
+
break;
|
|
332
|
+
case "DefineStmt":
|
|
333
|
+
for (const e of node.lhs ?? [])
|
|
334
|
+
if (e.kind === "Ident") declare(e.name);
|
|
335
|
+
break;
|
|
336
|
+
case "TypeSwitchStmt":
|
|
337
|
+
declare(node.assign);
|
|
338
|
+
break;
|
|
339
|
+
case "AssignStmt":
|
|
340
|
+
for (const e of node.lhs) flag(e, node);
|
|
341
|
+
break;
|
|
342
|
+
case "IncDecStmt":
|
|
343
|
+
flag(node.expr, node);
|
|
344
|
+
break;
|
|
345
|
+
case "UnaryExpr":
|
|
346
|
+
if (node.op === "&") flag(node.operand, node);
|
|
347
|
+
break;
|
|
348
|
+
case "SelectorExpr":
|
|
349
|
+
// Pointer-receiver method call or value: `G.Bump()` mutates G.
|
|
350
|
+
if (node._isMethodValue && node._type?._ptrRecv)
|
|
351
|
+
flag(node.expr, node);
|
|
352
|
+
break;
|
|
353
|
+
}
|
|
354
|
+
for (const key of Object.keys(node)) {
|
|
355
|
+
if (key.startsWith("_")) continue;
|
|
356
|
+
walk(node[key]);
|
|
357
|
+
}
|
|
358
|
+
if (opensScope) scopes.pop();
|
|
359
|
+
};
|
|
360
|
+
walk(body);
|
|
361
|
+
};
|
|
362
|
+
|
|
363
|
+
for (const p of programs) {
|
|
364
|
+
if (p._filename?.endsWith("_test.go")) continue;
|
|
365
|
+
this._currentFile = p._filename;
|
|
366
|
+
this._currentSource = p._source;
|
|
367
|
+
for (const d of p.decls) {
|
|
368
|
+
if ((d.kind === "FuncDecl" || d.kind === "MethodDecl") && d.body) {
|
|
369
|
+
if (d.name === "init" && d.kind === "FuncDecl") continue;
|
|
370
|
+
const locals = [];
|
|
371
|
+
if (d.recvName) locals.push(d.recvName);
|
|
372
|
+
for (const prm of d.params ?? []) if (prm.name) locals.push(prm.name);
|
|
373
|
+
for (const r of d.returnType?._namedReturns ?? [])
|
|
374
|
+
if (r.name) locals.push(r.name);
|
|
375
|
+
checkBody(d.body, locals);
|
|
376
|
+
} else if (d.kind === "VarDecl") {
|
|
377
|
+
// Closures in package-level initializers run after init.
|
|
378
|
+
for (const spec of d.decls ?? []) checkBody(spec.value, []);
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
|
|
178
384
|
_collectTypesPass(programs) {
|
|
179
385
|
for (const p of programs) {
|
|
180
386
|
for (const d of p.decls) {
|
|
@@ -241,6 +447,7 @@ export class TypeChecker {
|
|
|
241
447
|
if (d.kind === "FuncDecl" || d.kind === "MethodDecl")
|
|
242
448
|
this.collectFunc(d);
|
|
243
449
|
else if (d.kind === "TemplDecl") this._collectTemplDecl(d);
|
|
450
|
+
else if (d.kind === "CssDecl") this._collectCssDecl(d);
|
|
244
451
|
}
|
|
245
452
|
}
|
|
246
453
|
}
|
|
@@ -267,7 +474,11 @@ export class TypeChecker {
|
|
|
267
474
|
for (const p of programs) {
|
|
268
475
|
this._setCurrentFile(p);
|
|
269
476
|
for (const d of p.decls) {
|
|
270
|
-
if (
|
|
477
|
+
if (
|
|
478
|
+
d.kind === "FuncDecl" ||
|
|
479
|
+
d.kind === "MethodDecl" ||
|
|
480
|
+
d.kind === "TemplDecl"
|
|
481
|
+
) {
|
|
271
482
|
this.checkTopDecl(d, this.globals);
|
|
272
483
|
}
|
|
273
484
|
}
|
|
@@ -457,11 +668,75 @@ export class TypeChecker {
|
|
|
457
668
|
this.checkConstDecl(decl, scope);
|
|
458
669
|
break;
|
|
459
670
|
case "TypeDecl":
|
|
671
|
+
case "CssDecl":
|
|
672
|
+
break;
|
|
460
673
|
case "TemplDecl":
|
|
461
|
-
|
|
674
|
+
this._markTemplNodesUsed(decl.body);
|
|
675
|
+
break;
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
_markTokensUsed(tokens) {
|
|
680
|
+
if (!tokens) return;
|
|
681
|
+
for (const t of tokens) {
|
|
682
|
+
if (t.type === "IDENT" || t.type?.name === "IDENT") {
|
|
683
|
+
this.globals.lookup(t.value);
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
_markTemplNodesUsed(nodes) {
|
|
689
|
+
if (!nodes) return;
|
|
690
|
+
for (const n of nodes) {
|
|
691
|
+
if (n.kind === "TemplElement") {
|
|
692
|
+
for (const a of n.attrs || []) {
|
|
693
|
+
if (a.tokens) this._markTokensUsed(a.tokens);
|
|
694
|
+
}
|
|
695
|
+
this._markTemplNodesUsed(n.children);
|
|
696
|
+
} else if (n.kind === "TemplExpr" || n.kind === "TemplComponent") {
|
|
697
|
+
this._markTokensUsed(n.tokens);
|
|
698
|
+
} else if (n.kind === "TemplIf") {
|
|
699
|
+
this._markTokensUsed(n.condTokens);
|
|
700
|
+
this._markTemplNodesUsed(n.then);
|
|
701
|
+
this._markTemplNodesUsed(n.else_);
|
|
702
|
+
} else if (n.kind === "TemplFor") {
|
|
703
|
+
this._markTokensUsed(n.stmtTokens);
|
|
704
|
+
this._markTemplNodesUsed(n.body);
|
|
705
|
+
} else if (n.kind === "TemplSwitch") {
|
|
706
|
+
this._markTokensUsed(n.exprTokens);
|
|
707
|
+
for (const c of n.cases || []) {
|
|
708
|
+
this._markTokensUsed(c.caseTokens);
|
|
709
|
+
this._markTemplNodesUsed(c.body);
|
|
710
|
+
}
|
|
711
|
+
}
|
|
462
712
|
}
|
|
463
713
|
}
|
|
464
714
|
|
|
715
|
+
_collectCssDecl(decl) {
|
|
716
|
+
if (this.target === "wasm" || this.target === "both") {
|
|
717
|
+
this.recordBlocker("css declaration");
|
|
718
|
+
this.err("css declarations are not allowed in wasm packages", decl);
|
|
719
|
+
}
|
|
720
|
+
const paramTypes = (decl.params || []).map((p) =>
|
|
721
|
+
this.resolveTypeNode(p.type, this.globals),
|
|
722
|
+
);
|
|
723
|
+
const stringType = this.types.get("string") ?? {
|
|
724
|
+
kind: "basic",
|
|
725
|
+
name: "string",
|
|
726
|
+
};
|
|
727
|
+
const funcType = {
|
|
728
|
+
kind: "func",
|
|
729
|
+
params: paramTypes,
|
|
730
|
+
returns: [stringType],
|
|
731
|
+
variadic: false,
|
|
732
|
+
async: false,
|
|
733
|
+
};
|
|
734
|
+
if (this.globals.symbols.has(decl.name) && decl.name !== "init") {
|
|
735
|
+
this.err(`${decl.name} redeclared in this block`, decl);
|
|
736
|
+
}
|
|
737
|
+
this.globals.define(decl.name, funcType);
|
|
738
|
+
}
|
|
739
|
+
|
|
465
740
|
_collectTemplDecl(decl) {
|
|
466
741
|
const paramTypes = decl.params.map((p) =>
|
|
467
742
|
this.resolveTypeNode(p.type, this.globals),
|
|
@@ -486,6 +761,16 @@ export class TypeChecker {
|
|
|
486
761
|
}
|
|
487
762
|
|
|
488
763
|
checkFuncDecl(decl, outer) {
|
|
764
|
+
if (
|
|
765
|
+
(this.target === "wasm" || this.target === "both") &&
|
|
766
|
+
(decl.async || decl.isAsync)
|
|
767
|
+
) {
|
|
768
|
+
this.recordBlocker("async function", decl.name);
|
|
769
|
+
this.err(
|
|
770
|
+
"async functions are not supported in wasm packages; keep async code in a js package",
|
|
771
|
+
decl,
|
|
772
|
+
);
|
|
773
|
+
}
|
|
489
774
|
const inner = new Scope(outer);
|
|
490
775
|
this._injectTypeParams(decl, inner, outer);
|
|
491
776
|
for (const p of decl.params) {
|
|
@@ -497,6 +782,16 @@ export class TypeChecker {
|
|
|
497
782
|
}
|
|
498
783
|
|
|
499
784
|
checkMethodDecl(decl, outer) {
|
|
785
|
+
if (
|
|
786
|
+
(this.target === "wasm" || this.target === "both") &&
|
|
787
|
+
(decl.async || decl.isAsync)
|
|
788
|
+
) {
|
|
789
|
+
this.recordBlocker("async function", decl.name);
|
|
790
|
+
this.err(
|
|
791
|
+
"async functions are not supported in wasm packages; keep async code in a js package",
|
|
792
|
+
decl,
|
|
793
|
+
);
|
|
794
|
+
}
|
|
500
795
|
const inner = new Scope(outer);
|
|
501
796
|
const recvTypeName =
|
|
502
797
|
decl.recvType.kind === "GenericTypeName"
|
|
@@ -43,6 +43,13 @@ export const statementCheckMethods = {
|
|
|
43
43
|
this.checkExpr(stmt.expr, scope);
|
|
44
44
|
break;
|
|
45
45
|
case "DeferStmt":
|
|
46
|
+
if (this.target === "wasm" || this.target === "both") {
|
|
47
|
+
this.recordBlocker("defer statement");
|
|
48
|
+
this.err(
|
|
49
|
+
"'defer' is not yet supported in wasm packages (planned)",
|
|
50
|
+
stmt,
|
|
51
|
+
);
|
|
52
|
+
}
|
|
46
53
|
if (stmt.call.kind !== "CallExpr")
|
|
47
54
|
this.err("defer requires a function call", stmt.call);
|
|
48
55
|
this.checkExpr(stmt.call, scope);
|
|
@@ -9,6 +9,8 @@ import {
|
|
|
9
9
|
INT,
|
|
10
10
|
STRING,
|
|
11
11
|
UINT8,
|
|
12
|
+
UINT32,
|
|
13
|
+
UINT64,
|
|
12
14
|
VOID,
|
|
13
15
|
} from "../types.js";
|
|
14
16
|
|
|
@@ -428,6 +430,21 @@ export function setupCoreGlobals(globals, types) {
|
|
|
428
430
|
},
|
|
429
431
|
});
|
|
430
432
|
|
|
433
|
+
globals.define("bits", {
|
|
434
|
+
kind: "namespace",
|
|
435
|
+
name: "bits",
|
|
436
|
+
members: {
|
|
437
|
+
LeadingZeros32: { kind: "func", params: [UINT32], returns: [INT] },
|
|
438
|
+
TrailingZeros32: { kind: "func", params: [UINT32], returns: [INT] },
|
|
439
|
+
OnesCount32: { kind: "func", params: [UINT32], returns: [INT] },
|
|
440
|
+
RotateLeft32: { kind: "func", params: [UINT32, INT], returns: [UINT32] },
|
|
441
|
+
LeadingZeros64: { kind: "func", params: [UINT64], returns: [INT] },
|
|
442
|
+
TrailingZeros64: { kind: "func", params: [UINT64], returns: [INT] },
|
|
443
|
+
OnesCount64: { kind: "func", params: [UINT64], returns: [INT] },
|
|
444
|
+
RotateLeft64: { kind: "func", params: [UINT64, INT], returns: [UINT64] },
|
|
445
|
+
},
|
|
446
|
+
});
|
|
447
|
+
|
|
431
448
|
globals.define("errors", {
|
|
432
449
|
kind: "namespace",
|
|
433
450
|
name: "errors",
|
package/src/typechecker/types.js
CHANGED
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
// { kind: 'named', name: string, underlying: Type }
|
|
14
14
|
|
|
15
15
|
export class TypeCheckError extends Error {
|
|
16
|
-
constructor(msg, node, filename, sourceCode) {
|
|
16
|
+
constructor(msg, node, filename, sourceCode, hint = null) {
|
|
17
17
|
const lineNum = node?.line || node?._line;
|
|
18
18
|
const colNum = node?.col || node?._col;
|
|
19
19
|
const loc = filename
|
|
@@ -23,6 +23,7 @@ export class TypeCheckError extends Error {
|
|
|
23
23
|
: lineNum
|
|
24
24
|
? ` at line ${lineNum}:${colNum ?? 1}`
|
|
25
25
|
: "";
|
|
26
|
+
const hintText = hint ? `\n ${hint}` : "";
|
|
26
27
|
let lineContext = "";
|
|
27
28
|
if (lineNum && sourceCode) {
|
|
28
29
|
const lines = sourceCode.split("\n");
|
|
@@ -33,10 +34,63 @@ export class TypeCheckError extends Error {
|
|
|
33
34
|
lineContext = `\n${prefix}${lineStr}\n${" ".repeat(prefix.length)}${caretPad}^`;
|
|
34
35
|
}
|
|
35
36
|
}
|
|
36
|
-
super(`Type error${loc}: ${msg}${lineContext}`);
|
|
37
|
+
super(`Type error${loc}: ${msg}${hintText}${lineContext}`);
|
|
38
|
+
this.line = lineNum;
|
|
39
|
+
this.col = colNum;
|
|
37
40
|
}
|
|
38
41
|
}
|
|
39
42
|
|
|
43
|
+
export const BROWSER_GLOBALS = new Set([
|
|
44
|
+
"console",
|
|
45
|
+
"document",
|
|
46
|
+
"window",
|
|
47
|
+
"navigator",
|
|
48
|
+
"location",
|
|
49
|
+
"history",
|
|
50
|
+
"screen",
|
|
51
|
+
"performance",
|
|
52
|
+
"crypto",
|
|
53
|
+
"indexedDB",
|
|
54
|
+
"fetch",
|
|
55
|
+
"setTimeout",
|
|
56
|
+
"setInterval",
|
|
57
|
+
"clearTimeout",
|
|
58
|
+
"clearInterval",
|
|
59
|
+
"requestAnimationFrame",
|
|
60
|
+
"cancelAnimationFrame",
|
|
61
|
+
"Math",
|
|
62
|
+
"JSON",
|
|
63
|
+
"Date",
|
|
64
|
+
"RegExp",
|
|
65
|
+
"Promise",
|
|
66
|
+
"Error",
|
|
67
|
+
"Symbol",
|
|
68
|
+
"String",
|
|
69
|
+
"Number",
|
|
70
|
+
"Boolean",
|
|
71
|
+
"Array",
|
|
72
|
+
"Object",
|
|
73
|
+
"parseInt",
|
|
74
|
+
"parseFloat",
|
|
75
|
+
"isNaN",
|
|
76
|
+
"isFinite",
|
|
77
|
+
"encodeURIComponent",
|
|
78
|
+
"decodeURIComponent",
|
|
79
|
+
"atob",
|
|
80
|
+
"btoa",
|
|
81
|
+
"alert",
|
|
82
|
+
"confirm",
|
|
83
|
+
"prompt",
|
|
84
|
+
"localStorage",
|
|
85
|
+
"sessionStorage",
|
|
86
|
+
]);
|
|
87
|
+
|
|
88
|
+
// Stdlib packages the WASM backend can emit. Extend as src/backend/wasm/emit.js gains support;
|
|
89
|
+
// anything not listed is rejected at import with "not yet available in wasm packages".
|
|
90
|
+
export const WASM_SUPPORTED_STDLIB = new Set(["math", "math/bits", "testing"]);
|
|
91
|
+
|
|
92
|
+
export const VALID_TARGETS = new Set(["js", "wasm", "both"]);
|
|
93
|
+
|
|
40
94
|
// ── Static operator sets (module-level for reuse) ────────────
|
|
41
95
|
export const CMP_OPS = new Set(["==", "!=", "<", ">", "<=", ">="]);
|
|
42
96
|
export const LOG_OPS = new Set(["&&", "||"]);
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|