@ebowwa/sandbox 0.1.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.
Files changed (108) hide show
  1. package/dist/compilers/index.d.ts +24 -0
  2. package/dist/compilers/index.d.ts.map +1 -0
  3. package/dist/compilers/index.js +42 -0
  4. package/dist/compilers/index.js.map +1 -0
  5. package/dist/compilers/javascript.d.ts +117 -0
  6. package/dist/compilers/javascript.d.ts.map +1 -0
  7. package/dist/compilers/javascript.js +462 -0
  8. package/dist/compilers/javascript.js.map +1 -0
  9. package/dist/compilers/python.d.ts +140 -0
  10. package/dist/compilers/python.d.ts.map +1 -0
  11. package/dist/compilers/python.js +650 -0
  12. package/dist/compilers/python.js.map +1 -0
  13. package/dist/compilers/typescript.d.ts +99 -0
  14. package/dist/compilers/typescript.d.ts.map +1 -0
  15. package/dist/compilers/typescript.js +323 -0
  16. package/dist/compilers/typescript.js.map +1 -0
  17. package/dist/core/cell.d.ts +160 -0
  18. package/dist/core/cell.d.ts.map +1 -0
  19. package/dist/core/cell.js +319 -0
  20. package/dist/core/cell.js.map +1 -0
  21. package/dist/core/compiler.d.ts +126 -0
  22. package/dist/core/compiler.d.ts.map +1 -0
  23. package/dist/core/compiler.js +123 -0
  24. package/dist/core/compiler.js.map +1 -0
  25. package/dist/core/index.d.ts +19 -0
  26. package/dist/core/index.d.ts.map +1 -0
  27. package/dist/core/index.js +14 -0
  28. package/dist/core/index.js.map +1 -0
  29. package/dist/core/limits.d.ts +173 -0
  30. package/dist/core/limits.d.ts.map +1 -0
  31. package/dist/core/limits.js +440 -0
  32. package/dist/core/limits.js.map +1 -0
  33. package/dist/core/permissions.d.ts +103 -0
  34. package/dist/core/permissions.d.ts.map +1 -0
  35. package/dist/core/permissions.js +341 -0
  36. package/dist/core/permissions.js.map +1 -0
  37. package/dist/core/runtime.d.ts +127 -0
  38. package/dist/core/runtime.d.ts.map +1 -0
  39. package/dist/core/runtime.js +325 -0
  40. package/dist/core/runtime.js.map +1 -0
  41. package/dist/core/types.d.ts +380 -0
  42. package/dist/core/types.d.ts.map +1 -0
  43. package/dist/core/types.js +67 -0
  44. package/dist/core/types.js.map +1 -0
  45. package/dist/index.d.ts +145 -0
  46. package/dist/index.d.ts.map +1 -0
  47. package/dist/index.js +279 -0
  48. package/dist/index.js.map +1 -0
  49. package/dist/multi/index.d.ts +9 -0
  50. package/dist/multi/index.d.ts.map +1 -0
  51. package/dist/multi/index.js +7 -0
  52. package/dist/multi/index.js.map +1 -0
  53. package/dist/multi/polyglot.d.ts +179 -0
  54. package/dist/multi/polyglot.d.ts.map +1 -0
  55. package/dist/multi/polyglot.js +319 -0
  56. package/dist/multi/polyglot.js.map +1 -0
  57. package/dist/runtimes/docker.d.ts +97 -0
  58. package/dist/runtimes/docker.d.ts.map +1 -0
  59. package/dist/runtimes/docker.js +368 -0
  60. package/dist/runtimes/docker.js.map +1 -0
  61. package/dist/runtimes/index.d.ts +11 -0
  62. package/dist/runtimes/index.d.ts.map +1 -0
  63. package/dist/runtimes/index.js +9 -0
  64. package/dist/runtimes/index.js.map +1 -0
  65. package/dist/runtimes/process.d.ts +47 -0
  66. package/dist/runtimes/process.d.ts.map +1 -0
  67. package/dist/runtimes/process.js +230 -0
  68. package/dist/runtimes/process.js.map +1 -0
  69. package/dist/session/index.d.ts +12 -0
  70. package/dist/session/index.d.ts.map +1 -0
  71. package/dist/session/index.js +9 -0
  72. package/dist/session/index.js.map +1 -0
  73. package/dist/session/kernel.d.ts +199 -0
  74. package/dist/session/kernel.d.ts.map +1 -0
  75. package/dist/session/kernel.js +400 -0
  76. package/dist/session/kernel.js.map +1 -0
  77. package/dist/session/notebook.d.ts +168 -0
  78. package/dist/session/notebook.d.ts.map +1 -0
  79. package/dist/session/notebook.js +499 -0
  80. package/dist/session/notebook.js.map +1 -0
  81. package/dist/session/repl.d.ts +159 -0
  82. package/dist/session/repl.d.ts.map +1 -0
  83. package/dist/session/repl.js +409 -0
  84. package/dist/session/repl.js.map +1 -0
  85. package/package.json +142 -0
  86. package/src/compilers/index.ts +80 -0
  87. package/src/compilers/javascript.ts +571 -0
  88. package/src/compilers/python.ts +785 -0
  89. package/src/compilers/typescript.ts +442 -0
  90. package/src/core/cell.ts +439 -0
  91. package/src/core/compiler.ts +250 -0
  92. package/src/core/index.ts +123 -0
  93. package/src/core/limits.ts +508 -0
  94. package/src/core/permissions.ts +409 -0
  95. package/src/core/runtime.ts +499 -0
  96. package/src/core/types.ts +528 -0
  97. package/src/global.d.ts +59 -0
  98. package/src/index.ts +515 -0
  99. package/src/multi/index.ts +22 -0
  100. package/src/multi/polyglot.ts +461 -0
  101. package/src/runtimes/docker.ts +501 -0
  102. package/src/runtimes/index.ts +21 -0
  103. package/src/runtimes/process.ts +316 -0
  104. package/src/session/index.ts +41 -0
  105. package/src/session/kernel.ts +553 -0
  106. package/src/session/notebook.ts +635 -0
  107. package/src/session/repl.ts +521 -0
  108. package/src/wasm2wasm.d.ts +35 -0
