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,323 @@
|
|
|
1
|
+
const JAVA_STDIN_HELPER_CLASS = 'WasmIdleStdin';
|
|
2
|
+
const JAVA_SCANNER_COMPAT_SENTINEL = '// wasm-idle Scanner compatibility shim';
|
|
3
|
+
const JAVA_SCANNER_COMPAT_SHIM = `${JAVA_SCANNER_COMPAT_SENTINEL}
|
|
4
|
+
final class Scanner implements AutoCloseable {
|
|
5
|
+
private final java.io.InputStream input;
|
|
6
|
+
private int bufferedChar = Integer.MIN_VALUE;
|
|
7
|
+
private String bufferedToken = null;
|
|
8
|
+
private boolean closed = false;
|
|
9
|
+
|
|
10
|
+
Scanner(java.io.InputStream input) {
|
|
11
|
+
this.input = input;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
Scanner(String source) {
|
|
15
|
+
this(
|
|
16
|
+
new java.io.ByteArrayInputStream(
|
|
17
|
+
source != null ? source.getBytes() : new byte[0]
|
|
18
|
+
)
|
|
19
|
+
);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
private void ensureOpen() {
|
|
23
|
+
if (closed) {
|
|
24
|
+
throw new IllegalStateException("Scanner closed");
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
private int readByteInternal() {
|
|
29
|
+
try {
|
|
30
|
+
return input.read();
|
|
31
|
+
} catch (java.io.IOException error) {
|
|
32
|
+
throw new RuntimeException(error);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
private int readByte() {
|
|
37
|
+
ensureOpen();
|
|
38
|
+
if (bufferedChar != Integer.MIN_VALUE) {
|
|
39
|
+
int value = bufferedChar;
|
|
40
|
+
bufferedChar = Integer.MIN_VALUE;
|
|
41
|
+
return value;
|
|
42
|
+
}
|
|
43
|
+
return readByteInternal();
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
private int peekByte() {
|
|
47
|
+
ensureOpen();
|
|
48
|
+
if (bufferedChar == Integer.MIN_VALUE) {
|
|
49
|
+
bufferedChar = readByteInternal();
|
|
50
|
+
}
|
|
51
|
+
return bufferedChar;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
private boolean isWhitespace(int value) {
|
|
55
|
+
return value == ' ' || value == '\\n' || value == '\\r' || value == '\\t' || value == '\\f';
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
private boolean skipWhitespace() {
|
|
59
|
+
int value = peekByte();
|
|
60
|
+
while (value != -1 && isWhitespace(value)) {
|
|
61
|
+
readByte();
|
|
62
|
+
value = peekByte();
|
|
63
|
+
}
|
|
64
|
+
return value != -1;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
private String readTokenValue() {
|
|
68
|
+
java.lang.StringBuilder token = new java.lang.StringBuilder();
|
|
69
|
+
int value = peekByte();
|
|
70
|
+
while (value != -1 && !isWhitespace(value)) {
|
|
71
|
+
token.append((char) readByte());
|
|
72
|
+
value = peekByte();
|
|
73
|
+
}
|
|
74
|
+
return token.toString();
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
public boolean hasNext() {
|
|
78
|
+
ensureOpen();
|
|
79
|
+
if (bufferedToken != null) {
|
|
80
|
+
return true;
|
|
81
|
+
}
|
|
82
|
+
if (!skipWhitespace()) {
|
|
83
|
+
return false;
|
|
84
|
+
}
|
|
85
|
+
bufferedToken = readTokenValue();
|
|
86
|
+
return true;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
public String next() {
|
|
90
|
+
ensureOpen();
|
|
91
|
+
if (bufferedToken != null) {
|
|
92
|
+
String token = bufferedToken;
|
|
93
|
+
bufferedToken = null;
|
|
94
|
+
return token;
|
|
95
|
+
}
|
|
96
|
+
if (!skipWhitespace()) {
|
|
97
|
+
throw new RuntimeException("No more tokens");
|
|
98
|
+
}
|
|
99
|
+
return readTokenValue();
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
public String nextLine() {
|
|
103
|
+
ensureOpen();
|
|
104
|
+
java.lang.StringBuilder line = new java.lang.StringBuilder();
|
|
105
|
+
if (bufferedToken != null) {
|
|
106
|
+
line.append(bufferedToken);
|
|
107
|
+
bufferedToken = null;
|
|
108
|
+
}
|
|
109
|
+
int value = peekByte();
|
|
110
|
+
if (line.length() == 0 && value == -1) {
|
|
111
|
+
return "";
|
|
112
|
+
}
|
|
113
|
+
if (line.length() == 0 && value == '\\r') {
|
|
114
|
+
readByte();
|
|
115
|
+
if (peekByte() == '\\n') {
|
|
116
|
+
readByte();
|
|
117
|
+
}
|
|
118
|
+
return "";
|
|
119
|
+
}
|
|
120
|
+
if (line.length() == 0 && value == '\\n') {
|
|
121
|
+
readByte();
|
|
122
|
+
return "";
|
|
123
|
+
}
|
|
124
|
+
while (true) {
|
|
125
|
+
value = readByte();
|
|
126
|
+
if (value == -1 || value == '\\n' || value == '\\r') {
|
|
127
|
+
break;
|
|
128
|
+
}
|
|
129
|
+
line.append((char) value);
|
|
130
|
+
}
|
|
131
|
+
if (value == '\\r' && peekByte() == '\\n') {
|
|
132
|
+
readByte();
|
|
133
|
+
}
|
|
134
|
+
return line.toString();
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
public int nextInt() {
|
|
138
|
+
return Integer.parseInt(next());
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
public long nextLong() {
|
|
142
|
+
return Long.parseLong(next());
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
public float nextFloat() {
|
|
146
|
+
return Float.parseFloat(next());
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
public double nextDouble() {
|
|
150
|
+
return Double.parseDouble(next());
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
public boolean hasNextInt() {
|
|
154
|
+
if (!hasNext()) {
|
|
155
|
+
return false;
|
|
156
|
+
}
|
|
157
|
+
try {
|
|
158
|
+
Integer.parseInt(bufferedToken);
|
|
159
|
+
return true;
|
|
160
|
+
} catch (RuntimeException error) {
|
|
161
|
+
return false;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
public boolean hasNextLong() {
|
|
166
|
+
if (!hasNext()) {
|
|
167
|
+
return false;
|
|
168
|
+
}
|
|
169
|
+
try {
|
|
170
|
+
Long.parseLong(bufferedToken);
|
|
171
|
+
return true;
|
|
172
|
+
} catch (RuntimeException error) {
|
|
173
|
+
return false;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
public boolean hasNextFloat() {
|
|
178
|
+
if (!hasNext()) {
|
|
179
|
+
return false;
|
|
180
|
+
}
|
|
181
|
+
try {
|
|
182
|
+
Float.parseFloat(bufferedToken);
|
|
183
|
+
return true;
|
|
184
|
+
} catch (RuntimeException error) {
|
|
185
|
+
return false;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
public boolean hasNextDouble() {
|
|
190
|
+
if (!hasNext()) {
|
|
191
|
+
return false;
|
|
192
|
+
}
|
|
193
|
+
try {
|
|
194
|
+
Double.parseDouble(bufferedToken);
|
|
195
|
+
return true;
|
|
196
|
+
} catch (RuntimeException error) {
|
|
197
|
+
return false;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
public void close() {
|
|
202
|
+
if (closed) {
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
closed = true;
|
|
206
|
+
try {
|
|
207
|
+
input.close();
|
|
208
|
+
} catch (java.io.IOException error) {
|
|
209
|
+
throw new RuntimeException(error);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
}`;
|
|
213
|
+
export const prepareJavaStdinInjection = (code, stdin) => {
|
|
214
|
+
const usesStdin = code.includes('System.in');
|
|
215
|
+
const usesScanner = /\bScanner\b/.test(code) &&
|
|
216
|
+
!code.includes(JAVA_SCANNER_COMPAT_SENTINEL) &&
|
|
217
|
+
!/\b(?:class|interface|enum|record)\s+Scanner\b/.test(code) &&
|
|
218
|
+
!/^[ \t]*import[ \t]+(?!java\.util\.Scanner\b)(?!static\b)[\w.]+\.Scanner[ \t]*;[ \t]*$/m.test(code);
|
|
219
|
+
if (!usesStdin) {
|
|
220
|
+
if (!usesScanner) {
|
|
221
|
+
return {
|
|
222
|
+
usesStdin: false,
|
|
223
|
+
stdinCacheKey: '',
|
|
224
|
+
transformedCode: code,
|
|
225
|
+
helperSourcePath: null,
|
|
226
|
+
helperSource: null
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
const transformedCode = code
|
|
230
|
+
.replace(/^[ \t]*import[ \t]+java\.util\.Scanner[ \t]*;[ \t]*$/gm, (match) => match.replace(/[^\r\n]/g, ' '))
|
|
231
|
+
.replaceAll(/\bjava\.util\.Scanner\b/g, 'Scanner');
|
|
232
|
+
return {
|
|
233
|
+
usesStdin: false,
|
|
234
|
+
stdinCacheKey: '',
|
|
235
|
+
transformedCode: `${transformedCode.trimEnd()}\n\n${JAVA_SCANNER_COMPAT_SHIM}\n`,
|
|
236
|
+
helperSourcePath: null,
|
|
237
|
+
helperSource: null
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
const packageMatch = code.match(/^\s*package\s+([A-Za-z_][\w.]*)\s*;/m);
|
|
241
|
+
const packageName = packageMatch?.[1] || '';
|
|
242
|
+
const helperSourcePath = packageName
|
|
243
|
+
? `${packageName.replaceAll('.', '/')}/${JAVA_STDIN_HELPER_CLASS}.java`
|
|
244
|
+
: `${JAVA_STDIN_HELPER_CLASS}.java`;
|
|
245
|
+
let transformedCode = code.replaceAll('System.in', `${JAVA_STDIN_HELPER_CLASS}.open()`);
|
|
246
|
+
if (usesScanner) {
|
|
247
|
+
transformedCode = transformedCode
|
|
248
|
+
.replace(/^[ \t]*import[ \t]+java\.util\.Scanner[ \t]*;[ \t]*$/gm, (match) => match.replace(/[^\r\n]/g, ' '))
|
|
249
|
+
.replaceAll(/\bjava\.util\.Scanner\b/g, 'Scanner');
|
|
250
|
+
transformedCode = `${transformedCode.trimEnd()}\n\n${JAVA_SCANNER_COMPAT_SHIM}\n`;
|
|
251
|
+
}
|
|
252
|
+
const encodedBytes = [...new TextEncoder().encode(stdin)].join(', ');
|
|
253
|
+
return {
|
|
254
|
+
usesStdin: true,
|
|
255
|
+
stdinCacheKey: stdin,
|
|
256
|
+
transformedCode,
|
|
257
|
+
helperSourcePath,
|
|
258
|
+
helperSource: `${packageName ? `package ${packageName};\n\n` : ''}import java.io.InputStream;
|
|
259
|
+
import org.teavm.jso.JSObject;
|
|
260
|
+
import org.teavm.jso.browser.Window;
|
|
261
|
+
import org.teavm.jso.core.JSFunction;
|
|
262
|
+
import org.teavm.jso.core.JSMapLike;
|
|
263
|
+
|
|
264
|
+
final class ${JAVA_STDIN_HELPER_CLASS} extends InputStream {
|
|
265
|
+
private static final byte[] INITIAL_DATA = new byte[] { ${encodedBytes} };
|
|
266
|
+
private static final ${JAVA_STDIN_HELPER_CLASS} INSTANCE = new ${JAVA_STDIN_HELPER_CLASS}();
|
|
267
|
+
private int position = 0;
|
|
268
|
+
|
|
269
|
+
private ${JAVA_STDIN_HELPER_CLASS}() {
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
static InputStream open() {
|
|
273
|
+
return INSTANCE;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
private int readFromHost() {
|
|
277
|
+
Window current = Window.current();
|
|
278
|
+
if (current == null) {
|
|
279
|
+
return -1;
|
|
280
|
+
}
|
|
281
|
+
JSMapLike<JSObject> globals = current.cast();
|
|
282
|
+
JSObject stdin = globals.get("wasmIdleJavaStdin");
|
|
283
|
+
if (stdin == null) {
|
|
284
|
+
return -1;
|
|
285
|
+
}
|
|
286
|
+
JSFunction readByte = stdin.<JSMapLike<JSObject>>cast().get("readByte").cast();
|
|
287
|
+
Object value = readByte.call(stdin);
|
|
288
|
+
return value != null ? Integer.parseInt(value.toString()) : -1;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
@Override
|
|
292
|
+
public int read(byte[] b, int off, int len) {
|
|
293
|
+
if (b == null) {
|
|
294
|
+
throw new NullPointerException();
|
|
295
|
+
}
|
|
296
|
+
if (off < 0 || len < 0 || len > b.length - off) {
|
|
297
|
+
throw new IndexOutOfBoundsException();
|
|
298
|
+
}
|
|
299
|
+
if (len == 0) {
|
|
300
|
+
return 0;
|
|
301
|
+
}
|
|
302
|
+
if (position < INITIAL_DATA.length) {
|
|
303
|
+
int count = Math.min(len, INITIAL_DATA.length - position);
|
|
304
|
+
System.arraycopy(INITIAL_DATA, position, b, off, count);
|
|
305
|
+
position += count;
|
|
306
|
+
return count;
|
|
307
|
+
}
|
|
308
|
+
int next = readFromHost();
|
|
309
|
+
if (next == -1) {
|
|
310
|
+
return -1;
|
|
311
|
+
}
|
|
312
|
+
b[off] = (byte) next;
|
|
313
|
+
return 1;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
@Override
|
|
317
|
+
public int read() {
|
|
318
|
+
return position < INITIAL_DATA.length ? INITIAL_DATA[position++] & 0xff : readFromHost();
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
`
|
|
322
|
+
};
|
|
323
|
+
};
|
|
@@ -4,6 +4,15 @@ export interface DebugVariable {
|
|
|
4
4
|
name: string;
|
|
5
5
|
value: string;
|
|
6
6
|
}
|
|
7
|
+
export type CompilerDiagnosticSeverity = 'error' | 'warning' | 'other';
|
|
8
|
+
export interface CompilerDiagnostic {
|
|
9
|
+
fileName?: string | null;
|
|
10
|
+
lineNumber: number;
|
|
11
|
+
columnNumber?: number;
|
|
12
|
+
endColumnNumber?: number;
|
|
13
|
+
severity: CompilerDiagnosticSeverity;
|
|
14
|
+
message: string;
|
|
15
|
+
}
|
|
7
16
|
export type DebugVariableKind = 'number' | 'bool' | 'array' | 'text';
|
|
8
17
|
export type DebugArrayElementKind = 'int' | 'float' | 'double' | 'bool' | 'char';
|
|
9
18
|
export interface DebugVariableMetadata {
|
|
@@ -36,4 +45,14 @@ export interface SandboxExecutionOptions {
|
|
|
36
45
|
debug?: boolean;
|
|
37
46
|
breakpoints?: number[];
|
|
38
47
|
pauseOnEntry?: boolean;
|
|
48
|
+
stdin?: string;
|
|
49
|
+
compileArgs?: string[];
|
|
50
|
+
programArgs?: string[];
|
|
51
|
+
cppVersion?: string;
|
|
52
|
+
cVersion?: string;
|
|
53
|
+
}
|
|
54
|
+
export interface ResolvedSandboxExecutionArgs {
|
|
55
|
+
compileArgs: string[];
|
|
56
|
+
programArgs: string[];
|
|
39
57
|
}
|
|
58
|
+
export declare function resolveSandboxExecutionArgs(language: string, args?: string[], options?: SandboxExecutionOptions): ResolvedSandboxExecutionArgs;
|
|
@@ -1 +1,15 @@
|
|
|
1
|
-
|
|
1
|
+
function cloneArgs(value) {
|
|
2
|
+
return Array.isArray(value) ? [...value] : [];
|
|
3
|
+
}
|
|
4
|
+
export function resolveSandboxExecutionArgs(language, args = [], options = {}) {
|
|
5
|
+
if (language === 'C' || language === 'CPP') {
|
|
6
|
+
return {
|
|
7
|
+
compileArgs: cloneArgs(options.compileArgs ?? args),
|
|
8
|
+
programArgs: cloneArgs(options.programArgs)
|
|
9
|
+
};
|
|
10
|
+
}
|
|
11
|
+
return {
|
|
12
|
+
compileArgs: cloneArgs(options.compileArgs),
|
|
13
|
+
programArgs: cloneArgs(options.programArgs ?? args)
|
|
14
|
+
};
|
|
15
|
+
}
|
|
@@ -1,27 +1,42 @@
|
|
|
1
|
+
import type { DebugCommand, DebugSessionEvent, SandboxExecutionOptions } from './options';
|
|
2
|
+
import { WorkerAssetBridge } from './assetBridge';
|
|
3
|
+
import { type PlaygroundRuntimeAssets } from './assets';
|
|
1
4
|
import type { Sandbox } from './sandbox';
|
|
2
|
-
import type { SandboxExecutionOptions } from './options';
|
|
3
5
|
declare class Python implements Sandbox {
|
|
4
6
|
ts: number;
|
|
5
7
|
output: any;
|
|
8
|
+
ondebug?: (event: DebugSessionEvent) => void;
|
|
6
9
|
image?: (data: {
|
|
7
10
|
mime: string;
|
|
8
11
|
b64: string;
|
|
9
12
|
ts?: number;
|
|
10
13
|
}) => void;
|
|
11
|
-
worker
|
|
14
|
+
worker?: Worker;
|
|
12
15
|
buffer: SharedArrayBuffer;
|
|
16
|
+
debugBuffer: SharedArrayBuffer;
|
|
17
|
+
watchBuffer: SharedArrayBuffer;
|
|
18
|
+
watchResultBuffer: SharedArrayBuffer;
|
|
13
19
|
interruptBuffer: SharedArrayBuffer;
|
|
14
|
-
|
|
20
|
+
pendingInput: string[];
|
|
15
21
|
begin: number;
|
|
16
22
|
elapse: number;
|
|
17
23
|
uid: number;
|
|
18
|
-
|
|
24
|
+
waitingForInput: boolean;
|
|
25
|
+
pendingEof: boolean;
|
|
26
|
+
exit: boolean;
|
|
27
|
+
assetBridge: WorkerAssetBridge | null;
|
|
28
|
+
load(runtimeAssets?: string | PlaygroundRuntimeAssets, code?: string, log?: boolean, _args?: string[], _options?: SandboxExecutionOptions, progress?: {
|
|
29
|
+
set?: (value: number) => void;
|
|
30
|
+
} | import('svelte/store').Writable<number>): Promise<void>;
|
|
19
31
|
write(input: string): void;
|
|
20
|
-
_write(input: string): void;
|
|
21
32
|
eof(): void;
|
|
33
|
+
private flushPendingInput;
|
|
22
34
|
run(code: string, prepare: boolean, _log?: boolean, _prog?: {
|
|
23
35
|
set?: (value: number) => void;
|
|
24
|
-
} | import('svelte/store').Writable<number>, _args?: string[],
|
|
36
|
+
} | import('svelte/store').Writable<number>, _args?: string[], options?: SandboxExecutionOptions): Promise<boolean | string>;
|
|
37
|
+
debugCommand(command: DebugCommand): void;
|
|
38
|
+
debugEvaluate(expression: string): Promise<string>;
|
|
39
|
+
kill(): void;
|
|
25
40
|
terminate(): void;
|
|
26
41
|
clear(): Promise<void>;
|
|
27
42
|
}
|
|
@@ -1,63 +1,129 @@
|
|
|
1
|
+
import { WorkerAssetBridge } from './assetBridge';
|
|
2
|
+
import { resolveRuntimeAssetConfig } from './assets';
|
|
3
|
+
import { bufferedSequence, flushBufferedEof, flushQueuedStdin, resetBufferedStdin, waitForBufferedSequenceChange } from './stdinBuffer';
|
|
1
4
|
class Python {
|
|
2
5
|
ts = Date.now();
|
|
3
6
|
output = null;
|
|
7
|
+
ondebug;
|
|
4
8
|
image;
|
|
5
9
|
worker = null;
|
|
6
10
|
buffer = new SharedArrayBuffer(1024);
|
|
11
|
+
debugBuffer = new SharedArrayBuffer(Int32Array.BYTES_PER_ELEMENT * 4);
|
|
12
|
+
watchBuffer = new SharedArrayBuffer(1024);
|
|
13
|
+
watchResultBuffer = new SharedArrayBuffer(1024);
|
|
7
14
|
interruptBuffer = new SharedArrayBuffer(1);
|
|
8
|
-
|
|
15
|
+
pendingInput = [];
|
|
9
16
|
begin = 0;
|
|
10
17
|
elapse = 0;
|
|
11
18
|
uid = 0;
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
19
|
+
waitingForInput = false;
|
|
20
|
+
pendingEof = false;
|
|
21
|
+
exit = true;
|
|
22
|
+
assetBridge = null;
|
|
23
|
+
load(runtimeAssets = '', code = '', log = true, _args = [], _options = {}, progress) {
|
|
24
|
+
return new Promise(async (resolve, reject) => {
|
|
25
|
+
this.pendingInput = [];
|
|
26
|
+
this.waitingForInput = false;
|
|
27
|
+
this.pendingEof = false;
|
|
28
|
+
const assetConfig = resolveRuntimeAssetConfig('python', runtimeAssets, typeof window !== 'undefined' ? window.location.href : '');
|
|
29
|
+
const needsWorkerReset = !this.worker || !this.assetBridge || !this.assetBridge.matches(assetConfig);
|
|
30
|
+
if (needsWorkerReset && this.worker) {
|
|
31
|
+
this.worker.terminate();
|
|
32
|
+
delete this.worker;
|
|
33
|
+
this.assetBridge = null;
|
|
34
|
+
}
|
|
15
35
|
if (!this.worker) {
|
|
16
36
|
this.worker = new (await import('./worker/python?worker')).default();
|
|
17
|
-
this.
|
|
18
|
-
this.worker.
|
|
37
|
+
this.assetBridge = new WorkerAssetBridge(this.worker, 'python', assetConfig, progress);
|
|
38
|
+
this.worker.onmessage = (event) => {
|
|
39
|
+
if (this.assetBridge?.handleMessage(event))
|
|
40
|
+
return;
|
|
41
|
+
if (event.data?.load)
|
|
42
|
+
resolve();
|
|
43
|
+
if (event.data?.error)
|
|
44
|
+
reject(event.data.error);
|
|
45
|
+
};
|
|
46
|
+
this.worker.postMessage({
|
|
47
|
+
load: true,
|
|
48
|
+
log,
|
|
49
|
+
code,
|
|
50
|
+
assets: {
|
|
51
|
+
baseUrl: assetConfig.baseUrl,
|
|
52
|
+
useAssetBridge: assetConfig.useAssetBridge
|
|
53
|
+
}
|
|
54
|
+
});
|
|
19
55
|
}
|
|
20
56
|
else {
|
|
57
|
+
if (!this.assetBridge)
|
|
58
|
+
return reject('Worker asset bridge unavailable');
|
|
59
|
+
this.assetBridge.rebind(this.worker, assetConfig, progress);
|
|
21
60
|
this.worker.postMessage({ log });
|
|
22
61
|
resolve();
|
|
23
62
|
}
|
|
24
63
|
});
|
|
25
64
|
}
|
|
26
65
|
write(input) {
|
|
27
|
-
this.
|
|
66
|
+
this.pendingInput.push(input);
|
|
67
|
+
this.pendingEof = false;
|
|
68
|
+
this.flushPendingInput();
|
|
28
69
|
}
|
|
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));
|
|
70
|
+
eof() {
|
|
71
|
+
this.pendingEof = true;
|
|
72
|
+
this.flushPendingInput();
|
|
38
73
|
}
|
|
39
|
-
|
|
40
|
-
|
|
74
|
+
flushPendingInput() {
|
|
75
|
+
if (!this.waitingForInput)
|
|
76
|
+
return;
|
|
77
|
+
if (flushQueuedStdin(this.pendingInput, this.buffer)) {
|
|
78
|
+
this.waitingForInput = false;
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
if (this.pendingEof) {
|
|
82
|
+
flushBufferedEof(this.buffer);
|
|
83
|
+
this.pendingEof = false;
|
|
84
|
+
this.waitingForInput = false;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
run(code, prepare, _log = true, _prog, _args = [], options = {}) {
|
|
88
|
+
this.exit = false;
|
|
41
89
|
return new Promise(async (resolve, reject) => {
|
|
90
|
+
if (!this.worker)
|
|
91
|
+
return reject('Worker not loaded');
|
|
42
92
|
const interrupt = new Uint8Array(this.interruptBuffer), _uid = ++this.uid;
|
|
43
93
|
const handler = (event) => {
|
|
94
|
+
if (this.assetBridge?.handleMessage(event))
|
|
95
|
+
return;
|
|
96
|
+
if (!this.worker)
|
|
97
|
+
return reject('Worker not loaded');
|
|
44
98
|
if (_uid !== this.uid)
|
|
45
99
|
return (this.worker.onmessage = null);
|
|
46
|
-
const {
|
|
47
|
-
if (buffer
|
|
48
|
-
this.
|
|
100
|
+
const { output, results, log, error, buffer, type, data: payload, debugEvent } = event.data;
|
|
101
|
+
if (buffer) {
|
|
102
|
+
this.waitingForInput = true;
|
|
103
|
+
this.flushPendingInput();
|
|
104
|
+
}
|
|
49
105
|
if (type === 'img' && payload)
|
|
50
106
|
this.image?.(payload);
|
|
51
107
|
if (output)
|
|
52
108
|
this.output(output);
|
|
109
|
+
if (debugEvent)
|
|
110
|
+
this.ondebug?.(debugEvent);
|
|
53
111
|
if (results) {
|
|
54
112
|
this.elapse = Date.now() - this.begin;
|
|
113
|
+
this.exit = true;
|
|
114
|
+
this.waitingForInput = false;
|
|
115
|
+
this.pendingEof = false;
|
|
116
|
+
this.ondebug?.({ type: 'stop' });
|
|
55
117
|
resolve(results);
|
|
56
118
|
}
|
|
57
119
|
if (log)
|
|
58
120
|
console.log(log);
|
|
59
121
|
if (error) {
|
|
60
122
|
this.elapse = Date.now() - this.begin;
|
|
123
|
+
this.exit = true;
|
|
124
|
+
this.waitingForInput = false;
|
|
125
|
+
this.pendingEof = false;
|
|
126
|
+
this.ondebug?.({ type: 'stop' });
|
|
61
127
|
reject(error);
|
|
62
128
|
}
|
|
63
129
|
};
|
|
@@ -68,22 +134,66 @@ class Python {
|
|
|
68
134
|
code,
|
|
69
135
|
prepare,
|
|
70
136
|
buffer: this.buffer,
|
|
137
|
+
debugBuffer: this.debugBuffer,
|
|
138
|
+
watchBuffer: this.watchBuffer,
|
|
139
|
+
watchResultBuffer: this.watchResultBuffer,
|
|
71
140
|
interrupt: this.interruptBuffer,
|
|
72
|
-
context: {}
|
|
141
|
+
context: {},
|
|
142
|
+
debug: !!options.debug,
|
|
143
|
+
breakpoints: [...(options.breakpoints || [])],
|
|
144
|
+
pauseOnEntry: !!options.pauseOnEntry
|
|
73
145
|
});
|
|
74
146
|
});
|
|
75
147
|
}
|
|
148
|
+
debugCommand(command) {
|
|
149
|
+
const control = new Int32Array(this.debugBuffer);
|
|
150
|
+
Atomics.store(control, 1, command === 'stepInto' ? 2 : command === 'nextLine' ? 3 : command === 'stepOut' ? 4 : 1);
|
|
151
|
+
Atomics.add(control, 0, 1);
|
|
152
|
+
Atomics.notify(control, 0);
|
|
153
|
+
this.ondebug?.({ type: 'resume', command });
|
|
154
|
+
}
|
|
155
|
+
async debugEvaluate(expression) {
|
|
156
|
+
if (!this.worker)
|
|
157
|
+
throw new Error('Worker not loaded');
|
|
158
|
+
resetBufferedStdin(this.watchResultBuffer);
|
|
159
|
+
const previousSequence = bufferedSequence(this.watchResultBuffer);
|
|
160
|
+
flushQueuedStdin([expression], this.watchBuffer);
|
|
161
|
+
const control = new Int32Array(this.debugBuffer);
|
|
162
|
+
Atomics.store(control, 1, 5);
|
|
163
|
+
Atomics.add(control, 0, 1);
|
|
164
|
+
Atomics.notify(control, 0);
|
|
165
|
+
return ((await waitForBufferedSequenceChange(this.watchResultBuffer, previousSequence, 5000)) ??
|
|
166
|
+
'?');
|
|
167
|
+
}
|
|
168
|
+
kill() {
|
|
169
|
+
this.terminate();
|
|
170
|
+
}
|
|
76
171
|
terminate() {
|
|
172
|
+
this.pendingEof = false;
|
|
77
173
|
new Uint8Array(this.interruptBuffer)[0] = 2;
|
|
174
|
+
const control = new Int32Array(this.debugBuffer);
|
|
175
|
+
Atomics.add(control, 0, 1);
|
|
176
|
+
Atomics.notify(control, 0);
|
|
78
177
|
}
|
|
79
178
|
async clear() {
|
|
80
179
|
this.terminate();
|
|
81
|
-
this.
|
|
180
|
+
this.pendingInput = [];
|
|
181
|
+
this.waitingForInput = false;
|
|
182
|
+
this.pendingEof = false;
|
|
82
183
|
if (this.worker)
|
|
83
184
|
this.worker.onmessage = null;
|
|
84
|
-
|
|
85
|
-
|
|
185
|
+
resetBufferedStdin(this.buffer);
|
|
186
|
+
resetBufferedStdin(this.watchBuffer);
|
|
187
|
+
resetBufferedStdin(this.watchResultBuffer);
|
|
188
|
+
const debugBuffer = new Int32Array(this.debugBuffer);
|
|
189
|
+
debugBuffer.fill(0);
|
|
86
190
|
await new Promise((resolve) => setTimeout(resolve, 200));
|
|
191
|
+
if (!this.exit) {
|
|
192
|
+
this.worker?.terminate?.();
|
|
193
|
+
delete this.worker;
|
|
194
|
+
this.assetBridge = null;
|
|
195
|
+
this.exit = true;
|
|
196
|
+
}
|
|
87
197
|
}
|
|
88
198
|
}
|
|
89
199
|
export default Python;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import type { DebugCommand, DebugSessionEvent, SandboxExecutionOptions } from './options';
|
|
1
|
+
import type { CompilerDiagnostic, DebugCommand, DebugSessionEvent, SandboxExecutionOptions } from './options';
|
|
2
|
+
import type { PlaygroundRuntimeAssets } from './assets';
|
|
2
3
|
import type { Writable } from 'svelte/store';
|
|
3
4
|
type ProgressLike = Writable<number> | {
|
|
4
5
|
set?: (value: number) => void;
|
|
@@ -6,7 +7,7 @@ type ProgressLike = Writable<number> | {
|
|
|
6
7
|
export interface Sandbox {
|
|
7
8
|
constructor: any;
|
|
8
9
|
eof: () => void;
|
|
9
|
-
load: (
|
|
10
|
+
load: (runtimeAssets?: string | PlaygroundRuntimeAssets, code?: string, log?: boolean, args?: string[], options?: SandboxExecutionOptions, progress?: ProgressLike) => Promise<void>;
|
|
10
11
|
run: (code: string, prepare: boolean, log?: boolean, prog?: ProgressLike, args?: string[], options?: SandboxExecutionOptions) => Promise<boolean | string>;
|
|
11
12
|
terminate: () => void;
|
|
12
13
|
clear: () => Promise<void>;
|
|
@@ -14,7 +15,9 @@ export interface Sandbox {
|
|
|
14
15
|
write?: (data: string) => void;
|
|
15
16
|
output?: (data: string) => void;
|
|
16
17
|
ondebug?: (event: DebugSessionEvent) => void;
|
|
18
|
+
oncompilerdiagnostic?: (diagnostic: CompilerDiagnostic) => void;
|
|
17
19
|
debugCommand?: (command: DebugCommand) => void;
|
|
20
|
+
debugEvaluate?: (expression: string) => Promise<string>;
|
|
18
21
|
image?: (data: {
|
|
19
22
|
mime: string;
|
|
20
23
|
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;
|