xbintsc 0.3.18 → 0.3.35
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 +33 -0
- package/dist/src/cli/main.js +124 -30
- package/dist/src/cli/main.js.map +1 -1
- package/dist/src/codegen/generator/calls/access.js +8 -1
- package/dist/src/codegen/generator/calls/access.js.map +1 -1
- package/dist/src/codegen/generator/state.js +20 -0
- package/dist/src/codegen/generator/state.js.map +1 -1
- package/dist/src/codegen/generator/tables.d.ts +6 -0
- package/dist/src/codegen/generator/tables.js +38 -0
- package/dist/src/codegen/generator/tables.js.map +1 -1
- package/dist/src/driver/bundler/commonjs.d.ts +52 -0
- package/dist/src/driver/bundler/commonjs.js +493 -0
- package/dist/src/driver/bundler/commonjs.js.map +1 -0
- package/dist/src/driver/bundler/graph.d.ts +2 -1
- package/dist/src/driver/bundler/graph.js +32 -3
- package/dist/src/driver/bundler/graph.js.map +1 -1
- package/dist/src/driver/bundler/merge.d.ts +2 -1
- package/dist/src/driver/bundler/merge.js +16 -2
- package/dist/src/driver/bundler/merge.js.map +1 -1
- package/dist/src/driver/bundler/types.d.ts +8 -0
- package/dist/src/driver/compiler.d.ts +8 -1
- package/dist/src/driver/compiler.js +75 -7
- package/dist/src/driver/compiler.js.map +1 -1
- package/dist/src/driver/config.d.ts +53 -0
- package/dist/src/driver/config.js +141 -0
- package/dist/src/driver/config.js.map +1 -0
- package/dist/src/driver/icon.d.ts +62 -0
- package/dist/src/driver/icon.js +151 -0
- package/dist/src/driver/icon.js.map +1 -0
- package/dist/src/driver/index.d.ts +4 -0
- package/dist/src/driver/index.js +4 -0
- package/dist/src/driver/index.js.map +1 -1
- package/dist/src/driver/mac-bundle.d.ts +41 -0
- package/dist/src/driver/mac-bundle.js +123 -0
- package/dist/src/driver/mac-bundle.js.map +1 -0
- package/dist/src/driver/win-icon.d.ts +27 -0
- package/dist/src/driver/win-icon.js +80 -0
- package/dist/src/driver/win-icon.js.map +1 -0
- package/dist/src/extensions/gui/html.d.ts +25 -0
- package/dist/src/extensions/gui/html.js +203 -0
- package/dist/src/extensions/gui/html.js.map +1 -0
- package/dist/src/extensions/gui/index.js +11 -0
- package/dist/src/extensions/gui/index.js.map +1 -1
- package/dist/src/extensions/registry.d.ts +29 -0
- package/dist/src/extensions/registry.js +14 -0
- package/dist/src/extensions/registry.js.map +1 -1
- package/dist/src/index.d.ts +1 -1
- package/dist/src/index.js.map +1 -1
- package/dist/src/lexer/scanner/identifiers.js +4 -12
- package/dist/src/lexer/scanner/identifiers.js.map +1 -1
- package/dist/src/parser/statements.js +17 -4
- package/dist/src/parser/statements.js.map +1 -1
- package/dist/tests/cli/main.test.js +46 -1
- package/dist/tests/cli/main.test.js.map +1 -1
- package/dist/tests/driver/bundler-resolve.test.d.ts +1 -0
- package/dist/tests/driver/bundler-resolve.test.js +230 -0
- package/dist/tests/driver/bundler-resolve.test.js.map +1 -0
- package/dist/tests/driver/config.test.d.ts +1 -0
- package/dist/tests/driver/config.test.js +124 -0
- package/dist/tests/driver/config.test.js.map +1 -0
- package/dist/tests/driver/icon.test.d.ts +1 -0
- package/dist/tests/driver/icon.test.js +421 -0
- package/dist/tests/driver/icon.test.js.map +1 -0
- package/dist/tests/driver/modules.test.js +177 -0
- package/dist/tests/driver/modules.test.js.map +1 -1
- package/dist/tests/e2e/fetch.test.d.ts +12 -0
- package/dist/tests/e2e/fetch.test.js +272 -0
- package/dist/tests/e2e/fetch.test.js.map +1 -0
- package/dist/tests/e2e/gui-dom.test.d.ts +5 -0
- package/dist/tests/e2e/gui-dom.test.js +102 -0
- package/dist/tests/e2e/gui-dom.test.js.map +1 -0
- package/dist/tests/e2e/gui-events.test.d.ts +5 -0
- package/dist/tests/e2e/gui-events.test.js +60 -0
- package/dist/tests/e2e/gui-events.test.js.map +1 -0
- package/dist/tests/e2e/gui-helpers.d.ts +31 -0
- package/dist/tests/e2e/gui-helpers.js +74 -0
- package/dist/tests/e2e/gui-helpers.js.map +1 -0
- package/dist/tests/e2e/gui-layout.test.d.ts +4 -0
- package/dist/tests/e2e/gui-layout.test.js +88 -0
- package/dist/tests/e2e/gui-layout.test.js.map +1 -0
- package/dist/tests/e2e/gui-lifecycle.test.d.ts +5 -0
- package/dist/tests/e2e/gui-lifecycle.test.js +58 -0
- package/dist/tests/e2e/gui-lifecycle.test.js.map +1 -0
- package/dist/tests/e2e/gui-paint.test.d.ts +5 -0
- package/dist/tests/e2e/gui-paint.test.js +111 -0
- package/dist/tests/e2e/gui-paint.test.js.map +1 -0
- package/dist/tests/e2e/gui-script.test.d.ts +5 -0
- package/dist/tests/e2e/gui-script.test.js +78 -0
- package/dist/tests/e2e/gui-script.test.js.map +1 -0
- package/dist/tests/e2e/gui-style.test.d.ts +5 -0
- package/dist/tests/e2e/gui-style.test.js +175 -0
- package/dist/tests/e2e/gui-style.test.js.map +1 -0
- package/dist/tests/e2e/icon.test.d.ts +7 -0
- package/dist/tests/e2e/icon.test.js +80 -0
- package/dist/tests/e2e/icon.test.js.map +1 -0
- package/dist/tests/e2e/npm-modules.test.d.ts +4 -1
- package/dist/tests/e2e/npm-modules.test.js +47 -1
- package/dist/tests/e2e/npm-modules.test.js.map +1 -1
- package/dist/tests/e2e/runtime-coverage-typed-array.test.d.ts +9 -0
- package/dist/tests/e2e/runtime-coverage-typed-array.test.js +140 -0
- package/dist/tests/e2e/runtime-coverage-typed-array.test.js.map +1 -0
- package/dist/tests/extensions/gui-index.test.d.ts +1 -0
- package/dist/tests/extensions/gui-index.test.js +49 -0
- package/dist/tests/extensions/gui-index.test.js.map +1 -0
- package/dist/tests/extensions/gui.test.d.ts +1 -0
- package/dist/tests/extensions/gui.test.js +177 -0
- package/dist/tests/extensions/gui.test.js.map +1 -0
- package/dist/tests/extensions/registry.test.js +36 -0
- package/dist/tests/extensions/registry.test.js.map +1 -1
- package/dist/tests/parser/statements.test.js +11 -0
- package/dist/tests/parser/statements.test.js.map +1 -1
- package/dist/tests/public-api.test.d.ts +1 -0
- package/dist/tests/public-api.test.js +31 -0
- package/dist/tests/public-api.test.js.map +1 -0
- package/package.json +1 -1
- package/runtime/ext_gui/css.cpp +4 -517
- package/runtime/ext_gui/css_internal.h +27 -0
- package/runtime/ext_gui/css_match.cpp +195 -0
- package/runtime/ext_gui/css_selector.cpp +207 -0
- package/runtime/ext_gui/css_sheet.cpp +129 -0
- package/runtime/ext_gui/document.cpp +90 -0
- package/runtime/ext_gui/document.h +23 -0
- package/runtime/ext_gui/dom.cpp +43 -403
- package/runtime/ext_gui/dom.h +7 -0
- package/runtime/ext_gui/dom_api.cpp +164 -0
- package/runtime/ext_gui/dom_api.h +12 -0
- package/runtime/ext_gui/dom_api_document.cpp +96 -0
- package/runtime/ext_gui/dom_api_events.cpp +123 -0
- package/runtime/ext_gui/dom_api_internal.h +135 -0
- package/runtime/ext_gui/dom_api_node.cpp +329 -0
- package/runtime/ext_gui/dom_api_node_read.cpp +294 -0
- package/runtime/ext_gui/dom_api_proto.cpp +130 -0
- package/runtime/ext_gui/dom_api_style.cpp +192 -0
- package/runtime/ext_gui/dom_internal.h +39 -0
- package/runtime/ext_gui/dom_parse.cpp +348 -0
- package/runtime/ext_gui/dom_serialize.cpp +54 -0
- package/runtime/ext_gui/gui.cpp +61 -245
- package/runtime/ext_gui/gui_engine.h +54 -0
- package/runtime/ext_gui/gui_events.cpp +294 -0
- package/runtime/ext_gui/gui_internal.h +29 -0
- package/runtime/ext_gui/layout.cpp +6 -597
- package/runtime/ext_gui/layout_flex.cpp +200 -0
- package/runtime/ext_gui/layout_flow.cpp +191 -0
- package/runtime/ext_gui/layout_flow_internal.h +149 -0
- package/runtime/ext_gui/layout_inline.cpp +181 -0
- package/runtime/ext_gui/layout_internal.h +20 -0
- package/runtime/ext_gui/renderer.cpp +15 -679
- package/runtime/ext_gui/renderer_image.cpp +233 -0
- package/runtime/ext_gui/renderer_internal.h +112 -0
- package/runtime/ext_gui/renderer_shaders.h +108 -0
- package/runtime/ext_gui/renderer_text.cpp +342 -0
- package/runtime/ext_gui/script.cpp +63 -0
- package/runtime/ext_gui/style.cpp +12 -660
- package/runtime/ext_gui/style_internal.h +33 -0
- package/runtime/ext_gui/style_print.cpp +113 -0
- package/runtime/ext_gui/style_ua.cpp +98 -0
- package/runtime/ext_gui/style_values.cpp +468 -0
- package/runtime/ext_gui/window.cpp +60 -0
- package/runtime/rt.h +6 -0
- package/runtime/rt_internal.h +4 -0
- package/runtime/xt_builtins.c +1 -0
- package/runtime/xt_containers/access.inc +32 -1
- package/runtime/xt_fetch/buffer.inc +64 -0
- package/runtime/xt_fetch/headers.inc +312 -0
- package/runtime/xt_fetch/request.inc +386 -0
- package/runtime/xt_fetch/response.inc +107 -0
- package/runtime/xt_fetch/socket.inc +72 -0
- package/runtime/xt_fetch/url.inc +108 -0
- package/runtime/xt_fetch.c +39 -0
- package/runtime/xt_stdlib2/set.inc +1 -1
- package/runtime/xt_typed_array.c +614 -0
- package/src/cli/main.ts +137 -29
- package/src/codegen/generator/calls/access.ts +9 -1
- package/src/codegen/generator/state.ts +20 -0
- package/src/codegen/generator/tables.ts +40 -0
- package/src/driver/bundler/commonjs.ts +553 -0
- package/src/driver/bundler/graph.ts +35 -2
- package/src/driver/bundler/merge.ts +18 -1
- package/src/driver/bundler/types.ts +8 -0
- package/src/driver/compiler.ts +85 -7
- package/src/driver/config.ts +173 -0
- package/src/driver/icon.ts +184 -0
- package/src/driver/index.ts +30 -0
- package/src/driver/mac-bundle.ts +156 -0
- package/src/driver/win-icon.ts +96 -0
- package/src/extensions/gui/html.ts +236 -0
- package/src/extensions/gui/index.ts +11 -0
- package/src/extensions/registry.ts +39 -0
- package/src/index.ts +1 -1
- package/src/lexer/scanner/identifiers.ts +4 -11
- package/src/parser/statements.ts +17 -4
- package/dist/tests/e2e/gui.test.d.ts +0 -12
- package/dist/tests/e2e/gui.test.js +0 -493
- package/dist/tests/e2e/gui.test.js.map +0 -1
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* macOS `.app` bundle generation.
|
|
3
|
+
*
|
|
4
|
+
* A bare Mach-O executable cannot carry a Finder icon, so when `app.bundle` is
|
|
5
|
+
* enabled on macOS the driver lays out a minimal bundle next to the executable:
|
|
6
|
+
*
|
|
7
|
+
* Demo.app/Contents/Info.plist
|
|
8
|
+
* Demo.app/Contents/MacOS/Demo
|
|
9
|
+
* Demo.app/Contents/Resources/AppIcon.icns
|
|
10
|
+
*
|
|
11
|
+
* An `.icns` input is copied verbatim; a `.png` is converted with the system
|
|
12
|
+
* `sips` + `iconutil` tools when available, otherwise the PNG is shipped and the
|
|
13
|
+
* app relies on the runtime Dock icon (set by the `gui` extension).
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import { chmodSync, copyFileSync, existsSync, mkdirSync, rmSync, writeFileSync } from "node:fs";
|
|
17
|
+
import { dirname, join } from "node:path";
|
|
18
|
+
import type { IconInfo } from "./icon.js";
|
|
19
|
+
import type { Runner } from "./toolchain.js";
|
|
20
|
+
|
|
21
|
+
export interface MacBundleOptions {
|
|
22
|
+
readonly executablePath: string;
|
|
23
|
+
/** Display name; also the executable name inside the bundle. */
|
|
24
|
+
readonly name: string;
|
|
25
|
+
readonly bundleId: string;
|
|
26
|
+
readonly icon?: IconInfo;
|
|
27
|
+
readonly runner: Runner;
|
|
28
|
+
readonly env?: Record<string, string>;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** `<dir>/<name>.app` for an executable at `<dir>/<name>`. */
|
|
32
|
+
export function bundlePathFor(executablePath: string, name: string): string {
|
|
33
|
+
return join(dirname(executablePath), `${name}.app`);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** Escape the five XML predefined entities for the plist values. */
|
|
37
|
+
function xmlEscape(text: string): string {
|
|
38
|
+
return text
|
|
39
|
+
.replace(/&/g, "&")
|
|
40
|
+
.replace(/</g, "<")
|
|
41
|
+
.replace(/>/g, ">")
|
|
42
|
+
.replace(/"/g, """);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export interface PlistOptions {
|
|
46
|
+
readonly name: string;
|
|
47
|
+
readonly bundleId: string;
|
|
48
|
+
readonly executable: string;
|
|
49
|
+
readonly iconFile?: string;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** Render a minimal `Info.plist` for a GUI application bundle. */
|
|
53
|
+
export function infoPlist(options: PlistOptions): string {
|
|
54
|
+
const entries: [string, string][] = [
|
|
55
|
+
["CFBundleName", options.name],
|
|
56
|
+
["CFBundleDisplayName", options.name],
|
|
57
|
+
["CFBundleIdentifier", options.bundleId],
|
|
58
|
+
["CFBundleExecutable", options.executable],
|
|
59
|
+
["CFBundlePackageType", "APPL"],
|
|
60
|
+
["CFBundleInfoDictionaryVersion", "6.0"],
|
|
61
|
+
["CFBundleShortVersionString", "1.0.0"],
|
|
62
|
+
["CFBundleVersion", "1"],
|
|
63
|
+
["LSMinimumSystemVersion", "10.13"],
|
|
64
|
+
["NSHighResolutionCapable", "true"],
|
|
65
|
+
];
|
|
66
|
+
if (options.iconFile) entries.push(["CFBundleIconFile", options.iconFile]);
|
|
67
|
+
const body = entries
|
|
68
|
+
.map(([key, value]) => `\t<key>${key}</key>\n\t<string>${xmlEscape(value)}</string>`)
|
|
69
|
+
.join("\n");
|
|
70
|
+
return [
|
|
71
|
+
'<?xml version="1.0" encoding="UTF-8"?>',
|
|
72
|
+
'<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">',
|
|
73
|
+
'<plist version="1.0">',
|
|
74
|
+
"<dict>",
|
|
75
|
+
body,
|
|
76
|
+
"</dict>",
|
|
77
|
+
"</plist>",
|
|
78
|
+
"",
|
|
79
|
+
].join("\n");
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const ICONSET_SIZES = [16, 32, 64, 128, 256, 512] as const;
|
|
83
|
+
|
|
84
|
+
/** Convert a PNG to `.icns` via `sips` + `iconutil`; false when unavailable. */
|
|
85
|
+
function convertPngToIcns(
|
|
86
|
+
runner: Runner,
|
|
87
|
+
pngPath: string,
|
|
88
|
+
outIcns: string,
|
|
89
|
+
env?: Record<string, string>,
|
|
90
|
+
): boolean {
|
|
91
|
+
const iconsetDir = join(dirname(outIcns), "AppIcon.iconset");
|
|
92
|
+
mkdirSync(iconsetDir, { recursive: true });
|
|
93
|
+
try {
|
|
94
|
+
for (const size of ICONSET_SIZES) {
|
|
95
|
+
for (const scale of [1, 2] as const) {
|
|
96
|
+
const pixels = size * scale;
|
|
97
|
+
if (pixels > 1024) continue;
|
|
98
|
+
const suffix = scale === 1 ? "" : "@2x";
|
|
99
|
+
const out = join(iconsetDir, `icon_${size}x${size}${suffix}.png`);
|
|
100
|
+
const result = runner.run(
|
|
101
|
+
"sips",
|
|
102
|
+
["-z", String(pixels), String(pixels), pngPath, "--out", out],
|
|
103
|
+
{ env },
|
|
104
|
+
);
|
|
105
|
+
if (result.status !== 0) return false;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
const result = runner.run("iconutil", ["-c", "icns", iconsetDir, "-o", outIcns], { env });
|
|
109
|
+
return result.status === 0 && existsSync(outIcns);
|
|
110
|
+
} catch {
|
|
111
|
+
return false;
|
|
112
|
+
} finally {
|
|
113
|
+
rmSync(iconsetDir, { recursive: true, force: true });
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Lay out the `.app` bundle containing a copy of `executablePath` and, when
|
|
119
|
+
* available, the icon. Returns the bundle path. Never throws for a missing
|
|
120
|
+
* image-conversion tool: it falls back to shipping the PNG.
|
|
121
|
+
*/
|
|
122
|
+
export function packageMacApp(options: MacBundleOptions): string {
|
|
123
|
+
const { executablePath, name, bundleId, icon, runner } = options;
|
|
124
|
+
const bundleDir = bundlePathFor(executablePath, name);
|
|
125
|
+
const contents = join(bundleDir, "Contents");
|
|
126
|
+
const macosDir = join(contents, "MacOS");
|
|
127
|
+
const resourcesDir = join(contents, "Resources");
|
|
128
|
+
mkdirSync(macosDir, { recursive: true });
|
|
129
|
+
mkdirSync(resourcesDir, { recursive: true });
|
|
130
|
+
|
|
131
|
+
const innerExecutable = join(macosDir, name);
|
|
132
|
+
copyFileSync(executablePath, innerExecutable);
|
|
133
|
+
chmodSync(innerExecutable, 0o755);
|
|
134
|
+
|
|
135
|
+
let iconFile: string | undefined;
|
|
136
|
+
if (icon) {
|
|
137
|
+
const icnsPath = join(resourcesDir, "AppIcon.icns");
|
|
138
|
+
if (icon.format === "icns") {
|
|
139
|
+
copyFileSync(icon.path, icnsPath);
|
|
140
|
+
iconFile = "AppIcon";
|
|
141
|
+
} else if (icon.format === "png") {
|
|
142
|
+
if (convertPngToIcns(runner, icon.path, icnsPath, options.env)) {
|
|
143
|
+
iconFile = "AppIcon";
|
|
144
|
+
} else {
|
|
145
|
+
copyFileSync(icon.path, join(resourcesDir, "AppIcon.png"));
|
|
146
|
+
iconFile = "AppIcon.png";
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
writeFileSync(
|
|
152
|
+
join(contents, "Info.plist"),
|
|
153
|
+
infoPlist({ name, bundleId, executable: name, ...(iconFile ? { iconFile } : {}) }),
|
|
154
|
+
);
|
|
155
|
+
return bundleDir;
|
|
156
|
+
}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Windows PE icon resource.
|
|
3
|
+
*
|
|
4
|
+
* Windows reads the application icon from a resource section in the `.exe`, so
|
|
5
|
+
* an embedded blob is not enough: the icon is wrapped in a `.rc` script and
|
|
6
|
+
* compiled to a resource object that is linked into the executable. `llvm-rc`
|
|
7
|
+
* (shipping with clang) is preferred; MinGW `windres` is the fallback. Both are
|
|
8
|
+
* optional — a missing resource compiler degrades to the runtime window icon
|
|
9
|
+
* instead of failing the build.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { existsSync, mkdirSync, writeFileSync } from "node:fs";
|
|
13
|
+
import { dirname, join } from "node:path";
|
|
14
|
+
import { hashParts } from "./cache.js";
|
|
15
|
+
import { IconError, toIcoBytes, type IconInfo } from "./icon.js";
|
|
16
|
+
import type { Runner } from "./toolchain.js";
|
|
17
|
+
|
|
18
|
+
export type ResourceCompilerKind = "llvm-rc" | "windres";
|
|
19
|
+
|
|
20
|
+
export interface ResourceCompiler {
|
|
21
|
+
readonly tool: string;
|
|
22
|
+
readonly kind: ResourceCompilerKind;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Locate a resource compiler. `xbintsc_RC` wins, then `llvm-rc` next to the
|
|
27
|
+
* resolved clang, then `llvm-rc` and `windres` on `PATH`.
|
|
28
|
+
*/
|
|
29
|
+
export function resolveResourceCompiler(runner: Runner, clang: string): ResourceCompiler | undefined {
|
|
30
|
+
const exe = process.platform === "win32" ? ".exe" : "";
|
|
31
|
+
// An explicit override is trusted: probe it and fall back rather than
|
|
32
|
+
// rejecting it on a path prefix check (it may be a bare command on PATH).
|
|
33
|
+
const override = process.env.xbintsc_RC;
|
|
34
|
+
if (override) {
|
|
35
|
+
const kind: ResourceCompilerKind = /windres/i.test(override) ? "windres" : "llvm-rc";
|
|
36
|
+
try {
|
|
37
|
+
if (runner.run(override, ["--version"]).status === 0) return { tool: override, kind };
|
|
38
|
+
} catch {
|
|
39
|
+
// Fall through to the discovered compilers.
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
const candidates: ResourceCompiler[] = [];
|
|
43
|
+
if (clang && (clang.includes("/") || clang.includes("\\"))) {
|
|
44
|
+
candidates.push({ tool: join(dirname(clang), `llvm-rc${exe}`), kind: "llvm-rc" });
|
|
45
|
+
}
|
|
46
|
+
candidates.push({ tool: `llvm-rc${exe}`, kind: "llvm-rc" });
|
|
47
|
+
candidates.push({ tool: `windres${exe}`, kind: "windres" });
|
|
48
|
+
|
|
49
|
+
for (const candidate of candidates) {
|
|
50
|
+
if ((candidate.tool.includes("/") || candidate.tool.includes("\\")) && !existsSync(candidate.tool)) continue;
|
|
51
|
+
try {
|
|
52
|
+
if (runner.run(candidate.tool, ["--version"]).status === 0) return candidate;
|
|
53
|
+
} catch {
|
|
54
|
+
// Not runnable; try the next candidate.
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
return undefined;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Compile the icon into a linkable PE resource object (`.res` for llvm-rc, a
|
|
62
|
+
* COFF `.o` for windres). Cached by contents and compiler kind.
|
|
63
|
+
*/
|
|
64
|
+
export function ensureWindowsIconResource(
|
|
65
|
+
runner: Runner,
|
|
66
|
+
cacheDir: string,
|
|
67
|
+
icon: IconInfo,
|
|
68
|
+
compiler: ResourceCompiler,
|
|
69
|
+
env?: Record<string, string>,
|
|
70
|
+
): string {
|
|
71
|
+
const icoBytes = toIcoBytes(icon);
|
|
72
|
+
const key = hashParts(["icon-res", compiler.kind, Buffer.from(icoBytes).toString("base64")]);
|
|
73
|
+
const icoName = `icon-${key}.ico`;
|
|
74
|
+
const icoPath = join(cacheDir, icoName);
|
|
75
|
+
const rcPath = join(cacheDir, `icon-${key}.rc`);
|
|
76
|
+
const objectPath = join(cacheDir, `icon-${key}.${compiler.kind === "windres" ? "o" : "res"}`);
|
|
77
|
+
if (existsSync(objectPath)) return objectPath;
|
|
78
|
+
|
|
79
|
+
mkdirSync(cacheDir, { recursive: true });
|
|
80
|
+
writeFileSync(icoPath, icoBytes);
|
|
81
|
+
// The .rc references the .ico by name; the compiler runs with `cacheDir` as
|
|
82
|
+
// its working directory so the relative path resolves.
|
|
83
|
+
writeFileSync(rcPath, `1 ICON "${icoName}"\n`);
|
|
84
|
+
|
|
85
|
+
const args =
|
|
86
|
+
compiler.kind === "windres"
|
|
87
|
+
? [rcPath, "-O", "coff", "-o", objectPath]
|
|
88
|
+
: ["/fo", objectPath, rcPath];
|
|
89
|
+
const result = runner.run(compiler.tool, args, { cwd: cacheDir, env });
|
|
90
|
+
if (result.status !== 0 || !existsSync(objectPath)) {
|
|
91
|
+
throw new IconError(
|
|
92
|
+
`Failed to compile the Windows icon resource with ${compiler.tool}:\n${result.stderr || result.stdout}`,
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
return objectPath;
|
|
96
|
+
}
|
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GUI `.html` asset loader.
|
|
3
|
+
*
|
|
4
|
+
* `import html from "./index.html"` normally yields the file contents as a
|
|
5
|
+
* string. When the file contains scripts, the loader also compiles them: each
|
|
6
|
+
* script body is wrapped in a function `(window, document) => { … }`,
|
|
7
|
+
* registered through the `__registerScript` builtin, and the element is
|
|
8
|
+
* replaced by an inert `<script data-xt-id="<hash>"></script>` marker. The
|
|
9
|
+
* engine runs the matching functions once the document has been parsed (see
|
|
10
|
+
* `runtime/ext_gui/script.cpp`).
|
|
11
|
+
*
|
|
12
|
+
* This is how xbintsc supports `<script>` without a JavaScript engine: the body
|
|
13
|
+
* is ordinary TypeScript compiled by the existing front end, ahead of time.
|
|
14
|
+
*
|
|
15
|
+
* Inline `<script lang="ts">body</script>` bodies are wrapped directly. External
|
|
16
|
+
* `<script src="./app.ts">` files are read, their top-level `import`/`export
|
|
17
|
+
* … from` statements are hoisted to the generated module (with relative
|
|
18
|
+
* specifiers rewritten so they resolve from the HTML file), and the remaining
|
|
19
|
+
* statements are wrapped in the script function. All scripts run in document
|
|
20
|
+
* order after parsing (there is no "eager" execution point), i.e. every script
|
|
21
|
+
* is effectively `defer`red.
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
25
|
+
import { dirname, relative, resolve } from "node:path";
|
|
26
|
+
import {
|
|
27
|
+
ModifierKind,
|
|
28
|
+
SyntaxKind,
|
|
29
|
+
type ExportDeclaration,
|
|
30
|
+
type ImportDeclaration,
|
|
31
|
+
type Modifier,
|
|
32
|
+
type Statement,
|
|
33
|
+
type StringLiteral,
|
|
34
|
+
} from "../../ast/nodes.js";
|
|
35
|
+
import { DiagnosticBag } from "../../diagnostics/diagnostic.js";
|
|
36
|
+
import { SourceFile } from "../../diagnostics/source.js";
|
|
37
|
+
import { Parser } from "../../parser/parser.js";
|
|
38
|
+
import { hashString } from "../../driver/cache.js";
|
|
39
|
+
import type { AssetLoadResult } from "../registry.js";
|
|
40
|
+
|
|
41
|
+
/** Matches an inline `<script …>body</script>` element, attributes included. */
|
|
42
|
+
const SCRIPT_RE = /<script\b([^>]*)>([\s\S]*?)<\/script\s*>/gi;
|
|
43
|
+
/** `<script src>` targets that are URLs (http:, data:, //host, …). */
|
|
44
|
+
const URL_SPEC = /^[a-z][a-z0-9+.-]*:/i;
|
|
45
|
+
|
|
46
|
+
/** Read a (double/single/unquoted) attribute value from a tag's attribute text. */
|
|
47
|
+
function attribute(attributes: string, name: string): string | undefined {
|
|
48
|
+
const pattern = new RegExp(
|
|
49
|
+
`(?:^|\\s)${name}\\s*=\\s*(?:"([^"]*)"|'([^']*)'|([^\\s"'=<>\`]+))`,
|
|
50
|
+
"i",
|
|
51
|
+
);
|
|
52
|
+
const match = pattern.exec(attributes);
|
|
53
|
+
if (!match) return undefined;
|
|
54
|
+
return match[1] ?? match[2] ?? match[3];
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Whether a script should be compiled. Defaults to TypeScript for a bare
|
|
59
|
+
* `<script>`; `type="module"` and the usual JS/TS MIME types are accepted,
|
|
60
|
+
* while data blocks (`application/json`, `text/template`, …) are left alone.
|
|
61
|
+
*/
|
|
62
|
+
function isExecutable(type: string | undefined, lang: string | undefined): boolean {
|
|
63
|
+
if (lang && /^(ts|tsx|typescript|js|jsx|javascript)$/i.test(lang)) return true;
|
|
64
|
+
if (type === undefined) return true;
|
|
65
|
+
switch (type.toLowerCase()) {
|
|
66
|
+
case "module":
|
|
67
|
+
case "text/typescript":
|
|
68
|
+
case "application/typescript":
|
|
69
|
+
case "text/javascript":
|
|
70
|
+
case "application/javascript":
|
|
71
|
+
case "text/ecmascript":
|
|
72
|
+
case "application/ecmascript":
|
|
73
|
+
return true;
|
|
74
|
+
default:
|
|
75
|
+
return false;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/** Rewrite a relative import specifier so it resolves from the HTML file. */
|
|
80
|
+
function rewriteSpecifier(htmlPath: string, scriptPath: string, specifier: string): string {
|
|
81
|
+
if (!specifier.startsWith(".")) return specifier;
|
|
82
|
+
const absolute = resolve(dirname(scriptPath), specifier);
|
|
83
|
+
let rel = relative(dirname(htmlPath), absolute).split("\\").join("/");
|
|
84
|
+
if (!rel.startsWith(".")) rel = `./${rel}`;
|
|
85
|
+
return rel;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/** The original text of an import/re-export with its specifier rewritten. */
|
|
89
|
+
function rewriteSpecifierText(
|
|
90
|
+
htmlPath: string,
|
|
91
|
+
scriptPath: string,
|
|
92
|
+
source: string,
|
|
93
|
+
statement: Statement,
|
|
94
|
+
specifier: StringLiteral,
|
|
95
|
+
): string {
|
|
96
|
+
const rewritten = rewriteSpecifier(htmlPath, scriptPath, specifier.value);
|
|
97
|
+
return source.slice(statement.start, specifier.start) + JSON.stringify(rewritten) + source.slice(specifier.end, statement.end);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/** Every local binding an import declaration introduces. */
|
|
101
|
+
function importLocalNames(declaration: ImportDeclaration): string[] {
|
|
102
|
+
const names: string[] = [];
|
|
103
|
+
const clause = declaration.importClause;
|
|
104
|
+
if (!clause) return names;
|
|
105
|
+
if (clause.name) names.push(clause.name.text);
|
|
106
|
+
const bindings = clause.namedBindings;
|
|
107
|
+
if (bindings) {
|
|
108
|
+
if (bindings.kind === SyntaxKind.NamedImports) {
|
|
109
|
+
for (const element of bindings.elements) names.push(element.name.text);
|
|
110
|
+
} else {
|
|
111
|
+
names.push(bindings.name.text);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
return names;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
interface CompiledModule {
|
|
118
|
+
/** Hoisted `import`/`export … from` statements (specifiers rewritten). */
|
|
119
|
+
imports: string[];
|
|
120
|
+
/** Local names the hoisted imports introduce. */
|
|
121
|
+
localNames: string[];
|
|
122
|
+
/** The module body, wrapped as a script function by the caller. */
|
|
123
|
+
body: string;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/** Split an external script module into hoisted imports and a body. */
|
|
127
|
+
function compileModuleSource(htmlPath: string, scriptPath: string): CompiledModule {
|
|
128
|
+
const source = readFileSync(scriptPath, "utf8");
|
|
129
|
+
const bag = new DiagnosticBag();
|
|
130
|
+
const parsed = new Parser(new SourceFile(scriptPath, source), bag).parseSourceFile();
|
|
131
|
+
const chars = source.split("");
|
|
132
|
+
const imports: string[] = [];
|
|
133
|
+
const localNames: string[] = [];
|
|
134
|
+
const blank = (start: number, end: number): void => {
|
|
135
|
+
for (let i = start; i < end; i += 1) chars[i] = " ";
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
for (const statement of parsed.statements) {
|
|
139
|
+
if (statement.kind === SyntaxKind.ImportDeclaration) {
|
|
140
|
+
const declaration = statement as ImportDeclaration;
|
|
141
|
+
/* The front-end has no side-effect `import "x"` form; spell it as an
|
|
142
|
+
* empty named import so the dependency is still loaded. */
|
|
143
|
+
const hoisted = declaration.importClause
|
|
144
|
+
? rewriteSpecifierText(htmlPath, scriptPath, source, statement, declaration.moduleSpecifier)
|
|
145
|
+
: `import {} from ${JSON.stringify(rewriteSpecifier(htmlPath, scriptPath, declaration.moduleSpecifier.value))}`;
|
|
146
|
+
imports.push(hoisted);
|
|
147
|
+
localNames.push(...importLocalNames(declaration));
|
|
148
|
+
blank(statement.start, statement.end);
|
|
149
|
+
continue;
|
|
150
|
+
}
|
|
151
|
+
if (statement.kind === SyntaxKind.ExportDeclaration) {
|
|
152
|
+
const declaration = statement as ExportDeclaration;
|
|
153
|
+
if (declaration.moduleSpecifier) {
|
|
154
|
+
imports.push(rewriteSpecifierText(htmlPath, scriptPath, source, statement, declaration.moduleSpecifier));
|
|
155
|
+
}
|
|
156
|
+
blank(statement.start, statement.end);
|
|
157
|
+
continue;
|
|
158
|
+
}
|
|
159
|
+
if (statement.kind === SyntaxKind.ExportAssignment) {
|
|
160
|
+
/* `export default …` is meaningless in a script module; drop it. */
|
|
161
|
+
blank(statement.start, statement.end);
|
|
162
|
+
continue;
|
|
163
|
+
}
|
|
164
|
+
/* Strip `export`/`export default` so the statement is valid inside the
|
|
165
|
+
* generated function. */
|
|
166
|
+
const modifiers = (statement as { modifiers?: Modifier[] }).modifiers ?? [];
|
|
167
|
+
for (const modifier of modifiers) {
|
|
168
|
+
if (modifier.modifierKind === ModifierKind.Export || modifier.modifierKind === ModifierKind.Default) {
|
|
169
|
+
blank(modifier.start, modifier.end);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
return { imports, localNames, body: chars.join("") };
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/** Resolve a `<script src>` value to a local file path (ignoring `?`/`#`). */
|
|
178
|
+
function resolveScriptPath(htmlPath: string, src: string): string {
|
|
179
|
+
const clean = src.split(/[?#]/, 1)[0] ?? src;
|
|
180
|
+
return resolve(dirname(htmlPath), clean);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/** Rewrite `<script>` bodies into registered AOT functions + an HTML string. */
|
|
184
|
+
export function loadHtmlAsset(path: string, source: string): AssetLoadResult {
|
|
185
|
+
const scripts: Array<{ id: string; body: string }> = [];
|
|
186
|
+
const imports: string[] = [];
|
|
187
|
+
const importedText = new Set<string>();
|
|
188
|
+
const bound = new Set<string>();
|
|
189
|
+
|
|
190
|
+
const html = source.replace(SCRIPT_RE, (match, attributes: string, inlineBody: string) => {
|
|
191
|
+
if (!isExecutable(attribute(attributes, "type"), attribute(attributes, "lang"))) return match;
|
|
192
|
+
const src = attribute(attributes, "src");
|
|
193
|
+
|
|
194
|
+
if (src !== undefined && !URL_SPEC.test(src) && !src.startsWith("//")) {
|
|
195
|
+
const scriptPath = resolveScriptPath(path, src);
|
|
196
|
+
if (!existsSync(scriptPath)) {
|
|
197
|
+
throw new Error(`gui: <script src="${src}"> not found (looked for ${scriptPath})`);
|
|
198
|
+
}
|
|
199
|
+
const compiled = compileModuleSource(path, scriptPath);
|
|
200
|
+
for (const text of compiled.imports) {
|
|
201
|
+
if (!importedText.has(text)) {
|
|
202
|
+
importedText.add(text);
|
|
203
|
+
imports.push(text);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
for (const name of compiled.localNames) {
|
|
207
|
+
if (bound.has(name)) {
|
|
208
|
+
throw new Error(
|
|
209
|
+
`gui: <script src="${src}"> imports '${name}', already imported by another <script src>; alias it in ${scriptPath}`,
|
|
210
|
+
);
|
|
211
|
+
}
|
|
212
|
+
bound.add(name);
|
|
213
|
+
}
|
|
214
|
+
const id = hashString(compiled.body);
|
|
215
|
+
scripts.push({ id, body: compiled.body });
|
|
216
|
+
return `<script data-xt-id="${id}"></script>`;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/* A `src` URL, or a data block: leave the element untouched. */
|
|
220
|
+
if (src !== undefined) return match;
|
|
221
|
+
if (inlineBody.trim() === "") return match;
|
|
222
|
+
const id = hashString(inlineBody);
|
|
223
|
+
scripts.push({ id, body: inlineBody });
|
|
224
|
+
return `<script data-xt-id="${id}"></script>`;
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
const parts: string[] = [...imports];
|
|
228
|
+
for (const { id, body } of scripts) {
|
|
229
|
+
parts.push(
|
|
230
|
+
`function __xt_script_${id}(window: any, document: any): void {\n${body}\n}\n` +
|
|
231
|
+
`__registerScript(${JSON.stringify(id)}, __xt_script_${id});`,
|
|
232
|
+
);
|
|
233
|
+
}
|
|
234
|
+
parts.push(`export default ${JSON.stringify(html)};`);
|
|
235
|
+
return { moduleSource: parts.join("\n") };
|
|
236
|
+
}
|
|
@@ -25,6 +25,7 @@ import { join } from "node:path";
|
|
|
25
25
|
import type { Extension, ExtensionModule } from "../registry.js";
|
|
26
26
|
import { findRuntimeDir, platformSlug } from "../../driver/paths.js";
|
|
27
27
|
import { findRuntimeLibrary } from "../../driver/runtime-lib.js";
|
|
28
|
+
import { loadHtmlAsset } from "./html.js";
|
|
28
29
|
|
|
29
30
|
/** Top-level `gui` module bindings. */
|
|
30
31
|
const guiModule: ExtensionModule = {
|
|
@@ -104,4 +105,14 @@ export const guiExtension: Extension = {
|
|
|
104
105
|
return ["-lX11", "-lwayland-client", "-lEGL", "-lGL", "-ldl", "-lpthread", "-lstdc++"];
|
|
105
106
|
},
|
|
106
107
|
modules: () => ({ gui: guiModule }),
|
|
108
|
+
/**
|
|
109
|
+
* `__registerScript(id, fn)` is emitted by the `.html` asset loader and
|
|
110
|
+
* called at module initialization; it records the AOT-compiled `<script>`
|
|
111
|
+
* body so the engine can run it after parsing a document.
|
|
112
|
+
*/
|
|
113
|
+
builtins: () => ({
|
|
114
|
+
__registerScript: { symbol: "xt_register_script" },
|
|
115
|
+
}),
|
|
116
|
+
/** Rewrite imported `.html` assets (see `html.ts`). */
|
|
117
|
+
assetLoaders: () => ({ ".html": loadHtmlAsset }),
|
|
107
118
|
};
|
|
@@ -52,6 +52,24 @@ export interface ExtensionModule {
|
|
|
52
52
|
readonly namespace?: string;
|
|
53
53
|
}
|
|
54
54
|
|
|
55
|
+
/** The result of an extension asset loader rewriting one imported file. */
|
|
56
|
+
export interface AssetLoadResult {
|
|
57
|
+
/**
|
|
58
|
+
* The TypeScript source the bundler parses in place of the raw file. It may
|
|
59
|
+
* contain its own `import`/`export` statements, which are followed normally.
|
|
60
|
+
*/
|
|
61
|
+
readonly moduleSource: string;
|
|
62
|
+
/**
|
|
63
|
+
* Extra files the transformed module depends on that are *not* reached
|
|
64
|
+
* through imports (for cache invalidation). Imported files are already
|
|
65
|
+
* tracked by the bundler.
|
|
66
|
+
*/
|
|
67
|
+
readonly dependencies?: readonly string[];
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/** Rewrites an imported file (selected by extension) into a TS module. */
|
|
71
|
+
export type AssetLoader = (path: string, source: string) => AssetLoadResult;
|
|
72
|
+
|
|
55
73
|
export interface Extension {
|
|
56
74
|
readonly name: string;
|
|
57
75
|
/** Description shown by `xbintsc ext list`. */
|
|
@@ -75,6 +93,13 @@ export interface Extension {
|
|
|
75
93
|
builtins?(): Readonly<Record<string, BuiltinFunction>>;
|
|
76
94
|
/** Modules importable as `import ... from "<specifier>"`. */
|
|
77
95
|
modules?(): Readonly<Record<string, ExtensionModule>>;
|
|
96
|
+
/**
|
|
97
|
+
* Rewrite imported assets into TS modules, keyed by file extension
|
|
98
|
+
* (including the dot, e.g. `".html"`). The bundler consults these right
|
|
99
|
+
* after reading a file and before parsing it, so the core compiler never
|
|
100
|
+
* learns about any particular asset format.
|
|
101
|
+
*/
|
|
102
|
+
assetLoaders?(): Readonly<Record<string, AssetLoader>>;
|
|
78
103
|
}
|
|
79
104
|
|
|
80
105
|
export class ExtensionRegistry {
|
|
@@ -159,6 +184,20 @@ export class ExtensionRegistry {
|
|
|
159
184
|
return merged;
|
|
160
185
|
}
|
|
161
186
|
|
|
187
|
+
/**
|
|
188
|
+
* Flatten every registered extension's asset loaders, keyed by file
|
|
189
|
+
* extension. A later extension overrides an earlier one for the same
|
|
190
|
+
* extension (mirroring `modules()`).
|
|
191
|
+
*/
|
|
192
|
+
assetLoaders(): Readonly<Record<string, AssetLoader>> {
|
|
193
|
+
const merged: Record<string, AssetLoader> = {};
|
|
194
|
+
for (const extension of this.extensions.values()) {
|
|
195
|
+
const loaders = extension.assetLoaders?.();
|
|
196
|
+
if (loaders) Object.assign(merged, loaders);
|
|
197
|
+
}
|
|
198
|
+
return merged;
|
|
199
|
+
}
|
|
200
|
+
|
|
162
201
|
runtimeSources(): readonly string[] {
|
|
163
202
|
const sources: string[] = [];
|
|
164
203
|
for (const extension of this.extensions.values()) {
|
package/src/index.ts
CHANGED
|
@@ -29,7 +29,7 @@ export {
|
|
|
29
29
|
numberLiteral,
|
|
30
30
|
booleanLiteral,
|
|
31
31
|
} from "./codegen/values.js";
|
|
32
|
-
export { ExtensionRegistry, createDefaultRegistry, coreExtension, type Extension, type ExtensionModule, type ModuleExport, type ModuleExports } from "./extensions/registry.js";
|
|
32
|
+
export { ExtensionRegistry, createDefaultRegistry, coreExtension, type Extension, type ExtensionModule, type ModuleExport, type ModuleExports, type AssetLoader, type AssetLoadResult } from "./extensions/registry.js";
|
|
33
33
|
export { bundledExtensions } from "./extensions/catalog.js";
|
|
34
34
|
export { nodeExtension } from "./extensions/node/index.js";
|
|
35
35
|
export type { NodeModule } from "./extensions/node/module.js";
|
|
@@ -2,9 +2,8 @@
|
|
|
2
2
|
* Identifier scanning (identifiers, keywords and private `#name`s).
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
-
import { DiagnosticCode } from "../../diagnostics/diagnostic.js";
|
|
6
5
|
import { KEYWORDS, Token, TokenKind } from "../token.js";
|
|
7
|
-
import {
|
|
6
|
+
import { isIdentifierPart } from "../char.js";
|
|
8
7
|
import type { Scanner } from "../scanner.js";
|
|
9
8
|
|
|
10
9
|
export interface IdentifierMethods {
|
|
@@ -14,17 +13,11 @@ export interface IdentifierMethods {
|
|
|
14
13
|
export const identifierMethods: IdentifierMethods = {
|
|
15
14
|
scanIdentifier(this: Scanner, start: number, precededByLineBreak: boolean): Token {
|
|
16
15
|
this.pos++;
|
|
16
|
+
/* `\` is itself an identifier part, so unicode escapes such as `a\u0041b`
|
|
17
|
+
* are kept verbatim in the identifier text (see the lexer tests). */
|
|
17
18
|
while (this.pos < this.text.length) {
|
|
18
|
-
|
|
19
|
-
if (isIdentifierPart(ch)) {
|
|
19
|
+
if (isIdentifierPart(this.text.charCodeAt(this.pos))) {
|
|
20
20
|
this.pos++;
|
|
21
|
-
} else if (ch === Char.Backslash && this.text.charCodeAt(this.pos + 1) === Char.LowerU) {
|
|
22
|
-
this.pos += 2;
|
|
23
|
-
const hex = this.text.slice(this.pos, this.pos + 4);
|
|
24
|
-
this.pos += 4;
|
|
25
|
-
if (!/^[0-9a-fA-F]{4}$/.test(hex)) {
|
|
26
|
-
this.error(DiagnosticCode.InvalidEscape, "Invalid unicode escape in identifier", this.pos - 4, this.pos);
|
|
27
|
-
}
|
|
28
21
|
} else {
|
|
29
22
|
break;
|
|
30
23
|
}
|
package/src/parser/statements.ts
CHANGED
|
@@ -195,8 +195,16 @@ export const statementMethods: StatementMethods = {
|
|
|
195
195
|
case TokenKind.EnumKeyword:
|
|
196
196
|
return this.parseEnumDeclaration([]);
|
|
197
197
|
case TokenKind.NamespaceKeyword:
|
|
198
|
-
case TokenKind.ModuleKeyword:
|
|
199
|
-
|
|
198
|
+
case TokenKind.ModuleKeyword: {
|
|
199
|
+
// `module` is a contextual keyword: `module.exports` / `module(x)` are
|
|
200
|
+
// ordinary expressions, only `module Foo {}` / `module "x" {}` is a
|
|
201
|
+
// declaration.
|
|
202
|
+
const next = this.lookAhead(1);
|
|
203
|
+
if (this.isIdentifierLike(next) || next.kind === TokenKind.StringLiteral) {
|
|
204
|
+
return this.parseModuleDeclaration([]);
|
|
205
|
+
}
|
|
206
|
+
break;
|
|
207
|
+
}
|
|
200
208
|
case TokenKind.DeclareKeyword: {
|
|
201
209
|
const modifiers = this.tryParseModifiers();
|
|
202
210
|
return this.parseStatementWithModifiers(modifiers);
|
|
@@ -228,8 +236,13 @@ export const statementMethods: StatementMethods = {
|
|
|
228
236
|
case TokenKind.EnumKeyword:
|
|
229
237
|
return this.parseEnumDeclaration(modifiers);
|
|
230
238
|
case TokenKind.NamespaceKeyword:
|
|
231
|
-
case TokenKind.ModuleKeyword:
|
|
232
|
-
|
|
239
|
+
case TokenKind.ModuleKeyword: {
|
|
240
|
+
const next = this.lookAhead(1);
|
|
241
|
+
if (this.isIdentifierLike(next) || next.kind === TokenKind.StringLiteral) {
|
|
242
|
+
return this.parseModuleDeclaration(modifiers);
|
|
243
|
+
}
|
|
244
|
+
return this.parseExpressionStatement();
|
|
245
|
+
}
|
|
233
246
|
default:
|
|
234
247
|
return this.parseExpressionStatement();
|
|
235
248
|
}
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* End-to-end tests for the `gui` extension.
|
|
3
|
-
*
|
|
4
|
-
* The suite compiles TypeScript programs that import `gui`, links them against
|
|
5
|
-
* the prebuilt `runtime/lib/<os>-<arch>/gui.a` and runs them. Programs close
|
|
6
|
-
* themselves through the `XT_GUI_AUTOCLOSE_MS` hook, so the tests never block on
|
|
7
|
-
* a real window.
|
|
8
|
-
*
|
|
9
|
-
* It is skipped when the GUI archive has not been built (run `npm run gui`
|
|
10
|
-
* first) and on Windows, where the archive is not produced yet.
|
|
11
|
-
*/
|
|
12
|
-
export {};
|