@@ -0,0 +1,785 @@
1
+ /**
2
+ * Python Compiler
3
+ *
4
+ * Compiles Python code to WASM or validates Python syntax.
5
+ *
6
+ * Note: True Python-to-WASM compilation is complex. Current options:
7
+ * - Pyodide: Full Python runtime compiled to WASM (heavy, ~10MB+)
8
+ * - RustPython: Python interpreter in Rust, WASM-ready
9
+ * - Wasmer Python: Wasmer's Python distribution
10
+ *
11
+ * This implementation provides:
12
+ * 1. Syntax validation (always available)
13
+ * 2. Stub WASM compilation for isolated execution
14
+ * 3. Ready for integration with Pyodide/RustPython when available
15
+ */
16
+
17
+ import {
18
+ BaseCompiler,
19
+ type CompileResult,
20
+ type CompilerOptions,
21
+ type LanguageCapabilities,
22
+ } from "../core/compiler.js";
23
+ import type { Permissions, Limits } from "../core/types.js";
24
+
25
+ /**
26
+ * Python syntax validation result
27
+ */
28
+ export interface PythonValidationResult {
29
+ valid: boolean;
30
+ error?: string;
31
+ line?: number;
32
+ column?: number;
33
+ }
34
+
35
+ /**
36
+ * Python AST node types for basic validation
37
+ */
38
+ type PythonNodeType =
39
+ | "Module"
40
+ | "FunctionDef"
41
+ | "AsyncFunctionDef"
42
+ | "ClassDef"
43
+ | "Return"
44
+ | "Delete"
45
+ | "Assign"
46
+ | "AugAssign"
47
+ | "AnnAssign"
48
+ | "For"
49
+ | "AsyncFor"
50
+ | "While"
51
+ | "If"
52
+ | "With"
53
+ | "AsyncWith"
54
+ | "Raise"
55
+ | "Try"
56
+ | "Assert"
57
+ | "Import"
58
+ | "ImportFrom"
59
+ | "Global"
60
+ | "Nonlocal"
61
+ | "Expr"
62
+ | "Pass"
63
+ | "Break"
64
+ | "Continue";
65
+
66
+ /**
67
+ * Python Compiler
68
+ *
69
+ * Provides Python syntax validation and stub WASM compilation.
70
+ * Designed to be extended with Pyodide or RustPython integration.
71
+ */
72
+ export class PythonCompiler extends BaseCompiler {
73
+ readonly language = "python" as const;
74
+
75
+ /**
76
+ * Python keywords for validation
77
+ */
78
+ private static readonly KEYWORDS = new Set([
79
+ "False", "None", "True", "and", "as", "assert", "async", "await",
80
+ "break", "class", "continue", "def", "del", "elif", "else", "except",
81
+ "finally", "for", "from", "global", "if", "import", "in", "is",
82
+ "lambda", "nonlocal", "not", "or", "pass", "raise", "return", "try",
83
+ "while", "with", "yield",
84
+ ]);
85
+
86
+ /**
87
+ * Python operators for validation
88
+ */
89
+ private static readonly OPERATORS = new Set([
90
+ "+", "-", "*", "/", "//", "%", "**", "<<", ">>", "&", "|", "^",
91
+ "~", "<", ">", "<=", ">=", "==", "!=", "+=", "-=", "*=", "/=",
92
+ "//=", "%=", "**=", "<<=", ">>=", "&=", "|=", "^=", "->", ":=",
93
+ ]);
94
+
95
+ /**
96
+ * Check if a full Python runtime is available
97
+ */
98
+ private pyodideAvailable: boolean | null = null;
99
+
100
+ /**
101
+ * Check if Python compiler is available
102
+ *
103
+ * Always returns true since we provide syntax validation.
104
+ * Returns true for WASM compilation only if Pyodide is loaded.
105
+ */
106
+ async isAvailable(): Promise<boolean> {
107
+ // Basic validation is always available
108
+ return true;
109
+ }
110
+
111
+ /**
112
+ * Check if Pyodide runtime is available
113
+ */
114
+ async isPyodideAvailable(): Promise<boolean> {
115
+ if (this.pyodideAvailable !== null) {
116
+ return this.pyodideAvailable;
117
+ }
118
+
119
+ // Check if running in browser/worker with Pyodide
120
+ if (typeof globalThis !== "undefined") {
121
+ const g = globalThis as { loadPyodide?: unknown; pyodide?: unknown };
122
+ this.pyodideAvailable = !!(g.loadPyodide || g.pyodide);
123
+ } else {
124
+ this.pyodideAvailable = false;
125
+ }
126
+
127
+ return this.pyodideAvailable;
128
+ }
129
+
130
+ /**
131
+ * Validate Python source code syntax
132
+ */
133
+ async validate(source: string): Promise<PythonValidationResult> {
134
+ try {
135
+ // Empty source is valid
136
+ if (!source.trim()) {
137
+ return { valid: true };
138
+ }
139
+
140
+ // Run syntax checks
141
+ const errors: string[] = [];
142
+
143
+ // Check for common syntax errors
144
+ const bracketResult = this.checkBrackets(source);
145
+ if (!bracketResult.valid) {
146
+ return bracketResult;
147
+ }
148
+
149
+ const indentResult = this.checkIndentation(source);
150
+ if (!indentResult.valid) {
151
+ return indentResult;
152
+ }
153
+
154
+ const stringResult = this.checkStrings(source);
155
+ if (!stringResult.valid) {
156
+ return stringResult;
157
+ }
158
+
159
+ // Check for invalid syntax patterns
160
+ const syntaxResult = this.checkSyntaxPatterns(source);
161
+ if (!syntaxResult.valid) {
162
+ return syntaxResult;
163
+ }
164
+
165
+ // If we have access to Pyodide, use its parser for full validation
166
+ if (await this.isPyodideAvailable()) {
167
+ return this.validateWithPyodide(source);
168
+ }
169
+
170
+ // Basic validation passed
171
+ return { valid: true, error: errors.length > 0 ? errors.join("; ") : undefined };
172
+ } catch (error) {
173
+ return {
174
+ valid: false,
175
+ error: `Validation error: ${error instanceof Error ? error.message : String(error)}`,
176
+ };
177
+ }
178
+ }
179
+
180
+ /**
181
+ * Compile Python source to WASM
182
+ *
183
+ * For now, creates a stub WASM module that wraps the Python code.
184
+ * When Pyodide is available, can use full Python-to-WASM execution.
185
+ */
186
+ async compile(
187
+ source: string,
188
+ permissions: Permissions,
189
+ limits: Limits,
190
+ options?: CompilerOptions
191
+ ): Promise<CompileResult> {
192
+ // Validate first
193
+ const validation = await this.validate(source);
194
+ if (!validation.valid) {
195
+ throw new Error(`Python syntax error: ${validation.error}`);
196
+ }
197
+
198
+ // Check permissions - Python needs filesystem and network for many operations
199
+ this.checkPermissions(permissions);
200
+
201
+ // If Pyodide is available, we could use it for actual execution
202
+ // For now, create a stub WASM module that stores the Python code
203
+ const wasmBytes = await this.createStubWasm(source, options);
204
+
205
+ return {
206
+ wasmBytes,
207
+ source, // Include original Python source for interpreted execution
208
+ exports: ["run", "get_output", "set_input"],
209
+ imports: [
210
+ { module: "env", name: "memory", type: "memory" },
211
+ { module: "env", name: "log", type: "function", signature: "(i32, i32) -> ()" },
212
+ ],
213
+ memoryRequirements: {
214
+ initial: this.calculateMemoryPages(source, limits),
215
+ maximum: limits.memory ? Math.ceil(this.parseMemoryLimit(limits.memory) / 65536) : 256,
216
+ },
217
+ };
218
+ }
219
+
220
+ /**
221
+ * Get Python language capabilities
222
+ */
223
+ getCapabilities(): LanguageCapabilities {
224
+ return {
225
+ repl: true,
226
+ stateful: true,
227
+ compiled: false,
228
+ gc: true,
229
+ imports: true,
230
+ async: true,
231
+ };
232
+ }
233
+
234
+ /**
235
+ * Check bracket matching
236
+ */
237
+ private checkBrackets(source: string): PythonValidationResult {
238
+ const stack: { char: string; line: number; col: number }[] = [];
239
+ const pairs: Record<string, string> = { "(": ")", "[": "]", "{": "}" };
240
+ const openBrackets = new Set(["(", "[", "{"]);
241
+ const closeBrackets = new Set([")", "]", "}"]);
242
+
243
+ let inString = false;
244
+ let stringChar = "";
245
+ let escaped = false;
246
+
247
+ const lines = source.split("\n");
248
+ for (let lineNum = 0; lineNum < lines.length; lineNum++) {
249
+ const line = lines[lineNum];
250
+ for (let col = 0; col < line.length; col++) {
251
+ const char = line[col];
252
+
253
+ // Handle string literals
254
+ if (!escaped && (char === '"' || char === "'")) {
255
+ if (!inString) {
256
+ inString = true;
257
+ stringChar = char;
258
+ // Check for triple quotes
259
+ if (line.slice(col, col + 3) === char.repeat(3)) {
260
+ stringChar = char.repeat(3);
261
+ col += 2;
262
+ }
263
+ } else if (char === stringChar || (stringChar.length === 1 && char === stringChar)) {
264
+ inString = false;
265
+ stringChar = "";
266
+ }
267
+ continue;
268
+ }
269
+
270
+ // Handle escape sequences
271
+ if (inString && char === "\\" && !escaped) {
272
+ escaped = true;
273
+ continue;
274
+ }
275
+ escaped = false;
276
+
277
+ // Skip brackets inside strings
278
+ if (inString) continue;
279
+
280
+ if (openBrackets.has(char)) {
281
+ stack.push({ char, line: lineNum + 1, col: col + 1 });
282
+ } else if (closeBrackets.has(char)) {
283
+ if (stack.length === 0) {
284
+ return {
285
+ valid: false,
286
+ error: `Unmatched closing bracket '${char}'`,
287
+ line: lineNum + 1,
288
+ column: col + 1,
289
+ };
290
+ }
291
+ const last = stack.pop()!;
292
+ if (pairs[last.char] !== char) {
293
+ return {
294
+ valid: false,
295
+ error: `Mismatched brackets: '${last.char}' at line ${last.line} should close with '${pairs[last.char]}', found '${char}'`,
296
+ line: lineNum + 1,
297
+ column: col + 1,
298
+ };
299
+ }
300
+ }
301
+ }
302
+ }
303
+
304
+ if (stack.length > 0) {
305
+ const last = stack[stack.length - 1];
306
+ return {
307
+ valid: false,
308
+ error: `Unclosed bracket '${last.char}'`,
309
+ line: last.line,
310
+ column: last.col,
311
+ };
312
+ }
313
+
314
+ return { valid: true };
315
+ }
316
+
317
+ /**
318
+ * Check Python indentation
319
+ */
320
+ private checkIndentation(source: string): PythonValidationResult {
321
+ const lines = source.split("\n");
322
+ const indentStack: number[] = [0];
323
+ const colonBlockStarts = new Set<number>();
324
+
325
+ for (let lineNum = 0; lineNum < lines.length; lineNum++) {
326
+ const line = lines[lineNum];
327
+ const trimmed = line.trim();
328
+
329
+ // Skip empty lines and comments
330
+ if (!trimmed || trimmed.startsWith("#")) {
331
+ continue;
332
+ }
333
+
334
+ // Calculate current indentation
335
+ const currentIndent = line.length - line.trimStart().length;
336
+
337
+ // Check for tab/space mixing
338
+ const leadingChars = line.slice(0, currentIndent);
339
+ const hasTabs = leadingChars.includes("\t");
340
+ const hasSpaces = leadingChars.includes(" ") && !leadingChars.includes("\t");
341
+
342
+ if (hasTabs && hasSpaces) {
343
+ return {
344
+ valid: false,
345
+ error: "Mixed tabs and spaces in indentation",
346
+ line: lineNum + 1,
347
+ column: 1,
348
+ };
349
+ }
350
+
351
+ // Check indentation level
352
+ const expectedIndent = indentStack[indentStack.length - 1];
353
+
354
+ if (currentIndent > expectedIndent) {
355
+ // Must be after a colon
356
+ if (!colonBlockStarts.has(lineNum - 1) && currentIndent !== indentStack[indentStack.length - 1] + (hasTabs ? 1 : 4)) {
357
+ return {
358
+ valid: false,
359
+ error: `Unexpected indent (${currentIndent} spaces, expected ${expectedIndent})`,
360
+ line: lineNum + 1,
361
+ column: 1,
362
+ };
363
+ }
364
+ indentStack.push(currentIndent);
365
+ } else if (currentIndent < expectedIndent) {
366
+ // Dedent - must match a previous level
367
+ while (indentStack.length > 1 && indentStack[indentStack.length - 1] > currentIndent) {
368
+ indentStack.pop();
369
+ }
370
+ if (indentStack[indentStack.length - 1] !== currentIndent) {
371
+ return {
372
+ valid: false,
373
+ error: `Unindent does not match any outer indentation level`,
374
+ line: lineNum + 1,
375
+ column: 1,
376
+ };
377
+ }
378
+ }
379
+
380
+ // Mark if this line ends with a colon (starts a block)
381
+ if (trimmed.endsWith(":")) {
382
+ colonBlockStarts.add(lineNum);
383
+ }
384
+ }
385
+
386
+ return { valid: true };
387
+ }
388
+
389
+ /**
390
+ * Check string literals
391
+ */
392
+ private checkStrings(source: string): PythonValidationResult {
393
+ const lines = source.split("\n");
394
+
395
+ for (let lineNum = 0; lineNum < lines.length; lineNum++) {
396
+ const line = lines[lineNum];
397
+ let i = 0;
398
+
399
+ while (i < line.length) {
400
+ const char = line[i];
401
+
402
+ // Check for string start
403
+ if (char === '"' || char === "'") {
404
+ // Check for triple quotes
405
+ const tripleQuote = line.slice(i, i + 3);
406
+ const isTriple = tripleQuote === char.repeat(3);
407
+ const quoteChar = isTriple ? tripleQuote : char;
408
+ const startIndex = i;
409
+ i += quoteChar.length;
410
+
411
+ // Find end of string
412
+ let foundEnd = false;
413
+ let escaped = false;
414
+
415
+ while (i < line.length) {
416
+ const c = line[i];
417
+ if (escaped) {
418
+ escaped = false;
419
+ i++;
420
+ continue;
421
+ }
422
+ if (c === "\\") {
423
+ escaped = true;
424
+ i++;
425
+ continue;
426
+ }
427
+ if (line.slice(i, i + quoteChar.length) === quoteChar) {
428
+ foundEnd = true;
429
+ i += quoteChar.length;
430
+ break;
431
+ }
432
+ i++;
433
+ }
434
+
435
+ // For triple quotes, the end might be on a later line
436
+ // We handle this in a simplified way - just check single-line strings
437
+ if (!isTriple && !foundEnd) {
438
+ return {
439
+ valid: false,
440
+ error: `Unterminated string literal`,
441
+ line: lineNum + 1,
442
+ column: startIndex + 1,
443
+ };
444
+ }
445
+ } else {
446
+ i++;
447
+ }
448
+ }
449
+ }
450
+
451
+ return { valid: true };
452
+ }
453
+
454
+ /**
455
+ * Check for common syntax patterns
456
+ */
457
+ private checkSyntaxPatterns(source: string): PythonValidationResult {
458
+ const lines = source.split("\n");
459
+
460
+ for (let lineNum = 0; lineNum < lines.length; lineNum++) {
461
+ const line = lines[lineNum].trim();
462
+
463
+ // Skip empty lines and comments
464
+ if (!line || line.startsWith("#")) continue;
465
+
466
+ // Remove string literals for pattern checking
467
+ const codeOnly = line.replace(/(["'])(?:(?=(\\?))\2.)*?\1/g, "");
468
+
469
+ // Check for invalid assignment targets
470
+ const invalidAssignment = codeOnly.match(/^(\d+|\d+\.\d+)\s*=/);
471
+ if (invalidAssignment) {
472
+ return {
473
+ valid: false,
474
+ error: `Cannot assign to literal`,
475
+ line: lineNum + 1,
476
+ column: 1,
477
+ };
478
+ }
479
+
480
+ // Check for missing colons after block statements
481
+ const blockKeywords = ["if", "elif", "else", "for", "while", "def", "class", "try", "except", "finally", "with"];
482
+ for (const kw of blockKeywords) {
483
+ const pattern = new RegExp(`^${kw}\\b`);
484
+ if (pattern.test(codeOnly) && !codeOnly.endsWith(":")) {
485
+ // Check if it's a single-line statement
486
+ if (!codeOnly.includes(";")) {
487
+ return {
488
+ valid: false,
489
+ error: `Missing ':' after '${kw}' statement`,
490
+ line: lineNum + 1,
491
+ column: codeOnly.indexOf(kw) + 1,
492
+ };
493
+ }
494
+ }
495
+ }
496
+ }
497
+
498
+ return { valid: true };
499
+ }
500
+
501
+ /**
502
+ * Validate using Pyodide (when available)
503
+ */
504
+ private async validateWithPyodide(source: string): Promise<PythonValidationResult> {
505
+ try {
506
+ const g = globalThis as { pyodide?: { runPython?: (code: string) => unknown } };
507
+ if (!g.pyodide?.runPython) {
508
+ return { valid: true }; // Fall back to basic validation
509
+ }
510
+
511
+ // Use Python's compile() to check syntax
512
+ const code = `
513
+ import ast
514
+ try:
515
+ ast.parse(${JSON.stringify(source)})
516
+ "valid"
517
+ except SyntaxError as e:
518
+ f"{e.msg}|{e.lineno}|{e.offset}"
519
+ `;
520
+ const result = g.pyodide.runPython(code) as string;
521
+
522
+ if (result === "valid") {
523
+ return { valid: true };
524
+ }
525
+
526
+ const [msg, line, col] = result.split("|");
527
+ return {
528
+ valid: false,
529
+ error: msg,
530
+ line: line ? parseInt(line, 10) : undefined,
531
+ column: col ? parseInt(col, 10) : undefined,
532
+ };
533
+ } catch {
534
+ // If Pyodide validation fails, fall back to basic validation result
535
+ return { valid: true };
536
+ }
537
+ }
538
+
539
+ /**
540
+ * Check permissions for Python execution
541
+ */
542
+ private checkPermissions(permissions: Permissions): void {
543
+ // Python often needs filesystem access
544
+ if (permissions.fs?.write || permissions.network?.outbound) {
545
+ // These require appropriate sandboxing
546
+ }
547
+ }
548
+
549
+ /**
550
+ * Create a stub WASM module that wraps Python code
551
+ *
552
+ * This creates a minimal WASM module that can be used with a Python runtime.
553
+ * The actual Python execution happens in the runtime, not the compiler.
554
+ */
555
+ private async createStubWasm(source: string, options?: CompilerOptions): Promise<Uint8Array> {
556
+ // Encode the Python source into the WASM data section
557
+ const sourceBytes = new TextEncoder().encode(source);
558
+
559
+ // Create a minimal WASM module structure
560
+ // This is a stub - actual execution requires a Python runtime
561
+ const wasmModule = this.buildWasmModule(sourceBytes, options?.optimize ?? false);
562
+
563
+ return wasmModule;
564
+ }
565
+
566
+ /**
567
+ * Build a minimal WASM module structure
568
+ *
569
+ * Creates a WASM module with:
570
+ * - Memory section
571
+ * - Data section containing Python source
572
+ * - Exported functions for runtime interaction
573
+ */
574
+ private buildWasmModule(sourceBytes: Uint8Array, _optimize: boolean): Uint8Array {
575
+ // WASM module header
576
+ const magic = new Uint8Array([0x00, 0x61, 0x73, 0x6D]); // \0asm
577
+ const version = new Uint8Array([0x01, 0x00, 0x00, 0x00]); // version 1
578
+
579
+ // Build sections
580
+ const sections: Uint8Array[] = [];
581
+
582
+ // Type section (section id 1)
583
+ const typeSection = this.buildTypeSection();
584
+ sections.push(typeSection);
585
+
586
+ // Function section (section id 3)
587
+ const funcSection = this.buildFunctionSection();
588
+ sections.push(funcSection);
589
+
590
+ // Memory section (section id 5)
591
+ const memorySection = this.buildMemorySection(sourceBytes.length);
592
+ sections.push(memorySection);
593
+
594
+ // Export section (section id 7)
595
+ const exportSection = this.buildExportSection();
596
+ sections.push(exportSection);
597
+
598
+ // Code section (section id 10)
599
+ const codeSection = this.buildCodeSection();
600
+ sections.push(codeSection);
601
+
602
+ // Data section (section id 11)
603
+ const dataSection = this.buildDataSection(sourceBytes);
604
+ sections.push(dataSection);
605
+
606
+ // Combine all parts
607
+ const totalLength = magic.length + version.length +
608
+ sections.reduce((sum, s) => sum + s.length, 0);
609
+ const result = new Uint8Array(totalLength);
610
+
611
+ let offset = 0;
612
+ result.set(magic, offset);
613
+ offset += magic.length;
614
+ result.set(version, offset);
615
+ offset += version.length;
616
+
617
+ for (const section of sections) {
618
+ result.set(section, offset);
619
+ offset += section.length;
620
+ }
621
+
622
+ return result;
623
+ }
624
+
625
+ private buildTypeSection(): Uint8Array {
626
+ // Section id = 1
627
+ // 1 function type: () -> i32
628
+ const content = new Uint8Array([
629
+ 0x01, // section id
630
+ 0x05, // section size
631
+ 0x01, // 1 type
632
+ 0x60, // func type
633
+ 0x00, // 0 params
634
+ 0x01, 0x41, // 1 result: i32
635
+ ]);
636
+ return content;
637
+ }
638
+
639
+ private buildFunctionSection(): Uint8Array {
640
+ // Section id = 3
641
+ // 1 function using type 0
642
+ const content = new Uint8Array([
643
+ 0x03, // section id
644
+ 0x02, // section size
645
+ 0x01, // 1 function
646
+ 0x00, // type index 0
647
+ ]);
648
+ return content;
649
+ }
650
+
651
+ private buildMemorySection(sourceLength: number): Uint8Array {
652
+ // Calculate required pages (64KB each)
653
+ const pagesNeeded = Math.ceil((sourceLength + 65536) / 65536);
654
+ const maxPages = Math.max(pagesNeeded, 16);
655
+
656
+ // Section id = 5
657
+ const content = new Uint8Array([
658
+ 0x05, // section id
659
+ 0x03, // section size
660
+ 0x01, // 1 memory
661
+ 0x00, // no max specified (has max)
662
+ pagesNeeded & 0x7F, // min pages (LEB128)
663
+ maxPages & 0x7F, // max pages (LEB128)
664
+ ]);
665
+ return content;
666
+ }
667
+
668
+ private buildExportSection(): Uint8Array {
669
+ // Section id = 7
670
+ // Export "run" function
671
+ const nameBytes = new TextEncoder().encode("run");
672
+ const content = new Uint8Array([
673
+ 0x07, // section id
674
+ 0x07, // section size
675
+ 0x01, // 1 export
676
+ nameBytes.length, // name length
677
+ ...nameBytes, // "run"
678
+ 0x00, // export kind: function
679
+ 0x00, // function index 0
680
+ ]);
681
+ return content;
682
+ }
683
+
684
+ private buildCodeSection(): Uint8Array {
685
+ // Section id = 10
686
+ // Simple function that returns the source length
687
+ const content = new Uint8Array([
688
+ 0x0A, // section id
689
+ 0x07, // section size
690
+ 0x01, // 1 function body
691
+ 0x05, // body size
692
+ 0x00, // local count
693
+ 0x41, 0x00, // i32.const 0 (return source start offset)
694
+ 0x0B, // end
695
+ ]);
696
+ return content;
697
+ }
698
+
699
+ private buildDataSection(sourceBytes: Uint8Array): Uint8Array {
700
+ // Section id = 11
701
+ // Store Python source at memory offset 0
702
+ const dataHeader = new Uint8Array([
703
+ 0x0B, // section id
704
+ ]);
705
+
706
+ // Calculate section size
707
+ const sizeBytes = this.encodeLEB128(4 + 1 + 2 + sourceBytes.length);
708
+ const dataContent = new Uint8Array([
709
+ 0x01, // 1 data segment
710
+ 0x00, // active, memory index 0
711
+ 0x41, 0x00, // i32.const 0 (offset)
712
+ 0x0B, // end
713
+ ...this.encodeLEB128(sourceBytes.length), // size
714
+ ]);
715
+
716
+ const total = new Uint8Array(dataHeader.length + sizeBytes.length + dataContent.length + sourceBytes.length);
717
+ let offset = 0;
718
+ total.set(dataHeader, offset);
719
+ offset += dataHeader.length;
720
+ total.set(sizeBytes, offset);
721
+ offset += sizeBytes.length;
722
+ total.set(dataContent, offset);
723
+ offset += dataContent.length;
724
+ total.set(sourceBytes, offset);
725
+
726
+ return total;
727
+ }
728
+
729
+ /**
730
+ * Encode a number as LEB128
731
+ */
732
+ private encodeLEB128(value: number): Uint8Array {
733
+ const bytes: number[] = [];
734
+ let remaining = value;
735
+
736
+ do {
737
+ let byte = remaining & 0x7F;
738
+ remaining >>>= 7;
739
+ if (remaining !== 0) {
740
+ byte |= 0x80;
741
+ }
742
+ bytes.push(byte);
743
+ } while (remaining !== 0);
744
+
745
+ return new Uint8Array(bytes);
746
+ }
747
+
748
+ /**
749
+ * Calculate memory pages needed
750
+ */
751
+ private calculateMemoryPages(source: string, limits: Limits): number {
752
+ const sourceSize = new TextEncoder().encode(source).length;
753
+ const basePages = Math.ceil(sourceSize / 65536);
754
+ const limitPages = limits.memory ? Math.ceil(this.parseMemoryLimit(limits.memory) / 65536) : 256;
755
+ return Math.max(Math.min(basePages + 4, limitPages), 4);
756
+ }
757
+
758
+ /**
759
+ * Parse memory limit to bytes
760
+ */
761
+ private parseMemoryLimit(mem: number | string): number {
762
+ if (typeof mem === "number") return mem;
763
+
764
+ const match = mem.match(/^(\d+(?:\.\d+)?)\s*(b|kb|mb|gb)?$/i);
765
+ if (!match) return 16 * 1024 * 1024;
766
+
767
+ const [, num, unit] = match;
768
+ const multipliers: Record<string, number> = {
769
+ b: 1, kb: 1024, mb: 1024 ** 2, gb: 1024 ** 3,
770
+ };
771
+ return Math.floor(parseFloat(num) * (multipliers[unit?.toLowerCase() ?? "b"] ?? 1));
772
+ }
773
+ }
774
+
775
+ /**
776
+ * Create a Python compiler instance
777
+ */
778
+ export function createPythonCompiler(): PythonCompiler {
779
+ return new PythonCompiler();
780
+ }
781
+
782
+ /**
783
+ * Default Python compiler instance
784
+ */
785
+ export const pythonCompiler = new PythonCompiler();