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

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
    }

    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
    }
}

const stdout = (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)
    }
}

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");
    }
}

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
        })
    }
}