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.
Files changed (38) hide show
  1. package/README.md +4 -2
  2. package/dist/clang/app.d.ts +4 -1
  3. package/dist/clang/app.js +159 -117
  4. package/dist/clang/index.d.ts +1 -0
  5. package/dist/clang/index.js +80 -2
  6. package/dist/debug/expression.js +135 -19
  7. package/dist/debug/index.d.ts +1 -1
  8. package/dist/debug/index.js +1 -1
  9. package/dist/debug/language/index.d.ts +1 -0
  10. package/dist/debug/language/index.js +1 -0
  11. package/dist/debug/language/python.d.ts +2 -0
  12. package/dist/debug/language/python.js +14 -0
  13. package/dist/index.d.ts +2 -1
  14. package/dist/index.js +1 -1
  15. package/dist/playground/clang.d.ts +5 -2
  16. package/dist/playground/clang.js +44 -18
  17. package/dist/playground/index.d.ts +2 -1
  18. package/dist/playground/index.js +7 -1
  19. package/dist/playground/java.d.ts +28 -0
  20. package/dist/playground/java.js +135 -0
  21. package/dist/playground/javaStdin.d.ts +8 -0
  22. package/dist/playground/javaStdin.js +90 -0
  23. package/dist/playground/options.d.ts +10 -0
  24. package/dist/playground/python.d.ts +15 -5
  25. package/dist/playground/python.js +98 -21
  26. package/dist/playground/sandbox.d.ts +3 -1
  27. package/dist/playground/stdinBuffer.d.ts +7 -0
  28. package/dist/playground/stdinBuffer.js +100 -0
  29. package/dist/playground/teavmConfig.d.ts +3 -0
  30. package/dist/playground/teavmConfig.js +14 -0
  31. package/dist/playground/worker/clang.js +2 -20
  32. package/dist/playground/worker/java.d.ts +1 -0
  33. package/dist/playground/worker/java.js +248 -0
  34. package/dist/playground/worker/python.js +253 -27
  35. package/dist/terminal/Terminal.svelte +41 -11
  36. package/dist/terminal/Terminal.svelte.d.ts +2 -1
  37. package/dist/terminal/types.d.ts +3 -2
  38. 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
- while (true) {
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 asyncio
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
- input = __pyodide__input_${ts}
292
- print = __pyodide__output_${ts}
293
-
294
- __builtins__.input = __pyodide__input_${ts}
295
- __builtins__.print = __pyodide__output_${ts}
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
- ${code}`);
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 { DebugCommand, DebugSessionEvent, SandboxExecutionOptions } from '../playground/options';
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
- term?.write(input + '\r\n');
152
- sandbox.write?.(input.replaceAll('\r', '\n') + '\n');
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
- term.write('\r\n');
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
- input += e.key;
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
- sandbox.kill?.();
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
- term?.write(text);
213
- sandbox.write?.(text);
243
+ applyPastedText(text);
214
244
  });
215
245
  }
216
246
  });