let pyodide = null; let inputControl = null; let inputBytes = null; async function initialize() { try { importScripts("/classroom-assets/vendor/pyodide/pyodide.js"); pyodide = await loadPyodide({ indexURL: "/classroom-assets/vendor/pyodide/" }); pyodide.setStdout({ write(buffer) { postMessage({ type: "stream", channel: "stdout", bytes: buffer }); return buffer.length; }, isatty: true }); pyodide.setStderr({ write(buffer) { postMessage({ type: "stream", channel: "stderr", bytes: buffer }); return buffer.length; }, isatty: true }); postMessage({ type: "ready" }); } catch (error) { postMessage({ type: "load_error", text: String(error) }); } } function readInteractiveInput() { if (!inputControl || !inputBytes) return ""; Atomics.store(inputControl, 0, 0); postMessage({ type: "input_request" }); Atomics.wait(inputControl, 0, 0); const length = Math.max(0, Math.min(Atomics.load(inputControl, 1), inputBytes.length)); const value = new TextDecoder().decode(inputBytes.slice(0, length)); Atomics.store(inputControl, 0, 0); return value; } self.onmessage = async event => { if (event.data.type !== "run" || !pyodide) return; const sharedInput = event.data.inputBuffer; if (!(sharedInput instanceof SharedArrayBuffer)) { postMessage({ type: "python_error", text: "瀏覽器未提供互動式 Python 所需的安全執行環境。" }); return; } inputControl = new Int32Array(sharedInput, 0, 2); inputBytes = new Uint8Array(sharedInput, 8); pyodide.setStdin({ stdin: readInteractiveInput, isatty: true }); try { const files = event.data.files || { "main.py": event.data.code || "" }; const entry = event.data.entry || "main.py"; pyodide.runPython(` import os, shutil, sys root = "/home/pyodide/classroom_project" shutil.rmtree(root, ignore_errors=True) os.makedirs(root, exist_ok=True) for name, module in list(sys.modules.items()): if str(getattr(module, "__file__", "")).startswith(root): del sys.modules[name] `); for (const [path, content] of Object.entries(files)) { const fullPath = `/home/pyodide/classroom_project/${path}`; pyodide.FS.mkdirTree(fullPath.slice(0, fullPath.lastIndexOf("/"))); pyodide.FS.writeFile(fullPath, content, { encoding: "utf8" }); } pyodide.globals.set("classroom_entry", entry); const succeeded = await pyodide.runPythonAsync(` import os, sys, traceback root = "/home/pyodide/classroom_project" os.chdir(root) if root not in sys.path: sys.path.insert(0, root) entry = os.path.join(root, classroom_entry) namespace = {"__name__": "__main__", "__file__": entry} ok = True try: with open(entry, "r", encoding="utf-8") as source: exec(compile(source.read(), entry, "exec"), namespace) except BaseException: ok = False traceback.print_exc() finally: sys.stdout.flush() sys.stderr.flush() ok `); postMessage({ type: succeeded ? "done" : "python_error", text: succeeded ? "" : "程式執行失敗" }); } catch (error) { postMessage({ type: "python_error", text: String(error) }); } }; initialize();