cpython/Tools/wasm/emscripten/web_example_pyrepl_jspi/src.mjs

195 lines
4.9 KiB
JavaScript
Raw Normal View History

// Much of this is adapted from here:
// https://github.com/mame/xterm-pty/blob/main/emscripten-pty.js
// Thanks to xterm-pty for making this possible!
import createEmscriptenModule from "./python.mjs";
import { openpty } from "https://unpkg.com/xterm-pty/index.mjs";
import "https://unpkg.com/@xterm/xterm/lib/xterm.js";
var term = new Terminal();
term.open(document.getElementById("terminal"));
const { master, slave: PTY } = openpty();
term.loadAddon(master);
globalThis.PTY = PTY;
async function setupStdlib(Module) {
const versionInt = Module.HEAPU32[Module._Py_Version >>> 2];
const major = (versionInt >>> 24) & 0xff;
const minor = (versionInt >>> 16) & 0xff;
// Prevent complaints about not finding exec-prefix by making a lib-dynload directory
Module.FS.mkdirTree(`/lib/python${major}.${minor}/lib-dynload/`);
const resp = await fetch(`python${major}.${minor}.zip`);
const stdlibBuffer = await resp.arrayBuffer();
Module.FS.writeFile(
`/lib/python${major}${minor}.zip`,
new Uint8Array(stdlibBuffer),
{ canOwn: true },
);
}
const tty_ops = {
ioctl_tcgets: () => {
const termios = PTY.ioctl("TCGETS");
const data = {
c_iflag: termios.iflag,
c_oflag: termios.oflag,
c_cflag: termios.cflag,
c_lflag: termios.lflag,
c_cc: termios.cc,
};
return data;
},
ioctl_tcsets: (_tty, _optional_actions, data) => {
PTY.ioctl("TCSETS", {
iflag: data.c_iflag,
oflag: data.c_oflag,
cflag: data.c_cflag,
lflag: data.c_lflag,
cc: data.c_cc,
});
return 0;
},
ioctl_tiocgwinsz: () => PTY.ioctl("TIOCGWINSZ").reverse(),
get_char: () => {
throw new Error("Should not happen");
},
put_char: () => {
throw new Error("Should not happen");
},
fsync: () => {},
};
const POLLIN = 1;
const POLLOUT = 4;
const waitResult = {
READY: 0,
SIGNAL: 1,
TIMEOUT: 2,
};
function onReadable() {
var handle;
var promise = new Promise((resolve) => {
handle = PTY.onReadable(() => resolve(waitResult.READY));
});
return [promise, handle];
}
function onSignal() {
// TODO: signal handling
var handle = { dispose() {} };
var promise = new Promise((resolve) => {});
return [promise, handle];
}
function onTimeout(timeout) {
var id;
var promise = new Promise((resolve) => {
if (timeout > 0) {
id = setTimeout(resolve, timeout, waitResult.TIMEOUT);
}
});
var handle = {
dispose() {
if (id) {
clearTimeout(id);
}
},
};
return [promise, handle];
}
async function waitForReadable(timeout) {
let p1, p2, p3;
let h1, h2, h3;
try {
[p1, h1] = onReadable();
[p2, h2] = onTimeout(timeout);
[p3, h3] = onSignal();
return await Promise.race([p1, p2, p3]);
} finally {
h1.dispose();
h2.dispose();
h3.dispose();
}
}
const FIONREAD = 0x541b;
const tty_stream_ops = {
async readAsync(stream, buffer, offset, length, pos /* ignored */) {
let readBytes = PTY.read(length);
if (length && !readBytes.length) {
const status = await waitForReadable(-1);
if (status === waitResult.READY) {
readBytes = PTY.read(length);
} else {
throw new Error("Not implemented");
}
}
buffer.set(readBytes, offset);
return readBytes.length;
},
write: (stream, buffer, offset, length) => {
// Note: default `buffer` is for some reason `HEAP8` (signed), while we want unsigned `HEAPU8`.
buffer = new Uint8Array(
buffer.buffer,
buffer.byteOffset,
buffer.byteLength,
);
const toWrite = Array.from(buffer.subarray(offset, offset + length));
PTY.write(toWrite);
return length;
},
async pollAsync(stream, timeout) {
if (!PTY.readable && timeout) {
await waitForReadable(timeout);
}
return (PTY.readable ? POLLIN : 0) | (PTY.writable ? POLLOUT : 0);
},
ioctl(stream, request, varargs) {
if (request === FIONREAD) {
const res = PTY.fromLdiscToUpperBuffer.length;
Module.HEAPU32[varargs / 4] = res;
return 0;
}
throw new Error("Unimplemented ioctl request");
},
};
async function setupStdio(Module) {
Object.assign(Module.TTY.default_tty_ops, tty_ops);
Object.assign(Module.TTY.stream_ops, tty_stream_ops);
}
const emscriptenSettings = {
async preRun(Module) {
Module.addRunDependency("pre-run");
Module.ENV.TERM = "xterm-256color";
// Uncomment next line to turn on tracing (messages go to browser console).
// Module.ENV.PYREPL_TRACE = "1";
// Leak module so we can try to show traceback if we crash on startup
globalThis.Module = Module;
await Promise.all([setupStdlib(Module), setupStdio(Module)]);
Module.removeRunDependency("pre-run");
},
};
try {
await createEmscriptenModule(emscriptenSettings);
} catch (e) {
// Show JavaScript exception and traceback
console.warn(e);
// Show Python exception and traceback
Module.__Py_DumpTraceback(2, Module._PyGILState_GetThisThreadState());
process.exit(1);
}