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.
- package/README.md +14 -14
- package/package.json +10 -38
- package/src/backend/js/expressions.js +2060 -389
- package/src/backend/js/index.js +586 -174
- package/src/backend/js/natives.js +383 -0
- package/src/backend/js/runtime.js +936 -1644
- package/src/backend/js/statements.js +1349 -132
- package/src/backend/js/stdlib/index.js +51 -94
- package/src/backend/js/templ.js +67 -118
- package/src/backend/js/type-info.js +436 -0
- package/src/backend/shared/fixed-float.js +20 -0
- package/src/backend/shared/fmt-types.js +80 -0
- package/src/backend/shared/host-unicode.js +36 -0
- package/src/backend/wasm/boundary.js +753 -872
- package/src/backend/wasm/emit-builtins.js +914 -237
- package/src/backend/wasm/emit-exprs.js +1456 -195
- package/src/backend/wasm/emit-maps.js +78 -380
- package/src/backend/wasm/emit-stmts.js +1659 -214
- package/src/backend/wasm/emit.js +614 -88
- package/src/backend/wasm/encode.js +43 -13
- package/src/backend/wasm/globals.js +491 -0
- package/src/backend/wasm/glue.js +2 -4
- package/src/backend/wasm/index.js +406 -3195
- package/src/backend/wasm/method-expr.js +392 -0
- package/src/backend/wasm/module-emitter.js +1615 -0
- package/src/backend/wasm/monomorph.js +164 -9
- package/src/backend/wasm/natives.js +596 -0
- package/src/backend/wasm/prescan.js +249 -0
- package/src/backend/wasm/prune-funcs.js +89 -0
- package/src/backend/wasm/stdlib/index.js +2 -37
- package/src/backend/wasm/stdlib/simd.js +293 -28
- package/src/backend/wasm/types.js +260 -27
- package/src/backend/wasm/variadic.js +93 -0
- package/src/backend/wasm/wat.js +47 -20
- package/src/{cli-core.js → cli/cli-core.js} +241 -11
- package/src/{test-runner.js → cli/test-runner.js} +188 -122
- package/src/{vendor.js → cli/vendor.js} +3 -4
- package/src/compiler.js +202 -386
- package/src/index.js +15 -169
- package/src/{lexer.js → lexer/lexer.js} +203 -62
- package/src/{templ-lexer.js → lexer/templ-lexer.js} +61 -18
- package/src/{tokens.js → lexer/tokens.js} +2 -0
- package/src/link/sourcemap.js +97 -0
- package/src/link/stdlib.js +737 -0
- package/src/link/type-names.js +148 -0
- package/src/link/wasm.js +318 -0
- package/src/lower/bounds.js +547 -0
- package/src/lower/boxing.js +1 -1
- package/src/lower/captures.js +267 -64
- package/src/lower/embedding.js +32 -3
- package/src/lower/errors-as.js +196 -0
- package/src/lower/fmt-formatters.js +905 -0
- package/src/lower/functions.js +2 -0
- package/src/lower/gen-types.js +274 -0
- package/src/lower/index.js +33 -19
- package/src/lower/json-codecs.js +1379 -0
- package/src/lower/ownership.js +47 -8
- package/src/lower/range.js +86 -1
- package/src/lower/reachability.js +150 -0
- package/src/lower/walk.js +12 -0
- package/src/parser/declarations.js +83 -15
- package/src/parser/expressions.js +110 -18
- package/src/parser/index.js +2 -1
- package/src/parser/statements.js +38 -22
- package/src/{templ-parser.js → parser/templ.js} +179 -17
- package/src/parser/types.js +70 -9
- package/src/resolver.js +21 -1
- package/src/typechecker/assignability.js +291 -19
- package/src/typechecker/const-fold.js +399 -0
- package/src/typechecker/constants.js +282 -0
- package/src/typechecker/expressions.js +1220 -183
- package/src/typechecker/index.js +659 -92
- package/src/typechecker/init-order.js +426 -0
- package/src/typechecker/operand-text.js +40 -0
- package/src/typechecker/resolve.js +613 -62
- package/src/typechecker/statements.js +212 -20
- package/src/typechecker/stdlib/excluded.js +902 -0
- package/src/typechecker/stdlib/index.js +38 -0
- package/src/typechecker/stdlib/simd.js +65 -15
- package/src/typechecker/termination.js +11 -2
- package/src/typechecker/types.js +268 -514
- package/stdlib/bytes/buffer.go +479 -0
- package/stdlib/bytes/bytes.go +1010 -0
- package/stdlib/bytes/reader.go +161 -0
- package/stdlib/cmp/cmp.go +65 -0
- package/stdlib/context/context.go +115 -0
- package/stdlib/crypto/rand/rand.go +34 -0
- package/stdlib/crypto/sha256/sha256.go +249 -0
- package/stdlib/encoding/base64/base64.go +331 -0
- package/stdlib/encoding/binary/binary.go +332 -0
- package/stdlib/encoding/hex/hex.go +140 -0
- package/stdlib/encoding/json/errors.go +31 -0
- package/stdlib/encoding/json/indent.go +71 -0
- package/stdlib/encoding/json/json.go +62 -0
- package/stdlib/encoding/json/number.go +12 -0
- package/stdlib/encoding/json/stream.go +173 -0
- package/stdlib/errors/errors.go +174 -0
- package/stdlib/fmt/errors.go +110 -0
- package/stdlib/fmt/format.go +732 -0
- package/stdlib/fmt/print.go +1232 -0
- package/stdlib/gom/gom.go +293 -0
- package/stdlib/hash/hash.go +53 -0
- package/stdlib/html/html.go +251 -0
- package/stdlib/internal/fmtrt/fmtrt.go +163 -0
- package/stdlib/internal/jsonrt/dec.go +871 -0
- package/stdlib/internal/jsonrt/enc.go +516 -0
- package/stdlib/internal/jsonrt/indent.go +135 -0
- package/stdlib/internal/jsonrt/number.go +27 -0
- package/stdlib/internal/jsonrt/reader.go +73 -0
- package/stdlib/internal/jsonrt/scanner.go +658 -0
- package/stdlib/io/io.go +502 -0
- package/stdlib/iter/iter.go +18 -0
- package/stdlib/maps/maps.go +131 -0
- package/stdlib/math/bits/bits.go +507 -0
- package/stdlib/math/extra.go +272 -0
- package/stdlib/math/math.go +439 -0
- package/stdlib/math/rand/rand.go +297 -0
- package/stdlib/os/os.go +93 -0
- package/stdlib/path/filepath/filepath.go +47 -0
- package/stdlib/path/path.go +394 -0
- package/stdlib/regexp/regexp.go +231 -0
- package/stdlib/slices/slices.go +526 -0
- package/stdlib/slices/sort.go +209 -0
- package/stdlib/slices/zsort_cmp.go +480 -0
- package/stdlib/slices/zsort_ordered.go +482 -0
- package/stdlib/sort/sort.go +500 -0
- package/stdlib/sort/zsort_func.go +480 -0
- package/stdlib/sort/zsort_iface.go +480 -0
- package/stdlib/strconv/float.go +1699 -0
- package/stdlib/strconv/isprint.go +752 -0
- package/stdlib/strconv/quote.go +526 -0
- package/stdlib/strconv/strconv.go +616 -0
- package/stdlib/strings/builder.go +95 -0
- package/stdlib/strings/reader.go +163 -0
- package/stdlib/strings/replacer.go +102 -0
- package/stdlib/strings/strings.go +807 -0
- package/stdlib/sync/sync.go +268 -0
- package/stdlib/testing/async_js.go +61 -0
- package/stdlib/testing/benchmark.go +297 -0
- package/stdlib/testing/testing.go +523 -0
- package/stdlib/time/duration.go +233 -0
- package/stdlib/time/format.go +745 -0
- package/stdlib/time/parse.go +790 -0
- package/stdlib/time/sleep_js.go +63 -0
- package/stdlib/time/time.go +834 -0
- package/stdlib/unicode/unicode.go +250 -0
- package/stdlib/unicode/utf8/utf8.go +464 -0
- package/src/backend/js/stdlib/base64.js +0 -12
- package/src/backend/js/stdlib/binary.js +0 -102
- package/src/backend/js/stdlib/bits.js +0 -46
- package/src/backend/js/stdlib/builder.js +0 -63
- package/src/backend/js/stdlib/bytes.js +0 -122
- package/src/backend/js/stdlib/errors.js +0 -44
- package/src/backend/js/stdlib/fmt.js +0 -178
- package/src/backend/js/stdlib/gom.js +0 -155
- package/src/backend/js/stdlib/hex.js +0 -17
- package/src/backend/js/stdlib/html.js +0 -20
- package/src/backend/js/stdlib/io.js +0 -137
- package/src/backend/js/stdlib/json.js +0 -972
- package/src/backend/js/stdlib/maps.js +0 -45
- package/src/backend/js/stdlib/math.js +0 -69
- package/src/backend/js/stdlib/os.js +0 -13
- package/src/backend/js/stdlib/path.js +0 -48
- package/src/backend/js/stdlib/rand.js +0 -32
- package/src/backend/js/stdlib/regexp.js +0 -54
- package/src/backend/js/stdlib/sha256.js +0 -12
- package/src/backend/js/stdlib/shared.js +0 -17
- package/src/backend/js/stdlib/simd.js +0 -24
- package/src/backend/js/stdlib/slices.js +0 -77
- package/src/backend/js/stdlib/sort.js +0 -30
- package/src/backend/js/stdlib/strconv.js +0 -74
- package/src/backend/js/stdlib/strings.js +0 -222
- package/src/backend/js/stdlib/sync.js +0 -13
- package/src/backend/js/stdlib/testing.js +0 -34
- package/src/backend/js/stdlib/time.js +0 -128
- package/src/backend/js/stdlib/unicode.js +0 -25
- package/src/backend/js/stdlib/utf8.js +0 -26
- package/src/backend/wasm/const-fold.js +0 -205
- package/src/backend/wasm/emit-stdlib.js +0 -13
- package/src/backend/wasm/stdlib/bytes.js +0 -5039
- package/src/backend/wasm/stdlib/core.js +0 -8903
- package/src/backend/wasm/stdlib/io.js +0 -2161
- package/src/backend/wasm/stdlib/packages.js +0 -2400
- package/src/backend/wasm/stdlib/pure.js +0 -720
- package/src/backend/wasm/stdlib/time.js +0 -1200
- package/src/typechecker/stdlib/core.js +0 -801
- package/src/typechecker/stdlib/extended.js +0 -914
- package/src/typechecker/stdlib.js +0 -22
- /package/src/{asset-manager.js → cli/asset-manager.js} +0 -0
- /package/src/{colors.js → cli/colors.js} +0 -0
- /package/src/{dev-server.js → cli/dev-server.js} +0 -0
- /package/src/{minifier.js → cli/minifier.js} +0 -0
- /package/src/{project-config.js → cli/project-config.js} +0 -0
- /package/src/{pwa.js → cli/pwa.js} +0 -0
package/src/index.js
CHANGED
|
@@ -14,30 +14,30 @@
|
|
|
14
14
|
// gofront init [dir] — scaffold a new GoFront project
|
|
15
15
|
|
|
16
16
|
import { statSync } from "node:fs";
|
|
17
|
-
import { dirname,
|
|
18
|
-
import { copyAssets } from "./asset-manager.js";
|
|
17
|
+
import { dirname, resolve } from "node:path";
|
|
19
18
|
import {
|
|
20
|
-
|
|
19
|
+
devReporters,
|
|
21
20
|
formatPrepSummary,
|
|
22
21
|
formatWrittenDesc,
|
|
23
22
|
GOFRONT_VERSION,
|
|
23
|
+
HELP_TEXT,
|
|
24
24
|
handleBuild,
|
|
25
25
|
handleCheck,
|
|
26
26
|
handleDev,
|
|
27
27
|
handleInit,
|
|
28
28
|
handlePrep,
|
|
29
29
|
handleTest,
|
|
30
|
-
maybeMinify,
|
|
31
30
|
parseBuildArgs,
|
|
32
31
|
parseCheckArgs,
|
|
33
32
|
parseDevArgs,
|
|
34
33
|
parseLegacyArgs,
|
|
35
34
|
parsePrepArgs,
|
|
36
35
|
parseTestArgs,
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
36
|
+
reportBuild,
|
|
37
|
+
reportCheck,
|
|
38
|
+
runSingleShot,
|
|
39
|
+
} from "./cli/cli-core.js";
|
|
40
|
+
import { colors, log, stamp } from "./cli/colors.js";
|
|
41
41
|
|
|
42
42
|
// ── Parse CLI args ───────────────────────────────────────────
|
|
43
43
|
|
|
@@ -48,35 +48,7 @@ if (args[0] === "--version" || args[0] === "-v") {
|
|
|
48
48
|
}
|
|
49
49
|
|
|
50
50
|
if (args.length === 0 || args[0] === "--help" || args[0] === "-h") {
|
|
51
|
-
console.log(
|
|
52
|
-
`
|
|
53
|
-
GoFront — a Go-inspired language that compiles to JavaScript and WebAssembly
|
|
54
|
-
|
|
55
|
-
Usage:
|
|
56
|
-
gofront dev [dir] [options] Start dev server with live reload (default port 3000)
|
|
57
|
-
gofront build [dir] [options] Build production bundle (-o <dir>, --pwa, --minify, --emit-wat, --release, --wasm-opt, --js-only)
|
|
58
|
-
gofront check [dir|dir/...] Type-check only (dir/... recurses into every package)
|
|
59
|
-
gofront test [dir|dir/...] [--dom] Run unit tests (-v verbose, -run <regex>)
|
|
60
|
-
gofront prep [dir] [--minify] Copy static assets and bundle vendor dependencies
|
|
61
|
-
gofront init [dir] Scaffold a new GoFront project
|
|
62
|
-
gofront <file.go> Compile single file and print to stdout
|
|
63
|
-
gofront <dir> (or gofront .) Compile all *.go in directory as one bundle
|
|
64
|
-
gofront <input> -o out.js Compile and write to file (wasm packages also emit app.wasm)
|
|
65
|
-
gofront <input> --release Production build with minification and Binaryen wasm optimization
|
|
66
|
-
gofront <input> --wasm-opt Optimize wasm output with Binaryen (-O3 + GUFA)
|
|
67
|
-
gofront <input> --check Type-check only
|
|
68
|
-
gofront <input> --watch Watch for changes and recompile
|
|
69
|
-
gofront <input> -o out.js --serve Watch + serve with live reload (default port 3000)
|
|
70
|
-
gofront <input> -o out.js --serve --port 8080 Use a custom port
|
|
71
|
-
gofront <input> -o out.js --copy-assets Compile and synchronize static assets
|
|
72
|
-
gofront <input> --source-map Append inline source map to output
|
|
73
|
-
gofront <input> --minify Minify output
|
|
74
|
-
gofront <input> --minify --mangle Minify and mangle identifiers
|
|
75
|
-
gofront <file.go> --ast Dump AST (debug)
|
|
76
|
-
gofront <file.go> --tokens Dump tokens (debug)
|
|
77
|
-
gofront --version Print version
|
|
78
|
-
`.trim(),
|
|
79
|
-
);
|
|
51
|
+
console.log(HELP_TEXT);
|
|
80
52
|
process.exit(0);
|
|
81
53
|
}
|
|
82
54
|
|
|
@@ -131,19 +103,7 @@ if (args[0] === "test") {
|
|
|
131
103
|
if (args[0] === "check") {
|
|
132
104
|
const { targetDir, forceTarget } = parseCheckArgs(args.slice(1));
|
|
133
105
|
try {
|
|
134
|
-
|
|
135
|
-
const okLine = (label, t) =>
|
|
136
|
-
log.info(`${colors.cyan(label)} — ${colors.green("OK")} ${ms(t)}`);
|
|
137
|
-
if (packages) {
|
|
138
|
-
for (const pkg of packages) {
|
|
139
|
-
okLine(relative(process.cwd(), pkg.dir) || ".", pkg.elapsedMs);
|
|
140
|
-
}
|
|
141
|
-
log.info(
|
|
142
|
-
`${colors.bold(`${packages.length} packages`)} — ${colors.bold(colors.green("OK"))} ${ms(elapsedMs)}`,
|
|
143
|
-
);
|
|
144
|
-
} else {
|
|
145
|
-
okLine(targetDir, elapsedMs);
|
|
146
|
-
}
|
|
106
|
+
reportCheck(handleCheck(targetDir, { forceTarget }), targetDir);
|
|
147
107
|
process.exit(0);
|
|
148
108
|
} catch (e) {
|
|
149
109
|
log.fail(e.message);
|
|
@@ -157,13 +117,7 @@ if (args[0] === "build") {
|
|
|
157
117
|
const buildOptions = parseBuildArgs(args.slice(1));
|
|
158
118
|
try {
|
|
159
119
|
const result = await handleBuild(buildOptions.targetDir, buildOptions);
|
|
160
|
-
|
|
161
|
-
log.info(
|
|
162
|
-
line
|
|
163
|
-
.replace(/^build complete/, colors.green("build complete"))
|
|
164
|
-
.replace(/\(([\d.]+ kB)\)$/, (_, s) => colors.dim(`(${s})`)),
|
|
165
|
-
);
|
|
166
|
-
}
|
|
120
|
+
reportBuild(result);
|
|
167
121
|
process.exit(0);
|
|
168
122
|
} catch (e) {
|
|
169
123
|
log.fail(e.message);
|
|
@@ -200,22 +154,19 @@ if (args[0] === "dev") {
|
|
|
200
154
|
}
|
|
201
155
|
}
|
|
202
156
|
|
|
157
|
+
const flags = parseLegacyArgs(args);
|
|
203
158
|
const {
|
|
204
159
|
inputArg,
|
|
205
160
|
outputFile,
|
|
206
|
-
checkOnly,
|
|
207
|
-
dumpAst,
|
|
208
|
-
dumpTokens,
|
|
209
161
|
sourceMap,
|
|
210
162
|
serve: serveMode,
|
|
211
163
|
watch: watchMode,
|
|
212
164
|
copyAssets: copyAssetsFlag,
|
|
213
|
-
wasmOpt,
|
|
214
165
|
minify: minifyOutput,
|
|
215
166
|
mangle: mangleOutput,
|
|
216
167
|
port: servePort,
|
|
217
168
|
forceTarget,
|
|
218
|
-
} =
|
|
169
|
+
} = flags;
|
|
219
170
|
|
|
220
171
|
// ── Determine input mode ─────────────────────────────────────
|
|
221
172
|
|
|
@@ -228,96 +179,10 @@ try {
|
|
|
228
179
|
process.exit(1);
|
|
229
180
|
}
|
|
230
181
|
|
|
231
|
-
function copyProjectAssets(verbose) {
|
|
232
|
-
try {
|
|
233
|
-
const { copied, skipped } = copyAssets(resolve("."));
|
|
234
|
-
if (verbose && (copied > 0 || skipped > 0)) {
|
|
235
|
-
log.info(`copied ${copied} assets (${skipped} skipped)`);
|
|
236
|
-
}
|
|
237
|
-
} catch (e) {
|
|
238
|
-
log.warn(`asset copy failed: ${e.message}`);
|
|
239
|
-
}
|
|
240
|
-
}
|
|
241
|
-
|
|
242
182
|
// ── Single-shot mode ─────────────────────────────────────────
|
|
243
183
|
|
|
244
184
|
if (!watchMode) {
|
|
245
|
-
|
|
246
|
-
const startMs = performance.now();
|
|
247
|
-
try {
|
|
248
|
-
result = runCompile(inputPath, isDir, {
|
|
249
|
-
sourceMap,
|
|
250
|
-
outputFile,
|
|
251
|
-
dumpTokens,
|
|
252
|
-
dumpAst,
|
|
253
|
-
wasmOpt,
|
|
254
|
-
forceTarget,
|
|
255
|
-
});
|
|
256
|
-
} catch (e) {
|
|
257
|
-
log.fail(e.message);
|
|
258
|
-
process.exit(1);
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
if (result.tokens) {
|
|
262
|
-
for (const tok of result.tokens) console.log(tok.toString());
|
|
263
|
-
process.exit(0);
|
|
264
|
-
}
|
|
265
|
-
if (result.ast) {
|
|
266
|
-
console.log(JSON.stringify(result.ast, null, 2));
|
|
267
|
-
process.exit(0);
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
if (checkOnly) {
|
|
271
|
-
const elapsedMs = (performance.now() - startMs).toFixed(0);
|
|
272
|
-
log.info(
|
|
273
|
-
`${colors.cyan(inputArg)} — ${colors.green("OK")} ${ms(elapsedMs)}`,
|
|
274
|
-
);
|
|
275
|
-
process.exit(0);
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
let js;
|
|
279
|
-
try {
|
|
280
|
-
js = maybeMinify(result.js, {
|
|
281
|
-
minify: minifyOutput,
|
|
282
|
-
mangle: mangleOutput,
|
|
283
|
-
sourceMap,
|
|
284
|
-
});
|
|
285
|
-
} catch (e) {
|
|
286
|
-
log.fail(`minify failed: ${e.message}`);
|
|
287
|
-
process.exit(1);
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
const elapsedMs = (performance.now() - startMs).toFixed(0);
|
|
291
|
-
|
|
292
|
-
if (outputFile) {
|
|
293
|
-
try {
|
|
294
|
-
const written = writeCompileOutput(resolve(outputFile), result, js);
|
|
295
|
-
log.info(
|
|
296
|
-
`wrote ${formatWrittenDesc(written, outputFile)} ${ms(elapsedMs)}`,
|
|
297
|
-
);
|
|
298
|
-
if (result.wasmOptInfo && result.wasmOptInfo.originalSize > 0) {
|
|
299
|
-
const origKb = (result.wasmOptInfo.originalSize / 1024).toFixed(1);
|
|
300
|
-
const optKb = (result.wasmOptInfo.optimizedSize / 1024).toFixed(1);
|
|
301
|
-
log.info(
|
|
302
|
-
`wasm-opt: app.wasm ${origKb} kB → ${optKb} kB (-${result.wasmOptInfo.percentSaved}%) [${result.wasmOptInfo.engine}]`,
|
|
303
|
-
);
|
|
304
|
-
}
|
|
305
|
-
} catch (e) {
|
|
306
|
-
log.fail(`cannot write '${outputFile}': ${e.message}`);
|
|
307
|
-
process.exit(1);
|
|
308
|
-
}
|
|
309
|
-
} else {
|
|
310
|
-
console.log(js);
|
|
311
|
-
if (result.wasm) {
|
|
312
|
-
log.warn(
|
|
313
|
-
"wasm packages were linked but app.wasm needs -o <file> to be written",
|
|
314
|
-
);
|
|
315
|
-
}
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
if (copyAssetsFlag) copyProjectAssets(true);
|
|
319
|
-
|
|
320
|
-
process.exit(0);
|
|
185
|
+
process.exit(runSingleShot(inputPath, isDir, flags));
|
|
321
186
|
}
|
|
322
187
|
|
|
323
188
|
// ── Watch mode ───────────────────────────────────────────────
|
|
@@ -342,26 +207,7 @@ try {
|
|
|
342
207
|
mangle: mangleOutput,
|
|
343
208
|
copyAssets: copyAssetsFlag,
|
|
344
209
|
forceTarget,
|
|
345
|
-
|
|
346
|
-
const changeNote = changedFile ? ` — ${changedFile} changed` : "";
|
|
347
|
-
const timing = colors.dim(`(${elapsedMs}ms${changeNote})`);
|
|
348
|
-
if (outputFile) {
|
|
349
|
-
console.error(
|
|
350
|
-
`${prefix()} ${colors.green("OK")} — wrote ${formatWrittenDesc(written, outputFile)} ${timing}`,
|
|
351
|
-
);
|
|
352
|
-
} else {
|
|
353
|
-
// Clear screen then print
|
|
354
|
-
process.stdout.write("\x1Bc");
|
|
355
|
-
console.log(js);
|
|
356
|
-
console.error(`${prefix()} ${colors.green("OK")} ${timing}`);
|
|
357
|
-
}
|
|
358
|
-
if (copyAssetsFlag) copyProjectAssets(false);
|
|
359
|
-
},
|
|
360
|
-
onError: (e) => {
|
|
361
|
-
console.error(`${prefix()} ${colors.bold(colors.red("ERROR"))}`);
|
|
362
|
-
for (const line of formatDiagnostic(e.message).split("\n"))
|
|
363
|
-
console.error(` ${line}`);
|
|
364
|
-
},
|
|
210
|
+
...devReporters({ outputFile, copyAssets: copyAssetsFlag }),
|
|
365
211
|
});
|
|
366
212
|
} catch (e) {
|
|
367
213
|
log.fail(e.message);
|
|
@@ -52,8 +52,8 @@ const KEYWORDS = new Set([
|
|
|
52
52
|
"case",
|
|
53
53
|
"default",
|
|
54
54
|
"map",
|
|
55
|
-
"new",
|
|
56
55
|
"fallthrough",
|
|
56
|
+
"goto",
|
|
57
57
|
"defer",
|
|
58
58
|
"async",
|
|
59
59
|
"await",
|
|
@@ -86,6 +86,11 @@ const ASSIGN_OP = {
|
|
|
86
86
|
":": [T.DEFINE, ":=", T.COLON, ":"],
|
|
87
87
|
};
|
|
88
88
|
|
|
89
|
+
const UNICODE_LETTER = /^\p{L}$/u;
|
|
90
|
+
const UNICODE_DIGIT = /^\p{Nd}$/u;
|
|
91
|
+
const isUnicodeLetter = (cp) => UNICODE_LETTER.test(String.fromCodePoint(cp));
|
|
92
|
+
const isUnicodeDigit = (cp) => UNICODE_DIGIT.test(String.fromCodePoint(cp));
|
|
93
|
+
|
|
89
94
|
export class LexError extends Error {
|
|
90
95
|
constructor(msg, line, col, filename, sourceCode) {
|
|
91
96
|
const loc = filename ? `${filename}:${line}:${col}` : `${line}:${col}`;
|
|
@@ -103,6 +108,42 @@ export class LexError extends Error {
|
|
|
103
108
|
}
|
|
104
109
|
}
|
|
105
110
|
|
|
111
|
+
const SIMPLE_ESCAPES = {
|
|
112
|
+
a: 7,
|
|
113
|
+
b: 8,
|
|
114
|
+
f: 12,
|
|
115
|
+
n: 10,
|
|
116
|
+
r: 13,
|
|
117
|
+
t: 9,
|
|
118
|
+
v: 11,
|
|
119
|
+
"\\": 92,
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
// Decodes the raw bytes of consecutive \xNN / octal escapes in a string
|
|
123
|
+
// literal: valid UTF-8 sequences become their code point, every other byte
|
|
124
|
+
// 0x80-0xff becomes the lone surrogate U+DC00+byte so the runtime can turn it
|
|
125
|
+
// back into that byte when the string is converted to []byte.
|
|
126
|
+
function decodeRawBytes(bytes) {
|
|
127
|
+
const dec = new TextDecoder("utf-8", { fatal: true });
|
|
128
|
+
let out = "";
|
|
129
|
+
for (let i = 0; i < bytes.length; ) {
|
|
130
|
+
const b = bytes[i];
|
|
131
|
+
const n = b < 0x80 ? 1 : b >= 0xf0 ? 4 : b >= 0xe0 ? 3 : b >= 0xc0 ? 2 : 0;
|
|
132
|
+
if (n > 0 && i + n <= bytes.length) {
|
|
133
|
+
try {
|
|
134
|
+
out += dec.decode(Uint8Array.from(bytes.slice(i, i + n)));
|
|
135
|
+
i += n;
|
|
136
|
+
continue;
|
|
137
|
+
} catch {
|
|
138
|
+
// invalid sequence: fall through to the lone-byte case
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
out += String.fromCharCode(b < 0x80 ? b : 0xdc00 + b);
|
|
142
|
+
i++;
|
|
143
|
+
}
|
|
144
|
+
return out;
|
|
145
|
+
}
|
|
146
|
+
|
|
106
147
|
export class Lexer {
|
|
107
148
|
constructor(source, filename = null) {
|
|
108
149
|
this.src = source;
|
|
@@ -113,6 +154,9 @@ export class Lexer {
|
|
|
113
154
|
this.filename = filename;
|
|
114
155
|
this.target = null;
|
|
115
156
|
this.targetDirective = null;
|
|
157
|
+
this.intrinsics = new Map(); // line -> name of a //gofront:intrinsic
|
|
158
|
+
this.natives = new Map(); // line -> name of a //gofront:native
|
|
159
|
+
this.embedDirectives = []; // {line, col} of each //go:embed line comment
|
|
116
160
|
}
|
|
117
161
|
|
|
118
162
|
// ── Primitives ──────────────────────────────────────────────
|
|
@@ -158,6 +202,18 @@ export class Lexer {
|
|
|
158
202
|
while (this.pos < this.src.length && this.peek() !== "\n")
|
|
159
203
|
this.advance();
|
|
160
204
|
const comment = this.src.slice(start, this.pos);
|
|
205
|
+
if (
|
|
206
|
+
/^\/\/go:embed(?:\s|$)/.test(comment) &&
|
|
207
|
+
/^[ \t]*$/.test(
|
|
208
|
+
this.src.slice(this.src.lastIndexOf("\n", start - 1) + 1, start),
|
|
209
|
+
)
|
|
210
|
+
)
|
|
211
|
+
this.embedDirectives.push({ line, col });
|
|
212
|
+
const intrinsic = comment.match(/^\/\/gofront:intrinsic\s+(\S+)/);
|
|
213
|
+
if (intrinsic) this.intrinsics.set(line, intrinsic[1]);
|
|
214
|
+
const native = comment.match(/^\/\/gofront:native\s+(\S+)/);
|
|
215
|
+
if (native) this.natives.set(line, native[1]);
|
|
216
|
+
if (!intrinsic && !native) this.forwardDirectives(line, start);
|
|
161
217
|
const match = comment.match(/^\/\/gofront:target\s+(\S+)/);
|
|
162
218
|
if (match) {
|
|
163
219
|
this.target = match[1];
|
|
@@ -222,29 +278,7 @@ export class Lexer {
|
|
|
222
278
|
if (parts === null) parts = [];
|
|
223
279
|
if (this.pos > start) parts.push(this.src.slice(start, this.pos));
|
|
224
280
|
this.advance(); // skip \
|
|
225
|
-
|
|
226
|
-
switch (esc) {
|
|
227
|
-
case "n":
|
|
228
|
-
parts.push("\n");
|
|
229
|
-
break;
|
|
230
|
-
case "t":
|
|
231
|
-
parts.push("\t");
|
|
232
|
-
break;
|
|
233
|
-
case "r":
|
|
234
|
-
parts.push("\r");
|
|
235
|
-
break;
|
|
236
|
-
case '"':
|
|
237
|
-
parts.push('"');
|
|
238
|
-
break;
|
|
239
|
-
case "\\":
|
|
240
|
-
parts.push("\\");
|
|
241
|
-
break;
|
|
242
|
-
case "0":
|
|
243
|
-
parts.push("\0");
|
|
244
|
-
break;
|
|
245
|
-
default:
|
|
246
|
-
parts.push(`\\${esc}`);
|
|
247
|
-
}
|
|
281
|
+
parts.push(this.readStringEscape());
|
|
248
282
|
start = this.pos;
|
|
249
283
|
} else {
|
|
250
284
|
this.advance();
|
|
@@ -253,6 +287,77 @@ export class Lexer {
|
|
|
253
287
|
throw new LexError("Unterminated string", l, c, this.filename, this.src);
|
|
254
288
|
}
|
|
255
289
|
|
|
290
|
+
// The escape after a consumed backslash in a string literal. \xNN and octal
|
|
291
|
+
// escapes are raw bytes; a run of them is decoded together so "\xc3\xa9" is
|
|
292
|
+
// "é" and "\xff" is one invalid byte (a lone surrogate, see decodeRawBytes).
|
|
293
|
+
readStringEscape() {
|
|
294
|
+
if (!this.isByteEscapeAt(this.pos))
|
|
295
|
+
return String.fromCodePoint(this.readEscape('"'));
|
|
296
|
+
const bytes = [this.readEscape('"')];
|
|
297
|
+
while (this.peek() === "\\" && this.isByteEscapeAt(this.pos + 1)) {
|
|
298
|
+
this.advance();
|
|
299
|
+
bytes.push(this.readEscape('"'));
|
|
300
|
+
}
|
|
301
|
+
return decodeRawBytes(bytes);
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
// A doc comment between a directive and its func carries the directive down
|
|
305
|
+
// so the parser still finds it on the line above the func.
|
|
306
|
+
forwardDirectives(line, start) {
|
|
307
|
+
const before = this.src.slice(
|
|
308
|
+
this.src.lastIndexOf("\n", start - 1) + 1,
|
|
309
|
+
start,
|
|
310
|
+
);
|
|
311
|
+
if (!/^[ \t]*$/.test(before)) return;
|
|
312
|
+
for (const map of [this.intrinsics, this.natives])
|
|
313
|
+
if (map.has(line - 1)) map.set(line, map.get(line - 1));
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
isByteEscapeAt(i) {
|
|
317
|
+
const n = this.src[i];
|
|
318
|
+
return n === "x" || (n >= "0" && n <= "7");
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
// Reads the escape after a consumed backslash and returns its code point.
|
|
322
|
+
// `quote` is the enclosing delimiter: `\'` is valid only in runes and
|
|
323
|
+
// `\"` only in strings. `\xNN` and octal escapes give 0-255 (a code point in
|
|
324
|
+
// rune literals; string literals decode runs of them as UTF-8 bytes).
|
|
325
|
+
readEscape(quote) {
|
|
326
|
+
const l = this.line,
|
|
327
|
+
c = this.col;
|
|
328
|
+
const fail = (msg) => {
|
|
329
|
+
throw new LexError(msg, l, c, this.filename, this.src);
|
|
330
|
+
};
|
|
331
|
+
const esc = this.advance();
|
|
332
|
+
const simple = SIMPLE_ESCAPES[esc];
|
|
333
|
+
if (simple !== undefined) return simple;
|
|
334
|
+
if (esc === quote) return esc.charCodeAt(0);
|
|
335
|
+
if (esc === "x") return this.readEscapeDigits(2, 16, fail);
|
|
336
|
+
if (esc === "u" || esc === "U") {
|
|
337
|
+
const v = this.readEscapeDigits(esc === "u" ? 4 : 8, 16, fail);
|
|
338
|
+
if (v > 0x10ffff || (v >= 0xd800 && v < 0xe000))
|
|
339
|
+
fail("escape is invalid Unicode code point");
|
|
340
|
+
return v;
|
|
341
|
+
}
|
|
342
|
+
if (esc >= "0" && esc <= "7") {
|
|
343
|
+
const v = Number(esc) * 64 + this.readEscapeDigits(2, 8, fail);
|
|
344
|
+
if (v > 255) fail("octal escape value exceeds 255");
|
|
345
|
+
return v;
|
|
346
|
+
}
|
|
347
|
+
return fail(`unknown escape sequence: \\${esc}`);
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
readEscapeDigits(n, radix, fail) {
|
|
351
|
+
let v = 0;
|
|
352
|
+
for (let i = 0; i < n; i++) {
|
|
353
|
+
const d = Number.parseInt(this.src[this.pos] ?? "", radix);
|
|
354
|
+
if (Number.isNaN(d)) fail("invalid character in escape sequence");
|
|
355
|
+
v = v * radix + d;
|
|
356
|
+
this.advance();
|
|
357
|
+
}
|
|
358
|
+
return v;
|
|
359
|
+
}
|
|
360
|
+
|
|
256
361
|
readRawString() {
|
|
257
362
|
const l = this.line,
|
|
258
363
|
c = this.col;
|
|
@@ -279,38 +384,11 @@ export class Lexer {
|
|
|
279
384
|
let code;
|
|
280
385
|
if (this.peek() === "\\") {
|
|
281
386
|
this.advance();
|
|
282
|
-
|
|
283
|
-
switch (esc) {
|
|
284
|
-
case "n":
|
|
285
|
-
code = 10;
|
|
286
|
-
break;
|
|
287
|
-
case "t":
|
|
288
|
-
code = 9;
|
|
289
|
-
break;
|
|
290
|
-
case "r":
|
|
291
|
-
code = 13;
|
|
292
|
-
break;
|
|
293
|
-
case "'":
|
|
294
|
-
code = 39;
|
|
295
|
-
break;
|
|
296
|
-
case "\\":
|
|
297
|
-
code = 92;
|
|
298
|
-
break;
|
|
299
|
-
case "0":
|
|
300
|
-
code = 0;
|
|
301
|
-
break;
|
|
302
|
-
default:
|
|
303
|
-
throw new LexError(
|
|
304
|
-
`Unknown escape in rune literal: \\${esc}`,
|
|
305
|
-
l,
|
|
306
|
-
c,
|
|
307
|
-
this.filename,
|
|
308
|
-
this.src,
|
|
309
|
-
);
|
|
310
|
-
}
|
|
387
|
+
code = this.readEscape("'");
|
|
311
388
|
} else if (this.peek() !== "'") {
|
|
312
389
|
code = this.src.codePointAt(this.pos);
|
|
313
390
|
this.advance();
|
|
391
|
+
if (code > 0xffff) this.pos++; // astral: skip the low surrogate
|
|
314
392
|
} else {
|
|
315
393
|
throw new LexError("Empty rune literal", l, c, this.filename, this.src);
|
|
316
394
|
}
|
|
@@ -392,6 +470,20 @@ export class Lexer {
|
|
|
392
470
|
return { n, isFloat: false };
|
|
393
471
|
}
|
|
394
472
|
|
|
473
|
+
// The leading "." is already consumed; reads digits and an optional exponent.
|
|
474
|
+
_readLeadingDotFloat() {
|
|
475
|
+
let n = "0.";
|
|
476
|
+
while (this.pos < this.src.length) {
|
|
477
|
+
const c = this.src.charCodeAt(this.pos);
|
|
478
|
+
if (c >= 48 && c <= 57) n += this.advance();
|
|
479
|
+
else if (c === 95) this.advance();
|
|
480
|
+
else break;
|
|
481
|
+
}
|
|
482
|
+
const c = this.src.charCodeAt(this.pos);
|
|
483
|
+
if (c === 101 || c === 69) n = this._readDecimalExponent(n);
|
|
484
|
+
return { n };
|
|
485
|
+
}
|
|
486
|
+
|
|
395
487
|
_readDecimalExponent(n) {
|
|
396
488
|
n += this.advance(); // consume e/E
|
|
397
489
|
if (this.peek() === "+" || this.peek() === "-") n += this.advance();
|
|
@@ -420,12 +512,10 @@ export class Lexer {
|
|
|
420
512
|
break;
|
|
421
513
|
}
|
|
422
514
|
}
|
|
423
|
-
if (
|
|
424
|
-
|
|
425
|
-
this.src.charCodeAt(this.pos) === 46
|
|
426
|
-
) {
|
|
515
|
+
if (this.src.charCodeAt(this.pos) === 46) {
|
|
516
|
+
// `1.` is a float literal; `1..` and `1.x` keep the dot separate.
|
|
427
517
|
const next = this.src.charCodeAt(this.pos + 1);
|
|
428
|
-
if (next
|
|
518
|
+
if (this._dotContinuesNumber(next)) {
|
|
429
519
|
isFloat = true;
|
|
430
520
|
n += this.advance();
|
|
431
521
|
while (this.pos < this.src.length) {
|
|
@@ -438,6 +528,7 @@ export class Lexer {
|
|
|
438
528
|
break;
|
|
439
529
|
}
|
|
440
530
|
}
|
|
531
|
+
if (n.endsWith(".")) n += "0";
|
|
441
532
|
}
|
|
442
533
|
}
|
|
443
534
|
if (this.pos < this.src.length) {
|
|
@@ -450,6 +541,39 @@ export class Lexer {
|
|
|
450
541
|
return { n, isFloat };
|
|
451
542
|
}
|
|
452
543
|
|
|
544
|
+
// Whether the "." at the cursor belongs to the decimal literal before it.
|
|
545
|
+
// `next` is the code after the dot: a digit, an exponent (`1.e3`), an
|
|
546
|
+
// imaginary suffix (`1.i`) or anything that cannot start a selector or a
|
|
547
|
+
// range-style `..` makes `1.` a float; `1.x` and `1..` keep the dot apart.
|
|
548
|
+
_dotContinuesNumber(next) {
|
|
549
|
+
if (Number.isNaN(next)) return true;
|
|
550
|
+
if (next >= 48 && next <= 57) return true;
|
|
551
|
+
if (next === 46) return false;
|
|
552
|
+
const after = this.src.charCodeAt(this.pos + 2);
|
|
553
|
+
const identAfter =
|
|
554
|
+
(after >= 97 && after <= 122) ||
|
|
555
|
+
(after >= 65 && after <= 90) ||
|
|
556
|
+
after === 95 ||
|
|
557
|
+
(after >= 48 && after <= 57);
|
|
558
|
+
if (next === 101 || next === 69) {
|
|
559
|
+
return (after >= 48 && after <= 57) || after === 43 || after === 45;
|
|
560
|
+
}
|
|
561
|
+
if (next === 105) return !identAfter;
|
|
562
|
+
return !(
|
|
563
|
+
(next >= 97 && next <= 122) ||
|
|
564
|
+
(next >= 65 && next <= 90) ||
|
|
565
|
+
next === 95 ||
|
|
566
|
+
next > 127
|
|
567
|
+
);
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
_startsUnicodeIdent() {
|
|
571
|
+
return (
|
|
572
|
+
this.peek().charCodeAt(0) > 127 &&
|
|
573
|
+
isUnicodeLetter(this.src.codePointAt(this.pos))
|
|
574
|
+
);
|
|
575
|
+
}
|
|
576
|
+
|
|
453
577
|
readIdent() {
|
|
454
578
|
const start = this.pos;
|
|
455
579
|
while (this.pos < this.src.length) {
|
|
@@ -461,6 +585,10 @@ export class Lexer {
|
|
|
461
585
|
code === 95
|
|
462
586
|
) {
|
|
463
587
|
this.pos++;
|
|
588
|
+
} else if (code > 127) {
|
|
589
|
+
const cp = this.src.codePointAt(this.pos);
|
|
590
|
+
if (!isUnicodeLetter(cp) && !isUnicodeDigit(cp)) break;
|
|
591
|
+
this.pos += cp > 0xffff ? 2 : 1;
|
|
464
592
|
} else {
|
|
465
593
|
break;
|
|
466
594
|
}
|
|
@@ -526,7 +654,7 @@ export class Lexer {
|
|
|
526
654
|
}
|
|
527
655
|
|
|
528
656
|
// Identifiers & keywords
|
|
529
|
-
if (/[a-zA-Z_]/.test(ch)) {
|
|
657
|
+
if (/[a-zA-Z_]/.test(ch) || this._startsUnicodeIdent()) {
|
|
530
658
|
const id = this.readIdent();
|
|
531
659
|
this.push(KEYWORDS.has(id) ? id : T.IDENT, id, l, c);
|
|
532
660
|
continue;
|
|
@@ -541,6 +669,9 @@ export class Lexer {
|
|
|
541
669
|
this.push(T.EOF, "", this.line, this.col);
|
|
542
670
|
this.tokens.target = this.target;
|
|
543
671
|
this.tokens.targetDirective = this.targetDirective;
|
|
672
|
+
this.tokens.intrinsics = this.intrinsics;
|
|
673
|
+
this.tokens.natives = this.natives;
|
|
674
|
+
this.tokens.embedDirectives = this.embedDirectives;
|
|
544
675
|
return this.tokens;
|
|
545
676
|
}
|
|
546
677
|
_readPlusOp(l, c) {
|
|
@@ -573,8 +704,10 @@ export class Lexer {
|
|
|
573
704
|
|
|
574
705
|
_readAmpOp(l, c) {
|
|
575
706
|
if (this.match("&")) this.push(T.AND, "&&", l, c);
|
|
576
|
-
else if (this.match("^"))
|
|
577
|
-
|
|
707
|
+
else if (this.match("^")) {
|
|
708
|
+
if (this.match("=")) this.push(T.AND_NOT_ASSIGN, "&^=", l, c);
|
|
709
|
+
else this.push(T.AND_NOT, "&^", l, c);
|
|
710
|
+
} else if (this.match("=")) this.push(T.AMP_ASSIGN, "&=", l, c);
|
|
578
711
|
else this.push(T.AMP, "&", l, c);
|
|
579
712
|
}
|
|
580
713
|
|
|
@@ -614,7 +747,15 @@ export class Lexer {
|
|
|
614
747
|
else this.push(T.PIPE, "|", l, c);
|
|
615
748
|
break;
|
|
616
749
|
case ".":
|
|
617
|
-
if (
|
|
750
|
+
if (/[0-9]/.test(this.peek() ?? "")) {
|
|
751
|
+
const { n } = this._readLeadingDotFloat();
|
|
752
|
+
if (this.src[this.pos] === "i") {
|
|
753
|
+
this.advance();
|
|
754
|
+
this.push(T.IMAG, n, l, c);
|
|
755
|
+
} else {
|
|
756
|
+
this.push(T.FLOAT, n, l, c);
|
|
757
|
+
}
|
|
758
|
+
} else if (this.peek() === "." && this.peek(1) === ".") {
|
|
618
759
|
this.advance();
|
|
619
760
|
this.advance();
|
|
620
761
|
this.push(T.ELLIPSIS, "...", l, c);
|