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.
Files changed (37) hide show
  1. package/README.md +4 -2
  2. package/dist/clang/app.d.ts +2 -1
  3. package/dist/clang/app.js +149 -132
  4. package/dist/clang/index.js +2 -2
  5. package/dist/debug/expression.js +135 -19
  6. package/dist/debug/index.d.ts +1 -1
  7. package/dist/debug/index.js +1 -1
  8. package/dist/debug/language/index.d.ts +1 -0
  9. package/dist/debug/language/index.js +1 -0
  10. package/dist/debug/language/python.d.ts +2 -0
  11. package/dist/debug/language/python.js +14 -0
  12. package/dist/index.d.ts +2 -1
  13. package/dist/index.js +1 -1
  14. package/dist/playground/clang.d.ts +5 -2
  15. package/dist/playground/clang.js +44 -18
  16. package/dist/playground/index.d.ts +2 -1
  17. package/dist/playground/index.js +7 -1
  18. package/dist/playground/java.d.ts +28 -0
  19. package/dist/playground/java.js +135 -0
  20. package/dist/playground/javaStdin.d.ts +8 -0
  21. package/dist/playground/javaStdin.js +90 -0
  22. package/dist/playground/options.d.ts +10 -0
  23. package/dist/playground/python.d.ts +15 -5
  24. package/dist/playground/python.js +98 -21
  25. package/dist/playground/sandbox.d.ts +3 -1
  26. package/dist/playground/stdinBuffer.d.ts +7 -0
  27. package/dist/playground/stdinBuffer.js +100 -0
  28. package/dist/playground/teavmConfig.d.ts +3 -0
  29. package/dist/playground/teavmConfig.js +14 -0
  30. package/dist/playground/worker/clang.js +2 -20
  31. package/dist/playground/worker/java.d.ts +1 -0
  32. package/dist/playground/worker/java.js +248 -0
  33. package/dist/playground/worker/python.js +253 -27
  34. package/dist/terminal/Terminal.svelte +41 -11
  35. package/dist/terminal/Terminal.svelte.d.ts +2 -1
  36. package/dist/terminal/types.d.ts +3 -2
  37. package/package.json +1 -1
package/README.md CHANGED
@@ -2,8 +2,10 @@
2
2
 
3
3
  ![wasm-idle](static/image.jpeg)
4
4
 
5
- Executes C++, Python Code with working stdio.
5
+ Executes C++, Python, and Java code with working stdio.
6
6
 
7
7
  Refer to src/lib/clang.
8
8
 
