wasm-idle 0.3.1 → 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 +4 -1
- package/dist/clang/app.js +159 -117
- package/dist/clang/index.d.ts +1 -0
- package/dist/clang/index.js +80 -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
|
@@ -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
|
+
});
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { flushQueuedStdin, readBufferedStdin, waitForBufferedStdin } from '../stdinBuffer';
|
|
1
2
|
self.document = {
|
|
2
3
|
querySelector() {
|
|
3
4
|
return null;
|
|
@@ -6,7 +7,7 @@ self.document = {
|
|
|
6
7
|
return [];
|
|
7
8
|
}
|
|
8
9
|
};
|
|
9
|
-
let stdinBufferPyodide, interruptBufferPyodide, pyodide, path = '', packageBaseUrl = '';
|
|
10
|
+
let stdinBufferPyodide, debugBufferPyodide, watchBufferPyodide, watchResultBufferPyodide, interruptBufferPyodide, pyodide, path = '', packageBaseUrl = '';
|
|
10
11
|
let jungolRobotReady = false;
|
|
11
12
|
const imageHook = `
|
|
12
13
|
if not globals().get("__wasm_idle_img_inited__", False):
|
|
@@ -202,7 +203,7 @@ async function loadPackages(code) {
|
|
|
202
203
|
}
|
|
203
204
|
}
|
|
204
205
|
self.onmessage = async (event) => {
|
|
205
|
-
const { code, buffer, load, interrupt, path: _p, prepare } = event.data;
|
|
206
|
+
const { code, buffer, debugBuffer, watchBuffer, watchResultBuffer, load, interrupt, path: _p, prepare, debug = false, breakpoints = [], pauseOnEntry = false } = event.data;
|
|
206
207
|
if (load) {
|
|
207
208
|
path = _p;
|
|
208
209
|
postMessage({ output: 'Loading Pyodide...' });
|
|
@@ -230,6 +231,9 @@ self.onmessage = async (event) => {
|
|
|
230
231
|
await loadPackages(code);
|
|
231
232
|
const ts = Date.now();
|
|
232
233
|
stdinBufferPyodide = new Int32Array(buffer);
|
|
234
|
+
debugBufferPyodide = new Int32Array(debugBuffer);
|
|
235
|
+
watchBufferPyodide = new Int32Array(watchBuffer);
|
|
236
|
+
watchResultBufferPyodide = new Int32Array(watchResultBuffer);
|
|
233
237
|
interruptBufferPyodide = new Uint8Array(interrupt);
|
|
234
238
|
pyodide.setInterruptBuffer(interruptBufferPyodide);
|
|
235
239
|
const toPythonStr = (obj) => {
|
|
@@ -246,23 +250,7 @@ self.onmessage = async (event) => {
|
|
|
246
250
|
self.prompt = self['__pyodide__input_' + ts] = (output) => {
|
|
247
251
|
if (output)
|
|
248
252
|
postMessage({ output });
|
|
249
|
-
|
|
250
|
-
postMessage({ buffer: true });
|
|
251
|
-
const res = Atomics.wait(stdinBufferPyodide, 0, 0, 100);
|
|
252
|
-
if (res === 'not-equal') {
|
|
253
|
-
try {
|
|
254
|
-
const cpy = new Int32Array(stdinBufferPyodide.byteLength);
|
|
255
|
-
cpy.set(stdinBufferPyodide);
|
|
256
|
-
stdinBufferPyodide.fill(0);
|
|
257
|
-
const dec = new TextDecoder();
|
|
258
|
-
const strInfo = dec.decode(cpy).replace(/\x00/g, ''), padding = parseInt(strInfo.slice(-1));
|
|
259
|
-
return strInfo.slice(0, -padding);
|
|
260
|
-
}
|
|
261
|
-
catch (e) {
|
|
262
|
-
postMessage({ log: { e } });
|
|
263
|
-
}
|
|
264
|
-
}
|
|
265
|
-
}
|
|
253
|
+
return waitForBufferedStdin(stdinBufferPyodide, () => postMessage({ buffer: true }));
|
|
266
254
|
};
|
|
267
255
|
self['__pyodide__output_' + ts] = (...data) => {
|
|
268
256
|
let sep = ' ', end = '\r\n', output = '', clear = [];
|
|
@@ -284,24 +272,262 @@ self.onmessage = async (event) => {
|
|
|
284
272
|
output += end;
|
|
285
273
|
postMessage({ output });
|
|
286
274
|
};
|
|
275
|
+
const debugPauseName = `__wasm_idle_python_debug_pause_${ts}`;
|
|
276
|
+
const debugWaitName = `__wasm_idle_python_debug_wait_${ts}`;
|
|
277
|
+
const debugReadWatchName = `__wasm_idle_python_debug_watch_read_${ts}`;
|
|
278
|
+
const debugWriteWatchName = `__wasm_idle_python_debug_watch_write_${ts}`;
|
|
279
|
+
self[debugPauseName] = (line, reason, localsJson, callStackJson) => {
|
|
280
|
+
let locals = [];
|
|
281
|
+
let callStack = [];
|
|
282
|
+
try {
|
|
283
|
+
locals = JSON.parse(localsJson);
|
|
284
|
+
}
|
|
285
|
+
catch {
|
|
286
|
+
locals = [];
|
|
287
|
+
}
|
|
288
|
+
try {
|
|
289
|
+
callStack = JSON.parse(callStackJson);
|
|
290
|
+
}
|
|
291
|
+
catch {
|
|
292
|
+
callStack = [];
|
|
293
|
+
}
|
|
294
|
+
postMessage({
|
|
295
|
+
debugEvent: {
|
|
296
|
+
type: 'pause',
|
|
297
|
+
line: Number(line),
|
|
298
|
+
reason,
|
|
299
|
+
locals,
|
|
300
|
+
callStack
|
|
301
|
+
}
|
|
302
|
+
});
|
|
303
|
+
};
|
|
304
|
+
self[debugWaitName] = () => {
|
|
305
|
+
const sequence = Atomics.load(debugBufferPyodide, 0);
|
|
306
|
+
while (true) {
|
|
307
|
+
if (interruptBufferPyodide?.[0] === 2)
|
|
308
|
+
return -1;
|
|
309
|
+
Atomics.wait(debugBufferPyodide, 0, sequence, 100);
|
|
310
|
+
if (interruptBufferPyodide?.[0] === 2)
|
|
311
|
+
return -1;
|
|
312
|
+
const command = Atomics.exchange(debugBufferPyodide, 1, 0);
|
|
313
|
+
if (command)
|
|
314
|
+
return command;
|
|
315
|
+
}
|
|
316
|
+
};
|
|
317
|
+
self[debugReadWatchName] = () => readBufferedStdin(watchBufferPyodide) || '';
|
|
318
|
+
self[debugWriteWatchName] = (value) => {
|
|
319
|
+
flushQueuedStdin([value], watchResultBufferPyodide);
|
|
320
|
+
};
|
|
321
|
+
const userFilename = '__wasm_idle_user__.py';
|
|
322
|
+
const normalizedBreakpoints = JSON.stringify([...(Array.isArray(breakpoints) ? breakpoints : [])]
|
|
323
|
+
.map((value) => Number(value))
|
|
324
|
+
.filter((value) => Number.isInteger(value) && value > 0));
|
|
287
325
|
try {
|
|
288
|
-
await pyodide.runPythonAsync(`import
|
|
326
|
+
await pyodide.runPythonAsync(`import ast
|
|
327
|
+
import builtins
|
|
328
|
+
import inspect
|
|
329
|
+
import json
|
|
330
|
+
import sys
|
|
289
331
|
from js import __pyodide__input_${ts}, __pyodide__output_${ts}
|
|
332
|
+
${debug ? `from js import ${debugPauseName}, ${debugWaitName}` : ''}
|
|
333
|
+
${debug ? `from js import ${debugReadWatchName}, ${debugWriteWatchName}` : ''}
|
|
290
334
|
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
335
|
+
__wasm_idle_input = __pyodide__input_${ts}
|
|
336
|
+
def __wasm_idle_input_wrapper(prompt = ""):
|
|
337
|
+
value = __wasm_idle_input(prompt)
|
|
338
|
+
if value is None:
|
|
339
|
+
raise EOFError
|
|
340
|
+
return value
|
|
341
|
+
__wasm_idle_output = __pyodide__output_${ts}
|
|
342
|
+
builtins.input = __wasm_idle_input_wrapper
|
|
343
|
+
builtins.print = __wasm_idle_output
|
|
296
344
|
|
|
297
345
|
${imageHook}
|
|
298
346
|
|
|
299
|
-
${
|
|
347
|
+
${debug
|
|
348
|
+
? `
|
|
349
|
+
__wasm_idle_debug_breakpoints = set(${normalizedBreakpoints})
|
|
350
|
+
__wasm_idle_debug_pause_on_entry = ${pauseOnEntry ? 'True' : 'False'}
|
|
351
|
+
__wasm_idle_debug_step_mode = None
|
|
352
|
+
__wasm_idle_debug_resume_skip = None
|
|
353
|
+
__wasm_idle_debug_next_depth = None
|
|
354
|
+
__wasm_idle_debug_next_line = None
|
|
355
|
+
__wasm_idle_debug_step_out_depth = None
|
|
356
|
+
|
|
357
|
+
def __wasm_idle_debug_depth(frame):
|
|
358
|
+
depth = 0
|
|
359
|
+
current = frame
|
|
360
|
+
while current is not None:
|
|
361
|
+
if current.f_code.co_filename == "${userFilename}":
|
|
362
|
+
depth += 1
|
|
363
|
+
current = current.f_back
|
|
364
|
+
return depth
|
|
365
|
+
|
|
366
|
+
def __wasm_idle_debug_preview(value, depth = 0):
|
|
367
|
+
if depth >= 2:
|
|
368
|
+
return "..."
|
|
369
|
+
if value is None:
|
|
370
|
+
return "None"
|
|
371
|
+
if isinstance(value, bool):
|
|
372
|
+
return "True" if value else "False"
|
|
373
|
+
if isinstance(value, (int, float)):
|
|
374
|
+
return repr(value)
|
|
375
|
+
if isinstance(value, (bytes, bytearray)):
|
|
376
|
+
text = repr(bytes(value))
|
|
377
|
+
return text if len(text) <= 80 else text[:77] + "..."
|
|
378
|
+
if isinstance(value, str):
|
|
379
|
+
text = repr(value)
|
|
380
|
+
return text if len(text) <= 80 else text[:77] + "..."
|
|
381
|
+
if isinstance(value, list):
|
|
382
|
+
items = [__wasm_idle_debug_preview(item, depth + 1) for item in value[:8]]
|
|
383
|
+
if len(value) > 8:
|
|
384
|
+
items.append("...")
|
|
385
|
+
return "[" + ", ".join(items) + "]"
|
|
386
|
+
if isinstance(value, tuple):
|
|
387
|
+
items = [__wasm_idle_debug_preview(item, depth + 1) for item in value[:8]]
|
|
388
|
+
if len(value) > 8:
|
|
389
|
+
items.append("...")
|
|
390
|
+
if len(value) == 1 and items:
|
|
391
|
+
return "(" + items[0] + ",)"
|
|
392
|
+
return "(" + ", ".join(items) + ")"
|
|
393
|
+
if isinstance(value, dict):
|
|
394
|
+
items = []
|
|
395
|
+
for index, (key, item) in enumerate(value.items()):
|
|
396
|
+
if index >= 6:
|
|
397
|
+
items.append("...")
|
|
398
|
+
break
|
|
399
|
+
items.append(__wasm_idle_debug_preview(key, depth + 1) + ": " + __wasm_idle_debug_preview(item, depth + 1))
|
|
400
|
+
return "{" + ", ".join(items) + "}"
|
|
401
|
+
if isinstance(value, set):
|
|
402
|
+
items = [__wasm_idle_debug_preview(item, depth + 1) for item in sorted(list(value), key = repr)[:6]]
|
|
403
|
+
if len(value) > 6:
|
|
404
|
+
items.append("...")
|
|
405
|
+
return "{" + ", ".join(items) + "}"
|
|
406
|
+
try:
|
|
407
|
+
text = repr(value)
|
|
408
|
+
return text if len(text) <= 80 else text[:77] + "..."
|
|
409
|
+
except Exception:
|
|
410
|
+
return "?"
|
|
411
|
+
|
|
412
|
+
def __wasm_idle_debug_locals(frame):
|
|
413
|
+
locals_preview = []
|
|
414
|
+
for name, value in frame.f_locals.items():
|
|
415
|
+
if name == "__builtins__" or name.startswith("__wasm_idle_") or name.startswith("."):
|
|
416
|
+
continue
|
|
417
|
+
locals_preview.append({"name": name, "value": __wasm_idle_debug_preview(value)})
|
|
418
|
+
locals_preview.sort(key = lambda item: item["name"])
|
|
419
|
+
return locals_preview
|
|
420
|
+
|
|
421
|
+
def __wasm_idle_debug_stack(frame):
|
|
422
|
+
stack = []
|
|
423
|
+
current = frame
|
|
424
|
+
while current is not None:
|
|
425
|
+
if current.f_code.co_filename == "${userFilename}":
|
|
426
|
+
stack.append({"functionName": current.f_code.co_name, "line": current.f_lineno})
|
|
427
|
+
current = current.f_back
|
|
428
|
+
return stack
|
|
429
|
+
|
|
430
|
+
def __wasm_idle_debug_trace(frame, event, arg):
|
|
431
|
+
global __wasm_idle_debug_pause_on_entry
|
|
432
|
+
global __wasm_idle_debug_step_mode
|
|
433
|
+
global __wasm_idle_debug_resume_skip
|
|
434
|
+
global __wasm_idle_debug_next_depth
|
|
435
|
+
global __wasm_idle_debug_next_line
|
|
436
|
+
global __wasm_idle_debug_step_out_depth
|
|
437
|
+
|
|
438
|
+
if frame.f_code.co_filename != "${userFilename}":
|
|
439
|
+
return None
|
|
440
|
+
if event != "line":
|
|
441
|
+
return __wasm_idle_debug_trace
|
|
442
|
+
|
|
443
|
+
depth = __wasm_idle_debug_depth(frame)
|
|
444
|
+
line = frame.f_lineno
|
|
445
|
+
if __wasm_idle_debug_resume_skip == (depth, line):
|
|
446
|
+
return __wasm_idle_debug_trace
|
|
447
|
+
if __wasm_idle_debug_resume_skip is not None:
|
|
448
|
+
__wasm_idle_debug_resume_skip = None
|
|
449
|
+
|
|
450
|
+
reason = None
|
|
451
|
+
if __wasm_idle_debug_pause_on_entry:
|
|
452
|
+
reason = "entry"
|
|
453
|
+
elif line in __wasm_idle_debug_breakpoints:
|
|
454
|
+
reason = "breakpoint"
|
|
455
|
+
elif __wasm_idle_debug_step_mode == "step":
|
|
456
|
+
reason = "step"
|
|
457
|
+
elif __wasm_idle_debug_step_mode == "next" and __wasm_idle_debug_next_depth is not None and depth <= __wasm_idle_debug_next_depth and line != __wasm_idle_debug_next_line:
|
|
458
|
+
reason = "nextLine"
|
|
459
|
+
elif __wasm_idle_debug_step_mode == "out" and __wasm_idle_debug_step_out_depth is not None and depth <= __wasm_idle_debug_step_out_depth:
|
|
460
|
+
reason = "stepOut"
|
|
461
|
+
|
|
462
|
+
if reason is None:
|
|
463
|
+
return __wasm_idle_debug_trace
|
|
464
|
+
|
|
465
|
+
__wasm_idle_debug_pause_on_entry = False
|
|
466
|
+
__wasm_idle_debug_step_mode = None
|
|
467
|
+
__wasm_idle_debug_next_depth = None
|
|
468
|
+
__wasm_idle_debug_next_line = None
|
|
469
|
+
__wasm_idle_debug_step_out_depth = None
|
|
470
|
+
|
|
471
|
+
${debugPauseName}(line, reason, json.dumps(__wasm_idle_debug_locals(frame)), json.dumps(__wasm_idle_debug_stack(frame)))
|
|
472
|
+
while True:
|
|
473
|
+
command = ${debugWaitName}()
|
|
474
|
+
if command < 0:
|
|
475
|
+
raise KeyboardInterrupt()
|
|
476
|
+
if command != 5:
|
|
477
|
+
break
|
|
478
|
+
try:
|
|
479
|
+
expression = ${debugReadWatchName}()
|
|
480
|
+
result = __wasm_idle_debug_preview(eval(expression, frame.f_globals, frame.f_locals))
|
|
481
|
+
except Exception as error:
|
|
482
|
+
result = "?" if error.__class__.__name__ == "NameError" else "error"
|
|
483
|
+
${debugWriteWatchName}(result)
|
|
484
|
+
__wasm_idle_debug_resume_skip = (depth, line)
|
|
485
|
+
if command == 2:
|
|
486
|
+
__wasm_idle_debug_step_mode = "step"
|
|
487
|
+
elif command == 3:
|
|
488
|
+
__wasm_idle_debug_step_mode = "next"
|
|
489
|
+
__wasm_idle_debug_next_depth = depth
|
|
490
|
+
__wasm_idle_debug_next_line = line
|
|
491
|
+
elif command == 4:
|
|
492
|
+
__wasm_idle_debug_step_mode = "out"
|
|
493
|
+
__wasm_idle_debug_step_out_depth = max(0, depth - 1)
|
|
494
|
+
return __wasm_idle_debug_trace
|
|
495
|
+
|
|
496
|
+
sys.settrace(__wasm_idle_debug_trace)
|
|
497
|
+
`
|
|
498
|
+
: ''}
|
|
499
|
+
|
|
500
|
+
try:
|
|
501
|
+
__wasm_idle_globals = {"__name__": "__main__"}
|
|
502
|
+
__wasm_idle_result = eval(
|
|
503
|
+
compile(${JSON.stringify(code)}, "${userFilename}", "exec", flags = ast.PyCF_ALLOW_TOP_LEVEL_AWAIT),
|
|
504
|
+
__wasm_idle_globals,
|
|
505
|
+
__wasm_idle_globals,
|
|
506
|
+
)
|
|
507
|
+
if inspect.isawaitable(__wasm_idle_result):
|
|
508
|
+
await __wasm_idle_result
|
|
509
|
+
finally:
|
|
510
|
+
sys.settrace(None)
|
|
511
|
+
${debug
|
|
512
|
+
? `
|
|
513
|
+
__wasm_idle_debug_step_mode = None
|
|
514
|
+
__wasm_idle_debug_resume_skip = None
|
|
515
|
+
__wasm_idle_debug_next_depth = None
|
|
516
|
+
__wasm_idle_debug_next_line = None
|
|
517
|
+
__wasm_idle_debug_step_out_depth = None
|
|
518
|
+
`
|
|
519
|
+
: ''}
|
|
520
|
+
`);
|
|
300
521
|
self.postMessage({ results: true });
|
|
301
522
|
}
|
|
302
523
|
catch (e) {
|
|
303
524
|
self.postMessage({ error: e.message || 'Unknown error' });
|
|
304
525
|
}
|
|
526
|
+
finally {
|
|
527
|
+
delete self[debugPauseName];
|
|
528
|
+
delete self[debugWaitName];
|
|
529
|
+
delete self[debugReadWatchName];
|
|
530
|
+
delete self[debugWriteWatchName];
|
|
531
|
+
}
|
|
305
532
|
}
|
|
306
533
|
};
|
|
307
|
-
export {};
|
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
-
import type {
|
|
2
|
+
import type {
|
|
3
|
+
CompilerDiagnostic,
|
|
4
|
+
DebugCommand,
|
|
5
|
+
DebugSessionEvent,
|
|
6
|
+
SandboxExecutionOptions
|
|
7
|
+
} from '../playground/options';
|
|
3
8
|
import type { Sandbox } from '../playground/sandbox';
|
|
4
9
|
import type { TerminalControl } from './types';
|
|
5
10
|
import { Theme, registerAllPlugins } from './';
|
|
@@ -17,6 +22,7 @@
|
|
|
17
22
|
onfinish?: () => void;
|
|
18
23
|
onkey?: (e: KeyboardEvent) => void;
|
|
19
24
|
ondebug?: (event: DebugSessionEvent) => void;
|
|
25
|
+
oncompilediagnostic?: (diagnostic: CompilerDiagnostic) => void;
|
|
20
26
|
onimage?: (payload: { mime: string; b64: string; ts?: number }) => void;
|
|
21
27
|
terminal?: TerminalControl;
|
|
22
28
|
}
|
|
@@ -29,6 +35,7 @@
|
|
|
29
35
|
onfinish,
|
|
30
36
|
onkey,
|
|
31
37
|
ondebug,
|
|
38
|
+
oncompilediagnostic,
|
|
32
39
|
onimage,
|
|
33
40
|
terminal = $bindable()
|
|
34
41
|
}: Props = $props();
|
|
@@ -69,6 +76,7 @@
|
|
|
69
76
|
}
|
|
70
77
|
sandbox.image = onimage;
|
|
71
78
|
sandbox.ondebug = ondebug;
|
|
79
|
+
sandbox.oncompilerdiagnostic = oncompilediagnostic;
|
|
72
80
|
sandbox.output = (output: string) =>
|
|
73
81
|
_tc === tc && term && term.write(output.replaceAll('\n', '\r\n'));
|
|
74
82
|
}
|
|
@@ -100,12 +108,33 @@
|
|
|
100
108
|
first = false;
|
|
101
109
|
}
|
|
102
110
|
|
|
111
|
+
function appendInputText(text: string) {
|
|
112
|
+
if (!text) return;
|
|
113
|
+
input += text;
|
|
114
|
+
term?.write(text);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function submitCurrentInput() {
|
|
118
|
+
term?.write('\r\n');
|
|
119
|
+
sandbox.write?.(input + '\n');
|
|
120
|
+
input = '';
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function applyPastedText(text: string) {
|
|
124
|
+
const lines = text.replaceAll('\r\n', '\n').replaceAll('\r', '\n').split('\n');
|
|
125
|
+
for (let i = 0; i < lines.length; i++) {
|
|
126
|
+
appendInputText(lines[i]);
|
|
127
|
+
if (i < lines.length - 1) submitCurrentInput();
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
103
131
|
const terminalControl: TerminalControl = {
|
|
104
132
|
async clear() {
|
|
105
133
|
await wait();
|
|
106
134
|
term?.reset();
|
|
107
135
|
term?.write(`\u001B[?25l\x1b[0m\x1b[?25h`);
|
|
108
136
|
if (term) term.options.cursorBlink = false;
|
|
137
|
+
input = '';
|
|
109
138
|
first = true;
|
|
110
139
|
await new Promise((r) => setTimeout(r, 100));
|
|
111
140
|
},
|
|
@@ -146,10 +175,14 @@
|
|
|
146
175
|
await wait();
|
|
147
176
|
sandbox.debugCommand?.(command);
|
|
148
177
|
},
|
|
178
|
+
async debugEvaluate(expression: string) {
|
|
179
|
+
await wait();
|
|
180
|
+
return (await sandbox.debugEvaluate?.(expression)) || '?';
|
|
181
|
+
},
|
|
149
182
|
async write(input: string) {
|
|
150
183
|
await wait();
|
|
151
|
-
|
|
152
|
-
|
|
184
|
+
applyPastedText(input);
|
|
185
|
+
if (!input.endsWith('\n') && !input.endsWith('\r')) submitCurrentInput();
|
|
153
186
|
}
|
|
154
187
|
};
|
|
155
188
|
|
|
@@ -186,9 +219,7 @@
|
|
|
186
219
|
const printable = !ev.altKey && !ev.ctrlKey && !ev.metaKey;
|
|
187
220
|
onkey?.(ev);
|
|
188
221
|
if (ev.key === 'Enter') {
|
|
189
|
-
|
|
190
|
-
sandbox.write?.(input + '\n');
|
|
191
|
-
input = '';
|
|
222
|
+
submitCurrentInput();
|
|
192
223
|
} else if (ev.key === 'Backspace') {
|
|
193
224
|
if (term.buffer.active.cursorX > 0) {
|
|
194
225
|
term.write('\b \b');
|
|
@@ -200,17 +231,16 @@
|
|
|
200
231
|
e.key <= String.fromCharCode(0x7e)) ||
|
|
201
232
|
e.key >= '\u00a0'
|
|
202
233
|
) {
|
|
203
|
-
|
|
204
|
-
term.write(e.key);
|
|
234
|
+
appendInputText(e.key);
|
|
205
235
|
}
|
|
206
236
|
} else if ((ev.ctrlKey || ev.metaKey) && ev.key === 'c') {
|
|
207
237
|
sandbox.kill?.();
|
|
208
238
|
} else if ((ev.ctrlKey || ev.metaKey) && ev.key === 'd') {
|
|
209
|
-
|
|
239
|
+
if (input.length > 0) submitCurrentInput();
|
|
240
|
+
else sandbox.eof?.();
|
|
210
241
|
} else if ((ev.ctrlKey || ev.metaKey) && ev.key === 'v') {
|
|
211
242
|
navigator.clipboard.readText().then((text) => {
|
|
212
|
-
|
|
213
|
-
sandbox.write?.(text);
|
|
243
|
+
applyPastedText(text);
|
|
214
244
|
});
|
|
215
245
|
}
|
|
216
246
|
});
|