File size: 3,180 Bytes
21c192a
ed832a7
 
21c192a
 
 
7547388
 
ed832a7
 
 
 
 
 
 
 
 
 
 
 
 
 
21c192a
 
 
 
 
 
ed832a7
 
 
 
 
 
 
 
 
 
 
21c192a
 
ed832a7
 
 
 
 
 
 
 
21c192a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
ed832a7
 
21c192a
 
ed832a7
 
21c192a
 
 
ed832a7
 
 
 
 
 
 
 
 
 
21c192a
ed832a7
21c192a
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
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();