gofront 1.7.0 → 1.9.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.
Files changed (194) hide show
  1. package/README.md +14 -14
  2. package/package.json +10 -38
  3. package/src/backend/js/expressions.js +2060 -389
  4. package/src/backend/js/index.js +586 -174
  5. package/src/backend/js/natives.js +383 -0
  6. package/src/backend/js/runtime.js +936 -1644
  7. package/src/backend/js/statements.js +1349 -132
  8. package/src/backend/js/stdlib/index.js +51 -94
  9. package/src/backend/js/templ.js +67 -118
  10. package/src/backend/js/type-info.js +436 -0
  11. package/src/backend/shared/fixed-float.js +20 -0
  12. package/src/backend/shared/fmt-types.js +80 -0
  13. package/src/backend/shared/host-unicode.js +36 -0
  14. package/src/backend/wasm/boundary.js +753 -872
  15. package/src/backend/wasm/emit-builtins.js +914 -237
  16. package/src/backend/wasm/emit-exprs.js +1456 -195
  17. package/src/backend/wasm/emit-maps.js +78 -380
  18. package/src/backend/wasm/emit-stmts.js +1659 -214
  19. package/src/backend/wasm/emit.js +614 -88
  20. package/src/backend/wasm/encode.js +43 -13
  21. package/src/backend/wasm/globals.js +491 -0
  22. package/src/backend/wasm/glue.js +2 -4
  23. package/src/backend/wasm/index.js +406 -3195
  24. package/src/backend/wasm/method-expr.js +392 -0
  25. package/src/backend/wasm/module-emitter.js +1615 -0
  26. package/src/backend/wasm/monomorph.js +164 -9
  27. package/src/backend/wasm/natives.js +596 -0
  28. package/src/backend/wasm/prescan.js +249 -0
  29. package/src/backend/wasm/prune-funcs.js +89 -0
  30. package/src/backend/wasm/stdlib/index.js +2 -37
  31. package/src/backend/wasm/stdlib/simd.js +293 -28
  32. package/src/backend/wasm/types.js +260 -27
  33. package/src/backend/wasm/variadic.js +93 -0
  34. package/src/backend/wasm/wat.js +47 -20
  35. package/src/{cli-core.js → cli/cli-core.js} +241 -11
  36. package/src/{test-runner.js → cli/test-runner.js} +188 -122
  37. package/src/{vendor.js → cli/vendor.js} +3 -4
  38. package/src/compiler.js +202 -386
  39. package/src/index.js +15 -169
  40. package/src/{lexer.js → lexer/lexer.js} +203 -62
  41. package/src/{templ-lexer.js → lexer/templ-lexer.js} +61 -18
  42. package/src/{tokens.js → lexer/tokens.js} +2 -0
  43. package/src/link/sourcemap.js +97 -0
  44. package/src/link/stdlib.js +737 -0
  45. package/src/link/type-names.js +148 -0
  46. package/src/link/wasm.js +318 -0
  47. package/src/lower/bounds.js +547 -0
  48. package/src/lower/boxing.js +1 -1
  49. package/src/lower/captures.js +267 -64
  50. package/src/lower/embedding.js +32 -3
  51. package/src/lower/errors-as.js +196 -0
  52. package/src/lower/fmt-formatters.js +905 -0
  53. package/src/lower/functions.js +2 -0
  54. package/src/lower/gen-types.js +274 -0
  55. package/src/lower/index.js +33 -19
  56. package/src/lower/json-codecs.js +1379 -0
  57. package/src/lower/ownership.js +47 -8
  58. package/src/lower/range.js +86 -1
  59. package/src/lower/reachability.js +150 -0
  60. package/src/lower/walk.js +12 -0
  61. package/src/parser/declarations.js +83 -15
  62. package/src/parser/expressions.js +110 -18
  63. package/src/parser/index.js +2 -1
  64. package/src/parser/statements.js +38 -22
  65. package/src/{templ-parser.js → parser/templ.js} +179 -17
  66. package/src/parser/types.js +70 -9
  67. package/src/resolver.js +21 -1
  68. package/src/typechecker/assignability.js +291 -19
  69. package/src/typechecker/const-fold.js +399 -0
  70. package/src/typechecker/constants.js +282 -0
  71. package/src/typechecker/expressions.js +1220 -183
  72. package/src/typechecker/index.js +659 -92
  73. package/src/typechecker/init-order.js +426 -0
  74. package/src/typechecker/operand-text.js +40 -0
  75. package/src/typechecker/resolve.js +613 -62
  76. package/src/typechecker/statements.js +212 -20
  77. package/src/typechecker/stdlib/excluded.js +902 -0
  78. package/src/typechecker/stdlib/index.js +38 -0
  79. package/src/typechecker/stdlib/simd.js +65 -15
  80. package/src/typechecker/termination.js +11 -2
  81. package/src/typechecker/types.js +268 -514
  82. package/stdlib/bytes/buffer.go +479 -0
  83. package/stdlib/bytes/bytes.go +1010 -0
  84. package/stdlib/bytes/reader.go +161 -0
  85. package/stdlib/cmp/cmp.go +65 -0
  86. package/stdlib/context/context.go +115 -0
  87. package/stdlib/crypto/rand/rand.go +34 -0
  88. package/stdlib/crypto/sha256/sha256.go +249 -0
  89. package/stdlib/encoding/base64/base64.go +331 -0
  90. package/stdlib/encoding/binary/binary.go +332 -0
  91. package/stdlib/encoding/hex/hex.go +140 -0
  92. package/stdlib/encoding/json/errors.go +31 -0
  93. package/stdlib/encoding/json/indent.go +71 -0
  94. package/stdlib/encoding/json/json.go +62 -0
  95. package/stdlib/encoding/json/number.go +12 -0
  96. package/stdlib/encoding/json/stream.go +173 -0
  97. package/stdlib/errors/errors.go +174 -0
  98. package/stdlib/fmt/errors.go +110 -0
  99. package/stdlib/fmt/format.go +732 -0
  100. package/stdlib/fmt/print.go +1232 -0
  101. package/stdlib/gom/gom.go +293 -0
  102. package/stdlib/hash/hash.go +53 -0
  103. package/stdlib/html/html.go +251 -0
  104. package/stdlib/internal/fmtrt/fmtrt.go +163 -0
  105. package/stdlib/internal/jsonrt/dec.go +871 -0
  106. package/stdlib/internal/jsonrt/enc.go +516 -0
  107. package/stdlib/internal/jsonrt/indent.go +135 -0
  108. package/stdlib/internal/jsonrt/number.go +27 -0
  109. package/stdlib/internal/jsonrt/reader.go +73 -0
  110. package/stdlib/internal/jsonrt/scanner.go +658 -0
  111. package/stdlib/io/io.go +502 -0
  112. package/stdlib/iter/iter.go +18 -0
  113. package/stdlib/maps/maps.go +131 -0
  114. package/stdlib/math/bits/bits.go +507 -0
  115. package/stdlib/math/extra.go +272 -0
  116. package/stdlib/math/math.go +439 -0
  117. package/stdlib/math/rand/rand.go +297 -0
  118. package/stdlib/os/os.go +93 -0
  119. package/stdlib/path/filepath/filepath.go +47 -0
  120. package/stdlib/path/path.go +394 -0
  121. package/stdlib/regexp/regexp.go +231 -0
  122. package/stdlib/slices/slices.go +526 -0
  123. package/stdlib/slices/sort.go +209 -0
  124. package/stdlib/slices/zsort_cmp.go +480 -0
  125. package/stdlib/slices/zsort_ordered.go +482 -0
  126. package/stdlib/sort/sort.go +500 -0
  127. package/stdlib/sort/zsort_func.go +480 -0
  128. package/stdlib/sort/zsort_iface.go +480 -0
  129. package/stdlib/strconv/float.go +1699 -0
  130. package/stdlib/strconv/isprint.go +752 -0
  131. package/stdlib/strconv/quote.go +526 -0
  132. package/stdlib/strconv/strconv.go +616 -0
  133. package/stdlib/strings/builder.go +95 -0
  134. package/stdlib/strings/reader.go +163 -0
  135. package/stdlib/strings/replacer.go +102 -0
  136. package/stdlib/strings/strings.go +807 -0
  137. package/stdlib/sync/sync.go +268 -0
  138. package/stdlib/testing/async_js.go +61 -0
  139. package/stdlib/testing/benchmark.go +297 -0
  140. package/stdlib/testing/testing.go +523 -0
  141. package/stdlib/time/duration.go +233 -0
  142. package/stdlib/time/format.go +745 -0
  143. package/stdlib/time/parse.go +790 -0
  144. package/stdlib/time/sleep_js.go +63 -0
  145. package/stdlib/time/time.go +834 -0
  146. package/stdlib/unicode/unicode.go +250 -0
  147. package/stdlib/unicode/utf8/utf8.go +464 -0
  148. package/src/backend/js/stdlib/base64.js +0 -12
  149. package/src/backend/js/stdlib/binary.js +0 -102
  150. package/src/backend/js/stdlib/bits.js +0 -46
  151. package/src/backend/js/stdlib/builder.js +0 -63
  152. package/src/backend/js/stdlib/bytes.js +0 -122
  153. package/src/backend/js/stdlib/errors.js +0 -44
  154. package/src/backend/js/stdlib/fmt.js +0 -178
  155. package/src/backend/js/stdlib/gom.js +0 -155
  156. package/src/backend/js/stdlib/hex.js +0 -17
  157. package/src/backend/js/stdlib/html.js +0 -20
  158. package/src/backend/js/stdlib/io.js +0 -137
  159. package/src/backend/js/stdlib/json.js +0 -972
  160. package/src/backend/js/stdlib/maps.js +0 -45
  161. package/src/backend/js/stdlib/math.js +0 -69
  162. package/src/backend/js/stdlib/os.js +0 -13
  163. package/src/backend/js/stdlib/path.js +0 -48
  164. package/src/backend/js/stdlib/rand.js +0 -32
  165. package/src/backend/js/stdlib/regexp.js +0 -54
  166. package/src/backend/js/stdlib/sha256.js +0 -12
  167. package/src/backend/js/stdlib/shared.js +0 -17
  168. package/src/backend/js/stdlib/simd.js +0 -24
  169. package/src/backend/js/stdlib/slices.js +0 -77
  170. package/src/backend/js/stdlib/sort.js +0 -30
  171. package/src/backend/js/stdlib/strconv.js +0 -74
  172. package/src/backend/js/stdlib/strings.js +0 -222
  173. package/src/backend/js/stdlib/sync.js +0 -13
  174. package/src/backend/js/stdlib/testing.js +0 -34
  175. package/src/backend/js/stdlib/time.js +0 -128
  176. package/src/backend/js/stdlib/unicode.js +0 -25
  177. package/src/backend/js/stdlib/utf8.js +0 -26
  178. package/src/backend/wasm/const-fold.js +0 -205
  179. package/src/backend/wasm/emit-stdlib.js +0 -13
  180. package/src/backend/wasm/stdlib/bytes.js +0 -5039
  181. package/src/backend/wasm/stdlib/core.js +0 -8903
  182. package/src/backend/wasm/stdlib/io.js +0 -2161
  183. package/src/backend/wasm/stdlib/packages.js +0 -2400
  184. package/src/backend/wasm/stdlib/pure.js +0 -720
  185. package/src/backend/wasm/stdlib/time.js +0 -1200
  186. package/src/typechecker/stdlib/core.js +0 -801
  187. package/src/typechecker/stdlib/extended.js +0 -914
  188. package/src/typechecker/stdlib.js +0 -22
  189. /package/src/{asset-manager.js → cli/asset-manager.js} +0 -0
  190. /package/src/{colors.js → cli/colors.js} +0 -0
  191. /package/src/{dev-server.js → cli/dev-server.js} +0 -0
  192. /package/src/{minifier.js → cli/minifier.js} +0 -0
  193. /package/src/{project-config.js → cli/project-config.js} +0 -0
  194. /package/src/{pwa.js → cli/pwa.js} +0 -0
