packages feed

ghc-wasm-bindgen-0.1.0.0: runtime-js/ghc_wasm_bindgen_internal.js

// ghc-wasm-bindgen runtime internals, imported by the generated bindings.

import jsffiImports from "./ghc_wasm_jsffi.js";

let wasm;
let ready;

export function init(source) {
    return (ready ??= load(source));
}

async function load(source) {
    if (typeof source === "string" || source instanceof URL) {
        const res = await fetch(source);
        if (!res.ok) throw new Error(`ghc-wasm-bindgen: fetching ${source} failed (${res.status})`);
        source = await res.arrayBuffer();
    }
    // jsffiImports needs the exports, which exist only after instantiation.
    const knot = {};
    const { instance } = await WebAssembly.instantiate(source, {
        ghc_wasm_jsffi: jsffiImports(knot),
        wasi_snapshot_preview1: wasi(knot),
    });
    Object.assign(knot, instance.exports);
    knot._initialize();
    wasm = knot;
}

// WASI subset the RTS uses for pure code; other calls return ENOSYS.
function wasi(exports) {
    const view = () => new DataView(exports.memory.buffer);
    const noArgs = (count, size) => {
        view().setUint32(count, 0, true);
        view().setUint32(size, 0, true);
        return 0;
    };
    const calls = {
        args_sizes_get: noArgs,
        environ_sizes_get: noArgs,
        args_get: () => 0,
        environ_get: () => 0,
        clock_time_get: (clock, _precision, out) => {
            const ms = clock === 0 ? Date.now() : performance.now();
            view().setBigUint64(out, BigInt(Math.round(ms * 1e6)), true);
            return 0;
        },
        fd_write: (fd, iovs, count, written) => {
            const v = view();
            let text = "";
            let total = 0;
            for (let i = 0; i < count; i++) {
                const ptr = v.getUint32(iovs + 8 * i, true);
                const len = v.getUint32(iovs + 8 * i + 4, true);
                text += new TextDecoder().decode(new Uint8Array(exports.memory.buffer, ptr, len));
                total += len;
            }
            v.setUint32(written, total, true);
            (fd === 2 ? console.warn : console.log)(text.replace(/\n$/, ""));
            return 0;
        },
        proc_exit: (code) => {
            throw new Error(`ghc-wasm-bindgen: the Haskell runtime exited (code ${code})`);
        },
    };
    const ENOSYS = 52;
    return new Proxy(calls, { get: (target, name) => target[name] ?? (() => ENOSYS) });
}

export class HaskellError extends Error {
    name = "HaskellError";
}

// __ghcWasmBindgenError is set by GhcWasmBindgen.Runtime.guarded.
export function call(fn, ...args) {
    if (!wasm) throw new Error("ghc-wasm-bindgen: await init() before calling the API");
    const result = wasm[fn](...args);
    const err = wasm.__ghcWasmBindgenError;
    if (err !== undefined) {
        wasm.__ghcWasmBindgenError = undefined;
        throw new HaskellError(err);
    }
    return result;
}

// Values: aeson JSON, with the options in GhcWasmBindgen.Runtime. Encoders
// check their input; decoders trust what Haskell returns.

// bigints are written as JSON numbers, and integers beyond 2^53 are read as
// bigints, so Integer and Natural keep their precision.
export function pack(v) {
    const tag = marker();
    const json = JSON.stringify(v, (_, x) => (typeof x === "bigint" ? tag + x : x));
    return json.replace(new RegExp(`"\\\\u0000${tag.slice(1)}(-?\\d+)"`, "g"), "$1");
}

export function unpack(s) {
    const tag = marker();
    const text = s.replace(/"(?:\\.|[^"\\])*"|-?\d+(?:\.\d+)?(?:[eE][+-]?\d+)?/g, (token) =>
        /^-?\d+$/.test(token) && !Number.isSafeInteger(Number(token)) ? JSON.stringify(tag + token) : token,
    );
    return JSON.parse(text, (_, x) => (typeof x === "string" && x.startsWith(tag) ? BigInt(x.slice(tag.length)) : x));
}

const marker = () => `\u0000${Math.random().toString(36).slice(2)}:`;

export const unpackBool = (x) => x !== 0; // JSFFI returns Bool as 0/1

export function invalid(type, v) {
    let shown;
    try {
        shown = JSON.stringify(v, (_, x) => (typeof x === "bigint" ? `${x}n` : x));
    } catch {
        shown = String(v);
    }
    throw new TypeError(`ghc-wasm-bindgen: not a valid ${type}: ${shown}`);
}

const check = (type, ok) => (x) => (ok(x) ? x : invalid(type, x));
const integral = (type, min, max) => check(type, (x) => Number.isInteger(x) && x >= min && x <= max);

