wasm-idle 0.3.2 → 0.4.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 +4 -2
- package/dist/clang/app.d.ts +2 -1
- package/dist/clang/app.js +149 -132
- package/dist/clang/index.js +2 -2
- 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 +2 -1
- package/dist/index.js +1 -1
- package/dist/playground/clang.d.ts +5 -2
- package/dist/playground/clang.js +44 -18
- package/dist/playground/index.d.ts +2 -1
- package/dist/playground/index.js +7 -1
- package/dist/playground/java.d.ts +28 -0
- package/dist/playground/java.js +135 -0
- package/dist/playground/javaStdin.d.ts +8 -0
- package/dist/playground/javaStdin.js +90 -0
- package/dist/playground/options.d.ts +10 -0
- package/dist/playground/python.d.ts +15 -5
- package/dist/playground/python.js +98 -21
- package/dist/playground/sandbox.d.ts +3 -1
- 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/clang.js +2 -20
- package/dist/playground/worker/java.d.ts +1 -0
- package/dist/playground/worker/java.js +248 -0
- package/dist/playground/worker/python.js +253 -27
- package/dist/terminal/Terminal.svelte +41 -11
- package/dist/terminal/Terminal.svelte.d.ts +2 -1
- package/dist/terminal/types.d.ts +3 -2
- package/package.json +1 -1
|
@@ -1,17 +1,27 @@
|
|
|
1
|
+
import { bufferedSequence, flushBufferedEof, flushQueuedStdin, resetBufferedStdin, waitForBufferedSequenceChange } from './stdinBuffer';
|
|
1
2
|
class Python {
|
|
2
3
|
ts = Date.now();
|
|
3
4
|
output = null;
|
|
5
|
+
ondebug;
|
|
4
6
|
image;
|
|
5
7
|
worker = null;
|
|
6
8
|
buffer = new SharedArrayBuffer(1024);
|
|
9
|
+
debugBuffer = new SharedArrayBuffer(Int32Array.BYTES_PER_ELEMENT * 4);
|
|
10
|
+
watchBuffer = new SharedArrayBuffer(1024);
|
|
11
|
+
watchResultBuffer = new SharedArrayBuffer(1024);
|
|
7
12
|
interruptBuffer = new SharedArrayBuffer(1);
|
|
8
|
-
|
|
13
|
+
pendingInput = [];
|
|
9
14
|
begin = 0;
|
|
10
15
|
elapse = 0;
|
|
11
16
|
uid = 0;
|
|
17
|
+
waitingForInput = false;
|
|
18
|
+
pendingEof = false;
|
|
19
|
+
exit = true;
|
|
12
20
|
load(path, code = '', log = true, _args = [], _options = {}) {
|
|
13
21
|
return new Promise(async (resolve) => {
|
|
14
|
-
this.
|
|
22
|
+
this.pendingInput = [];
|
|
23
|
+
this.waitingForInput = false;
|
|
24
|
+
this.pendingEof = false;
|
|
15
25
|
if (!this.worker) {
|
|
16
26
|
this.worker = new (await import('./worker/python?worker')).default();
|
|
17
27
|
this.worker.onmessage = () => resolve();
|
|
@@ -24,40 +34,65 @@ class Python {
|
|
|
24
34
|
});
|
|
25
35
|
}
|
|
26
36
|
write(input) {
|
|
27
|
-
this.
|
|
37
|
+
this.pendingInput.push(input);
|
|
38
|
+
this.pendingEof = false;
|
|
39
|
+
this.flushPendingInput();
|
|
28
40
|
}
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
strInfo += ' ';
|
|
33
|
-
strInfo += padding;
|
|
34
|
-
const buffer = new Int32Array(this.buffer);
|
|
35
|
-
const enc = new TextEncoder();
|
|
36
|
-
const data = enc.encode(strInfo);
|
|
37
|
-
buffer.set(new Int32Array(data.buffer.slice(data.byteOffset), 0));
|
|
41
|
+
eof() {
|
|
42
|
+
this.pendingEof = true;
|
|
43
|
+
this.flushPendingInput();
|
|
38
44
|
}
|
|
39
|
-
|
|
40
|
-
|
|
45
|
+
flushPendingInput() {
|
|
46
|
+
if (!this.waitingForInput)
|
|
47
|
+
return;
|
|
48
|
+
if (flushQueuedStdin(this.pendingInput, this.buffer)) {
|
|
49
|
+
this.waitingForInput = false;
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
if (this.pendingEof) {
|
|
53
|
+
flushBufferedEof(this.buffer);
|
|
54
|
+
this.pendingEof = false;
|
|
55
|
+
this.waitingForInput = false;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
run(code, prepare, _log = true, _prog, _args = [], options = {}) {
|
|
59
|
+
this.exit = false;
|
|
41
60
|
return new Promise(async (resolve, reject) => {
|
|
61
|
+
if (!this.worker)
|
|
62
|
+
return reject('Worker not loaded');
|
|
42
63
|
const interrupt = new Uint8Array(this.interruptBuffer), _uid = ++this.uid;
|
|
43
64
|
const handler = (event) => {
|
|
65
|
+
if (!this.worker)
|
|
66
|
+
return reject('Worker not loaded');
|
|
44
67
|
if (_uid !== this.uid)
|
|
45
68
|
return (this.worker.onmessage = null);
|
|
46
|
-
const {
|
|
47
|
-
if (buffer
|
|
48
|
-
this.
|
|
69
|
+
const { output, results, log, error, buffer, type, data: payload, debugEvent } = event.data;
|
|
70
|
+
if (buffer) {
|
|
71
|
+
this.waitingForInput = true;
|
|
72
|
+
this.flushPendingInput();
|
|
73
|
+
}
|
|
49
74
|
if (type === 'img' && payload)
|
|
50
75
|
this.image?.(payload);
|
|
51
76
|
if (output)
|
|
52
77
|
this.output(output);
|
|
78
|
+
if (debugEvent)
|
|
79
|
+
this.ondebug?.(debugEvent);
|
|
53
80
|
if (results) {
|
|
54
81
|
this.elapse = Date.now() - this.begin;
|
|
82
|
+
this.exit = true;
|
|
83
|
+
this.waitingForInput = false;
|
|
84
|
+
this.pendingEof = false;
|
|
85
|
+
this.ondebug?.({ type: 'stop' });
|
|
55
86
|
resolve(results);
|
|
56
87
|
}
|
|
57
88
|
if (log)
|
|
58
89
|
console.log(log);
|
|
59
90
|
if (error) {
|
|
60
91
|
this.elapse = Date.now() - this.begin;
|
|
92
|
+
this.exit = true;
|
|
93
|
+
this.waitingForInput = false;
|
|
94
|
+
this.pendingEof = false;
|
|
95
|
+
this.ondebug?.({ type: 'stop' });
|
|
61
96
|
reject(error);
|
|
62
97
|
}
|
|
63
98
|
};
|
|
@@ -68,22 +103,64 @@ class Python {
|
|
|
68
103
|
code,
|
|
69
104
|
prepare,
|
|
70
105
|
buffer: this.buffer,
|
|
106
|
+
debugBuffer: this.debugBuffer,
|
|
107
|
+
watchBuffer: this.watchBuffer,
|
|
108
|
+
watchResultBuffer: this.watchResultBuffer,
|
|
71
109
|
interrupt: this.interruptBuffer,
|
|
72
|
-
context: {}
|
|
110
|
+
context: {},
|
|
111
|
+
debug: !!options.debug,
|
|
112
|
+
breakpoints: [...(options.breakpoints || [])],
|
|
113
|
+
pauseOnEntry: !!options.pauseOnEntry
|
|
73
114
|
});
|
|
74
115
|
});
|
|
75
116
|
}
|
|
117
|
+
debugCommand(command) {
|
|
118
|
+
const control = new Int32Array(this.debugBuffer);
|
|
119
|
+
Atomics.store(control, 1, command === 'stepInto' ? 2 : command === 'nextLine' ? 3 : command === 'stepOut' ? 4 : 1);
|
|
120
|
+
Atomics.add(control, 0, 1);
|
|
121
|
+
Atomics.notify(control, 0);
|
|
122
|
+
this.ondebug?.({ type: 'resume', command });
|
|
123
|
+
}
|
|
124
|
+
async debugEvaluate(expression) {
|
|
125
|
+
if (!this.worker)
|
|
126
|
+
throw new Error('Worker not loaded');
|
|
127
|
+
resetBufferedStdin(this.watchResultBuffer);
|
|
128
|
+
const previousSequence = bufferedSequence(this.watchResultBuffer);
|
|
129
|
+
flushQueuedStdin([expression], this.watchBuffer);
|
|
130
|
+
const control = new Int32Array(this.debugBuffer);
|
|
131
|
+
Atomics.store(control, 1, 5);
|
|
132
|
+
Atomics.add(control, 0, 1);
|
|
133
|
+
Atomics.notify(control, 0);
|
|
134
|
+
return (await waitForBufferedSequenceChange(this.watchResultBuffer, previousSequence, 5000)) ?? '?';
|
|
135
|
+
}
|
|
136
|
+
kill() {
|
|
137
|
+
this.terminate();
|
|
138
|
+
}
|
|
76
139
|
terminate() {
|
|
140
|
+
this.pendingEof = false;
|
|
77
141
|
new Uint8Array(this.interruptBuffer)[0] = 2;
|
|
142
|
+
const control = new Int32Array(this.debugBuffer);
|
|
143
|
+
Atomics.add(control, 0, 1);
|
|
144
|
+
Atomics.notify(control, 0);
|
|
78
145
|
}
|
|
79
146
|
async clear() {
|
|
80
147
|
this.terminate();
|
|
81
|
-
this.
|
|
148
|
+
this.pendingInput = [];
|
|
149
|
+
this.waitingForInput = false;
|
|
150
|
+
this.pendingEof = false;
|
|
82
151
|
if (this.worker)
|
|
83
152
|
this.worker.onmessage = null;
|
|
84
|
-
|
|
85
|
-
|
|
153
|
+
resetBufferedStdin(this.buffer);
|
|
154
|
+
resetBufferedStdin(this.watchBuffer);
|
|
155
|
+
resetBufferedStdin(this.watchResultBuffer);
|
|
156
|
+
const debugBuffer = new Int32Array(this.debugBuffer);
|
|
157
|
+
debugBuffer.fill(0);
|
|
86
158
|
await new Promise((resolve) => setTimeout(resolve, 200));
|
|
159
|
+
if (!this.exit) {
|
|
160
|
+
this.worker?.terminate?.();
|
|
161
|
+
delete this.worker;
|
|
162
|
+
this.exit = true;
|
|
163
|
+
}
|
|
87
164
|
}
|
|
88
165
|
}
|
|
89
166
|
export default Python;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { DebugCommand, DebugSessionEvent, SandboxExecutionOptions } from './options';
|
|
1
|
+
import type { CompilerDiagnostic, DebugCommand, DebugSessionEvent, SandboxExecutionOptions } from './options';
|
|
2
2
|
import type { Writable } from 'svelte/store';
|
|
3
3
|
type ProgressLike = Writable<number> | {
|
|
4
4
|
set?: (value: number) => void;
|
|
@@ -14,7 +14,9 @@ export interface Sandbox {
|
|
|
14
14
|
write?: (data: string) => void;
|
|
15
15
|
output?: (data: string) => void;
|
|
16
16
|
ondebug?: (event: DebugSessionEvent) => void;
|
|
17
|
+
oncompilerdiagnostic?: (diagnostic: CompilerDiagnostic) => void;
|
|
17
18
|
debugCommand?: (command: DebugCommand) => void;
|
|
19
|
+
debugEvaluate?: (expression: string) => Promise<string>;
|
|
18
20
|
image?: (data: {
|
|
19
21
|
mime: string;
|
|
20
22
|
b64: string;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export declare const flushQueuedStdin: (queue: string[], buffer: SharedArrayBuffer | Int32Array) => boolean;
|
|
2
|
+
export declare const readBufferedStdin: (buffer: SharedArrayBuffer | Int32Array) => string | null;
|
|
3
|
+
export declare const bufferedSequence: (buffer: SharedArrayBuffer | Int32Array) => number;
|
|
4
|
+
export declare const waitForBufferedSequenceChange: (buffer: SharedArrayBuffer | Int32Array, sequence: number, timeoutMs?: number) => Promise<string | null>;
|
|
5
|
+
export declare const flushBufferedEof: (buffer: SharedArrayBuffer | Int32Array) => void;
|
|
6
|
+
export declare const waitForBufferedStdin: (buffer: Int32Array, requestInput: () => void) => string | null;
|
|
7
|
+
export declare const resetBufferedStdin: (buffer: SharedArrayBuffer | Int32Array) => void;
|
|
@@ -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;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { waitForBufferedStdin } from '../stdinBuffer';
|
|
1
2
|
self.document = {
|
|
2
3
|
querySelectorAll() {
|
|
3
4
|
return [];
|
|
@@ -9,25 +10,7 @@ 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
|
|
@@ -79,4 +62,3 @@ self.onmessage = async (event) => {
|
|
|
79
62
|
}
|
|
80
63
|
}
|
|
81
64
|
};
|
|
82
|
-
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
import { prepareJavaStdinInjection } from '../javaStdin';
|
|
2
|
+
import { waitForBufferedStdin } from '../stdinBuffer';
|
|
3
|
+
import { resolveTeaVmAssetUrl } from '../teavmConfig';
|
|
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 isGzip = (bytes) => bytes.length >= 2 && bytes[0] === 0x1f && bytes[1] === 0x8b;
|
|
19
|
+
const maybeDecompressWasm = async (bytes) => {
|
|
20
|
+
if (!isGzip(bytes))
|
|
21
|
+
return bytes;
|
|
22
|
+
if (typeof DecompressionStream === 'undefined') {
|
|
23
|
+
throw new Error('Generated WebAssembly is gzip-compressed, but DecompressionStream is unavailable');
|
|
24
|
+
}
|
|
25
|
+
const stream = new Blob([Uint8Array.from(bytes)])
|
|
26
|
+
.stream()
|
|
27
|
+
.pipeThrough(new DecompressionStream('gzip'));
|
|
28
|
+
return new Uint8Array(await new Response(stream).arrayBuffer());
|
|
29
|
+
};
|
|
30
|
+
const flushStdout = () => {
|
|
31
|
+
if (!stdoutBuffer)
|
|
32
|
+
return;
|
|
33
|
+
self.postMessage({ output: stdoutBuffer });
|
|
34
|
+
stdoutBuffer = '';
|
|
35
|
+
};
|
|
36
|
+
const flushStderr = () => {
|
|
37
|
+
if (!stderrBuffer)
|
|
38
|
+
return;
|
|
39
|
+
self.postMessage({ output: stderrBuffer });
|
|
40
|
+
stderrBuffer = '';
|
|
41
|
+
};
|
|
42
|
+
const pushStdout = (charCode) => {
|
|
43
|
+
stdoutBuffer += String.fromCharCode(charCode);
|
|
44
|
+
if (charCode === 10)
|
|
45
|
+
flushStdout();
|
|
46
|
+
};
|
|
47
|
+
const pushStderr = (charCode) => {
|
|
48
|
+
stderrBuffer += String.fromCharCode(charCode);
|
|
49
|
+
if (charCode === 10)
|
|
50
|
+
flushStderr();
|
|
51
|
+
};
|
|
52
|
+
self.addEventListener('message', async (event) => {
|
|
53
|
+
const { load, baseUrl, buffer, code, prepare, args = [], stdin = '' } = event.data;
|
|
54
|
+
try {
|
|
55
|
+
if (load) {
|
|
56
|
+
if (!compiler || loadedBaseUrl !== baseUrl) {
|
|
57
|
+
loadedBaseUrl = baseUrl;
|
|
58
|
+
const runtimeSource = await fetch(resolveTeaVmAssetUrl(baseUrl, 'compiler.wasm-runtime.js')).then((response) => {
|
|
59
|
+
if (!response.ok)
|
|
60
|
+
throw new Error(`Failed to load TeaVM compiler runtime: ${response.status}`);
|
|
61
|
+
return response.text();
|
|
62
|
+
});
|
|
63
|
+
const runtimeUrl = URL.createObjectURL(new Blob([runtimeSource], { type: 'text/javascript;charset=utf-8' }));
|
|
64
|
+
const runtimeModule = await import(/* @vite-ignore */ runtimeUrl);
|
|
65
|
+
const runtimeLoadFn = runtimeModule.load;
|
|
66
|
+
runtimeLoad = runtimeLoadFn;
|
|
67
|
+
const compilerWasm = await fetch(resolveTeaVmAssetUrl(baseUrl, 'compiler.wasm')).then(async (response) => {
|
|
68
|
+
if (!response.ok)
|
|
69
|
+
throw new Error(`Failed to load TeaVM compiler wasm: ${response.status}`);
|
|
70
|
+
return await maybeDecompressWasm(new Uint8Array(await response.arrayBuffer()));
|
|
71
|
+
});
|
|
72
|
+
const compilerModule = await runtimeLoadFn(compilerWasm, {
|
|
73
|
+
stackDeobfuscator: { enabled: false }
|
|
74
|
+
});
|
|
75
|
+
compilerLib = compilerModule.exports;
|
|
76
|
+
compiler = compilerLib.createCompiler();
|
|
77
|
+
const [sdk, runtimeClasslib] = await Promise.all([
|
|
78
|
+
fetch(resolveTeaVmAssetUrl(baseUrl, 'compile-classlib-teavm.bin')).then(async (response) => {
|
|
79
|
+
if (!response.ok)
|
|
80
|
+
throw new Error(`Failed to load TeaVM SDK: ${response.status}`);
|
|
81
|
+
return new Int8Array(await response.arrayBuffer());
|
|
82
|
+
}),
|
|
83
|
+
fetch(resolveTeaVmAssetUrl(baseUrl, 'runtime-classlib-teavm.bin')).then(async (response) => {
|
|
84
|
+
if (!response.ok)
|
|
85
|
+
throw new Error(`Failed to load TeaVM runtime classlib: ${response.status}`);
|
|
86
|
+
return new Int8Array(await response.arrayBuffer());
|
|
87
|
+
})
|
|
88
|
+
]);
|
|
89
|
+
compiler.setSdk(sdk);
|
|
90
|
+
compiler.setTeaVMClasslib(runtimeClasslib);
|
|
91
|
+
compiledCode = '';
|
|
92
|
+
compiledStdin = '';
|
|
93
|
+
compiledMainClass = '';
|
|
94
|
+
compiledWasm = null;
|
|
95
|
+
}
|
|
96
|
+
self.postMessage({ load: true });
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
if (!compiler || !runtimeLoad)
|
|
100
|
+
throw new Error('TeaVM compiler not loaded');
|
|
101
|
+
stdoutBuffer = '';
|
|
102
|
+
stderrBuffer = '';
|
|
103
|
+
const stdinInjection = prepareJavaStdinInjection(code, stdin);
|
|
104
|
+
if (prepare ||
|
|
105
|
+
compiledCode !== code ||
|
|
106
|
+
compiledStdin !== stdinInjection.stdinCacheKey ||
|
|
107
|
+
!compiledWasm) {
|
|
108
|
+
const packageMatch = code.match(/^\s*package\s+([A-Za-z_][\w.]*)\s*;/m);
|
|
109
|
+
const typeMatch = code.match(/^\s*public\s+(?:final\s+|abstract\s+)?(?:class|record|enum|interface)\s+([A-Za-z_]\w*)\b/m) ||
|
|
110
|
+
code.match(/^\s*(?:final\s+|abstract\s+)?(?:class|record|enum|interface)\s+([A-Za-z_]\w*)\b/m);
|
|
111
|
+
const className = typeMatch?.[1];
|
|
112
|
+
if (!className)
|
|
113
|
+
throw new Error('Java source must define a top-level class, record, enum, or interface');
|
|
114
|
+
const packageName = packageMatch?.[1] || '';
|
|
115
|
+
const sourcePath = packageName
|
|
116
|
+
? `${packageName.replaceAll('.', '/')}/${className}.java`
|
|
117
|
+
: `${className}.java`;
|
|
118
|
+
const mainClass = packageName ? `${packageName}.${className}` : className;
|
|
119
|
+
currentSourcePath = sourcePath;
|
|
120
|
+
const diagnosticLines = [];
|
|
121
|
+
const diagnosticRegistration = compiler.onDiagnostic((diagnostic) => {
|
|
122
|
+
const severity = diagnostic.severity
|
|
123
|
+
? String(diagnostic.severity).toLowerCase()
|
|
124
|
+
: 'error';
|
|
125
|
+
const location = diagnostic.fileName
|
|
126
|
+
? `${diagnostic.fileName}:${diagnostic.lineNumber || 0}${diagnostic.columnNumber ? `:${diagnostic.columnNumber}` : ''}`
|
|
127
|
+
: 'TeaVM';
|
|
128
|
+
diagnosticLines.push(`${location}: ${severity}: ${diagnostic.message}`);
|
|
129
|
+
const fileName = diagnostic.fileName ? String(diagnostic.fileName) : null;
|
|
130
|
+
if (fileName &&
|
|
131
|
+
fileName !== currentSourcePath &&
|
|
132
|
+
fileName !== currentSourcePath.split('/').pop()) {
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
self.postMessage({
|
|
136
|
+
diagnostic: {
|
|
137
|
+
fileName,
|
|
138
|
+
lineNumber: Number(diagnostic.lineNumber) || 1,
|
|
139
|
+
columnNumber: Number(diagnostic.columnNumber) || 1,
|
|
140
|
+
severity: severity === 'warning'
|
|
141
|
+
? 'warning'
|
|
142
|
+
: severity === 'other'
|
|
143
|
+
? 'other'
|
|
144
|
+
: 'error',
|
|
145
|
+
message: String(diagnostic.message || '')
|
|
146
|
+
}
|
|
147
|
+
});
|
|
148
|
+
});
|
|
149
|
+
const disposeDiagnosticRegistration = () => {
|
|
150
|
+
if (typeof diagnosticRegistration === 'function') {
|
|
151
|
+
diagnosticRegistration();
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
diagnosticRegistration?.destroy?.();
|
|
155
|
+
};
|
|
156
|
+
compiler.clearSourceFiles?.();
|
|
157
|
+
compiler.clearInputClassFiles?.();
|
|
158
|
+
compiler.clearOutputFiles?.();
|
|
159
|
+
compiler.addSourceFile(sourcePath, stdinInjection.transformedCode);
|
|
160
|
+
if (stdinInjection.usesStdin &&
|
|
161
|
+
stdinInjection.helperSourcePath &&
|
|
162
|
+
stdinInjection.helperSource) {
|
|
163
|
+
compiler.addSourceFile(stdinInjection.helperSourcePath, stdinInjection.helperSource);
|
|
164
|
+
}
|
|
165
|
+
const javacOk = compiler.compile();
|
|
166
|
+
if (!javacOk) {
|
|
167
|
+
disposeDiagnosticRegistration();
|
|
168
|
+
throw new Error(diagnosticLines.join('\n') || 'TeaVM javac compilation failed');
|
|
169
|
+
}
|
|
170
|
+
const mainClasses = Array.from(compiler.detectMainClasses());
|
|
171
|
+
if (mainClasses.length !== 1) {
|
|
172
|
+
disposeDiagnosticRegistration();
|
|
173
|
+
throw new Error(mainClasses.length === 0
|
|
174
|
+
? 'Main method not found'
|
|
175
|
+
: 'Multiple main methods found');
|
|
176
|
+
}
|
|
177
|
+
const generateOk = compiler.generateWebAssembly({
|
|
178
|
+
outputName: 'app',
|
|
179
|
+
mainClass
|
|
180
|
+
});
|
|
181
|
+
disposeDiagnosticRegistration();
|
|
182
|
+
if (!generateOk) {
|
|
183
|
+
throw new Error(diagnosticLines.join('\n') || 'TeaVM WebAssembly generation failed');
|
|
184
|
+
}
|
|
185
|
+
compiledCode = code;
|
|
186
|
+
compiledStdin = stdinInjection.stdinCacheKey;
|
|
187
|
+
compiledMainClass = mainClass;
|
|
188
|
+
compiledWasm = await maybeDecompressWasm(new Uint8Array(compiler.getWebAssemblyOutputFile('app.wasm')));
|
|
189
|
+
}
|
|
190
|
+
if (prepare) {
|
|
191
|
+
self.postMessage({ results: true });
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
stdinBufferJava = new Int32Array(buffer);
|
|
195
|
+
const workerGlobal = globalThis;
|
|
196
|
+
const previousWindow = workerGlobal.window;
|
|
197
|
+
workerGlobal.window = workerGlobal;
|
|
198
|
+
workerGlobal.wasmIdleJavaStdin = {
|
|
199
|
+
readByte() {
|
|
200
|
+
while (true) {
|
|
201
|
+
if (stdinChunkOffsetJava < stdinChunkJava.length) {
|
|
202
|
+
return stdinChunkJava[stdinChunkOffsetJava++] ?? -1;
|
|
203
|
+
}
|
|
204
|
+
const chunk = waitForBufferedStdin(stdinBufferJava, () => self.postMessage({ buffer: true }));
|
|
205
|
+
if (chunk === null) {
|
|
206
|
+
return -1;
|
|
207
|
+
}
|
|
208
|
+
stdinChunkJava = new TextEncoder().encode(chunk);
|
|
209
|
+
stdinChunkOffsetJava = 0;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
};
|
|
213
|
+
try {
|
|
214
|
+
const module = await runtimeLoad(compiledWasm, {
|
|
215
|
+
installImports(imports) {
|
|
216
|
+
imports.teavmConsole.putcharStdout = pushStdout;
|
|
217
|
+
imports.teavmConsole.putcharStderr = pushStderr;
|
|
218
|
+
},
|
|
219
|
+
stackDeobfuscator: {
|
|
220
|
+
enabled: false
|
|
221
|
+
}
|
|
222
|
+
});
|
|
223
|
+
module.exports.main(args);
|
|
224
|
+
flushStdout();
|
|
225
|
+
flushStderr();
|
|
226
|
+
self.postMessage({ results: true, mainClass: compiledMainClass });
|
|
227
|
+
}
|
|
228
|
+
finally {
|
|
229
|
+
delete workerGlobal.wasmIdleJavaStdin;
|
|
230
|
+
if (previousWindow === undefined) {
|
|
231
|
+
Reflect.deleteProperty(workerGlobal, 'window');
|
|
232
|
+
}
|
|
233
|
+
else {
|
|
234
|
+
workerGlobal.window = previousWindow;
|
|
235
|
+
}
|
|
236
|
+
stdinChunkJava = new Uint8Array(0);
|
|
237
|
+
stdinChunkOffsetJava = 0;
|
|
238
|
+
stdinBufferJava = null;
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
catch (error) {
|
|
242
|
+
flushStdout();
|
|
243
|
+
flushStderr();
|
|
244
|
+
self.postMessage({
|
|
245
|
+
error: error instanceof Error ? error.message : String(error)
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
});
|