package/src/compiler.js CHANGED
@@ -19,26 +19,42 @@
19
19
 
20
20
  import { readdirSync, readFileSync, statSync } from "node:fs";
21
21
  import { basename, dirname, join, relative, resolve, sep } from "node:path";
22
- import {
23
- buildSourceMap,
24
- buildWasmSourceMap,
25
- CodeGen,
26
- } from "./backend/js/index.js";
22
+ import { CodeGen } from "./backend/js/index.js";
27
23
  import { compileWasmModule, optimizeWasm } from "./backend/wasm/index.js";
28
- import { log } from "./colors.js";
24
+ import { log } from "./cli/colors.js";
29
25
  import { parseDts } from "./dts-parser.js";
30
- import { Lexer } from "./lexer.js";
31
- import { lower } from "./lower/index.js";
26
+ import { Lexer } from "./lexer/lexer.js";
27
+ import { TemplLexer } from "./lexer/templ-lexer.js";
28
+ import {
29
+ attachSourceMap,
30
+ buildWasmMap,
31
+ mergeCompilationChunks,
32
+ WASM_MAP_URL,
33
+ } from "./link/sourcemap.js";
34
+ import {
35
+ checkGeneratedGo,
36
+ finishStdlibJs,
37
+ importStdlibPackages,
38
+ mangleCollidingTypes,
39
+ renameLocalTypes,
40
+ withOriginalTypeNames,
41
+ } from "./link/stdlib.js";
42
+ import {
43
+ linkIntoBundle,
44
+ registerPackageUnit,
45
+ wasmMarker,
46
+ } from "./link/wasm.js";
47
+ import { lower, takesElementAddress } from "./lower/index.js";
32
48
  import { Parser } from "./parser/index.js";