// Numbers. Int and Word are 32 bits on wasm32; 64-bit types must be safe integers.
export const encodeDouble = check("Double", (x) => typeof x === "number");
export const encodeFloat = check("Float", (x) => typeof x === "number");
export const encodeInt = integral("Int", -(2 ** 31), 2 ** 31 - 1);
export const encodeWord = integral("Word", 0, 2 ** 32 - 1);
export const encodeInt8 = integral("Int8", -(2 ** 7), 2 ** 7 - 1);
export const encodeInt16 = integral("Int16", -(2 ** 15), 2 ** 15 - 1);
export const encodeInt32 = integral("Int32", -(2 ** 31), 2 ** 31 - 1);
export const encodeInt64 = integral("Int64", Number.MIN_SAFE_INTEGER, Number.MAX_SAFE_INTEGER);
export const encodeWord8 = integral("Word8", 0, 2 ** 8 - 1);
export const encodeWord16 = integral("Word16", 0, 2 ** 16 - 1);
export const encodeWord32 = integral("Word32", 0, 2 ** 32 - 1);
export const encodeWord64 = integral("Word64", 0, Number.MAX_SAFE_INTEGER);
export const decodeNumber = (v) => Number(v);

export const encodeInteger = check("Integer", (x) => typeof x === "bigint");
export const encodeNatural = check("Natural", (x) => typeof x === "bigint" && x >= 0n);
export const decodeInteger = (v) => BigInt(v);

export const encodeBool = check("Bool", (x) => typeof x === "boolean");
export const decodeBool = (v) => v === true || v === "true";

export const encodeString = check("String", (x) => typeof x === "string");
export const encodeChar = check("Char", (x) => typeof x === "string" && [...x].length === 1);
export const decodeString = (v) => v;

export const encodeDay = check("Day", (x) => typeof x === "string" && /^-?\d{4,}-\d{2}-\d{2}$/.test(x));
export const encodeUTCTime = (x) =>
    x instanceof Date && !Number.isNaN(x.getTime()) ? x.toISOString() : invalid("UTCTime", x);
export const decodeUTCTime = (v) => new Date(v);

// Containers.
export const encodeList = (xs, f) => (Array.isArray(xs) ? xs.map((x) => f(x)) : invalid("list", xs));
export const decodeList = (v, f) => v.map((x) => f(x));

export const encodeNonEmpty = (xs, f) =>
    Array.isArray(xs) && xs.length > 0 ? xs.map((x) => f(x)) : invalid("NonEmpty", xs);

export const encodeSet = (xs, f) => (xs instanceof Set ? [...xs].map((x) => f(x)) : invalid("Set", xs));
export const decodeSet = (v, f) => new Set(v.map((x) => f(x)));

// Maps with string, number, Bool, Char or date keys are objects; any other
// key type makes them arrays of pairs.
export const encodeTextKeyMap = (m, key, value) =>
    m instanceof Map
        ? Object.fromEntries([...m].map(([k, x]) => [String(key(k)), value(x)]))
        : invalid("Map", m);
export const decodeTextKeyMap = (v, key, value) => new Map(Object.entries(v).map(([k, x]) => [key(k), value(x)]));

export const encodePairMap = (m, key, value) =>
    m instanceof Map ? [...m].map(([k, x]) => [key(k), value(x)]) : invalid("Map", m);
export const decodePairMap = (v, key, value) => new Map(v.map(([k, x]) => [key(k), value(x)]));

export const encodeMaybe = (x, f) => (x === null ? null : f(x));
export const decodeMaybe = (v, f) => (v === null ? null : f(v));

export const encodeEither = (x, left, right) =>
    x !== null && typeof x === "object" && "Left" in x
        ? { Left: left(x.Left) }
        : x !== null && typeof x === "object" && "Right" in x
          ? { Right: right(x.Right) }
          : invalid("Either", x);
export const decodeEither = (v, left, right) => ("Left" in v ? { Left: left(v.Left) } : { Right: right(v.Right) });

export const encodeTuple = (xs, fs) =>
    Array.isArray(xs) && xs.length === fs.length ? fs.map((f, i) => f(xs[i])) : invalid(`${fs.length}-tuple`, xs);
export const decodeTuple = (v, fs) => fs.map((f, i) => f(v[i]));

// User data types.
export const encodeRecord = (tag, fields) => ({ $tag: tag, ...fields });
export const encodePositional = (tag, args) =>
    args.length === 0 ? { $tag: tag } : { $tag: tag, contents: args.length === 1 ? args[0] : args };
export const encodeEnum = (tag) => tag;

export const decodeTag = (v) => (typeof v === "string" ? v : v?.$tag);
export const decodeField = (v, name) => v[name];
export const decodeArg = (v, i, n) => (n === 1 ? v.contents : v.contents[i]);