wasm-idle 0.3.2 → 0.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +22 -2
- package/dist/clang/app.d.ts +2 -1
- package/dist/clang/app.js +166 -137
- package/dist/clang/index.d.ts +6 -0
- package/dist/clang/index.js +137 -21
- package/dist/debug/expression.js +135 -19
- package/dist/debug/index.d.ts +1 -1
- package/dist/debug/index.js +1 -1
- package/dist/debug/language/index.d.ts +1 -0
- package/dist/debug/language/index.js +1 -0
- package/dist/debug/language/python.d.ts +2 -0
- package/dist/debug/language/python.js +14 -0
- package/dist/index.d.ts +3 -1
- package/dist/index.js +1 -1
- package/dist/playground/assetBridge.d.ts +20 -0
- package/dist/playground/assetBridge.js +192 -0
- package/dist/playground/assets.d.ts +32 -0
- package/dist/playground/assets.js +97 -0
- package/dist/playground/clang.d.ts +11 -3
- package/dist/playground/clang.js +57 -20
- package/dist/playground/index.d.ts +2 -1
- package/dist/playground/index.js +10 -2
- package/dist/playground/java.d.ts +33 -0
- package/dist/playground/java.js +161 -0
- package/dist/playground/javaStdin.d.ts +8 -0
- package/dist/playground/javaStdin.js +323 -0
- package/dist/playground/options.d.ts +19 -0
- package/dist/playground/options.js +15 -1
- package/dist/playground/python.d.ts +21 -6
- package/dist/playground/python.js +135 -25
- package/dist/playground/sandbox.d.ts +5 -2
- package/dist/playground/stdinBuffer.d.ts +7 -0
- package/dist/playground/stdinBuffer.js +100 -0
- package/dist/playground/teavmConfig.d.ts +3 -0
- package/dist/playground/teavmConfig.js +14 -0
- package/dist/playground/worker/assets.d.ts +12 -0
- package/dist/playground/worker/assets.js +257 -0
- package/dist/playground/worker/clang.js +13 -23
- package/dist/playground/worker/java.d.ts +1 -0
- package/dist/playground/worker/java.js +228 -0
- package/dist/playground/worker/python.js +264 -58
- package/dist/terminal/Terminal.svelte +98 -18
- package/dist/terminal/Terminal.svelte.d.ts +4 -1
- package/dist/terminal/theme.js +2 -2
- package/dist/terminal/types.d.ts +4 -2
- package/package.json +11 -11
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
const SEQUENCE_INDEX = 0;
|
|
2
|
+
const LENGTH_INDEX = 1;
|
|
3
|
+
const HEADER_BYTES = Int32Array.BYTES_PER_ELEMENT * 2;
|
|
4
|
+
const EOF_LENGTH = -1;
|
|
5
|
+
const encoder = new TextEncoder();
|
|
6
|
+
const decoder = new TextDecoder();
|
|
7
|
+
const controlViewOf = (buffer) => buffer instanceof Int32Array ? buffer : new Int32Array(buffer);
|
|
8
|
+
const payloadViewOf = (control) => new Uint8Array(control.buffer, control.byteOffset + HEADER_BYTES, control.byteLength - HEADER_BYTES);
|
|
9
|
+
const splitChunk = (input, maxBytes) => {
|
|
10
|
+
const encoded = encoder.encode(input);
|
|
11
|
+
if (encoded.length <= maxBytes) {
|
|
12
|
+
return { chunk: input, bytes: encoded, rest: '' };
|
|
13
|
+
}
|
|
14
|
+
let left = 0;
|
|
15
|
+
let right = input.length;
|
|
16
|
+
while (left < right) {
|
|
17
|
+
const middle = Math.ceil((left + right) / 2);
|
|
18
|
+
if (encoder.encode(input.slice(0, middle)).length <= maxBytes) {
|
|
19
|
+
left = middle;
|
|
20
|
+
}
|
|
21
|
+
else {
|
|
22
|
+
right = middle - 1;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
const chunk = input.slice(0, left);
|
|
26
|
+
return {
|
|
27
|
+
chunk,
|
|
28
|
+
bytes: encoder.encode(chunk),
|
|
29
|
+
rest: input.slice(left)
|
|
30
|
+
};
|
|
31
|
+
};
|
|
32
|
+
export const flushQueuedStdin = (queue, buffer) => {
|
|
33
|
+
if (!queue.length)
|
|
34
|
+
return false;
|
|
35
|
+
const control = controlViewOf(buffer);
|
|
36
|
+
const payload = payloadViewOf(control);
|
|
37
|
+
const next = queue[0];
|
|
38
|
+
const { bytes, rest } = splitChunk(next, payload.length);
|
|
39
|
+
payload.fill(0);
|
|
40
|
+
payload.set(bytes);
|
|
41
|
+
Atomics.store(control, LENGTH_INDEX, bytes.length);
|
|
42
|
+
Atomics.add(control, SEQUENCE_INDEX, 1);
|
|
43
|
+
Atomics.notify(control, SEQUENCE_INDEX);
|
|
44
|
+
if (rest) {
|
|
45
|
+
queue[0] = rest;
|
|
46
|
+
}
|
|
47
|
+
else {
|
|
48
|
+
queue.shift();
|
|
49
|
+
}
|
|
50
|
+
return true;
|
|
51
|
+
};
|
|
52
|
+
export const readBufferedStdin = (buffer) => {
|
|
53
|
+
const control = controlViewOf(buffer);
|
|
54
|
+
const length = Atomics.load(control, LENGTH_INDEX);
|
|
55
|
+
if (length === EOF_LENGTH)
|
|
56
|
+
return null;
|
|
57
|
+
const payload = payloadViewOf(control);
|
|
58
|
+
return decoder.decode(payload.slice(0, length));
|
|
59
|
+
};
|
|
60
|
+
export const bufferedSequence = (buffer) => Atomics.load(controlViewOf(buffer), SEQUENCE_INDEX);
|
|
61
|
+
export const waitForBufferedSequenceChange = (buffer, sequence, timeoutMs = 5000) => new Promise((resolve, reject) => {
|
|
62
|
+
const startedAt = Date.now();
|
|
63
|
+
const poll = () => {
|
|
64
|
+
if (bufferedSequence(buffer) !== sequence) {
|
|
65
|
+
resolve(readBufferedStdin(buffer));
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
if (Date.now() - startedAt >= timeoutMs) {
|
|
69
|
+
reject(new Error('Timed out waiting for buffered value'));
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
setTimeout(poll, 10);
|
|
73
|
+
};
|
|
74
|
+
poll();
|
|
75
|
+
});
|
|
76
|
+
export const flushBufferedEof = (buffer) => {
|
|
77
|
+
const control = controlViewOf(buffer);
|
|
78
|
+
const payload = payloadViewOf(control);
|
|
79
|
+
payload.fill(0);
|
|
80
|
+
Atomics.store(control, LENGTH_INDEX, EOF_LENGTH);
|
|
81
|
+
Atomics.add(control, SEQUENCE_INDEX, 1);
|
|
82
|
+
Atomics.notify(control, SEQUENCE_INDEX);
|
|
83
|
+
};
|
|
84
|
+
export const waitForBufferedStdin = (buffer, requestInput) => {
|
|
85
|
+
const sequence = Atomics.load(buffer, SEQUENCE_INDEX);
|
|
86
|
+
requestInput();
|
|
87
|
+
while (true) {
|
|
88
|
+
const result = Atomics.wait(buffer, SEQUENCE_INDEX, sequence, 100);
|
|
89
|
+
if (result === 'not-equal') {
|
|
90
|
+
return readBufferedStdin(buffer);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
};
|
|
94
|
+
export const resetBufferedStdin = (buffer) => {
|
|
95
|
+
const control = controlViewOf(buffer);
|
|
96
|
+
const payload = payloadViewOf(control);
|
|
97
|
+
payload.fill(0);
|
|
98
|
+
Atomics.store(control, SEQUENCE_INDEX, 0);
|
|
99
|
+
Atomics.store(control, LENGTH_INDEX, 0);
|
|
100
|
+
};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export const normalizeTeaVmBaseUrl = (baseUrl) => baseUrl.endsWith('/') ? baseUrl : `${baseUrl}/`;
|
|
2
|
+
const normalizePathPrefix = (pathPrefix) => {
|
|
3
|
+
if (!pathPrefix)
|
|
4
|
+
return '';
|
|
5
|
+
return pathPrefix.endsWith('/') ? pathPrefix.slice(0, -1) : pathPrefix;
|
|
6
|
+
};
|
|
7
|
+
export const resolveTeaVmBaseUrl = (pathPrefix = '', currentUrl = '') => {
|
|
8
|
+
const configuredBaseUrl = (import.meta.env.PUBLIC_TEAVM_BASE_URL || '').trim();
|
|
9
|
+
const baseUrl = configuredBaseUrl
|
|
10
|
+
? normalizeTeaVmBaseUrl(configuredBaseUrl)
|
|
11
|
+
: `${normalizePathPrefix(pathPrefix) || ''}/teavm/`;
|
|
12
|
+
return currentUrl ? new URL(baseUrl, currentUrl).href : baseUrl;
|
|
13
|
+
};
|
|
14
|
+
export const resolveTeaVmAssetUrl = (baseUrl, assetPath) => new URL(assetPath, normalizeTeaVmBaseUrl(baseUrl)).href;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export interface WorkerRuntimeAssetConfig {
|
|
2
|
+
baseUrl: string;
|
|
3
|
+
useAssetBridge: boolean;
|
|
4
|
+
}
|
|
5
|
+
interface LoadedWorkerAsset {
|
|
6
|
+
bytes: Uint8Array;
|
|
7
|
+
mimeType?: string;
|
|
8
|
+
}
|
|
9
|
+
export declare function configureWorkerRuntimeAssets(config: WorkerRuntimeAssetConfig | null): void;
|
|
10
|
+
export declare function handleWorkerAssetMessage(data: any): boolean;
|
|
11
|
+
export declare function loadWorkerRuntimeAsset(asset: string): Promise<LoadedWorkerAsset>;
|
|
12
|
+
export {};
|
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
const decoder = new TextDecoder();
|
|
2
|
+
const originalFetch = globalThis.fetch.bind(globalThis);
|
|
3
|
+
const NativeXMLHttpRequest = globalThis.XMLHttpRequest;
|
|
4
|
+
let activeConfig = null;
|
|
5
|
+
let interceptorsInstalled = false;
|
|
6
|
+
let nextAssetRequestId = 0;
|
|
7
|
+
const pendingAssetRequests = new Map();
|
|
8
|
+
const responseBuffer = (bytes) => {
|
|
9
|
+
const buffer = bytes.buffer;
|
|
10
|
+
return bytes.byteOffset === 0 && bytes.byteLength === buffer.byteLength
|
|
11
|
+
? buffer
|
|
12
|
+
: buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength);
|
|
13
|
+
};
|
|
14
|
+
const trackedAssetUrl = (input) => {
|
|
15
|
+
if (!activeConfig)
|
|
16
|
+
return null;
|
|
17
|
+
if (typeof input === 'string')
|
|
18
|
+
return new URL(input, activeConfig.baseUrl).href;
|
|
19
|
+
if (input instanceof URL)
|
|
20
|
+
return input.href;
|
|
21
|
+
return input.url;
|
|
22
|
+
};
|
|
23
|
+
const trackedAssetName = (url) => {
|
|
24
|
+
if (!activeConfig || !url.startsWith(activeConfig.baseUrl))
|
|
25
|
+
return null;
|
|
26
|
+
return url.slice(activeConfig.baseUrl.length);
|
|
27
|
+
};
|
|
28
|
+
const isTrackedAssetUrl = (url) => trackedAssetName(url) !== null;
|
|
29
|
+
const loadAssetFromBridge = async (asset) => {
|
|
30
|
+
const id = ++nextAssetRequestId;
|
|
31
|
+
return await new Promise((resolve, reject) => {
|
|
32
|
+
pendingAssetRequests.set(id, { resolve, reject });
|
|
33
|
+
self.postMessage({
|
|
34
|
+
assetRequest: {
|
|
35
|
+
id,
|
|
36
|
+
asset
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
});
|
|
40
|
+
};
|
|
41
|
+
const loadAssetFromUrl = async (url, asset) => {
|
|
42
|
+
const response = await originalFetch(url);
|
|
43
|
+
if (!response.ok)
|
|
44
|
+
throw new Error(`Failed to load ${asset}: ${response.status}`);
|
|
45
|
+
const total = Number(response.headers.get('content-length') || 0) || undefined;
|
|
46
|
+
const mimeType = response.headers.get('content-type') || undefined;
|
|
47
|
+
if (!response.body) {
|
|
48
|
+
const bytes = new Uint8Array(await response.arrayBuffer());
|
|
49
|
+
self.postMessage({
|
|
50
|
+
assetProgress: {
|
|
51
|
+
asset,
|
|
52
|
+
loaded: bytes.byteLength,
|
|
53
|
+
total: total ?? bytes.byteLength
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
return { bytes, mimeType };
|
|
57
|
+
}
|
|
58
|
+
const reader = response.body.getReader();
|
|
59
|
+
let receivedLength = 0;
|
|
60
|
+
const chunks = [];
|
|
61
|
+
while (true) {
|
|
62
|
+
const { done, value } = await reader.read();
|
|
63
|
+
if (done)
|
|
64
|
+
break;
|
|
65
|
+
if (!value)
|
|
66
|
+
continue;
|
|
67
|
+
const chunk = Uint8Array.from(value);
|
|
68
|
+
chunks.push(chunk);
|
|
69
|
+
receivedLength += chunk.byteLength;
|
|
70
|
+
self.postMessage({
|
|
71
|
+
assetProgress: {
|
|
72
|
+
asset,
|
|
73
|
+
loaded: receivedLength,
|
|
74
|
+
total
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
const bytes = new Uint8Array(receivedLength);
|
|
79
|
+
let position = 0;
|
|
80
|
+
for (const chunk of chunks) {
|
|
81
|
+
bytes.set(chunk, position);
|
|
82
|
+
position += chunk.byteLength;
|
|
83
|
+
}
|
|
84
|
+
self.postMessage({
|
|
85
|
+
assetProgress: {
|
|
86
|
+
asset,
|
|
87
|
+
loaded: receivedLength,
|
|
88
|
+
total: total ?? receivedLength
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
return { bytes, mimeType };
|
|
92
|
+
};
|
|
93
|
+
async function loadTrackedAsset(url) {
|
|
94
|
+
const asset = trackedAssetName(url);
|
|
95
|
+
if (!asset || !activeConfig)
|
|
96
|
+
throw new Error('Untracked runtime asset request');
|
|
97
|
+
return activeConfig.useAssetBridge
|
|
98
|
+
? await loadAssetFromBridge(asset)
|
|
99
|
+
: await loadAssetFromUrl(url, asset);
|
|
100
|
+
}
|
|
101
|
+
function createTrackedResponse(asset) {
|
|
102
|
+
return new Response(responseBuffer(asset.bytes), {
|
|
103
|
+
status: 200,
|
|
104
|
+
headers: asset.mimeType ? { 'Content-Type': asset.mimeType } : undefined
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
function installTrackedFetch() {
|
|
108
|
+
if (typeof NativeXMLHttpRequest === 'undefined')
|
|
109
|
+
return;
|
|
110
|
+
class RuntimeAssetXMLHttpRequest {
|
|
111
|
+
responseType = '';
|
|
112
|
+
response = null;
|
|
113
|
+
responseText = '';
|
|
114
|
+
readyState = 0;
|
|
115
|
+
status = 0;
|
|
116
|
+
statusText = '';
|
|
117
|
+
timeout = 0;
|
|
118
|
+
withCredentials = false;
|
|
119
|
+
onload = null;
|
|
120
|
+
onerror = null;
|
|
121
|
+
onprogress = null;
|
|
122
|
+
onreadystatechange = null;
|
|
123
|
+
native = null;
|
|
124
|
+
url = '';
|
|
125
|
+
open(method, url) {
|
|
126
|
+
const resolvedUrl = trackedAssetUrl(url);
|
|
127
|
+
if (!resolvedUrl || !isTrackedAssetUrl(resolvedUrl)) {
|
|
128
|
+
const nativeUrl = resolvedUrl || (url instanceof URL ? url.href : String(url));
|
|
129
|
+
this.native = new NativeXMLHttpRequest();
|
|
130
|
+
this.native.responseType = this.responseType;
|
|
131
|
+
this.native.timeout = this.timeout;
|
|
132
|
+
this.native.withCredentials = this.withCredentials;
|
|
133
|
+
this.native.onload = (event) => {
|
|
134
|
+
this.response = this.native?.response;
|
|
135
|
+
this.responseText = this.native?.responseText || '';
|
|
136
|
+
this.readyState = this.native?.readyState || 0;
|
|
137
|
+
this.status = this.native?.status || 0;
|
|
138
|
+
this.statusText = this.native?.statusText || '';
|
|
139
|
+
this.onreadystatechange?.call(this, event);
|
|
140
|
+
this.onload?.call(this, event);
|
|
141
|
+
};
|
|
142
|
+
this.native.onerror = (event) => {
|
|
143
|
+
this.readyState = this.native?.readyState || 4;
|
|
144
|
+
this.status = this.native?.status || 0;
|
|
145
|
+
this.statusText = this.native?.statusText || '';
|
|
146
|
+
this.onreadystatechange?.call(this, event);
|
|
147
|
+
this.onerror?.call(this, event);
|
|
148
|
+
};
|
|
149
|
+
this.native.onprogress = (event) => {
|
|
150
|
+
this.onprogress?.call(this, event);
|
|
151
|
+
};
|
|
152
|
+
this.native.onreadystatechange = (event) => {
|
|
153
|
+
this.readyState = this.native?.readyState || 0;
|
|
154
|
+
this.onreadystatechange?.call(this, event);
|
|
155
|
+
};
|
|
156
|
+
this.native.open(method, nativeUrl);
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
this.url = resolvedUrl;
|
|
160
|
+
this.readyState = 1;
|
|
161
|
+
this.onreadystatechange?.call(this, new ProgressEvent('readystatechange'));
|
|
162
|
+
}
|
|
163
|
+
setRequestHeader(name, value) {
|
|
164
|
+
this.native?.setRequestHeader(name, value);
|
|
165
|
+
}
|
|
166
|
+
async send(body) {
|
|
167
|
+
if (this.native) {
|
|
168
|
+
this.native.send(body);
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
try {
|
|
172
|
+
const loaded = await loadTrackedAsset(this.url);
|
|
173
|
+
const buffer = responseBuffer(loaded.bytes);
|
|
174
|
+
this.status = 200;
|
|
175
|
+
this.statusText = 'OK';
|
|
176
|
+
this.readyState = 4;
|
|
177
|
+
if (this.responseType === 'arraybuffer') {
|
|
178
|
+
this.response = buffer;
|
|
179
|
+
}
|
|
180
|
+
else if (this.responseType === 'blob') {
|
|
181
|
+
this.response = new Blob([buffer], {
|
|
182
|
+
type: loaded.mimeType || 'application/octet-stream'
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
else {
|
|
186
|
+
const text = decoder.decode(loaded.bytes);
|
|
187
|
+
this.responseText = text;
|
|
188
|
+
this.response = text;
|
|
189
|
+
}
|
|
190
|
+
const progressEvent = new ProgressEvent('progress', {
|
|
191
|
+
lengthComputable: true,
|
|
192
|
+
loaded: loaded.bytes.byteLength,
|
|
193
|
+
total: loaded.bytes.byteLength
|
|
194
|
+
});
|
|
195
|
+
this.onprogress?.call(this, progressEvent);
|
|
196
|
+
this.onreadystatechange?.call(this, new ProgressEvent('readystatechange'));
|
|
197
|
+
this.onload?.call(this, new ProgressEvent('load'));
|
|
198
|
+
}
|
|
199
|
+
catch (error) {
|
|
200
|
+
this.readyState = 4;
|
|
201
|
+
this.status = 0;
|
|
202
|
+
this.statusText = error instanceof Error ? error.message : String(error);
|
|
203
|
+
this.onreadystatechange?.call(this, new ProgressEvent('readystatechange'));
|
|
204
|
+
this.onerror?.call(this, new ProgressEvent('error'));
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
abort() {
|
|
208
|
+
this.native?.abort();
|
|
209
|
+
}
|
|
210
|
+
getAllResponseHeaders() {
|
|
211
|
+
return this.native?.getAllResponseHeaders() || '';
|
|
212
|
+
}
|
|
213
|
+
getResponseHeader(name) {
|
|
214
|
+
return this.native?.getResponseHeader(name) || null;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
globalThis.XMLHttpRequest = RuntimeAssetXMLHttpRequest;
|
|
218
|
+
}
|
|
219
|
+
function installRuntimeAssetInterceptors() {
|
|
220
|
+
if (interceptorsInstalled)
|
|
221
|
+
return;
|
|
222
|
+
interceptorsInstalled = true;
|
|
223
|
+
globalThis.fetch = (async (input, init) => {
|
|
224
|
+
const resolvedUrl = trackedAssetUrl(input);
|
|
225
|
+
if (!resolvedUrl || !isTrackedAssetUrl(resolvedUrl))
|
|
226
|
+
return originalFetch(input, init);
|
|
227
|
+
return createTrackedResponse(await loadTrackedAsset(resolvedUrl));
|
|
228
|
+
});
|
|
229
|
+
installTrackedFetch();
|
|
230
|
+
}
|
|
231
|
+
export function configureWorkerRuntimeAssets(config) {
|
|
232
|
+
activeConfig = config;
|
|
233
|
+
installRuntimeAssetInterceptors();
|
|
234
|
+
}
|
|
235
|
+
export function handleWorkerAssetMessage(data) {
|
|
236
|
+
const response = data?.assetResponse;
|
|
237
|
+
if (!response)
|
|
238
|
+
return false;
|
|
239
|
+
const pending = pendingAssetRequests.get(response.id);
|
|
240
|
+
if (!pending)
|
|
241
|
+
return true;
|
|
242
|
+
pendingAssetRequests.delete(response.id);
|
|
243
|
+
if (!response.ok) {
|
|
244
|
+
pending.reject(new Error(response.error || 'Runtime asset request failed'));
|
|
245
|
+
return true;
|
|
246
|
+
}
|
|
247
|
+
pending.resolve({
|
|
248
|
+
bytes: new Uint8Array(response.bytes),
|
|
249
|
+
mimeType: response.mimeType || undefined
|
|
250
|
+
});
|
|
251
|
+
return true;
|
|
252
|
+
}
|
|
253
|
+
export async function loadWorkerRuntimeAsset(asset) {
|
|
254
|
+
if (!activeConfig)
|
|
255
|
+
throw new Error('Runtime asset config unavailable');
|
|
256
|
+
return await loadTrackedAsset(new URL(asset, activeConfig.baseUrl).href);
|
|
257
|
+
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { waitForBufferedStdin } from '../stdinBuffer';
|
|
1
2
|
self.document = {
|
|
2
3
|
querySelectorAll() {
|
|
3
4
|
return [];
|
|
@@ -9,32 +10,14 @@ async function loadClang(path, log) {
|
|
|
9
10
|
clang = new Clang({
|
|
10
11
|
stdout: (output) => postMessage({ output }),
|
|
11
12
|
onDebugEvent: (debugEvent) => postMessage({ debugEvent }),
|
|
12
|
-
stdin: () => {
|
|
13
|
-
while (true) {
|
|
14
|
-
postMessage({ buffer: true });
|
|
15
|
-
const res = Atomics.wait(stdinBufferClang, 0, 0, 100);
|
|
16
|
-
if (res === 'not-equal') {
|
|
17
|
-
try {
|
|
18
|
-
const cpy = new Int32Array(stdinBufferClang.byteLength);
|
|
19
|
-
cpy.set(stdinBufferClang);
|
|
20
|
-
stdinBufferClang.fill(0);
|
|
21
|
-
const dec = new TextDecoder();
|
|
22
|
-
const strInfo = dec.decode(cpy).replace(/\x00/g, ''), padding = parseInt(strInfo.slice(-1));
|
|
23
|
-
return strInfo.slice(0, -padding);
|
|
24
|
-
}
|
|
25
|
-
catch (e) {
|
|
26
|
-
postMessage({ log: { e } });
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
},
|
|
13
|
+
stdin: () => waitForBufferedStdin(stdinBufferClang, () => postMessage({ buffer: true })) ?? '',
|
|
31
14
|
progress: (value) => postMessage({ progress: value }),
|
|
32
15
|
log,
|
|
33
16
|
path
|
|
34
17
|
});
|
|
35
18
|
}
|
|
36
19
|
self.onmessage = async (event) => {
|
|
37
|
-
const { code, buffer, debugBuffer, load, interrupt, log, path, prepare,
|
|
20
|
+
const { code, buffer, debugBuffer, load, interrupt, log, path, prepare, language, compileArgs, programArgs, cppVersion, cVersion, debug, breakpoints, pauseOnEntry } = event.data;
|
|
38
21
|
if (load) {
|
|
39
22
|
await loadClang(path, log);
|
|
40
23
|
postMessage({ load: true });
|
|
@@ -45,7 +28,11 @@ self.onmessage = async (event) => {
|
|
|
45
28
|
interruptBufferClang = new Uint8Array(interrupt);
|
|
46
29
|
try {
|
|
47
30
|
await clang.compileLink(code, {
|
|
48
|
-
|
|
31
|
+
language,
|
|
32
|
+
compileArgs,
|
|
33
|
+
programArgs,
|
|
34
|
+
cppVersion,
|
|
35
|
+
cVersion,
|
|
49
36
|
debug,
|
|
50
37
|
breakpoints,
|
|
51
38
|
pauseOnEntry,
|
|
@@ -65,7 +52,11 @@ self.onmessage = async (event) => {
|
|
|
65
52
|
interruptBufferClang = new Uint8Array(interrupt);
|
|
66
53
|
try {
|
|
67
54
|
await clang.compileLinkRun(code, {
|
|
68
|
-
|
|
55
|
+
language,
|
|
56
|
+
compileArgs,
|
|
57
|
+
programArgs,
|
|
58
|
+
cppVersion,
|
|
59
|
+
cVersion,
|
|
69
60
|
debug,
|
|
70
61
|
breakpoints,
|
|
71
62
|
pauseOnEntry,
|
|
@@ -79,4 +70,3 @@ self.onmessage = async (event) => {
|
|
|
79
70
|
}
|
|
80
71
|
}
|
|
81
72
|
};
|
|
82
|
-
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
import { prepareJavaStdinInjection } from '../javaStdin';
|
|
2
|
+
import { waitForBufferedStdin } from '../stdinBuffer';
|
|
3
|
+
import { configureWorkerRuntimeAssets, handleWorkerAssetMessage, loadWorkerRuntimeAsset } from './assets';
|
|
4
|
+
let compilerLib = null;
|
|
5
|
+
let compiler = null;
|
|
6
|
+
let runtimeLoad = null;
|
|
7
|
+
let loadedBaseUrl = '';
|
|
8
|
+
let stdoutBuffer = '';
|
|
9
|
+
let stderrBuffer = '';
|
|
10
|
+
let stdinBufferJava = null;
|
|
11
|
+
let stdinChunkJava = new Uint8Array(0);
|
|
12
|
+
let stdinChunkOffsetJava = 0;
|
|
13
|
+
let compiledCode = '';
|
|
14
|
+
let compiledStdin = '';
|
|
15
|
+
let compiledMainClass = '';
|
|
16
|
+
let compiledWasm = null;
|
|
17
|
+
let currentSourcePath = '';
|
|
18
|
+
const decoder = new TextDecoder();
|
|
19
|
+
const toInt8Array = (bytes) => new Int8Array(bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength));
|
|
20
|
+
const flushStdout = () => {
|
|
21
|
+
if (!stdoutBuffer)
|
|
22
|
+
return;
|
|
23
|
+
self.postMessage({ output: stdoutBuffer });
|
|
24
|
+
stdoutBuffer = '';
|
|
25
|
+
};
|
|
26
|
+
const flushStderr = () => {
|
|
27
|
+
if (!stderrBuffer)
|
|
28
|
+
return;
|
|
29
|
+
self.postMessage({ output: stderrBuffer });
|
|
30
|
+
stderrBuffer = '';
|
|
31
|
+
};
|
|
32
|
+
const pushStdout = (charCode) => {
|
|
33
|
+
stdoutBuffer += String.fromCharCode(charCode);
|
|
34
|
+
if (charCode === 10)
|
|
35
|
+
flushStdout();
|
|
36
|
+
};
|
|
37
|
+
const pushStderr = (charCode) => {
|
|
38
|
+
stderrBuffer += String.fromCharCode(charCode);
|
|
39
|
+
if (charCode === 10)
|
|
40
|
+
flushStderr();
|
|
41
|
+
};
|
|
42
|
+
self.addEventListener('message', async (event) => {
|
|
43
|
+
if (handleWorkerAssetMessage(event.data))
|
|
44
|
+
return;
|
|
45
|
+
const { load, assets, buffer, code, prepare, args = [], stdin = '' } = event.data;
|
|
46
|
+
try {
|
|
47
|
+
if (load) {
|
|
48
|
+
const runtimeAssets = assets;
|
|
49
|
+
configureWorkerRuntimeAssets(runtimeAssets || null);
|
|
50
|
+
const baseUrl = runtimeAssets?.baseUrl || '';
|
|
51
|
+
if (!compiler || loadedBaseUrl !== baseUrl) {
|
|
52
|
+
loadedBaseUrl = baseUrl;
|
|
53
|
+
const runtimeSource = decoder.decode((await loadWorkerRuntimeAsset('compiler.wasm-runtime.js')).bytes);
|
|
54
|
+
const runtimeUrl = URL.createObjectURL(new Blob([runtimeSource], { type: 'text/javascript;charset=utf-8' }));
|
|
55
|
+
const runtimeModule = await import(/* @vite-ignore */ runtimeUrl);
|
|
56
|
+
URL.revokeObjectURL(runtimeUrl);
|
|
57
|
+
const runtimeLoadFn = runtimeModule.load;
|
|
58
|
+
runtimeLoad = runtimeLoadFn;
|
|
59
|
+
const compilerWasm = (await loadWorkerRuntimeAsset('compiler.wasm')).bytes;
|
|
60
|
+
const compilerModule = await runtimeLoadFn(compilerWasm, {
|
|
61
|
+
stackDeobfuscator: { enabled: false }
|
|
62
|
+
});
|
|
63
|
+
compilerLib = compilerModule.exports;
|
|
64
|
+
compiler = compilerLib.createCompiler();
|
|
65
|
+
const [sdk, runtimeClasslib] = await Promise.all([
|
|
66
|
+
loadWorkerRuntimeAsset('compile-classlib-teavm.bin').then(({ bytes }) => toInt8Array(bytes)),
|
|
67
|
+
loadWorkerRuntimeAsset('runtime-classlib-teavm.bin').then(({ bytes }) => toInt8Array(bytes))
|
|
68
|
+
]);
|
|
69
|
+
compiler.setSdk(sdk);
|
|
70
|
+
compiler.setTeaVMClasslib(runtimeClasslib);
|
|
71
|
+
compiledCode = '';
|
|
72
|
+
compiledStdin = '';
|
|
73
|
+
compiledMainClass = '';
|
|
74
|
+
compiledWasm = null;
|
|
75
|
+
}
|
|
76
|
+
self.postMessage({ load: true });
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
if (!compiler || !runtimeLoad)
|
|
80
|
+
throw new Error('TeaVM compiler not loaded');
|
|
81
|
+
stdoutBuffer = '';
|
|
82
|
+
stderrBuffer = '';
|
|
83
|
+
const stdinInjection = prepareJavaStdinInjection(code, stdin);
|
|
84
|
+
if (prepare ||
|
|
85
|
+
compiledCode !== code ||
|
|
86
|
+
compiledStdin !== stdinInjection.stdinCacheKey ||
|
|
87
|
+
!compiledWasm) {
|
|
88
|
+
const packageMatch = code.match(/^\s*package\s+([A-Za-z_][\w.]*)\s*;/m);
|
|
89
|
+
const typeMatch = code.match(/^\s*public\s+(?:final\s+|abstract\s+)?(?:class|record|enum|interface)\s+([A-Za-z_]\w*)\b/m) ||
|
|
90
|
+
code.match(/^\s*(?:final\s+|abstract\s+)?(?:class|record|enum|interface)\s+([A-Za-z_]\w*)\b/m);
|
|
91
|
+
const className = typeMatch?.[1];
|
|
92
|
+
if (!className)
|
|
93
|
+
throw new Error('Java source must define a top-level class, record, enum, or interface');
|
|
94
|
+
const packageName = packageMatch?.[1] || '';
|
|
95
|
+
const sourcePath = packageName
|
|
96
|
+
? `${packageName.replaceAll('.', '/')}/${className}.java`
|
|
97
|
+
: `${className}.java`;
|
|
98
|
+
const mainClass = packageName ? `${packageName}.${className}` : className;
|
|
99
|
+
currentSourcePath = sourcePath;
|
|
100
|
+
const diagnosticLines = [];
|
|
101
|
+
const diagnosticRegistration = compiler.onDiagnostic((diagnostic) => {
|
|
102
|
+
const severity = diagnostic.severity
|
|
103
|
+
? String(diagnostic.severity).toLowerCase()
|
|
104
|
+
: 'error';
|
|
105
|
+
const location = diagnostic.fileName
|
|
106
|
+
? `${diagnostic.fileName}:${diagnostic.lineNumber || 0}${diagnostic.columnNumber ? `:${diagnostic.columnNumber}` : ''}`
|
|
107
|
+
: 'TeaVM';
|
|
108
|
+
diagnosticLines.push(`${location}: ${severity}: ${diagnostic.message}`);
|
|
109
|
+
const fileName = diagnostic.fileName ? String(diagnostic.fileName) : null;
|
|
110
|
+
if (fileName &&
|
|
111
|
+
fileName !== currentSourcePath &&
|
|
112
|
+
fileName !== currentSourcePath.split('/').pop()) {
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
self.postMessage({
|
|
116
|
+
diagnostic: {
|
|
117
|
+
fileName,
|
|
118
|
+
lineNumber: Number(diagnostic.lineNumber) || 1,
|
|
119
|
+
columnNumber: Number(diagnostic.columnNumber) || 1,
|
|
120
|
+
severity: severity === 'warning'
|
|
121
|
+
? 'warning'
|
|
122
|
+
: severity === 'other'
|
|
123
|
+
? 'other'
|
|
124
|
+
: 'error',
|
|
125
|
+
message: String(diagnostic.message || '')
|
|
126
|
+
}
|
|
127
|
+
});
|
|
128
|
+
});
|
|
129
|
+
const disposeDiagnosticRegistration = () => {
|
|
130
|
+
if (typeof diagnosticRegistration === 'function') {
|
|
131
|
+
diagnosticRegistration();
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
diagnosticRegistration?.destroy?.();
|
|
135
|
+
};
|
|
136
|
+
compiler.clearSourceFiles?.();
|
|
137
|
+
compiler.clearInputClassFiles?.();
|
|
138
|
+
compiler.clearOutputFiles?.();
|
|
139
|
+
compiler.addSourceFile(sourcePath, stdinInjection.transformedCode);
|
|
140
|
+
if (stdinInjection.usesStdin &&
|
|
141
|
+
stdinInjection.helperSourcePath &&
|
|
142
|
+
stdinInjection.helperSource) {
|
|
143
|
+
compiler.addSourceFile(stdinInjection.helperSourcePath, stdinInjection.helperSource);
|
|
144
|
+
}
|
|
145
|
+
const javacOk = compiler.compile();
|
|
146
|
+
if (!javacOk) {
|
|
147
|
+
disposeDiagnosticRegistration();
|
|
148
|
+
throw new Error(diagnosticLines.join('\n') || 'TeaVM javac compilation failed');
|
|
149
|
+
}
|
|
150
|
+
const mainClasses = Array.from(compiler.detectMainClasses());
|
|
151
|
+
if (mainClasses.length !== 1) {
|
|
152
|
+
disposeDiagnosticRegistration();
|
|
153
|
+
throw new Error(mainClasses.length === 0
|
|
154
|
+
? 'Main method not found'
|
|
155
|
+
: 'Multiple main methods found');
|
|
156
|
+
}
|
|
157
|
+
const generateOk = compiler.generateWebAssembly({
|
|
158
|
+
outputName: 'app',
|
|
159
|
+
mainClass
|
|
160
|
+
});
|
|
161
|
+
disposeDiagnosticRegistration();
|
|
162
|
+
if (!generateOk) {
|
|
163
|
+
throw new Error(diagnosticLines.join('\n') || 'TeaVM WebAssembly generation failed');
|
|
164
|
+
}
|
|
165
|
+
compiledCode = code;
|
|
166
|
+
compiledStdin = stdinInjection.stdinCacheKey;
|
|
167
|
+
compiledMainClass = mainClass;
|
|
168
|
+
compiledWasm = new Uint8Array(compiler.getWebAssemblyOutputFile('app.wasm'));
|
|
169
|
+
}
|
|
170
|
+
if (prepare) {
|
|
171
|
+
self.postMessage({ results: true });
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
stdinBufferJava = new Int32Array(buffer);
|
|
175
|
+
const workerGlobal = globalThis;
|
|
176
|
+
const previousWindow = workerGlobal.window;
|
|
177
|
+
workerGlobal.window = workerGlobal;
|
|
178
|
+
workerGlobal.wasmIdleJavaStdin = {
|
|
179
|
+
readByte() {
|
|
180
|
+
while (true) {
|
|
181
|
+
if (stdinChunkOffsetJava < stdinChunkJava.length) {
|
|
182
|
+
return stdinChunkJava[stdinChunkOffsetJava++] ?? -1;
|
|
183
|
+
}
|
|
184
|
+
const chunk = waitForBufferedStdin(stdinBufferJava, () => self.postMessage({ buffer: true }));
|
|
185
|
+
if (chunk === null) {
|
|
186
|
+
return -1;
|
|
187
|
+
}
|
|
188
|
+
stdinChunkJava = new TextEncoder().encode(chunk);
|
|
189
|
+
stdinChunkOffsetJava = 0;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
};
|
|
193
|
+
try {
|
|
194
|
+
const module = await runtimeLoad(compiledWasm, {
|
|
195
|
+
installImports(imports) {
|
|
196
|
+
imports.teavmConsole.putcharStdout = pushStdout;
|
|
197
|
+
imports.teavmConsole.putcharStderr = pushStderr;
|
|
198
|
+
},
|
|
199
|
+
stackDeobfuscator: {
|
|
200
|
+
enabled: false
|
|
201
|
+
}
|
|
202
|
+
});
|
|
203
|
+
module.exports.main(args);
|
|
204
|
+
flushStdout();
|
|
205
|
+
flushStderr();
|
|
206
|
+
self.postMessage({ results: true, mainClass: compiledMainClass });
|
|
207
|
+
}
|
|
208
|
+
finally {
|
|
209
|
+
delete workerGlobal.wasmIdleJavaStdin;
|
|
210
|
+
if (previousWindow === undefined) {
|
|
211
|
+
Reflect.deleteProperty(workerGlobal, 'window');
|
|
212
|
+
}
|
|
213
|
+
else {
|
|
214
|
+
workerGlobal.window = previousWindow;
|
|
215
|
+
}
|
|
216
|
+
stdinChunkJava = new Uint8Array(0);
|
|
217
|
+
stdinChunkOffsetJava = 0;
|
|
218
|
+
stdinBufferJava = null;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
catch (error) {
|
|
222
|
+
flushStdout();
|
|
223
|
+
flushStderr();
|
|
224
|
+
self.postMessage({
|
|
225
|
+
error: error instanceof Error ? error.message : String(error)
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
});
|