49
+ import { TemplParser } from "./parser/templ.js";
33
50
  import {
34
51
  isBuiltinPackage,
35
52
  isLocalPath,
36
53
  resolveAll,
37
54
  resolveGwDir,
55
+ resolveStdlibSource,
38
56
  } from "./resolver.js";
39
- import { TemplLexer } from "./templ-lexer.js";
40
- import { TemplParser } from "./templ-parser.js";
41
- import { TypeCheckError, TypeChecker } from "./typechecker/index.js";
57
+ import { EXCLUDED_STDLIB_PACKAGES, TypeChecker } from "./typechecker/index.js";
42
58
  import { WASM_SUPPORTED_STDLIB } from "./typechecker/types.js";
43
59
 
44
60
  // ── Parse cache ──────────────────────────────────────────────
@@ -46,10 +62,21 @@ import { WASM_SUPPORTED_STDLIB } from "./typechecker/types.js";
46
62
  // Map<filePath, { mtime: number, ast: object }>
47
63
  const _parseCache = new Map();
48
64
 
65
+ // A cached AST keeps everything the build attached to it (checked types,
66
+ // lowered stdlib programs), so a build of a Go-source stdlib importer pins
67
+ // megabytes. The cache is therefore bounded; the least recently used file goes
68
+ // first.
69
+ const PARSE_CACHE_LIMIT = 16;
70
+
49
71
  export function clearParseCache() {
50
72
  _parseCache.clear();
51
73
  }
52
74
 
75
+ // Drops one file, e.g. a throwaway source that was deleted after its build.
76
+ export function evictParseCache(filePath) {
77
+ _parseCache.delete(resolve(filePath));
78
+ }
79
+
53
80
  export function parseCacheSize() {
54
81
  return _parseCache.size;
55
82
  }