9
- Powered by [wasm-clang](https://github.com/binji/wasm-clang) and Pyodide.
9
+ Java uses TeaVM's browser compiler/runtime. TeaVM compiler/runtime/classlib assets are bundled under `static/teavm/` by default, and the asset base URL can be overridden with `PUBLIC_TEAVM_BASE_URL`.
10
+
11
+ Powered by [wasm-clang](https://github.com/binji/wasm-clang), Pyodide, and TeaVM.
@@ -52,7 +52,8 @@ export default class App {
52
52
  constructor(module: WebAssembly.Module, memfs: MemFS, name: string, ...args: string[]);
53
53
  run(): Promise<boolean | undefined>;
54
54
  proc_exit(code: number): void;
55
- __wasm_idle_debug_enter(functionId: number): number;
55
+ private pauseDebugSession;
56
+ __wasm_idle_debug_enter(functionId: number, line: number): number;
56
57
  __wasm_idle_debug_leave(functionId: number): number;
57
58
  __wasm_idle_debug_value_num(functionId: number, slot: number, value: number): number;
58
59
  __wasm_idle_debug_value_bool(functionId: number, slot: number, value: number): number;
package/dist/clang/app.js CHANGED
@@ -68,135 +68,9 @@ export default class App {
68
68
  proc_exit(code) {
69
69
  throw new ProcExit(code);
70
70
  }
71
- __wasm_idle_debug_enter(functionId) {
72
- const session = this.debugSession;
73
- if (!session?.buffer)
74
- return ESUCCESS;
75
- session.callDepth += 1;
76
- session.currentFunctionId = functionId;
77
- session.frames.push({
78
- functionId,
79
- functionName: session.functionMetadata[functionId] || `fn_${functionId}`,
80
- line: 0,
81
- values: new Map()
82
- });
83
- this.trace(`enter(function=${functionId}, depth=${session.callDepth})`);
84
- return ESUCCESS;
85
- }
86
- __wasm_idle_debug_leave(functionId) {
87
- const session = this.debugSession;
88
- if (!session?.buffer)
89
- return ESUCCESS;
90
- this.trace(`leave(function=${functionId}, depth=${session.callDepth})`);
91
- session.callDepth = Math.max(0, session.callDepth - 1);
92
- if (session.currentFunctionId === functionId)
93
- session.currentFunctionId = 0;
94
- for (let index = session.frames.length - 1; index >= 0; index -= 1) {
95
- if (session.frames[index]?.functionId === functionId) {
96
- session.frames.splice(index, 1);
97
- break;
98
- }
99
- }
100
- return ESUCCESS;
101
- }
102
- __wasm_idle_debug_value_num(functionId, slot, value) {
103
- const session = this.debugSession;
104
- if (!session?.buffer)
105
- return ESUCCESS;
106
- if (functionId === 0) {
107
- session.globalValues.set(slot, Number.isInteger(value) ? String(value) : `${value}`);
108
- return ESUCCESS;
109
- }
110
- for (let index = session.frames.length - 1; index >= 0; index -= 1) {
111
- const frame = session.frames[index];
112
- if (frame?.functionId !== functionId)
113
- continue;
114
- frame.values.set(slot, Number.isInteger(value) ? String(value) : `${value}`);
115
- break;
116
- }
117
- return ESUCCESS;
118
- }
119
- __wasm_idle_debug_value_bool(functionId, slot, value) {
120
- const session = this.debugSession;
121
- if (!session?.buffer)
122
- return ESUCCESS;
123
- if (functionId === 0) {
124
- session.globalValues.set(slot, value ? 'true' : 'false');
125
- return ESUCCESS;
126
- }
127
- for (let index = session.frames.length - 1; index >= 0; index -= 1) {
128
- const frame = session.frames[index];
129
- if (frame?.functionId !== functionId)
130
- continue;
131
- frame.values.set(slot, value ? 'true' : 'false');
132
- break;
133
- }
134
- return ESUCCESS;
135
- }
136
- __wasm_idle_debug_value_addr(functionId, slot, value) {
137
- const session = this.debugSession;
138
- if (!session?.buffer)
139
- return ESUCCESS;
140
- if (functionId === 0) {
141
- session.globalValues.set(slot, String(value >>> 0));
142
- return ESUCCESS;
143
- }
144
- for (let index = session.frames.length - 1; index >= 0; index -= 1) {
145
- const frame = session.frames[index];
146
- if (frame?.functionId !== functionId)
147
- continue;
148
- frame.values.set(slot, String(value >>> 0));
149
- break;
150
- }
151
- return ESUCCESS;
152
- }
153
- __wasm_idle_debug_value_text(functionId, slot, ptr, len) {
154
- const session = this.debugSession;
155
- if (!session?.buffer)
156
- return ESUCCESS;
157
- this.mem?.check?.();
158
- const text = this.mem?.readStr ? this.mem.readStr(ptr, len) : '?';
159
- if (functionId === 0) {
160
- session.globalValues.set(slot, text);
161
- return ESUCCESS;
162
- }
163
- for (let index = session.frames.length - 1; index >= 0; index -= 1) {
164
- const frame = session.frames[index];
165
- if (frame?.functionId !== functionId)
166
- continue;
167
- frame.values.set(slot, text);
168
- break;
169
- }
170
- return ESUCCESS;
171
- }
172
- __wasm_idle_debug_line(functionId, line) {
173
- const session = this.debugSession;
174
- if (!session?.buffer)
175
- return ESUCCESS;
176
- if (session.resumeSkipActive) {
177
- if (functionId === session.resumeSkipFunctionId && line === session.resumeSkipLine) {
178
- return ESUCCESS;
179
- }
180
- session.resumeSkipActive = false;
181
- session.resumeSkipFunctionId = 0;
182
- session.resumeSkipLine = 0;
183
- }
184
- let reason = '';
185
- if (session.pauseOnEntry)
186
- reason = 'entry';
187
- else if (session.breakpoints.has(line))
188
- reason = 'breakpoint';
189
- else if (session.stepArmed)
190
- reason = 'step';
191
- else if (session.nextLineArmed &&
192
- functionId === session.nextLineFunctionId &&
193
- line !== session.nextLineLine) {
194
- reason = 'nextLine';
195
- }
196
- else if (session.stepOutArmed && session.callDepth <= session.stepOutDepth) {
197
- reason = 'stepOut';
198
- }
199
- if (!reason)
71
+ pauseDebugSession(session, functionId, line, reason) {
72
+ const buffer = session.buffer;
73
+ if (!buffer)
200
74
  return ESUCCESS;
201
75
  session.currentFunctionId = functionId;
202
76
  session.currentLine = line;
@@ -314,14 +188,14 @@ export default class App {
314
188
  .reverse()
315
189
  .map((stackFrame) => ({ functionName: stackFrame.functionName, line: stackFrame.line }))
316
190
  });
317
- const sequence = Atomics.load(session.buffer, 0);
191
+ const sequence = Atomics.load(buffer, 0);
318
192
  while (true) {
319
193
  if (session.interruptBuffer?.[0] === 2)
320
194
  throw new AbortError();
321
- Atomics.wait(session.buffer, 0, sequence, 100);
195
+ Atomics.wait(buffer, 0, sequence, 100);
322
196
  if (session.interruptBuffer?.[0] === 2)
323
197
  throw new AbortError();
324
- const command = Atomics.exchange(session.buffer, 1, 0);
198
+ const command = Atomics.exchange(buffer, 1, 0);
325
199
  if (command === 1) {
326
200
  session.resumeSkipActive = true;
327
201
  session.resumeSkipFunctionId = session.currentFunctionId;
@@ -354,6 +228,149 @@ export default class App {
354
228
  }
355
229
  }
356
230
  }
231
+ __wasm_idle_debug_enter(functionId, line) {
232
+ const session = this.debugSession;
233
+ if (!session?.buffer)
234
+ return ESUCCESS;
235
+ session.callDepth += 1;
236
+ session.currentFunctionId = functionId;
237
+ session.currentLine = line;
238
+ session.frames.push({
239
+ functionId,
240
+ functionName: session.functionMetadata[functionId] || `fn_${functionId}`,
241
+ line,
242
+ values: new Map()
243
+ });
244
+ this.trace(`enter(function=${functionId}, line=${line}, depth=${session.callDepth})`);
245
+ if (session.pauseOnEntry) {
246
+ return this.pauseDebugSession(session, functionId, line, 'entry');
247
+ }
248
+ if (session.stepArmed) {
249
+ return this.pauseDebugSession(session, functionId, line, 'step');
250
+ }
251
+ return ESUCCESS;
252
+ }
253
+ __wasm_idle_debug_leave(functionId) {
254
+ const session = this.debugSession;
255
+ if (!session?.buffer)
256
+ return ESUCCESS;
257
+ this.trace(`leave(function=${functionId}, depth=${session.callDepth})`);
258
+ if (session.nextLineArmed && functionId === session.nextLineFunctionId) {
259
+ session.nextLineArmed = false;
260
+ session.stepArmed = true;
261
+ }
262
+ session.callDepth = Math.max(0, session.callDepth - 1);
263
+ if (session.currentFunctionId === functionId)
264
+ session.currentFunctionId = 0;
265
+ for (let index = session.frames.length - 1; index >= 0; index -= 1) {
266
+ if (session.frames[index]?.functionId === functionId) {
267
+ session.frames.splice(index, 1);
268
+ break;
269
+ }
270
+ }
271
+ return ESUCCESS;
272
+ }
273
+ __wasm_idle_debug_value_num(functionId, slot, value) {
274
+ const session = this.debugSession;
275
+ if (!session?.buffer)
276
+ return ESUCCESS;
277
+ if (functionId === 0) {
278
+ session.globalValues.set(slot, Number.isInteger(value) ? String(value) : `${value}`);
279
+ return ESUCCESS;
280
+ }
281
+ for (let index = session.frames.length - 1; index >= 0; index -= 1) {
282
+ const frame = session.frames[index];
283
+ if (frame?.functionId !== functionId)
284
+ continue;
285
+ frame.values.set(slot, Number.isInteger(value) ? String(value) : `${value}`);
286
+ break;
287
+ }
288
+ return ESUCCESS;
289
+ }
290
+ __wasm_idle_debug_value_bool(functionId, slot, value) {
291
+ const session = this.debugSession;
292
+ if (!session?.buffer)
293
+ return ESUCCESS;
294
+ if (functionId === 0) {
295
+ session.globalValues.set(slot, value ? 'true' : 'false');
296
+ return ESUCCESS;
297
+ }
298
+ for (let index = session.frames.length - 1; index >= 0; index -= 1) {
299
+ const frame = session.frames[index];
300
+ if (frame?.functionId !== functionId)
301
+ continue;
302
+ frame.values.set(slot, value ? 'true' : 'false');
303
+ break;
304
+ }
305
+ return ESUCCESS;
306
+ }
307
+ __wasm_idle_debug_value_addr(functionId, slot, value) {
308
+ const session = this.debugSession;
309
+ if (!session?.buffer)
310
+ return ESUCCESS;
311
+ if (functionId === 0) {
312
+ session.globalValues.set(slot, String(value >>> 0));
313
+ return ESUCCESS;
314
+ }
315
+ for (let index = session.frames.length - 1; index >= 0; index -= 1) {
316
+ const frame = session.frames[index];
317
+ if (frame?.functionId !== functionId)
318
+ continue;
319
+ frame.values.set(slot, String(value >>> 0));
320
+ break;
321
+ }
322
+ return ESUCCESS;
323
+ }
324
+ __wasm_idle_debug_value_text(functionId, slot, ptr, len) {
325
+ const session = this.debugSession;
326
+ if (!session?.buffer)
327
+ return ESUCCESS;
328
+ this.mem?.check?.();
329
+ const text = this.mem?.readStr ? this.mem.readStr(ptr, len) : '?';
330
+ if (functionId === 0) {
331
+ session.globalValues.set(slot, text);
332
+ return ESUCCESS;
333
+ }
334
+ for (let index = session.frames.length - 1; index >= 0; index -= 1) {
335
+ const frame = session.frames[index];
336
+ if (frame?.functionId !== functionId)
337
+ continue;
338
+ frame.values.set(slot, text);
339
+ break;
340
+ }
341
+ return ESUCCESS;
342
+ }
343
+ __wasm_idle_debug_line(functionId, line) {
344
+ const session = this.debugSession;
345
+ if (!session?.buffer)
346
+ return ESUCCESS;
347
+ if (session.resumeSkipActive) {
348
+ if (functionId === session.resumeSkipFunctionId && line === session.resumeSkipLine) {
349
+ return ESUCCESS;
350
+ }
351
+ session.resumeSkipActive = false;
352
+ session.resumeSkipFunctionId = 0;
353
+ session.resumeSkipLine = 0;
354
+ }
355
+ let reason = '';
356
+ if (session.pauseOnEntry)
357
+ reason = 'entry';
358
+ else if (session.breakpoints.has(line))
359
+ reason = 'breakpoint';
360
+ else if (session.stepArmed)
361
+ reason = 'step';
362
+ else if (session.nextLineArmed &&
363
+ functionId === session.nextLineFunctionId &&
364
+ line !== session.nextLineLine) {
365
+ reason = 'nextLine';
366
+ }
367
+ else if (session.stepOutArmed && session.callDepth <= session.stepOutDepth) {
368
+ reason = 'stepOut';
369
+ }
370
+ if (!reason)
371
+ return ESUCCESS;
372
+ return this.pauseDebugSession(session, functionId, line, reason);
373
+ }
357
374
  environ_sizes_get(environ_count_out, environ_buf_size_out) {
358
375
  this.mem.check();
359
376
  let size = 0;
@@ -151,7 +151,7 @@ export default class Clang {
151
151
  '#include <string>',
152
152
  '#include <type_traits>',
153
153
  '#include <vector>',
154
- 'extern "C" __attribute__((import_module("env"), import_name("__wasm_idle_debug_enter"))) void __wasm_idle_debug_enter(int functionId);',
154
+ 'extern "C" __attribute__((import_module("env"), import_name("__wasm_idle_debug_enter"))) void __wasm_idle_debug_enter(int functionId, int line);',
155
155
  'extern "C" __attribute__((import_module("env"), import_name("__wasm_idle_debug_leave"))) void __wasm_idle_debug_leave(int functionId);',
156
156
  'extern "C" __attribute__((import_module("env"), import_name("__wasm_idle_debug_value_num"))) void __wasm_idle_debug_value_num(int functionId, int slot, double value);',
157
157
  'extern "C" __attribute__((import_module("env"), import_name("__wasm_idle_debug_value_bool"))) void __wasm_idle_debug_value_bool(int functionId, int slot, int value);',
@@ -584,7 +584,7 @@ export default class Clang {
584
584
  nextVariableSlot = 1;
585
585
  currentFunctionVariables = new Map();
586
586
  currentFunctionContainers = new Map();
587
- instrumented.push(`${indent} __wasm_idle_debug_enter(${currentFunctionId});`);
587
+ instrumented.push(`${indent} __wasm_idle_debug_enter(${currentFunctionId}, ${index + 1});`);
588
588
  const parameterSource = startsInlineFunctionBody
589
589
  ? normalized.slice(normalized.indexOf('(') + 1, normalized.lastIndexOf(')'))
590
590
  : pendingFunctionHeader?.parameters || '';
@@ -8,6 +8,38 @@ export function evaluateDebugExpression(expression, variables) {
8
8
  values.set(variable.name, variable.value);
9
9
  const parsedValues = new Map();
10
10
  const tokens = [];
11
+ const parseQuotedValue = (text, start) => {
12
+ const quote = text[start];
13
+ if (quote !== "'" && quote !== '"')
14
+ throw new Error('expected quoted string');
15
+ let index = start + 1;
16
+ let value = '';
17
+ while (index < text.length) {
18
+ const character = text[index];
19
+ if (!character)
20
+ break;
21
+ if (character === '\\') {
22
+ const escaped = text[index + 1];
23
+ if (!escaped)
24
+ throw new Error('unterminated string literal');
25
+ if (escaped === 'n')
26
+ value += '\n';
27
+ else if (escaped === 'r')
28
+ value += '\r';
29
+ else if (escaped === 't')
30
+ value += '\t';
31
+ else
32
+ value += escaped;
33
+ index += 2;
34
+ continue;
35
+ }
36
+ if (character === quote)
37
+ return { value, next: index + 1 };
38
+ value += character;
39
+ index += 1;
40
+ }
41
+ throw new Error('unterminated string literal');
42
+ };
11
43
  for (let index = 0; index < source.length;) {
12
44
  const character = source[index];
13
45
  if (!character)
@@ -32,6 +64,12 @@ export function evaluateDebugExpression(expression, variables) {
32
64
  index += operator.length;
33
65
  continue;
34
66
  }
67
+ if (character === "'" || character === '"') {
68
+ const parsed = parseQuotedValue(source, index);
69
+ tokens.push({ type: 'string', value: parsed.value });
70
+ index = parsed.next;
71
+ continue;
72
+ }
35
73
  const number = source.slice(index).match(/^\d+(?:\.\d+)?/);
36
74
  if (number?.[0]) {
37
75
  tokens.push({ type: 'number', value: number[0] });
@@ -40,8 +78,26 @@ export function evaluateDebugExpression(expression, variables) {
40
78
  }
41
79
  const identifier = source.slice(index).match(/^[A-Za-z_]\w*/);
42
80
  if (identifier?.[0]) {
43
- if (identifier[0] === 'true' || identifier[0] === 'false') {
44
- tokens.push({ type: 'boolean', value: identifier[0] === 'true' });
81
+ if (identifier[0] === 'true' ||
82
+ identifier[0] === 'false' ||
83
+ identifier[0] === 'True' ||
84
+ identifier[0] === 'False') {
85
+ tokens.push({
86
+ type: 'boolean',
87
+ value: identifier[0] === 'true' || identifier[0] === 'True'
88
+ });
89
+ }
90
+ else if (identifier[0] === 'null' || identifier[0] === 'None') {
91
+ tokens.push({ type: 'null' });
92
+ }
93
+ else if (identifier[0] === 'and') {
94
+ tokens.push({ type: 'operator', value: '&&' });
95
+ }
96
+ else if (identifier[0] === 'or') {
97
+ tokens.push({ type: 'operator', value: '||' });
98
+ }
99
+ else if (identifier[0] === 'not') {
100
+ tokens.push({ type: 'operator', value: '!' });
45
101
  }
46
102
  else {
47
103
  tokens.push({ type: 'identifier', value: identifier[0] });
@@ -81,16 +137,44 @@ export function evaluateDebugExpression(expression, variables) {
81
137
  throw new Error('unsupported array preview');
82
138
  }
83
139
  }
84
- if (character === "'") {
85
- const end = text.indexOf("'", index + 1);
86
- if (end === -1)
87
- throw new Error('unsupported char preview');
88
- return { value: text.slice(index + 1, end), next: end + 1 };
140
+ if (character === '(') {
141
+ index += 1;
142
+ const items = [];
143
+ while (true) {
144
+ while (/\s/.test(text[index] || ''))
145
+ index += 1;
146
+ if (text[index] === ')')
147
+ return { value: items, next: index + 1 };
148
+ if (text.startsWith('...', index))
149
+ throw new Error('unavailable');
150
+ const item = parsePreviewValue(text, index);
151
+ items.push(item.value);
152
+ index = item.next;
153
+ while (/\s/.test(text[index] || ''))
154
+ index += 1;
155
+ if (text[index] === ',') {
156
+ index += 1;
157
+ continue;
158
+ }
159
+ if (text[index] === ')')
160
+ return { value: items, next: index + 1 };
161
+ throw new Error('unsupported tuple preview');
162
+ }
89
163
  }
164
+ if (character === "'" || character === '"')
165
+ return parseQuotedValue(text, index);
90
166
  if (text.startsWith('true', index))
91
167
  return { value: true, next: index + 4 };
92
168
  if (text.startsWith('false', index))
93
169
  return { value: false, next: index + 5 };
170
+ if (text.startsWith('True', index))
171
+ return { value: true, next: index + 4 };
172
+ if (text.startsWith('False', index))
173
+ return { value: false, next: index + 5 };
174
+ if (text.startsWith('null', index))
175
+ return { value: null, next: index + 4 };
176
+ if (text.startsWith('None', index))
177
+ return { value: null, next: index + 4 };
94
178
  const number = text.slice(index).match(/^-?\d+(?:\.\d+)?/);
95
179
  if (number?.[0])
96
180
  return { value: Number(number[0]), next: index + number[0].length };
@@ -102,17 +186,24 @@ export function evaluateDebugExpression(expression, variables) {
102
186
  const value = values.get(name);
103
187
  if (value == null || value === '?')
104
188
  throw new Error('unavailable');
105
- if (value === 'true' || value === 'false') {
106
- const parsed = value === 'true';
189
+ if (value === 'true' || value === 'false' || value === 'True' || value === 'False') {
190
+ const parsed = value === 'true' || value === 'True';
107
191
  parsedValues.set(name, parsed);
108
192
  return parsed;
109
193
  }
194
+ if (value === 'null' || value === 'None') {
195
+ parsedValues.set(name, null);
196
+ return null;
197
+ }
110
198
  const numeric = Number(value);
111
199
  if (!Number.isNaN(numeric)) {
112
200
  parsedValues.set(name, numeric);
113
201
  return numeric;
114
202
  }
115
- if (value.startsWith('[')) {
203
+ if (value.startsWith('[') ||
204
+ value.startsWith('(') ||
205
+ value.startsWith("'") ||
206
+ value.startsWith('"')) {
116
207
  const parsed = parsePreviewValue(value).value;
117
208
  parsedValues.set(name, parsed);
118
209
  return parsed;
@@ -131,10 +222,21 @@ export function evaluateDebugExpression(expression, variables) {
131
222
  cursor += 1;
132
223
  return token.value;
133
224
  }
225
+ if (token.type === 'null') {
226
+ cursor += 1;
227
+ return null;
228
+ }
229
+ if (token.type === 'string') {
230
+ cursor += 1;
231
+ return token.value;
232
+ }
134
233
  if (token.type === 'identifier') {
135
234
  cursor += 1;
136
235
  let resolved = resolveIdentifierValue(token.value);
137
- while (tokens[cursor]?.type === 'bracket' && tokens[cursor]?.value === '[') {
236
+ while (true) {
237
+ const bracket = tokens[cursor];
238
+ if (!bracket || bracket.type !== 'bracket' || bracket.value !== '[')
239
+ break;
138
240
  cursor += 1;
139
241
  const index = Number(parseOr());
140
242
  const closing = tokens[cursor];
@@ -201,8 +303,14 @@ export function evaluateDebugExpression(expression, variables) {
201
303
  return left;
202
304
  cursor += 1;
203
305
  const right = parseMul();
204
- if (operator.value === '+')
205
- left = Number(left) + Number(right);
306
+ if (operator.value === '+') {
307
+ if (typeof left === 'string' || typeof right === 'string') {
308
+ left = `${left ?? 'null'}${right ?? 'null'}`;
309
+ }
310
+ else {
311
+ left = Number(left) + Number(right);
312
+ }
313
+ }
206
314
  if (operator.value === '-')
207
315
  left = Number(left) - Number(right);
208
316
  }
@@ -215,14 +323,16 @@ export function evaluateDebugExpression(expression, variables) {
215
323
  return left;
216
324
  cursor += 1;
217
325
  const right = parseAdd();
326
+ const leftValue = typeof left === 'string' && typeof right === 'string' ? left : Number(left);
327
+ const rightValue = typeof left === 'string' && typeof right === 'string' ? right : Number(right);
218
328
  if (operator.value === '<')
219
- left = Number(left) < Number(right);
329
+ left = leftValue < rightValue;
220
330
  if (operator.value === '<=')
221
- left = Number(left) <= Number(right);
331
+ left = leftValue <= rightValue;
222
332
  if (operator.value === '>')
223
- left = Number(left) > Number(right);
333
+ left = leftValue > rightValue;
224
334
  if (operator.value === '>=')
225
- left = Number(left) >= Number(right);
335
+ left = leftValue >= rightValue;
226
336
  }
227
337
  };
228
338
  const parseEquality = () => {
@@ -241,7 +351,10 @@ export function evaluateDebugExpression(expression, variables) {
241
351
  };
242
352
  const parseAnd = () => {
243
353
  let left = parseEquality();
244
- while (tokens[cursor]?.type === 'operator' && tokens[cursor]?.value === '&&') {
354
+ while (true) {
355
+ const operator = tokens[cursor];
356
+ if (!operator || operator.type !== 'operator' || operator.value !== '&&')
357
+ break;
245
358
  cursor += 1;
246
359
  left = Boolean(left) && Boolean(parseEquality());
247
360
  }
@@ -249,7 +362,10 @@ export function evaluateDebugExpression(expression, variables) {
249
362
  };
250
363
  const parseOr = () => {
251
364
  let left = parseAnd();
252
- while (tokens[cursor]?.type === 'operator' && tokens[cursor]?.value === '||') {
365
+ while (true) {
366
+ const operator = tokens[cursor];
367
+ if (!operator || operator.type !== 'operator' || operator.value !== '||')
368
+ break;
253
369
  cursor += 1;
254
370
  left = Boolean(left) || Boolean(parseAnd());
255
371
  }
@@ -1,5 +1,5 @@
1
1
  export { evaluateDebugExpression } from './expression';
2
2
  export { selectInlineDebugLocals } from './locals';
3
- export { cppDebugLanguageAdapter } from './language';
3
+ export { cppDebugLanguageAdapter, pythonDebugLanguageAdapter } from './language';
4
4
  export { MonacoDebugView } from './editor';
5
5
  export type { DebugLanguageAdapter } from './language';
@@ -1,4 +1,4 @@
1
1
  export { evaluateDebugExpression } from './expression';
2
2
  export { selectInlineDebugLocals } from './locals';
3
- export { cppDebugLanguageAdapter } from './language';
3
+ export { cppDebugLanguageAdapter, pythonDebugLanguageAdapter } from './language';
4
4
  export { MonacoDebugView } from './editor';
@@ -1,2 +1,3 @@
1
1
  export { cppDebugLanguageAdapter } from './cpp';
2
+ export { pythonDebugLanguageAdapter } from './python';
2
3
  export type { DebugLanguageAdapter } from './types';
@@ -1 +1,2 @@
1
1
  export { cppDebugLanguageAdapter } from './cpp';
2
+ export { pythonDebugLanguageAdapter } from './python';
@@ -0,0 +1,2 @@
1
+ import type { DebugLanguageAdapter } from './types';
2
+ export declare const pythonDebugLanguageAdapter: DebugLanguageAdapter;
@@ -0,0 +1,14 @@
1
+ import { evaluateDebugExpression } from '../expression';
2
+ const selectInlinePythonLocals = (lineText, locals) => {
3
+ const sanitized = lineText
4
+ .replace(/#.*$/g, ' ')
5
+ .replace(/"(?:\\.|[^"\\])*"/g, ' ')
6
+ .replace(/'(?:\\.|[^'\\])*'/g, ' ');
7
+ const identifiers = new Set(sanitized.match(/[A-Za-z_]\w*/g) || []);
8
+ return locals.filter((variable) => identifiers.has(variable.name));
9
+ };
10
+ export const pythonDebugLanguageAdapter = {
11
+ id: 'python',
12
+ evaluateExpression: evaluateDebugExpression,
13
+ selectInlineLocals: selectInlinePythonLocals
14
+ };
package/dist/index.d.ts CHANGED
@@ -1,7 +1,8 @@
1
1
  import Terminal from './terminal';
2
- export { cppDebugLanguageAdapter, evaluateDebugExpression, MonacoDebugView, selectInlineDebugLocals } from './debug';
2
+ export { cppDebugLanguageAdapter, pythonDebugLanguageAdapter, evaluateDebugExpression, MonacoDebugView, selectInlineDebugLocals } from './debug';
3
3
  import playground from './playground';
4
4
  export default Terminal;
5
5
  export { playground };
6
6
  export type { DebugCommand, DebugFrame, DebugSessionEvent, DebugVariable, SandboxExecutionOptions, TerminalControl } from './terminal';
7
+ export type { CompilerDiagnostic } from './playground/options';
7
8
  export type { DebugLanguageAdapter } from './debug';
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import Terminal from './terminal';
2
- export { cppDebugLanguageAdapter, evaluateDebugExpression, MonacoDebugView, selectInlineDebugLocals } from './debug';
2
+ export { cppDebugLanguageAdapter, pythonDebugLanguageAdapter, evaluateDebugExpression, MonacoDebugView, selectInlineDebugLocals } from './debug';
3
3
  import playground from './playground';
4
4
  export default Terminal;
5
5
  export { playground };