]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
gh-127111: Apply prettier formatter to Emscripten web example (#127551)
authorHood Chatham <roberthoodchatham@gmail.com>
Thu, 5 Dec 2024 00:25:06 +0000 (01:25 +0100)
committerGitHub <noreply@github.com>
Thu, 5 Dec 2024 00:25:06 +0000 (08:25 +0800)
Cleaned up formatting (and a stray closing tag) of the web example HTML and JS.

Tools/wasm/emscripten/web_example/python.html
Tools/wasm/emscripten/web_example/python.worker.mjs

index fae1e9ad4e8acb65a3f023278f7d0a1ba5069b9f..078f86eb764419ada3c257fdfb86e56e2c719b32 100644 (file)
-<!DOCTYPE html>
+<!doctype html>
 <html lang="en">
-<head>
-    <meta charset="UTF-8">
-    <meta http-equiv="X-UA-Compatible" content="IE=edge">
-    <meta name="viewport" content="width=device-width, initial-scale=1.0">
-    <meta name="author" content="Katie Bell">
-    <meta name="description" content="Simple REPL for Python WASM">
-    <title>wasm-python terminal</title>
-    <link rel="stylesheet" href="https://unpkg.com/xterm@4.18.0/css/xterm.css" crossorigin integrity="sha384-4eEEn/eZgVHkElpKAzzPx/Kow/dTSgFk1BNe+uHdjHa+NkZJDh5Vqkq31+y7Eycd"/>
-    <style>
-        body {
-            font-family: arial;
-            max-width: 800px;
-            margin: 0 auto
-        }
-        #code {
-            width: 100%;
-            height: 180px;
-        }
-        #info {
-            padding-top: 20px;
-        }
-        .button-container {
-            display: flex;
-            justify-content: end;
-            height: 50px;
-            align-items: center;
-            gap: 10px;
-        }
-        button {
-            padding: 6px 18px;
-        }
-    </style>
-    <script src="https://unpkg.com/xterm@4.18.0/lib/xterm.js" crossorigin integrity="sha384-yYdNmem1ioP5Onm7RpXutin5A8TimLheLNQ6tnMi01/ZpxXdAwIm2t4fJMx1Djs+"/></script>
-    <script type="module">
-class WorkerManager {
-    constructor(workerURL, standardIO, readyCallBack, finishedCallback) {
-        this.workerURL = workerURL
-        this.worker = null
-        this.standardIO = standardIO
-        this.readyCallBack = readyCallBack
-        this.finishedCallback = finishedCallback
-
-        this.initialiseWorker()
-    }
-
-    async initialiseWorker() {
-        if (!this.worker) {
-            this.worker = new Worker(this.workerURL, {type: "module"})
-            this.worker.addEventListener('message', this.handleMessageFromWorker)
-        }
-    }
-
-    async run(options) {
-        this.worker.postMessage({
-            type: 'run',
-            args: options.args || [],
-            files: options.files || {}
-        })
-    }
-
-    reset() {
-        if (this.worker) {
-            this.worker.terminate()
-            this.worker = null
-        }
-        this.standardIO.message('Worker process terminated.')
-        this.initialiseWorker()
-    }
-
-    handleStdinData(inputValue) {
-        if (this.stdinbuffer && this.stdinbufferInt) {
-            let startingIndex = 1
-            if (this.stdinbufferInt[0] > 0) {
-                startingIndex = this.stdinbufferInt[0]
+    <head>
+        <meta charset="UTF-8" />
+        <meta http-equiv="X-UA-Compatible" content="IE=edge" />
+        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+        <meta name="author" content="Katie Bell" />
+        <meta name="description" content="Simple REPL for Python WASM" />
+        <title>wasm-python terminal</title>
+        <link
+            rel="stylesheet"
+            href="https://unpkg.com/xterm@4.18.0/css/xterm.css"
+            crossorigin
+            integrity="sha384-4eEEn/eZgVHkElpKAzzPx/Kow/dTSgFk1BNe+uHdjHa+NkZJDh5Vqkq31+y7Eycd"
+        />
+        <style>
+            body {
+                font-family: arial;
+                max-width: 800px;
+                margin: 0 auto;
             }
-            const data = new TextEncoder().encode(inputValue)
-            data.forEach((value, index) => {
-                this.stdinbufferInt[startingIndex + index] = value
-            })
-
-            this.stdinbufferInt[0] = startingIndex + data.length - 1
-            Atomics.notify(this.stdinbufferInt, 0, 1)
-        }
-    }
-
-    handleMessageFromWorker = (event) => {
-        const type = event.data.type
-        if (type === 'ready') {
-            this.readyCallBack()
-        } else if (type === 'stdout') {
-            this.standardIO.stdout(event.data.stdout)
-        } else if (type === 'stderr') {
-            this.standardIO.stderr(event.data.stderr)
-        } else if (type === 'stdin') {
-            // Leave it to the terminal to decide whether to chunk it into lines
-            // or send characters depending on the use case.
-            this.stdinbuffer = event.data.buffer
-            this.stdinbufferInt = new Int32Array(this.stdinbuffer)
-            this.standardIO.stdin().then((inputValue) => {
-                this.handleStdinData(inputValue)
-            })
-        } else if (type === 'finished') {
-            this.standardIO.message(`Exited with status: ${event.data.returnCode}`)
-            this.finishedCallback()
-        }
-    }
-}
-
-class WasmTerminal {
-
-    constructor() {
-        this.inputBuffer = new BufferQueue();
-        this.input = ''
-        this.resolveInput = null
-        this.activeInput = false
-        this.inputStartCursor = null
-
-        this.xterm = new Terminal(
-            { scrollback: 10000, fontSize: 14, theme: { background: '#1a1c1f' }, cols: 100}
-        );
-
-        this.xterm.onKey((keyEvent) => {
-            // Fix for iOS Keyboard Jumping on space
-            if (keyEvent.key === " ") {
-                keyEvent.domEvent.preventDefault();
+            #code {
+                width: 100%;
+                height: 180px;
             }
-        });
-
-        this.xterm.onData(this.handleTermData)
-    }
-
-    open(container) {
-        this.xterm.open(container);
-    }
-
-    handleTermData = (data) => {
-        const ord = data.charCodeAt(0);
-        data = data.replace(/\r(?!\n)/g, "\n")  // Convert lone CRs to LF
-
-        // Handle pasted data
-        if (data.length > 1 && data.includes("\n")) {
-            let alreadyWrittenChars = 0;
-            // If line already had data on it, merge pasted data with it
-            if (this.input != '') {
-                this.inputBuffer.addData(this.input);
-                alreadyWrittenChars = this.input.length;
-                this.input = '';
+            #info {
+                padding-top: 20px;
             }
-            this.inputBuffer.addData(data);
-            // If input is active, write the first line
-            if (this.activeInput) {
-                let line = this.inputBuffer.nextLine();
-                this.writeLine(line.slice(alreadyWrittenChars));
-                this.resolveInput(line);
-                this.activeInput = false;
+            .button-container {
+                display: flex;
+                justify-content: end;
+                height: 50px;
+                align-items: center;
+                gap: 10px;
             }
-        // When input isn't active, add to line buffer
-        } else if (!this.activeInput) {
-            // Skip non-printable characters
-            if (!(ord === 0x1b || ord == 0x7f || ord < 32)) {
-                this.inputBuffer.addData(data);
+            button {
+                padding: 6px 18px;
             }
-        // TODO: Handle ANSI escape sequences
-        } else if (ord === 0x1b) {
-        // Handle special characters
-        } else if (ord < 32 || ord === 0x7f) {
-            switch (data) {
-                case "\x0c": // CTRL+L
-                    this.clear();
-                    break;
-                case "\n": // ENTER
-                case "\x0a": // CTRL+J
-                case "\x0d": // CTRL+M
-                    this.resolveInput(this.input + this.writeLine('\n'));
-                    this.input = '';
+        </style>
+        <script
+            src="https://unpkg.com/xterm@4.18.0/lib/xterm.js"
+            crossorigin
+            integrity="sha384-yYdNmem1ioP5Onm7RpXutin5A8TimLheLNQ6tnMi01/ZpxXdAwIm2t4fJMx1Djs+"
+        />
+        <script type="module">
+            class WorkerManager {
+                constructor(
+                    workerURL,
+                    standardIO,
+                    readyCallBack,
+                    finishedCallback,
+                ) {
+                    this.workerURL = workerURL;
+                    this.worker = null;
+                    this.standardIO = standardIO;
+                    this.readyCallBack = readyCallBack;
+                    this.finishedCallback = finishedCallback;
+
+                    this.initialiseWorker();
+                }
+
+                async initialiseWorker() {
+                    if (!this.worker) {
+                        this.worker = new Worker(this.workerURL, {
+                            type: "module",
+                        });
+                        this.worker.addEventListener(
+                            "message",
+                            this.handleMessageFromWorker,
+                        );
+                    }
+                }
+
+                async run(options) {
+                    this.worker.postMessage({
+                        type: "run",
+                        args: options.args || [],
+                        files: options.files || {},
+                    });
+                }
+
+                reset() {
+                    if (this.worker) {
+                        this.worker.terminate();
+                        this.worker = null;
+                    }
+                    this.standardIO.message("Worker process terminated.");
+                    this.initialiseWorker();
+                }
+
+                handleStdinData(inputValue) {
+                    if (this.stdinbuffer && this.stdinbufferInt) {
+                        let startingIndex = 1;
+                        if (this.stdinbufferInt[0] > 0) {
+                            startingIndex = this.stdinbufferInt[0];
+                        }
+                        const data = new TextEncoder().encode(inputValue);
+                        data.forEach((value, index) => {
+                            this.stdinbufferInt[startingIndex + index] = value;
+                        });
+
+                        this.stdinbufferInt[0] =
+                            startingIndex + data.length - 1;
+                        Atomics.notify(this.stdinbufferInt, 0, 1);
+                    }
+                }
+
+                handleMessageFromWorker = (event) => {
+                    const type = event.data.type;
+                    if (type === "ready") {
+                        this.readyCallBack();
+                    } else if (type === "stdout") {
+                        this.standardIO.stdout(event.data.stdout);
+                    } else if (type === "stderr") {
+                        this.standardIO.stderr(event.data.stderr);
+                    } else if (type === "stdin") {
+                        // Leave it to the terminal to decide whether to chunk it into lines
+                        // or send characters depending on the use case.
+                        this.stdinbuffer = event.data.buffer;
+                        this.stdinbufferInt = new Int32Array(this.stdinbuffer);
+                        this.standardIO.stdin().then((inputValue) => {
+                            this.handleStdinData(inputValue);
+                        });
+                    } else if (type === "finished") {
+                        this.standardIO.message(
+                            `Exited with status: ${event.data.returnCode}`,
+                        );
+                        this.finishedCallback();
+                    }
+                };
+            }
+
+            class WasmTerminal {
+                constructor() {
+                    this.inputBuffer = new BufferQueue();
+                    this.input = "";
+                    this.resolveInput = null;
                     this.activeInput = false;
-                    break;
-                case "\x7F": // BACKSPACE
-                case "\x08": // CTRL+H
-                    this.handleCursorErase(true);
-                    break;
-                case "\x04": // CTRL+D
-                    // Send empty input
-                    if (this.input === '') {
-                        this.resolveInput('')
-                        this.activeInput = false;
+                    this.inputStartCursor = null;
+
+                    this.xterm = new Terminal({
+                        scrollback: 10000,
+                        fontSize: 14,
+                        theme: { background: "#1a1c1f" },
+                        cols: 100,
+                    });
+
+                    this.xterm.onKey((keyEvent) => {
+                        // Fix for iOS Keyboard Jumping on space
+                        if (keyEvent.key === " ") {
+                            keyEvent.domEvent.preventDefault();
+                        }
+                    });
+
+                    this.xterm.onData(this.handleTermData);
+                }
+
+                open(container) {
+                    this.xterm.open(container);
+                }
+
+                handleTermData = (data) => {
+                    const ord = data.charCodeAt(0);
+                    data = data.replace(/\r(?!\n)/g, "\n"); // Convert lone CRs to LF
+
+                    // Handle pasted data
+                    if (data.length > 1 && data.includes("\n")) {
+                        let alreadyWrittenChars = 0;
+                        // If line already had data on it, merge pasted data with it
+                        if (this.input != "") {
+                            this.inputBuffer.addData(this.input);
+                            alreadyWrittenChars = this.input.length;
+                            this.input = "";
+                        }
+                        this.inputBuffer.addData(data);
+                        // If input is active, write the first line
+                        if (this.activeInput) {
+                            let line = this.inputBuffer.nextLine();
+                            this.writeLine(line.slice(alreadyWrittenChars));
+                            this.resolveInput(line);
+                            this.activeInput = false;
+                        }
+                        // When input isn't active, add to line buffer
+                    } else if (!this.activeInput) {
+                        // Skip non-printable characters
+                        if (!(ord === 0x1b || ord == 0x7f || ord < 32)) {
+                            this.inputBuffer.addData(data);
+                        }
+                        // TODO: Handle ANSI escape sequences
+                    } else if (ord === 0x1b) {
+                        // Handle special characters
+                    } else if (ord < 32 || ord === 0x7f) {
+                        switch (data) {
+                            case "\x0c": // CTRL+L
+                                this.clear();
+                                break;
+                            case "\n": // ENTER
+                            case "\x0a": // CTRL+J
+                            case "\x0d": // CTRL+M
+                                this.resolveInput(
+                                    this.input + this.writeLine("\n"),
+                                );
+                                this.input = "";
+                                this.activeInput = false;
+                                break;
+                            case "\x7F": // BACKSPACE
+                            case "\x08": // CTRL+H
+                                this.handleCursorErase(true);
+                                break;
+                            case "\x04": // CTRL+D
+                                // Send empty input
+                                if (this.input === "") {
+                                    this.resolveInput("");
+                                    this.activeInput = false;
+                                }
+                        }
+                    } else {
+                        this.handleCursorInsert(data);
+                    }
+                };
+
+                writeLine(line) {
+                    this.xterm.write(line.slice(0, -1));
+                    this.xterm.write("\r\n");
+                    return line;
+                }
+
+                handleCursorInsert(data) {
+                    this.input += data;
+                    this.xterm.write(data);
+                }
+
+                handleCursorErase() {
+                    // Don't delete past the start of input
+                    if (
+                        this.xterm.buffer.active.cursorX <=
+                        this.inputStartCursor
+                    ) {
+                        return;
+                    }
+                    this.input = this.input.slice(0, -1);
+                    this.xterm.write("\x1B[D");
+                    this.xterm.write("\x1B[P");
+                }
+
+                prompt = async () => {
+                    this.activeInput = true;
+                    // Hack to allow stdout/stderr to finish before we figure out where input starts
+                    setTimeout(() => {
+                        this.inputStartCursor =
+                            this.xterm.buffer.active.cursorX;
+                    }, 1);
+                    // If line buffer has a line ready, send it immediately
+                    if (this.inputBuffer.hasLineReady()) {
+                        return new Promise((resolve, reject) => {
+                            resolve(
+                                this.writeLine(this.inputBuffer.nextLine()),
+                            );
+                            this.activeInput = false;
+                        });
+                        // If line buffer has an incomplete line, use it for the active line
+                    } else if (this.inputBuffer.lastLineIsIncomplete()) {
+                        // Hack to ensure cursor input start doesn't end up after user input
+                        setTimeout(() => {
+                            this.handleCursorInsert(
+                                this.inputBuffer.nextLine(),
+                            );
+                        }, 1);
                     }
+                    return new Promise((resolve, reject) => {
+                        this.resolveInput = (value) => {
+                            resolve(value);
+                        };
+                    });
+                };
+
+                clear() {
+                    this.xterm.clear();
+                }
+
+                print(charCode) {
+                    let array = [charCode];
+                    if (charCode == 10) {
+                        array = [13, 10]; // Replace \n with \r\n
+                    }
+                    this.xterm.write(new Uint8Array(array));
+                }
             }
-        } else {
-            this.handleCursorInsert(data);
-        }
-    }
-
-    writeLine(line) {
-        this.xterm.write(line.slice(0, -1))
-        this.xterm.write('\r\n');
-        return line;
-    }
-
-    handleCursorInsert(data) {
-        this.input += data;
-        this.xterm.write(data)
-    }
-
-    handleCursorErase() {
-        // Don't delete past the start of input
-        if (this.xterm.buffer.active.cursorX <= this.inputStartCursor) {
-            return
-        }
-        this.input = this.input.slice(0, -1)
-        this.xterm.write('\x1B[D')
-        this.xterm.write('\x1B[P')
-    }
-
-    prompt = async () => {
-        this.activeInput = true
-        // Hack to allow stdout/stderr to finish before we figure out where input starts
-        setTimeout(() => {this.inputStartCursor = this.xterm.buffer.active.cursorX}, 1)
-        // If line buffer has a line ready, send it immediately
-        if (this.inputBuffer.hasLineReady()) {
-            return new Promise((resolve, reject) => {
-                resolve(this.writeLine(this.inputBuffer.nextLine()));
-                this.activeInput = false;
-            })
-        // If line buffer has an incomplete line, use it for the active line
-        } else if (this.inputBuffer.lastLineIsIncomplete()) {
-            // Hack to ensure cursor input start doesn't end up after user input
-            setTimeout(() => {this.handleCursorInsert(this.inputBuffer.nextLine())}, 1);
-        }
-        return new Promise((resolve, reject) => {
-            this.resolveInput = (value) => {
-                resolve(value)
+
+            class BufferQueue {
+                constructor(xterm) {
+                    this.buffer = [];
+                }
+
+                isEmpty() {
+                    return this.buffer.length == 0;
+                }
+
+                lastLineIsIncomplete() {
+                    return (
+                        !this.isEmpty() &&
+                        !this.buffer[this.buffer.length - 1].endsWith("\n")
+                    );
+                }
+
+                hasLineReady() {
+                    return !this.isEmpty() && this.buffer[0].endsWith("\n");
+                }
+
+                addData(data) {
+                    let lines = data.match(/.*(\n|$)/g);
+                    if (this.lastLineIsIncomplete()) {
+                        this.buffer[this.buffer.length - 1] += lines.shift();
+                    }
+                    for (let line of lines) {
+                        this.buffer.push(line);
+                    }
+                }
+
+                nextLine() {
+                    return this.buffer.shift();
+                }
             }
-        })
-    }
-
-    clear() {
-        this.xterm.clear();
-    }
-
-    print(charCode) {
-        let array = [charCode];
-        if (charCode == 10) {
-            array = [13, 10];  // Replace \n with \r\n
-        }
-        this.xterm.write(new Uint8Array(array));
-    }
-}
-
-class BufferQueue {
-    constructor(xterm) {
-        this.buffer = []
-    }
-
-    isEmpty() {
-        return this.buffer.length == 0
-    }
-
-    lastLineIsIncomplete() {
-        return !this.isEmpty() && !this.buffer[this.buffer.length-1].endsWith("\n")
-    }
-
-    hasLineReady() {
-        return !this.isEmpty() && this.buffer[0].endsWith("\n")
-    }
-
-    addData(data) {
-        let lines = data.match(/.*(\n|$)/g)
-        if (this.lastLineIsIncomplete()) {
-            this.buffer[this.buffer.length-1] += lines.shift()
-        }
-        for (let line of lines) {
-            this.buffer.push(line)
-        }
-    }
-
-    nextLine() {
-        return this.buffer.shift()
-    }
-}
-
-const runButton = document.getElementById('run')
-const replButton = document.getElementById('repl')
-const stopButton = document.getElementById('stop')
-const clearButton = document.getElementById('clear')
-
-const codeBox = document.getElementById('codebox')
-
-window.onload = () => {
-    const terminal = new WasmTerminal()
-    terminal.open(document.getElementById('terminal'))
-
-    const stdio = {
-        stdout: (charCode) => { terminal.print(charCode) },
-        stderr: (charCode) => { terminal.print(charCode) },
-        stdin: async () => {
-            return await terminal.prompt()
-        },
-        message: (text) => { terminal.writeLine(`\r\n${text}\r\n`) },
-    }
-
-    const programRunning = (isRunning) => {
-        if (isRunning) {
-            replButton.setAttribute('disabled', true)
-            runButton.setAttribute('disabled', true)
-            stopButton.removeAttribute('disabled')
-        } else {
-            replButton.removeAttribute('disabled')
-            runButton.removeAttribute('disabled')
-            stopButton.setAttribute('disabled', true)
-        }
-    }
-
-    runButton.addEventListener('click', (e) => {
-        terminal.clear()
-        programRunning(true)
-        const code = codeBox.value
-        pythonWorkerManager.run({args: ['main.py'], files: {'main.py': code}})
-    })
-
-    replButton.addEventListener('click', (e) => {
-        terminal.clear()
-        programRunning(true)
-        // Need to use "-i -" to force interactive mode.
-        // Looks like isatty always returns false in emscripten
-        pythonWorkerManager.run({args: ['-i', '-'], files: {}})
-    })
-
-    stopButton.addEventListener('click', (e) => {
-        programRunning(false)
-        pythonWorkerManager.reset()
-    })
-
-    clearButton.addEventListener('click', (e) => {
-        terminal.clear()
-    })
-
-    const readyCallback = () => {
-        replButton.removeAttribute('disabled')
-        runButton.removeAttribute('disabled')
-        clearButton.removeAttribute('disabled')
-    }
-
-    const finishedCallback = () => {
-        programRunning(false)
-    }
-
-    const pythonWorkerManager = new WorkerManager('./python.worker.mjs', stdio, readyCallback, finishedCallback)
-}
-    </script>
-</head>
-<body>
-    <h1>Simple REPL for Python WASM</h1>
-<textarea id="codebox" cols="108" rows="16">
+
+            const runButton = document.getElementById("run");
+            const replButton = document.getElementById("repl");
+            const stopButton = document.getElementById("stop");
+            const clearButton = document.getElementById("clear");
+
+            const codeBox = document.getElementById("codebox");
+
+            window.onload = () => {
+                const terminal = new WasmTerminal();
+                terminal.open(document.getElementById("terminal"));
+
+                const stdio = {
+                    stdout: (charCode) => {
+                        terminal.print(charCode);
+                    },
+                    stderr: (charCode) => {
+                        terminal.print(charCode);
+                    },
+                    stdin: async () => {
+                        return await terminal.prompt();
+                    },
+                    message: (text) => {
+                        terminal.writeLine(`\r\n${text}\r\n`);
+                    },
+                };
+
+                const programRunning = (isRunning) => {
+                    if (isRunning) {
+                        replButton.setAttribute("disabled", true);
+                        runButton.setAttribute("disabled", true);
+                        stopButton.removeAttribute("disabled");
+                    } else {
+                        replButton.removeAttribute("disabled");
+                        runButton.removeAttribute("disabled");
+                        stopButton.setAttribute("disabled", true);
+                    }
+                };
+
+                runButton.addEventListener("click", (e) => {
+                    terminal.clear();
+                    programRunning(true);
+                    const code = codeBox.value;
+                    pythonWorkerManager.run({
+                        args: ["main.py"],
+                        files: { "main.py": code },
+                    });
+                });
+
+                replButton.addEventListener("click", (e) => {
+                    terminal.clear();
+                    programRunning(true);
+                    // Need to use "-i -" to force interactive mode.
+                    // Looks like isatty always returns false in emscripten
+                    pythonWorkerManager.run({ args: ["-i", "-"], files: {} });
+                });
+
+                stopButton.addEventListener("click", (e) => {
+                    programRunning(false);
+                    pythonWorkerManager.reset();
+                });
+
+                clearButton.addEventListener("click", (e) => {
+                    terminal.clear();
+                });
+
+                const readyCallback = () => {
+                    replButton.removeAttribute("disabled");
+                    runButton.removeAttribute("disabled");
+                    clearButton.removeAttribute("disabled");
+                };
+
+                const finishedCallback = () => {
+                    programRunning(false);
+                };
+
+                const pythonWorkerManager = new WorkerManager(
+                    "./python.worker.mjs",
+                    stdio,
+                    readyCallback,
+                    finishedCallback,
+                );
+            };
+        </script>
+    </head>
+    <body>
+        <h1>Simple REPL for Python WASM</h1>
+        <textarea id="codebox" cols="108" rows="16">
 print('Welcome to WASM!')
-</textarea>
-    <div class="button-container">
-      <button id="run" disabled>Run</button>
-      <button id="repl" disabled>Start REPL</button>
-      <button id="stop" disabled>Stop</button>
-      <button id="clear" disabled>Clear</button>
-    </div>
-    <div id="terminal"></div>
-    <div id="info">
-        The simple REPL provides a limited Python experience in the browser.
-        <a href="https://github.com/python/cpython/blob/main/Tools/wasm/README.md">
-        Tools/wasm/README.md</a> contains a list of known limitations and
-        issues. Networking, subprocesses, and threading are not available.
-    </div>
-</body>
+</textarea
+        >
+        <div class="button-container">
+            <button id="run" disabled>Run</button>
+            <button id="repl" disabled>Start REPL</button>
+            <button id="stop" disabled>Stop</button>
+            <button id="clear" disabled>Clear</button>
+        </div>
+        <div id="terminal"></div>
+        <div id="info">
+            The simple REPL provides a limited Python experience in the browser.
+            <a
+                href="https://github.com/python/cpython/blob/main/Tools/wasm/README.md"
+            >
+                Tools/wasm/README.md
+            </a>
+            contains a list of known limitations and issues. Networking,
+            subprocesses, and threading are not available.
+        </div>
+    </body>
 </html>
index 42c2e1e08af24bc29bdef902eccb3da957f07860..8043e4199667438c9725d8cbe590d977e61c4168 100644 (file)
 import createEmscriptenModule from "./python.mjs";
 
 class StdinBuffer {
-    constructor() {
-        this.sab = new SharedArrayBuffer(128 * Int32Array.BYTES_PER_ELEMENT)
-        this.buffer = new Int32Array(this.sab)
-        this.readIndex = 1;
-        this.numberOfCharacters = 0;
-        this.sentNull = true
-    }
+  constructor() {
+    this.sab = new SharedArrayBuffer(128 * Int32Array.BYTES_PER_ELEMENT);
+    this.buffer = new Int32Array(this.sab);
+    this.readIndex = 1;
+    this.numberOfCharacters = 0;
+    this.sentNull = true;
+  }
 
-    prompt() {
-        this.readIndex = 1
-        Atomics.store(this.buffer, 0, -1)
-        postMessage({
-            type: 'stdin',
-            buffer: this.sab
-        })
-        Atomics.wait(this.buffer, 0, -1)
-        this.numberOfCharacters = this.buffer[0]
-    }
+  prompt() {
+    this.readIndex = 1;
+    Atomics.store(this.buffer, 0, -1);
+    postMessage({
+      type: "stdin",
+      buffer: this.sab,
+    });
+    Atomics.wait(this.buffer, 0, -1);
+    this.numberOfCharacters = this.buffer[0];
+  }
 
-    stdin = () => {
-        while (this.numberOfCharacters + 1 === this.readIndex) {
-            if (!this.sentNull) {
-                // Must return null once to indicate we're done for now.
-                this.sentNull = true
-                return null
-            }
-            this.sentNull = false
-            // Prompt will reset this.readIndex to 1
-            this.prompt()
-        }
-        const char = this.buffer[this.readIndex]
-        this.readIndex += 1
-        return char
+  stdin = () => {
+    while (this.numberOfCharacters + 1 === this.readIndex) {
+      if (!this.sentNull) {
+        // Must return null once to indicate we're done for now.
+        this.sentNull = true;
+        return null;
+      }
+      this.sentNull = false;
+      // Prompt will reset this.readIndex to 1
+      this.prompt();
     }
+    const char = this.buffer[this.readIndex];
+    this.readIndex += 1;
+    return char;
+  };
 }
 
 const stdout = (charCode) => {
-    if (charCode) {
-        postMessage({
-            type: 'stdout',
-            stdout: charCode,
-        })
-    } else {
-        console.log(typeof charCode, charCode)
-    }
-}
+  if (charCode) {
+    postMessage({
+      type: "stdout",
+      stdout: charCode,
+    });
+  } else {
+    console.log(typeof charCode, charCode);
+  }
+};
 
 const stderr = (charCode) => {
-    if (charCode) {
-        postMessage({
-            type: 'stderr',
-            stderr: charCode,
-        })
-    } else {
-        console.log(typeof charCode, charCode)
-    }
-}
+  if (charCode) {
+    postMessage({
+      type: "stderr",
+      stderr: charCode,
+    });
+  } else {
+    console.log(typeof charCode, charCode);
+  }
+};
 
-const stdinBuffer = new StdinBuffer()
+const stdinBuffer = new StdinBuffer();
 
 const emscriptenSettings = {
-    noInitialRun: true,
-    stdin: stdinBuffer.stdin,
-    stdout: stdout,
-    stderr: stderr,
-    onRuntimeInitialized: () => {
-        postMessage({type: 'ready', stdinBuffer: stdinBuffer.sab})
-    },
-    async preRun(Module) {
-        const versionHex = Module.HEAPU32[Module._Py_Version/4].toString(16);
-        const versionTuple = versionHex.padStart(8, "0").match(/.{1,2}/g).map((x) => parseInt(x, 16));
-        const [major, minor, ..._] = versionTuple;
-        // Prevent complaints about not finding exec-prefix by making a lib-dynload directory
-        Module.FS.mkdirTree(`/lib/python${major}.${minor}/lib-dynload/`);
-        Module.addRunDependency("install-stdlib");
-        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 });
-        Module.removeRunDependency("install-stdlib");
-    }
-}
+  noInitialRun: true,
+  stdin: stdinBuffer.stdin,
+  stdout: stdout,
+  stderr: stderr,
+  onRuntimeInitialized: () => {
+    postMessage({ type: "ready", stdinBuffer: stdinBuffer.sab });
+  },
+  async preRun(Module) {
+    const versionHex = Module.HEAPU32[Module._Py_Version / 4].toString(16);
+    const versionTuple = versionHex
+      .padStart(8, "0")
+      .match(/.{1,2}/g)
+      .map((x) => parseInt(x, 16));
+    const [major, minor, ..._] = versionTuple;
+    // Prevent complaints about not finding exec-prefix by making a lib-dynload directory
+    Module.FS.mkdirTree(`/lib/python${major}.${minor}/lib-dynload/`);
+    Module.addRunDependency("install-stdlib");
+    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 },
+    );
+    Module.removeRunDependency("install-stdlib");
+  },
+};
 
 const modulePromise = createEmscriptenModule(emscriptenSettings);
 
-
 onmessage = async (event) => {
-    if (event.data.type === 'run') {
-        const Module = await modulePromise;
-        if (event.data.files) {
-            for (const [filename, contents] of Object.entries(event.data.files)) {
-                Module.FS.writeFile(filename, contents)
-            }
-        }
-        const ret = Module.callMain(event.data.args);
-        postMessage({
-            type: 'finished',
-            returnCode: ret
-        })
+  if (event.data.type === "run") {
+    const Module = await modulePromise;
+    if (event.data.files) {
+      for (const [filename, contents] of Object.entries(event.data.files)) {
+        Module.FS.writeFile(filename, contents);
+      }
     }
-}
-
+    const ret = Module.callMain(event.data.args);
+    postMessage({
+      type: "finished",
+      returnCode: ret,
+    });
+  }
+};