@@ -57,9 +84,14 @@ export function parseCacheSize() {
57
84
  // ── Helpers ──────────────────────────────────────────────────
58
85
 
59
86
  function parseGoFrontFile(filePath) {
87
+ const key = resolve(filePath);
60
88
  const mtime = statSync(filePath).mtimeMs;
61
- const cached = _parseCache.get(filePath);
62
- if (cached && cached.mtime === mtime) return cached.ast;
89
+ const cached = _parseCache.get(key);
90
+ if (cached && cached.mtime === mtime) {
91
+ _parseCache.delete(key);
92
+ _parseCache.set(key, cached);
93
+ return cached.ast;
94
+ }
63
95
 
64
96
  const source = readFileSync(filePath, "utf8");
65
97
  const filename = basename(filePath);
@@ -74,7 +106,9 @@ function parseGoFrontFile(filePath) {
74
106
  ast._path = resolve(filePath);
75
107
  // compileFiles overwrites `target` with the resolved one; keep the directive.
76
108
  ast._declaredTarget = ast.target;
77
- _parseCache.set(filePath, { mtime, ast });
109
+ _parseCache.set(key, { mtime, ast });
110
+ if (_parseCache.size > PARSE_CACHE_LIMIT)
111
+ _parseCache.delete(_parseCache.keys().next().value);
78
112
  return ast;
79
113
  }
80
114
 
@@ -89,103 +123,11 @@ export function gwFilesIn(dir, { includeTests = false } = {}) {
89
123
  .map((f) => join(dir, f));
90
124
  }
91
125
 
92
- function normalizeChunk(c) {
93
- if (typeof c === "string") {
94
- return { js: c, sourceFiles: [], sourcesContent: [], mappings: [] };
95
- }
96
- return c;
97
- }
98
-
99
- function appendChunkMappings(dest, mappings, localToUnifiedIdx, lineOffset) {
100
- if (!mappings) return;
101
- for (const m of mappings) {
102
- dest.push({
103
- genLine: m.genLine + lineOffset,
104
- srcLine: m.srcLine,
105
- srcFileIdx: localToUnifiedIdx[m.srcFileIdx ?? 0] ?? 0,
106
- });
107
- }
108
- }
109
-
110
- function mergeCompilationChunks(chunks) {
111
- const activeChunks = chunks
112
- .filter(Boolean)
113
- .map(normalizeChunk)
114
- .filter((c) => c.js.length > 0);
115
-
116
- if (activeChunks.length === 0) {
117
- return { js: "", sourceFiles: [], sourcesContent: [], mappings: [] };
118
- }
119
-
120
- const unifiedSources = [];
121
- const unifiedSourcesContent = [];
122
- const fileIdxMap = new Map();
123
-
124
- function getOrAddFile(filePath, content) {
125
- const key = resolve(filePath);
126
- let idx = fileIdxMap.get(key);
127
- if (idx === undefined) {
128
- idx = unifiedSources.length;
129
- fileIdxMap.set(key, idx);
130
- unifiedSources.push(key);
131
- unifiedSourcesContent.push(content);
132
- }
133
- return idx;
134
- }
135
-
136
- const combinedJsParts = [];
137
- const mergedMappings = [];
138
- let currentLineOffset = 0;
139
-
140
- for (const chunk of activeChunks) {
141
- combinedJsParts.push(chunk.js);
142
- const localToUnifiedIdx = (chunk.sourceFiles ?? []).map((file, i) =>
143
- getOrAddFile(file, chunk.sourcesContent?.[i] ?? ""),
144
- );
145
- appendChunkMappings(
146
- mergedMappings,
147
- chunk.mappings,
148
- localToUnifiedIdx,
149
- currentLineOffset,
150
- );
151
- currentLineOffset += chunk.js.split("\n").length;
152
- }
153
-
154
- return {
155
- js: combinedJsParts.join("\n"),
156
- sourceFiles: unifiedSources,
157
- sourcesContent: unifiedSourcesContent,
158
- mappings: mergedMappings,
159
- };
160
- }
161
-
162
- function attachSourceMap(merged, outputDir) {
163
- const relativeSources = merged.sourceFiles.map((f) => relative(outputDir, f));
164
- const mapJson = buildSourceMap(
165
- relativeSources,
166
- merged.mappings,
167
- merged.sourcesContent,
168
- );
169
- const b64 = Buffer.from(mapJson).toString("base64");
170
- merged.js += `\n//# sourceMappingURL=data:application/json;base64,${b64}`;
171
- }
172
-
173
- const WASM_MAP_URL = "app.wasm.map";
174
-
175
- // Function-level `app.wasm.map` (sources relative to the output dir).
176
- function buildWasmMap(info, outputDir) {
177
- if (!info || info.mappings.length === 0) return null;
178
- return buildWasmSourceMap(
179
- info.sources.map((f) => relative(outputDir, f)),
180
- info.mappings,
181
- );
182
- }
183
-
184
126
  // Shared tail of compileFiles / compileSingleFile: link `app.wasm`, run
185
127
  // wasm-opt when requested and produce the wasm source map.
186
128
  function finishWasm(options, merged, pkgTarget, outputDir) {
187
129
  const wantMap = Boolean(options.sourceMap && !options.isDependency);
188
- let { wasm, wat, sourceMapInfo } = linkIntoBundle(
130
+ let { wasm, wat, sourceMapInfo, stringTable } = linkIntoBundle(
189
131
  options,
190
132
  merged,
191
133
  pkgTarget,
@@ -210,174 +152,19 @@ function finishWasm(options, merged, pkgTarget, outputDir) {
210
152
  wasmOptInfo = opt;
211
153
  if (opt.sourceMap) wasmSourceMap = opt.sourceMap;
212
154
  }
213
- return { wasm, wat, wasmOptInfo, wasmSourceMap };
155
+ return { wasm, wat, wasmOptInfo, wasmSourceMap, stringTable };
214
156
  }
215
157
 
216
- // ── WASM linking ──────────────────────────────────────────────
217
- //
218
- // Every `wasm`/`both` package reached during a build registers a unit in the
219
- // shared `options.wasmUnits` array. The root compile links all units into a
220
- // single `app.wasm` and splices the generated JS facade into the bundle where
221
- // the first wasm package's JS would have been.
222
-
223
- const WASM_MARKER_RE = /^\/\*__GOFRONT_WASM_UNIT:[^*]*\*\/$/m;
224
-
225
- function wasmMarker(pkgName) {
226
- return `/*__GOFRONT_WASM_UNIT:${pkgName}*/`;
227
- }
228
-
229
- function registerWasmUnit(options, unit) {
230
- const units = options.wasmUnits;
231
- if (!units) return;
232
- if (units.some((u) => u.key === unit.key)) return;
233
- units.push(unit);
234
- }
235
-
236
- // Registers a `wasm`/`both` package as a link unit and returns the target the
237
- // package's own code is emitted for. `options.wasmTests` runs a `both`
238
- // package's tests through the wasm backend (the package is then treated as
239
- // `wasm` for linking so its test functions are exposed by the facade).
240
- function registerPackageUnit(options, unit) {
241
- const { pkgTarget, programs } = unit;
242
- if (pkgTarget !== "wasm" && pkgTarget !== "both") return pkgTarget;
243
- const emitTarget =
244
- options.wasmTests && options.isTest && pkgTarget === "both"
245
- ? "wasm"
246
- : pkgTarget;
247
- registerWasmUnit(options, {
248
- ...unit,
249
- target: emitTarget,
250
- programs:
251
- emitTarget === pkgTarget
252
- ? programs
253
- : programs.map((p) => ({ ...p, target: emitTarget })),
254
- });
255
- return emitTarget;
256
- }
257
-
258
- function linkWasmUnits(
259
- units,
260
- { emitWat = false, rootTarget = "js", sourceMappingURL = null } = {},
261
- ) {
262
- if (!units.some((u) => u.target === "wasm")) return null;
263
- checkLinkCollisions(units);
264
-
265
- const types = new Map();
266
- const bundledPackages = new Set();
267
- const programs = [];
268
- for (const u of units) {
269
- for (const [k, v] of u.types) types.set(k, v);
270
- for (const b of u.bundledPackages) bundledPackages.add(b);
271
- programs.push(...u.programs);
272
- }
273
- const checker = {
274
- types,
275
- errors: [],
276
- err(msg, node) {
277
- const e = new TypeCheckError(
278
- msg,
279
- node,
280
- this._currentFile,
281
- this._currentSource,
282
- );
283
- this.errors.push(e);
284
- return e;
285
- },
286
- };
287
- const lowerRes = lower(programs, checker);
288
- return compileWasmModule(programs, checker, lowerRes, {
289
- boundary: true,
290
- bundledPackages,
291
- emitWat,
292
- callMain: rootTarget === "wasm",
293
- sourceMappingURL,
294
- });
295
- }
296
-
297
- // The linked module is one flat namespace (like the JS bundle), so a top-level
298
- // name declared by two different packages would silently resolve to whichever
299
- // was emitted last. Reject it up front.
300
- function topLevelDeclNames(decl) {
301
- switch (decl.kind) {
302
- case "FuncDecl":
303
- return [decl.name];
304
- case "MethodDecl":
305
- return [`${decl.recvType?.name ?? "?"}.${decl.name}`];
306
- case "TypeDecl":
307
- return [decl.name];
308
- case "VarDecl":
309
- case "ConstDecl":
310
- return (decl.decls ?? []).flatMap((spec) => spec.names ?? []);
311
- default:
312
- return [];
313
- }
314
- }
315
-
316
- function checkLinkCollisions(units) {
317
- const owner = new Map(); // name -> pkgName
318
- const errors = [];
319
- for (const u of units) {
320
- for (const p of u.programs) {
321
- for (const d of p.decls ?? []) {
322
- for (const name of topLevelDeclNames(d)) {
323
- if (name === "_") continue;
324
- const prev = owner.get(name);
325
- if (prev === undefined) owner.set(name, u.pkgName);
326
- else if (prev !== u.pkgName)
327
- errors.push(
328
- `cannot link wasm packages: '${name}' is declared in both '${prev}' and '${u.pkgName}' (linked wasm packages share one namespace)`,
329
- );
330
- }
331
- }
332
- }
333
- }
334
- if (errors.length > 0) throw new Error([...new Set(errors)].join("\n"));
335
- }
336
-
337
- // Replaces the first wasm marker with the facade (dropping the others) and
338
- // shifts source-map lines that follow it.
339
- function spliceFacade(merged, facade) {
340
- const lines = merged.js.split("\n");
341
- const first = lines.findIndex((l) => WASM_MARKER_RE.test(l));
342
- if (first < 0) {
343
- merged.js = `${facade}\n${merged.js}`;
344
- const shift = facade.split("\n").length;
345
- for (const m of merged.mappings) m.genLine += shift;
346
- return;
347
- }
348
- const facadeLines = facade.split("\n");
349
- const delta = facadeLines.length - 1;
350
- for (const m of merged.mappings) {
351
- if (m.genLine > first) m.genLine += delta;
352
- }
353
- const out = [...lines.slice(0, first), ...facadeLines];
354
- for (let i = first + 1; i < lines.length; i++) {
355
- if (!WASM_MARKER_RE.test(lines[i])) out.push(lines[i]);
356
- }
357
- merged.js = out.join("\n");
358
- }
359
-
360
- // Root compiles only: link every registered unit into app.wasm and splice the
361
- // facade into the merged bundle. Returns `{ wasm, wat, sourceMapInfo }`
362
- // (nulls when there is nothing to link).
363
- function linkIntoBundle(options, merged, pkgTarget, { sourceMappingURL } = {}) {
364
- const none = { wasm: null, wat: null, sourceMapInfo: null };
365
- if (options.isDependency) return none;
366
- const linked = linkWasmUnits(options.wasmUnits, {
367
- emitWat: options.emitWat,
368
- rootTarget: pkgTarget,
369
- sourceMappingURL,
370
- });
371
- if (!linked) return none;
372
- spliceFacade(merged, linked.facade);
373
- return {
374
- wasm: linked.wasm,
375
- wat: linked.wat ?? null,
376
- sourceMapInfo: linked.sourceMapInfo ?? null,
377
- };
158
+ // Prints non-fatal diagnostics (e.g. JS Number precision of a constant).
159
+ function logWarnings(warnings) {
160
+ for (const w of warnings) log.warn(w);
378
161
  }
379
162
 
380
163
  // ── Import resolution ─────────────────────────────────────────
164
+
165
+ // What link/stdlib.js needs from this module. Injected rather than imported
166
+ // so that link/stdlib.js does not depend on compiler.js.
167
+ const STDLIB_DEPS = { gwFilesIn, resolveImports, TypeChecker };
381
168
  //
382
169
  // Shared by compileFiles (multi-file) and the single-file path in index.js.
383
170
  // Mutates checker, jsImports, bundledPackages, and preambles in place.
@@ -417,18 +204,17 @@ function resolveImports(
417
204
  "js: imports are not allowed in wasm packages",
418
205
  impNode,
419
206
  );
420
- } else if (path === "gom") {
421
- checker.recordBlocker("gom usage", "gom");
422
- checker.err(
423
- "package 'gom' is not available in wasm packages",
424
- impNode,
425
- );
426
207
  } else if (path === "gofront/shared" && pkgTarget === "both") {
427
208
  checker.err(
428
209
  "'gofront/shared' requires a wasm-only package (//gofront:target wasm): a both package has no linear memory on the JS side",
429
210
  impNode,
430
211
  );
431
- } else if (!isLocalPath(path)) {
212
+ } else if (
213
+ !isLocalPath(path) &&
214
+ !EXCLUDED_STDLIB_PACKAGES.has(path) &&
215
+ (isBuiltinPackage(path) ||
216
+ !resolveStdlibSource(path, options.stdlibRoot))
217
+ ) {
432
218
  if (!isBuiltinPackage(path)) {
433
219
  checker.recordBlocker("js: import", path);
434
220
  checker.err(
@@ -469,13 +255,27 @@ function resolveImports(
469
255
  ...imp,
470
256
  imports: imp.imports.filter(({ alias }) => alias !== "_"),
471
257
  }));
472
- const resolved = resolveAll(allImportsNoSideEffect, fromFile, parseDts);
258
+ const resolved = resolveAll(
259
+ allImportsNoSideEffect,
260
+ fromFile,
261
+ parseDts,
262
+ options.stdlibRoot,
263
+ );
473
264
  for (const [path, info] of resolved) {
474
265
  if (!info) continue;
475
266
  checker.addDefinitions(info.types, info.values);
476
267
  jsImports.set(path, [...info.values.keys()]);
477
268
  }
478
269
 
270
+ importStdlibPackages(
271
+ programs,
272
+ checker,
273
+ preambles,
274
+ bundledDirs,
275
+ options,
276
+ STDLIB_DEPS,
277
+ );
278
+
479
279
  // local GoFront packages (./subdir)
480
280
  const seenLocalPaths = new Set();
481
281
  for (const p of programs) {
@@ -501,6 +301,9 @@ function resolveImports(
501
301
  isDependency: true,
502
302
  });
503
303
  if (!alreadyBundled) preambles.push(dep);
304
+ // A dependency that hands out `&xs[i]` makes whole-element
305
+ // stores in this package write in place too.
306
+ if (dep.elemAddrTaken) checker.depElemAddrTaken = true;
504
307
 
505
308
  // Target import rules for local packages
506
309
  const impNode = {
@@ -541,7 +344,10 @@ function resolveImports(
541
344
 
542
345
  if (alias === "_") continue;
543
346
  if (alias === ".") {
544
- checker.addDefinitions(dep.exportedTypes, dep.exportedSymbols);
347
+ checker.addDefinitions(dep.exportedTypes, dep.exportedSymbols, {
348
+ external: false,
349
+ consts: dep.exportedConsts,
350
+ });
545
351
  continue;
546
352
  }
547
353
  const nameUsed = alias ?? dep.pkgName;
@@ -551,6 +357,7 @@ function resolveImports(
551
357
  dep.exportedSymbols,
552
358
  dep.exportedTypes,
553
359
  dep.target,
360
+ dep.exportedConsts,
554
361
  );
555
362
  checker.trackImport(nameUsed, { _line }, p._filename, p._source);
556
363
  }
@@ -584,112 +391,29 @@ export function resolvePackageTarget(declared, pkgDir, options = {}) {
584
391
  }
585
392
 
586
393
  export function compileSingleFile(inputPath, options = {}) {
587
- const {
588
- sourceMap = false,
589
- outputFile = null,
590
- dumpTokens = false,
591
- dumpAst = false,
592
- } = options;
593
- options = { ...options, wasmUnits: options.wasmUnits ?? [] };
594
-
595
- let source;
596
- try {
597
- source = readFileSync(inputPath, "utf8");
598
- } catch (e) {
599
- throw new Error(`cannot read '${inputPath}': ${e.message}`);
600
- }
394
+ const { outputFile = null, dumpTokens = false, dumpAst = false } = options;
601
395
 
602
- const tokens = new Lexer(source, basename(inputPath)).tokenize();
603
- if (dumpTokens) return { tokens };
604
-
605
- const ast = new Parser(tokens, basename(inputPath), source).parse();
606
- ast._source = source;
607
- ast._path = resolve(inputPath);
608
- if (dumpAst) return { ast };
609
-
610
- const outputDir =
611
- options.outputDir ??
612
- (outputFile ? dirname(resolve(outputFile)) : resolve("."));
613
-
614
- const checker = new TypeChecker();
615
- const jsImports = new Map();
616
- const bundledPackages = new Set();
617
- const preambles = [];
618
-
619
- const pkgTarget = resolvePackageTarget(
620
- ast.target ?? options.target ?? "js",
621
- dirname(resolve(inputPath)),
622
- options,
623
- );
624
- ast.target = pkgTarget;
625
- checker.target = pkgTarget;
626
- checker.pkgName = ast.pkg?.name ?? "main";
627
-
628
- resolveImports(
629
- [ast],
630
- inputPath,
631
- checker,
632
- jsImports,
633
- bundledPackages,
634
- preambles,
635
- options.bundledDirs ?? new Set(),
636
- { ...options, outputDir },
637
- );
638
-
639
- const errors = checker.check(ast);
640
- checker.reportUnusedImports();
641
- if (errors.length > 0) {
642
- throw new Error(errors.map((e) => e.message).join("\n"));
643
- }
644
- for (const w of checker.warnings) log.warn(w);
645
-
646
- if (pkgTarget === "wasm" || pkgTarget === "both") {
647
- registerWasmUnit(options, {
648
- key: resolve(inputPath),
649
- pkgName: checker.pkgName,
650
- target: pkgTarget,
651
- programs: [ast],
652
- types: checker.types,
653
- bundledPackages,
654
- });
396
+ if (dumpTokens || dumpAst) {
397
+ let source;
398
+ try {
399
+ source = readFileSync(inputPath, "utf8");
400
+ } catch (e) {
401
+ throw new Error(`cannot read '${inputPath}': ${e.message}`);
402
+ }
403
+ const tokens = new Lexer(source, basename(inputPath)).tokenize();
404
+ if (dumpTokens) return { tokens };
405
+ const ast = new Parser(tokens, basename(inputPath), source).parse();
406
+ ast._source = source;
407
+ ast._path = resolve(inputPath);
408
+ return { ast };
655
409
  }
656
410
 
657
- const isStrict = Boolean(options.strict || pkgTarget === "both");
658
- const cg = new CodeGen(checker, jsImports, bundledPackages, {
659
- target: pkgTarget,
660
- strict: isStrict,
411
+ return compileFiles([resolve(inputPath)], {
412
+ ...options,
413
+ outputDir:
414
+ options.outputDir ??
415
+ (outputFile ? dirname(resolve(outputFile)) : resolve(".")),
661
416
  });
662
- const mainJs =
663
- pkgTarget === "wasm" ? wasmMarker(checker.pkgName) : cg.generate(ast);
664
-
665
- const mainChunk = {
666
- js: mainJs,
667
- sourceFiles: [resolve(inputPath)],
668
- sourcesContent: [source],
669
- mappings: pkgTarget === "wasm" ? [] : cg.getMappings(),
670
- };
671
-
672
- const merged = mergeCompilationChunks([...preambles, mainChunk]);
673
- const { wasm, wat, wasmOptInfo, wasmSourceMap } = finishWasm(
674
- { ...options, sourceMap },
675
- merged,
676
- pkgTarget,
677
- outputDir,
678
- );
679
-
680
- if (sourceMap && !options.isDependency) {
681
- attachSourceMap(merged, outputDir);
682
- }
683
-
684
- return {
685
- js: merged.js,
686
- css: cg.getCss(),
687
- target: pkgTarget,
688
- wasm,
689
- wat,
690
- wasmOptInfo,
691
- wasmSourceMap,
692
- };
693
417
  }
694
418
 
695
419
  export function compileDir(dir, options = {}) {
@@ -716,7 +440,12 @@ export function compileFiles(files, options = {}) {
716
440
  options = {
717
441
  ...options,
718
442
  wasmUnits: options.wasmUnits ?? [],
443
+ stdlibCache: options.stdlibCache ?? new Map(),
444
+ jsRootPrograms: options.jsRootPrograms ?? [],
719
445
  rootDir: options.rootDir ?? fromDir,
446
+ bundledDirs: options.bundledDirs ?? new Set(),
447
+ typeOwners: options.typeOwners ?? new Map(),
448
+ jsonBuild: options.jsonBuild ?? { js: false, wasm: false },
720
449
  };
721
450
  const outputDir =
722
451
  options.outputDir ??
@@ -744,6 +473,15 @@ export function compileFiles(files, options = {}) {
744
473
  );
745
474
  const pkgName = pkgNames[0];
746
475
 
476
+ // Local packages share one flat JS module: a type name another package
477
+ // already declared is renamed here (`Pt` -> `Pt$$b`).
478
+ const renamedTypes = mangleLocalTypes(
479
+ programs,
480
+ resolve(fromDir),
481
+ pkgName,
482
+ options.typeOwners,
483
+ );
484
+
747
485
  // Validate target directives across files in this package
748
486
  const declaredTargets = new Map();
749
487
  for (const p of programs) {
@@ -770,6 +508,11 @@ export function compileFiles(files, options = {}) {
770
508
  );
771
509
  for (const p of programs) p.target = pkgTarget;
772
510
 
511
+ // The test runner's driver is a source that exists only in memory: it is
512
+ // parsed and checked with the package but never written next to it.
513
+ const driver = testDriverProgram(options, programs, pkgTarget, fromDir);
514
+ if (driver) programs.push(driver);
515
+
773
516
  // ── 2. Resolve imports ────────────────────────────────────────
774
517
  const checker = new TypeChecker();
775
518
  checker.target = pkgTarget;
@@ -792,6 +535,13 @@ export function compileFiles(files, options = {}) {
792
535
 
793
536
  // ── 3. Type-check ─────────────────────────────────────────────
794
537
  const errors = checker.checkAll(programs);
538
+ const codecs = checkGeneratedGo(
539
+ programs,
540
+ checker,
541
+ preambles,
542
+ options,
543
+ STDLIB_DEPS,
544
+ );
795
545
  checker.reportUnusedImports();
796
546
  if (errors.length > 0) {
797
547
  const msgs = errors.map((e) => e.message).join("\n");
@@ -804,7 +554,7 @@ export function compileFiles(files, options = {}) {
804
554
  key: resolve(fromDir),
805
555
  pkgName,
806
556
  pkgTarget,
807
- programs,
557
+ programs: [...programs, ...codecs],
808
558
  types: checker.types,
809
559
  bundledPackages,
810
560
  });
@@ -813,13 +563,15 @@ export function compileFiles(files, options = {}) {
813
563
  const codegen = new CodeGen(checker, jsImports, bundledPackages, {
814
564
  target: pkgTarget,
815
565
  strict: isStrict,
566
+ natives: options.natives,
816
567
  });
817
568
  const mainJs =
818
569
  emitTarget === "wasm"
819
570
  ? wasmMarker(pkgName)
820
- : codegen.generateAll(programs, {
571
+ : codegen.generateAll([...programs, ...codecs], {
821
572
  isTest: options.isTest ?? false,
822
573
  });
574
+ logWarnings(codegen.warnings);
823
575
 
824
576
  const mainChunk = {
825
577
  js: mainJs,
@@ -829,7 +581,8 @@ export function compileFiles(files, options = {}) {
829
581
  };
830
582
 
831
583
  const merged = mergeCompilationChunks([...preambles, mainChunk]);
832
- const { wasm, wat, wasmOptInfo, wasmSourceMap } = finishWasm(
584
+ finishStdlibJs(merged, options, [...programs, ...codecs], emitTarget);
585
+ const { wasm, wat, wasmOptInfo, wasmSourceMap, stringTable } = finishWasm(
833
586
  options,
834
587
  merged,
835
588
  pkgTarget,
@@ -848,17 +601,80 @@ export function compileFiles(files, options = {}) {
848
601
  return {
849
602
  pkgName,
850
603
  target: pkgTarget,
604
+ elemAddrTaken:
605
+ checker.depElemAddrTaken === true ||
606
+ programs.some((p) => takesElementAddress(p.decls)),
851
607
  js: merged.js,
852
608
  css: allCss,
853
609
  wasm,
854
610
  wat,
855
611
  wasmOptInfo,
856
612
  wasmSourceMap,
857
- programs,
613
+ stringTable,
614
+ programs: driver ? programs.filter((p) => p !== driver) : programs,
858
615
  exportedSymbols: checker.getExportedSymbols(),
859
- exportedTypes: checker.getExportedTypes(),
616
+ exportedTypes: withOriginalTypeNames(
617
+ checker.getExportedTypes(),
618
+ renamedTypes,
619
+ ),
620
+ exportedConsts: checker.getExportedConsts(),
860
621
  sourceFiles: merged.sourceFiles,
861
622
  sourcesContent: merged.sourcesContent,
862
623
  mappings: merged.mappings,
863
624
  };
864
625
  }
626
+
627
+ // Parses the in-memory driver source `options.testDriver(programs, target)`
628
+ // returns (null: no driver) as a `_test.go` file of the package.
629
+ function testDriverProgram(options, programs, pkgTarget, fromDir) {
630
+ if (!options.testDriver || options.isDependency) return null;
631
+ const source = options.testDriver(programs, pkgTarget);
632
+ if (!source) return null;
633
+ const filename = "gofront_testmain_test.go";
634
+ const ast = new Parser(
635
+ new Lexer(source, filename).tokenize(),
636
+ filename,
637
+ source,
638
+ ).parse();
639
+ ast._source = source;
640
+ ast._path = join(fromDir, filename);
641
+ ast._declaredTarget = ast.target;
642
+ ast.target = pkgTarget;
643
+ return ast;
644
+ }
645
+
646
+ // The renamed ASTs must not be reused from the parse cache by a later build.
647
+ function mangleLocalTypes(programs, pkgKey, pkgName, owners) {
648
+ const renamed = mangleCollidingTypes(programs, pkgKey, pkgName, owners);
649
+ const locals = renameLocalTypes(programs);
650
+ if (renamed.size > 0 || locals)
651
+ for (const p of programs) _parseCache.delete(p._path);
652
+ return renamed;
653
+ }
654
+
655
+ export function compileWasm(source, options = {}) {
656
+ const filename = options.filename ?? "main.go";
657
+ const tokens = new Lexer(source, filename).tokenize();
658
+ const ast = new Parser(tokens, filename, source).parse();
659
+
660
+ const checker = new TypeChecker();
661
+ checker.target = "wasm";
662
+ checker.pkgName = ast.pkg?.name ?? "main";
663
+
664
+ const errors = checker.check(ast);
665
+ if (errors.length > 0) {
666
+ return { wasm: null, errors };
667
+ }
668
+
669
+ const lowerRes = lower([ast], checker);
670
+ const res = compileWasmModule([ast], checker, lowerRes, {
671
+ ...options,
672
+ exportAll: options.exportAll ?? true,
673
+ });
674
+
675
+ if (checker.errors?.length > 0) {
676
+ return { wasm: null, errors: checker.errors };
677
+ }
678
+
679
+ return { ...res, errors: [] };
680
+ }