mirror of
				https://github.com/python/cpython.git
				synced 2025-11-04 07:31:38 +00:00 
			
		
		
		
	
		
			
	
	
		
			195 lines
		
	
	
	
		
			4.9 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
		
		
			
		
	
	
			195 lines
		
	
	
	
		
			4.9 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| 
								 | 
							
								// 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);
							 | 
						||
| 
								 | 
							
								}
							 |