gofront 1.2.3 → 1.3.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/package.json +6 -4
- package/src/cli-core.js +520 -15
- package/src/codegen/expressions.js +229 -30
- package/src/codegen/index.js +38 -20
- package/src/codegen/runtime.js +14 -2
- package/src/codegen/statements.js +95 -15
- package/src/codegen/stdlib/builder.js +2 -2
- package/src/codegen/stdlib/bytes.js +1 -1
- package/src/codegen/stdlib/fmt.js +3 -4
- package/src/codegen/stdlib/io.js +1 -2
- package/src/codegen/templ.js +1 -1
- package/src/dev-server.js +279 -83
- package/src/index.js +87 -16
- package/src/parser/expressions.js +11 -0
- package/src/pwa.js +138 -0
- package/src/typechecker/assignability.js +61 -8
- package/src/typechecker/resolve.js +37 -3
- package/src/typechecker/stdlib/core.js +16 -13
- package/src/typechecker/stdlib/extended.js +11 -2
- package/src/typechecker/stdlib/web.js +418 -0
- package/src/typechecker/stdlib.js +2 -0
- package/src/typechecker/types.js +78 -17
- package/src/vendor.js +158 -43
package/src/pwa.js
ADDED
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
// Offline PWA & Service Worker Generation for GoFront.
|
|
2
|
+
// Implements zero-configuration pre-caching and service worker emission.
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
existsSync,
|
|
6
|
+
mkdirSync,
|
|
7
|
+
readdirSync,
|
|
8
|
+
readFileSync,
|
|
9
|
+
writeFileSync,
|
|
10
|
+
} from "node:fs";
|
|
11
|
+
import { join, resolve } from "node:path";
|
|
12
|
+
|
|
13
|
+
export const PWA_REGISTER_SNIPPET = `<script id="gofront-pwa">
|
|
14
|
+
if ('serviceWorker' in navigator) {
|
|
15
|
+
window.addEventListener('load', () => {
|
|
16
|
+
navigator.serviceWorker.register('./sw.js').catch(() => {});
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
</script>`;
|
|
20
|
+
|
|
21
|
+
export function collectPwaAssets(outDir) {
|
|
22
|
+
const assets = [];
|
|
23
|
+
function walk(dir, prefix = "") {
|
|
24
|
+
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
|
25
|
+
const rel = prefix ? `${prefix}/${entry.name}` : entry.name;
|
|
26
|
+
const full = join(dir, entry.name);
|
|
27
|
+
if (entry.isDirectory()) {
|
|
28
|
+
walk(full, rel);
|
|
29
|
+
} else if (entry.isFile()) {
|
|
30
|
+
if (entry.name !== "sw.js" && !entry.name.endsWith(".map")) {
|
|
31
|
+
assets.push(`./${rel}`);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
if (existsSync(outDir)) {
|
|
37
|
+
walk(outDir);
|
|
38
|
+
}
|
|
39
|
+
if (assets.includes("./index.html") && !assets.includes("./")) {
|
|
40
|
+
assets.unshift("./");
|
|
41
|
+
}
|
|
42
|
+
return assets.sort();
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function generateServiceWorkerSource(assets, version) {
|
|
46
|
+
const assetList = JSON.stringify(assets, null, 2);
|
|
47
|
+
return `// GoFront PWA Service Worker — offline cache
|
|
48
|
+
const CACHE_NAME = "gofront-${version}";
|
|
49
|
+
const ASSETS = ${assetList};
|
|
50
|
+
|
|
51
|
+
self.addEventListener("install", (event) => {
|
|
52
|
+
event.waitUntil(
|
|
53
|
+
caches.open(CACHE_NAME).then((cache) => {
|
|
54
|
+
return cache.addAll(ASSETS);
|
|
55
|
+
}).then(() => self.skipWaiting()),
|
|
56
|
+
);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
self.addEventListener("activate", (event) => {
|
|
60
|
+
event.waitUntil(
|
|
61
|
+
caches.keys().then((keys) => {
|
|
62
|
+
return Promise.all(
|
|
63
|
+
keys.map((key) => {
|
|
64
|
+
if (key !== CACHE_NAME && key.startsWith("gofront-")) {
|
|
65
|
+
return caches.delete(key);
|
|
66
|
+
}
|
|
67
|
+
}),
|
|
68
|
+
);
|
|
69
|
+
}).then(() => self.clients.claim()),
|
|
70
|
+
);
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
self.addEventListener("fetch", (event) => {
|
|
74
|
+
if (event.request.method !== "GET") return;
|
|
75
|
+
event.respondWith(
|
|
76
|
+
caches.match(event.request).then((cached) => {
|
|
77
|
+
if (cached) return cached;
|
|
78
|
+
return fetch(event.request).then((response) => {
|
|
79
|
+
if (!response || response.status !== 200 || response.type !== "basic") {
|
|
80
|
+
return response;
|
|
81
|
+
}
|
|
82
|
+
const clone = response.clone();
|
|
83
|
+
caches.open(CACHE_NAME).then((cache) => {
|
|
84
|
+
cache.put(event.request, clone);
|
|
85
|
+
});
|
|
86
|
+
return response;
|
|
87
|
+
}).catch(() => {
|
|
88
|
+
if (event.request.headers?.get?.("accept")?.includes("text/html")) {
|
|
89
|
+
return caches.match("./") || caches.match("./index.html");
|
|
90
|
+
}
|
|
91
|
+
});
|
|
92
|
+
}),
|
|
93
|
+
);
|
|
94
|
+
});
|
|
95
|
+
`;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export function injectPwaRegistration(html) {
|
|
99
|
+
if (
|
|
100
|
+
html.includes("gofront-pwa") ||
|
|
101
|
+
html.includes("navigator.serviceWorker.register")
|
|
102
|
+
) {
|
|
103
|
+
return html;
|
|
104
|
+
}
|
|
105
|
+
if (/<\/body>/i.test(html)) {
|
|
106
|
+
return html.replace(/<\/body>/i, () => `${PWA_REGISTER_SNIPPET}\n</body>`);
|
|
107
|
+
}
|
|
108
|
+
return `${html}\n${PWA_REGISTER_SNIPPET}\n`;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export function generatePwa(outDir, options = {}) {
|
|
112
|
+
const resolvedOut = resolve(outDir);
|
|
113
|
+
mkdirSync(resolvedOut, { recursive: true });
|
|
114
|
+
|
|
115
|
+
const assets = collectPwaAssets(resolvedOut);
|
|
116
|
+
const version = options.version ?? `v${Date.now().toString(36)}`;
|
|
117
|
+
const swName = options.swName ?? "sw.js";
|
|
118
|
+
const swPath = join(resolvedOut, swName);
|
|
119
|
+
|
|
120
|
+
const swSource = generateServiceWorkerSource(assets, version);
|
|
121
|
+
writeFileSync(swPath, swSource);
|
|
122
|
+
|
|
123
|
+
const indexPath = join(resolvedOut, "index.html");
|
|
124
|
+
if (existsSync(indexPath)) {
|
|
125
|
+
const html = readFileSync(indexPath, "utf8");
|
|
126
|
+
const updatedHtml = injectPwaRegistration(html);
|
|
127
|
+
if (updatedHtml !== html) {
|
|
128
|
+
writeFileSync(indexPath, updatedHtml);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
return {
|
|
133
|
+
swPath,
|
|
134
|
+
assetsCount: assets.length,
|
|
135
|
+
version,
|
|
136
|
+
assets,
|
|
137
|
+
};
|
|
138
|
+
}
|
|
@@ -7,7 +7,6 @@ import {
|
|
|
7
7
|
CMP_OPS,
|
|
8
8
|
COMPLEX128,
|
|
9
9
|
FLOAT64,
|
|
10
|
-
INT,
|
|
11
10
|
isAny,
|
|
12
11
|
isComplex,
|
|
13
12
|
isComplexOrNumeric,
|
|
@@ -17,6 +16,8 @@ import {
|
|
|
17
16
|
isString,
|
|
18
17
|
isUntyped,
|
|
19
18
|
LOG_OPS,
|
|
19
|
+
SIZED_FLOAT_NAMES,
|
|
20
|
+
SIZED_INT_NAMES,
|
|
20
21
|
STRING,
|
|
21
22
|
typeStr,
|
|
22
23
|
UNTYPED_COMPLEX,
|
|
@@ -28,8 +29,18 @@ import {
|
|
|
28
29
|
|
|
29
30
|
// Untyped constant assignability: maps source.base → Set of compatible target.name values
|
|
30
31
|
const UNTYPED_COMPAT = {
|
|
31
|
-
int: new Set([
|
|
32
|
-
|
|
32
|
+
int: new Set([
|
|
33
|
+
...SIZED_INT_NAMES,
|
|
34
|
+
...SIZED_FLOAT_NAMES,
|
|
35
|
+
"complex128",
|
|
36
|
+
"complex64",
|
|
37
|
+
]),
|
|
38
|
+
float64: new Set([
|
|
39
|
+
...SIZED_FLOAT_NAMES,
|
|
40
|
+
...SIZED_INT_NAMES,
|
|
41
|
+
"complex128",
|
|
42
|
+
"complex64",
|
|
43
|
+
]),
|
|
33
44
|
string: new Set(["string"]),
|
|
34
45
|
bool: new Set(["bool"]),
|
|
35
46
|
complex128: new Set(["complex128", "complex64"]),
|
|
@@ -92,16 +103,23 @@ export const assignabilityMethods = {
|
|
|
92
103
|
},
|
|
93
104
|
|
|
94
105
|
_binaryResultTypeNumeric(lt, rt) {
|
|
95
|
-
const
|
|
96
|
-
const
|
|
106
|
+
const lName = lt.kind === "untyped" ? lt.base : lt.name;
|
|
107
|
+
const rName = rt.kind === "untyped" ? rt.base : rt.name;
|
|
108
|
+
const lFloat = lName === "float32" || lName === "float64";
|
|
109
|
+
const rFloat = rName === "float32" || rName === "float64";
|
|
97
110
|
const isFloat = lFloat || rFloat;
|
|
98
111
|
if (lt.kind === "untyped" && rt.kind === "untyped")
|
|
99
112
|
return isFloat ? UNTYPED_FLOAT : UNTYPED_INT;
|
|
100
113
|
if (lt.kind === "untyped")
|
|
101
|
-
return isFloat && rt.name !== "float64"
|
|
114
|
+
return isFloat && rt.name !== "float64" && rt.name !== "float32"
|
|
115
|
+
? FLOAT64
|
|
116
|
+
: rt;
|
|
102
117
|
if (rt.kind === "untyped")
|
|
103
|
-
return isFloat && lt.name !== "float64"
|
|
104
|
-
|
|
118
|
+
return isFloat && lt.name !== "float64" && lt.name !== "float32"
|
|
119
|
+
? FLOAT64
|
|
120
|
+
: lt;
|
|
121
|
+
if (lt.name === "float32" && rt.name === "float32") return lt;
|
|
122
|
+
return isFloat ? FLOAT64 : lt;
|
|
105
123
|
},
|
|
106
124
|
|
|
107
125
|
_checkComplexCmpOp(op, lt, rt, node) {
|
|
@@ -151,6 +169,7 @@ export const assignabilityMethods = {
|
|
|
151
169
|
if (isUntyped(source) && this._isUntypedAssignable(target, source)) return;
|
|
152
170
|
if (this._isNumericCoercible(target, source)) return;
|
|
153
171
|
if (this._checkArrayAssignable(target, source, node)) return;
|
|
172
|
+
if (this._checkTypedArrayAssignable(target, source, node)) return;
|
|
154
173
|
if (typeStr(target) !== typeStr(source))
|
|
155
174
|
this._assertAssignableTypeMismatch(target, source, node);
|
|
156
175
|
},
|
|
@@ -222,6 +241,40 @@ export const assignabilityMethods = {
|
|
|
222
241
|
return false;
|
|
223
242
|
},
|
|
224
243
|
|
|
244
|
+
_checkTypedArrayAssignable(target, source, node) {
|
|
245
|
+
const isTa = (t) => t?.underlying?._isTypedArray || t?._isTypedArray;
|
|
246
|
+
const getSlice = (t) =>
|
|
247
|
+
t?.kind === "slice"
|
|
248
|
+
? t
|
|
249
|
+
: t?.underlying?.kind === "slice"
|
|
250
|
+
? t.underlying
|
|
251
|
+
: null;
|
|
252
|
+
const targetTa = isTa(target);
|
|
253
|
+
const sourceTa = isTa(source);
|
|
254
|
+
const targetSlice = getSlice(target);
|
|
255
|
+
const sourceSlice = getSlice(source);
|
|
256
|
+
|
|
257
|
+
if ((targetTa && sourceSlice) || (targetSlice && sourceTa)) {
|
|
258
|
+
const taElem = targetTa
|
|
259
|
+
? (target.underlying?._elemType ?? target._elemType)
|
|
260
|
+
: (source.underlying?._elemType ?? source._elemType);
|
|
261
|
+
const slElem = targetSlice ? targetSlice.elem : sourceSlice.elem;
|
|
262
|
+
if (typeStr(taElem) === typeStr(slElem)) return true;
|
|
263
|
+
this.err(`Cannot assign ${typeStr(source)} to ${typeStr(target)}`, node);
|
|
264
|
+
return true;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
if (targetTa && sourceTa) {
|
|
268
|
+
const tElem = target.underlying?._elemType ?? target._elemType;
|
|
269
|
+
const sElem = source.underlying?._elemType ?? source._elemType;
|
|
270
|
+
if (typeStr(tElem) === typeStr(sElem)) return true;
|
|
271
|
+
this.err(`Cannot assign ${typeStr(source)} to ${typeStr(target)}`, node);
|
|
272
|
+
return true;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
return false;
|
|
276
|
+
},
|
|
277
|
+
|
|
225
278
|
// ── Interface satisfaction ────────────────────────────────────────
|
|
226
279
|
|
|
227
280
|
_sigParamsMatch(reqList, actList) {
|
|
@@ -6,8 +6,10 @@ import {
|
|
|
6
6
|
BASIC_TYPES,
|
|
7
7
|
defaultType,
|
|
8
8
|
isAny,
|
|
9
|
+
isTypedArraySlice,
|
|
9
10
|
Scope,
|
|
10
11
|
TAINTED_ANY,
|
|
12
|
+
typedArrayConstructorForElem,
|
|
11
13
|
typeStr,
|
|
12
14
|
VOID,
|
|
13
15
|
} from "./types.js";
|
|
@@ -387,11 +389,17 @@ export const resolveMethods = {
|
|
|
387
389
|
return this._fieldTypeInterface(base, baseType, field, node);
|
|
388
390
|
case "namespace":
|
|
389
391
|
return this._fieldTypeNamespace(base, field, node);
|
|
392
|
+
case "slice":
|
|
393
|
+
if (isTypedArraySlice(base)) {
|
|
394
|
+
return this._fieldTypeTypedArraySlice(base, baseType, field, node);
|
|
395
|
+
}
|
|
396
|
+
break;
|
|
390
397
|
default:
|
|
391
|
-
|
|
392
|
-
return this._fieldTypeBadAccess(base, baseType, field, node);
|
|
393
|
-
return ANY;
|
|
398
|
+
break;
|
|
394
399
|
}
|
|
400
|
+
if (base && !isAny(base))
|
|
401
|
+
return this._fieldTypeBadAccess(base, baseType, field, node);
|
|
402
|
+
return ANY;
|
|
395
403
|
},
|
|
396
404
|
|
|
397
405
|
_fieldTypeStruct(base, baseType, field, node) {
|
|
@@ -402,9 +410,35 @@ export const resolveMethods = {
|
|
|
402
410
|
|
|
403
411
|
_fieldTypeInterface(base, baseType, field, node) {
|
|
404
412
|
if (base.methods?.has(field)) return base.methods.get(field);
|
|
413
|
+
if (base.fields?.has(field)) return base.fields.get(field);
|
|
414
|
+
if (base._isOpen) return ANY;
|
|
405
415
|
return this.err(`No method '${field}' on ${typeStr(baseType)}`, node);
|
|
406
416
|
},
|
|
407
417
|
|
|
418
|
+
_fieldTypeTypedArraySlice(base, baseType, field, node) {
|
|
419
|
+
const taName = typedArrayConstructorForElem(base.elem);
|
|
420
|
+
if (taName) {
|
|
421
|
+
const taType = this.types.get(taName);
|
|
422
|
+
if (taType) {
|
|
423
|
+
const underlying = taType.kind === "named" ? taType.underlying : taType;
|
|
424
|
+
if (underlying?.fields?.has(field)) return underlying.fields.get(field);
|
|
425
|
+
if (underlying?.methods?.has(field)) {
|
|
426
|
+
const m = underlying.methods.get(field);
|
|
427
|
+
if (field === "subarray" || field === "slice") {
|
|
428
|
+
return {
|
|
429
|
+
kind: "func",
|
|
430
|
+
params: m.params,
|
|
431
|
+
returns: [baseType],
|
|
432
|
+
variadic: m.variadic,
|
|
433
|
+
};
|
|
434
|
+
}
|
|
435
|
+
return m;
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
return this._fieldTypeBadAccess(base, baseType, field, node);
|
|
440
|
+
},
|
|
441
|
+
|
|
408
442
|
_fieldTypeBadAccess(base, baseType, field, node) {
|
|
409
443
|
if (base.kind === "pointer" && field === "value") return base.base ?? ANY;
|
|
410
444
|
const badKinds = ["basic", "slice", "array", "map", "func", "tuple"];
|
|
@@ -1,7 +1,16 @@
|
|
|
1
1
|
// Core standard library type definitions — browser globals, fmt, strings, bytes,
|
|
2
2
|
// strconv, sort, math, errors, time, unicode, os, slices, html, io.
|
|
3
3
|
|
|
4
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
ANY,
|
|
6
|
+
BOOL,
|
|
7
|
+
ERROR,
|
|
8
|
+
FLOAT64,
|
|
9
|
+
INT,
|
|
10
|
+
STRING,
|
|
11
|
+
UINT8,
|
|
12
|
+
VOID,
|
|
13
|
+
} from "../types.js";
|
|
5
14
|
|
|
6
15
|
const TIME_T = { kind: "named", name: "time.Time", underlying: ANY };
|
|
7
16
|
|
|
@@ -64,14 +73,8 @@ export function setupCoreGlobals(globals, types) {
|
|
|
64
73
|
"Blob",
|
|
65
74
|
"File",
|
|
66
75
|
"FileReader",
|
|
67
|
-
"ArrayBuffer",
|
|
68
|
-
"Uint8Array",
|
|
69
76
|
"TextEncoder",
|
|
70
77
|
"TextDecoder",
|
|
71
|
-
"WebGLRenderingContext",
|
|
72
|
-
"WebGL2RenderingContext",
|
|
73
|
-
"GPUDevice",
|
|
74
|
-
"GPUAdapter",
|
|
75
78
|
];
|
|
76
79
|
for (const g of browserGlobals) globals.define(g, ANY);
|
|
77
80
|
|
|
@@ -220,7 +223,7 @@ export function setupCoreGlobals(globals, types) {
|
|
|
220
223
|
},
|
|
221
224
|
});
|
|
222
225
|
|
|
223
|
-
const BYTE_SLICE = { kind: "slice", elem:
|
|
226
|
+
const BYTE_SLICE = { kind: "slice", elem: UINT8 };
|
|
224
227
|
const byFn1 = (ret) => ({
|
|
225
228
|
kind: "func",
|
|
226
229
|
params: [BYTE_SLICE],
|
|
@@ -320,13 +323,13 @@ export function setupCoreGlobals(globals, types) {
|
|
|
320
323
|
Unquote: { kind: "func", params: [STRING], returns: [STRING, ERROR] },
|
|
321
324
|
AppendInt: {
|
|
322
325
|
kind: "func",
|
|
323
|
-
params: [
|
|
324
|
-
returns: [
|
|
326
|
+
params: [BYTE_SLICE, INT, INT],
|
|
327
|
+
returns: [BYTE_SLICE],
|
|
325
328
|
},
|
|
326
329
|
AppendFloat: {
|
|
327
330
|
kind: "func",
|
|
328
|
-
params: [
|
|
329
|
-
returns: [
|
|
331
|
+
params: [BYTE_SLICE, FLOAT64, INT, INT, INT],
|
|
332
|
+
returns: [BYTE_SLICE],
|
|
330
333
|
},
|
|
331
334
|
},
|
|
332
335
|
});
|
|
@@ -570,7 +573,7 @@ export function setupCoreGlobals(globals, types) {
|
|
|
570
573
|
},
|
|
571
574
|
});
|
|
572
575
|
|
|
573
|
-
const BYTE_SLICE_IO = { kind: "slice", elem:
|
|
576
|
+
const BYTE_SLICE_IO = { kind: "slice", elem: UINT8 };
|
|
574
577
|
globals.define("io", {
|
|
575
578
|
kind: "namespace",
|
|
576
579
|
name: "io",
|
|
@@ -1,7 +1,16 @@
|
|
|
1
1
|
// Extended standard library type definitions — gom, maps, regexp, rand, utf8,
|
|
2
2
|
// path, strings.Builder / bytes.Buffer types, and built-in functions.
|
|
3
3
|
|
|
4
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
ANY,
|
|
6
|
+
BOOL,
|
|
7
|
+
ERROR,
|
|
8
|
+
FLOAT64,
|
|
9
|
+
INT,
|
|
10
|
+
STRING,
|
|
11
|
+
UINT8,
|
|
12
|
+
VOID,
|
|
13
|
+
} from "../types.js";
|
|
5
14
|
|
|
6
15
|
export function setupExtendedGlobals(globals, types) {
|
|
7
16
|
const GOM_NODE_T = {
|
|
@@ -209,7 +218,7 @@ export function setupExtendedGlobals(globals, types) {
|
|
|
209
218
|
},
|
|
210
219
|
});
|
|
211
220
|
|
|
212
|
-
const BYTE_SLICE_T = { kind: "slice", elem:
|
|
221
|
+
const BYTE_SLICE_T = { kind: "slice", elem: UINT8 };
|
|
213
222
|
const _builderBase = [
|
|
214
223
|
["WriteString", { kind: "func", params: [STRING], returns: [INT, ERROR] }],
|
|
215
224
|
["WriteByte", { kind: "func", params: [INT], returns: [ERROR] }],
|