UNPKG

@georgestagg/webr

Version:

The statistical programming langauge R compiled into WASM for use in a web browser and node.

4 lines 136 kB
{ "version": 3, "sources": ["../webR/chan/queue.ts", "../webR/compat.ts", "../webR/utils.ts", "../webR/chan/task-common.ts", "../webR/chan/message.ts", "../webR/chan/task-main.ts", "../webR/chan/task-worker.ts", "../webR/chan/channel-shared.ts", "../webR/chan/channel-service.ts", "../webR/chan/channel.ts", "../webR/config.ts", "../webR/robj.ts", "../webR/proxy.ts", "../console/console.ts", "../webR/webr-main.ts"], "sourcesContent": ["// From https://stackoverflow.com/questions/47157428/how-to-implement-a-pseudo-blocking-async-queue-in-js-ts\nexport class AsyncQueue<T> {\n #promises: Promise<T>[];\n #resolvers: ((t: T) => void)[];\n\n constructor() {\n this.#resolvers = [];\n this.#promises = [];\n }\n\n put(t: T) {\n if (!this.#resolvers.length) {\n this.#add();\n }\n const resolve = this.#resolvers.shift()!;\n resolve(t);\n }\n\n async get() {\n if (!this.#promises.length) {\n this.#add();\n }\n const promise = this.#promises.shift()!;\n return promise;\n }\n\n isEmpty() {\n return !this.#promises.length;\n }\n\n isBlocked() {\n return !!this.#resolvers.length;\n }\n\n get length() {\n return this.#promises.length - this.#resolvers.length;\n }\n\n #add() {\n this.#promises.push(\n new Promise((resolve) => {\n this.#resolvers.push(resolve);\n })\n );\n }\n}\n", "interface Process {\n browser: string | undefined;\n release: { [key: string]: string };\n}\ndeclare let process: Process;\n\nexport const IN_NODE =\n typeof process !== 'undefined' &&\n process.release &&\n process.release.name === 'node' &&\n typeof process.browser === 'undefined';\n\n// Adapted from https://github.com/pyodide/pyodide/blob/main/src/js/compat.ts\nexport let loadScript: (url: string) => Promise<void>;\nif (globalThis.document) {\n loadScript = (url) =>\n new Promise((resolve, reject) => {\n const script = document.createElement('script');\n script.src = url;\n script.onload = () => resolve();\n script.onerror = reject;\n document.head.appendChild(script);\n });\n} else if (globalThis.importScripts) {\n loadScript = async (url) => {\n try {\n globalThis.importScripts(url);\n } catch (e) {\n if (e instanceof TypeError) {\n await import(url);\n } else {\n throw e;\n }\n }\n };\n} else if (IN_NODE) {\n loadScript = async (url: string) => {\n const nodePathMod = (await import('path')).default;\n await import(nodePathMod.resolve(url));\n };\n} else {\n throw new Error('Cannot determine runtime environment');\n}\n", "import { IN_NODE } from './compat';\nimport { RawType } from './robj';\n\nexport type ResolveFn = (_value?: unknown) => void;\nexport type RejectFn = (_reason?: any) => void;\n\nexport function promiseHandles() {\n const out = {\n resolve: (_value?: unknown) => {},\n reject: (_reason?: any) => {},\n promise: null as unknown as Promise<unknown>,\n };\n\n const promise = new Promise((resolve, reject) => {\n out.resolve = resolve;\n out.reject = reject;\n });\n out.promise = promise;\n\n return out;\n}\n\nexport function sleep(ms: number) {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n\nexport function replaceInObject(\n obj: unknown,\n test: (obj: any) => boolean,\n replacer: (obj: any, ...replacerArgs: any[]) => unknown,\n ...replacerArgs: unknown[]\n): unknown {\n if (obj === null || typeof obj !== 'object') {\n return obj;\n }\n if (test(obj)) {\n return replacer(obj, ...replacerArgs);\n }\n if (Array.isArray(obj) || ArrayBuffer.isView(obj)) {\n return (obj as unknown[]).map((v) => replaceInObject(v, test, replacer, ...replacerArgs));\n }\n return Object.fromEntries(\n Object.entries(obj).map(([k, v], i) => [k, replaceInObject(v, test, replacer, ...replacerArgs)])\n );\n}\n\nexport function unpackScalarVectors(obj: RawType) {\n return replaceInObject(\n obj,\n (obj: any) =>\n 'values' in obj &&\n (Array.isArray(obj.values) || ArrayBuffer.isView(obj)) &&\n obj.values.length === 1,\n (obj: any) => obj.values[0]\n ) as RawType;\n}\n\n/* Workaround for loading a cross-origin script.\n *\n * When fetching a worker script, the fetch is required by the spec to\n * use \"same-origin\" mode. This is to avoid loading a worker with a\n * cross-origin global scope, which can allow for a cross-origin\n * restriction bypass.\n *\n * When the fetch URL begins with 'http', we assume the request is\n * cross-origin. We download the content of the URL using a XHR first,\n * create a blob URL containing the requested content, then load the\n * blob URL as a script.\n *\n * The origin of a blob URL is the same as that of the environment that\n * created the URL, and so the global scope of the resulting worker is\n * no longer cross-origin. In that case, the cross-origin restriction\n * bypass is not possible, and the script is permitted to be loaded.\n */\nexport function newCrossOriginWorker(url: string, cb: (worker: Worker) => void): void {\n const req = new XMLHttpRequest();\n req.open('get', url, true);\n req.onload = () => {\n const worker = new Worker(URL.createObjectURL(new Blob([req.responseText])));\n cb(worker);\n };\n req.send();\n}\n\nexport function isCrossOrigin(urlString: string) {\n if (IN_NODE) return false;\n const url1 = new URL(location.href);\n const url2 = new URL(urlString, location.origin);\n if (url1.host === url2.host && url1.port === url2.port && url1.protocol === url2.protocol) {\n return false;\n }\n return true;\n}\n", "// Original code from Synclink and Comlink. Released under Apache 2.0.\n\nexport const SZ_BUF_DOESNT_FIT = 0;\nexport const SZ_BUF_FITS_IDX = 1;\nexport const SZ_BUF_SIZE_IDX = 0;\n\nexport interface Endpoint extends EventSource {\n postMessage(message: any, transfer?: Transferable[]): void;\n start?: () => void;\n}\n\nexport interface EventSource {\n addEventListener(type: string, listener: EventListenerOrEventListenerObject, options?: {}): void;\n\n removeEventListener(\n type: string,\n listener: EventListenerOrEventListenerObject,\n options?: {}\n ): void;\n}\n\nexport function toWireValue(value: any): [any, Transferable[]] {\n return [value, transferCache.get(value) || []];\n}\n\nconst transferCache = new WeakMap<any, Transferable[]>();\nexport function transfer<T>(obj: T, transfers: Transferable[]): T {\n transferCache.set(obj, transfers);\n return obj;\n}\n\nexport type UUID = string;\n\nexport const UUID_LENGTH = 63;\n\nexport function generateUUID(): UUID {\n const result = Array.from({ length: 4 }, randomSegment).join('-');\n if (result.length !== UUID_LENGTH) {\n throw new Error('comlink internal error: UUID has the wrong length');\n }\n return result;\n}\n\nfunction randomSegment() {\n let result = Math.floor(Math.random() * Number.MAX_SAFE_INTEGER).toString(16);\n const pad = 15 - result.length;\n if (pad > 0) {\n result = Array.from({ length: pad }, () => 0).join('') + result;\n }\n return result;\n}\n", "import { generateUUID, transfer, UUID } from './task-common';\n\nexport interface Message {\n type: string;\n data?: any;\n}\n\nexport interface Request {\n type: 'request';\n data: {\n uuid: UUID;\n msg: Message;\n };\n}\n\nexport interface Response {\n type: 'response';\n data: {\n uuid: UUID;\n resp: unknown;\n };\n}\n\nexport function newRequest(msg: Message, transferables?: [Transferable]): Request {\n return newRequestResponseMessage(\n {\n type: 'request',\n data: {\n uuid: generateUUID(),\n msg: msg,\n },\n },\n transferables\n );\n}\n\nexport function newResponse(uuid: UUID, resp: unknown, transferables?: [Transferable]): Response {\n return newRequestResponseMessage(\n {\n type: 'response',\n data: {\n uuid,\n resp,\n },\n },\n transferables\n );\n}\n\nfunction newRequestResponseMessage<T>(msg: T, transferables?: [Transferable]): T {\n // Signal to Synclink that the data contains objects we wish to\n // transfer, as in `postMessage()`\n if (transferables) {\n transfer(msg, transferables);\n }\n return msg;\n}\n\nexport interface SyncRequest {\n type: 'sync-request';\n data: {\n msg: Message;\n reqData: SyncRequestData;\n };\n}\n\nexport interface SyncRequestData {\n taskId?: number;\n sizeBuffer: Int32Array;\n signalBuffer: Int32Array;\n dataBuffer: Uint8Array;\n}\n\nexport function newSyncRequest(msg: Message, data: SyncRequestData): SyncRequest {\n return {\n type: 'sync-request',\n data: { msg, reqData: data },\n };\n}\n\nconst encoder = new TextEncoder();\nconst decoder = new TextDecoder('utf-8');\n\n// TODO: Pass a `replacer` function\nexport function encodeData(data: any): Uint8Array {\n return encoder.encode(JSON.stringify(data));\n}\n\nexport function decodeData(data: Uint8Array): unknown {\n return JSON.parse(decoder.decode(data)) as unknown;\n}\n", "// Original code from Synclink and Comlink. Released under Apache 2.0.\n\nimport { Endpoint, SZ_BUF_FITS_IDX, SZ_BUF_SIZE_IDX, generateUUID } from './task-common';\n\nimport { sleep } from '../utils';\nimport { SyncRequestData, encodeData } from './message';\n\nimport { IN_NODE } from '../compat';\nimport type { Worker as NodeWorker } from 'worker_threads';\n\nconst encoder = new TextEncoder();\n\n/**\n * Respond to a blocking request. Most of the work has already been done in\n * asynclink, we are just responsible here for getting the return value back to\n * the requester through this slightly convoluted Atomics protocol.\n *\n * @param {Endpoint} endpoint A message port to receive messages from. Other\n * thread is blocked, so we can't send messages back.\n * @param {SyncRequestData} data The message that was recieved. We will use it\n * to read out the buffers to write the answer into. NOTE: requester\n * owns buffers.\n * @param {any} response The value we want to send back to the requester. We\n * have to encode it into data_buffer.\n */\nexport async function syncResponse(endpoint: Endpoint, data: SyncRequestData, response: any) {\n try {\n let { taskId, sizeBuffer, dataBuffer, signalBuffer } = data;\n // console.warn(msg);\n\n const bytes = encodeData(response);\n const fits = bytes.length <= dataBuffer.length;\n\n Atomics.store(sizeBuffer, SZ_BUF_SIZE_IDX, bytes.length);\n Atomics.store(sizeBuffer, SZ_BUF_FITS_IDX, +fits);\n if (!fits) {\n // console.log(\" need larger buffer\", taskId)\n // Request larger buffer\n const [uuid, dataPromise] = requestResponseMessage(endpoint);\n\n // Write UUID into dataBuffer so syncRequest knows where to respond to.\n dataBuffer.set(encoder.encode(uuid));\n await signalRequester(signalBuffer, taskId!);\n\n // Wait for response with new bigger dataBuffer\n dataBuffer = (await dataPromise).dataBuffer as Uint8Array;\n }\n\n // Encode result into dataBuffer\n dataBuffer.set(bytes);\n Atomics.store(sizeBuffer, SZ_BUF_FITS_IDX, +true);\n\n // console.log(\" signaling completion\", taskId)\n await signalRequester(signalBuffer, taskId as number);\n } catch (e) {\n console.warn(e);\n }\n}\n\nfunction requestResponseMessage(ep: Endpoint): [string, Promise<any>] {\n const id = generateUUID();\n return [\n id,\n new Promise((resolve) => {\n if (IN_NODE) {\n (ep as unknown as NodeWorker).once('message', (message: any) => {\n if (!message.id || message.id !== id) {\n return;\n }\n resolve(message);\n });\n } else {\n ep.addEventListener('message', function l(ev: MessageEvent) {\n if (!ev.data || !ev.data.id || ev.data.id !== id) {\n return;\n }\n ep.removeEventListener('message', l as EventListenerOrEventListenerObject);\n resolve(ev.data);\n } as EventListenerOrEventListenerObject);\n }\n if (ep.start) {\n ep.start();\n }\n }),\n ];\n}\n\nasync function signalRequester(signalBuffer: Int32Array, taskId: number) {\n const index = (taskId >> 1) % 32;\n let sleepTime = 1;\n while (Atomics.compareExchange(signalBuffer, index + 1, 0, taskId) !== 0) {\n // No Atomics.asyncWait except on Chrome =(\n await sleep(sleepTime);\n if (sleepTime < 32) {\n // exponential backoff\n sleepTime *= 2;\n }\n }\n Atomics.or(signalBuffer, 0, 1 << index);\n Atomics.notify(signalBuffer, 0);\n}\n", "// Original code from Synclink and Comlink. Released under Apache 2.0.\n\nimport {\n Endpoint,\n SZ_BUF_DOESNT_FIT,\n SZ_BUF_FITS_IDX,\n SZ_BUF_SIZE_IDX,\n UUID_LENGTH,\n} from './task-common';\n\nimport { newSyncRequest, Message, decodeData } from './message';\n\nconst decoder = new TextDecoder('utf-8');\n\nexport class SyncTask {\n endpoint: Endpoint;\n msg: Message;\n transfers: Transferable[];\n\n #scheduled = false;\n #resolved: boolean;\n #result?: any;\n #exception?: any;\n\n // sync only\n taskId?: number;\n #syncGen?: Generator<void, unknown, void>;\n sizeBuffer?: Int32Array;\n signalBuffer?: Int32Array;\n syncifier = new _Syncifier();\n\n constructor(endpoint: Endpoint, msg: Message, transfers: Transferable[] = []) {\n this.endpoint = endpoint;\n this.msg = msg;\n this.transfers = transfers;\n this.#resolved = false;\n }\n\n scheduleSync() {\n if (this.#scheduled) {\n return;\n }\n this.#scheduled = true;\n\n this.syncifier.scheduleTask(this);\n this.#syncGen = this.doSync();\n this.#syncGen.next();\n return this;\n }\n\n poll() {\n if (!this.#scheduled) {\n throw new Error('Task not synchronously scheduled');\n }\n\n const { done, value } = this.#syncGen!.next();\n if (!done) {\n return false;\n }\n\n this.#resolved = true;\n this.#result = value;\n\n return true;\n }\n\n *doSync() {\n // just use syncRequest.\n const { endpoint, msg, transfers } = this;\n const sizeBuffer = new Int32Array(new SharedArrayBuffer(8));\n const signalBuffer = this.signalBuffer!;\n const taskId = this.taskId;\n\n // Ensure status is cleared. We will notify\n let dataBuffer = acquireDataBuffer(UUID_LENGTH);\n // console.log(\"===requesting\", taskId);\n\n const syncMsg = newSyncRequest(msg, {\n sizeBuffer,\n dataBuffer,\n signalBuffer,\n taskId,\n });\n\n endpoint.postMessage(syncMsg, transfers);\n yield;\n\n if (Atomics.load(sizeBuffer, SZ_BUF_FITS_IDX) === SZ_BUF_DOESNT_FIT) {\n // There wasn't enough space, make a bigger dataBuffer.\n // First read uuid for response out of current dataBuffer\n const id = decoder.decode(dataBuffer.slice(0, UUID_LENGTH));\n releaseDataBuffer(dataBuffer);\n const size = Atomics.load(sizeBuffer, SZ_BUF_SIZE_IDX);\n dataBuffer = acquireDataBuffer(size);\n // console.log(\"===bigger data buffer\", taskId);\n endpoint.postMessage({ id, dataBuffer });\n yield;\n }\n\n const size = Atomics.load(sizeBuffer, SZ_BUF_SIZE_IDX);\n // console.log(\"===completing\", taskId);\n return decodeData(dataBuffer.slice(0, size));\n }\n\n get result() {\n if (this.#exception) {\n throw this.#exception;\n }\n // console.log(this.#resolved);\n if (this.#resolved) {\n return this.#result as unknown;\n }\n throw new Error('Not ready.');\n }\n\n syncify(): any {\n this.scheduleSync();\n this.syncifier.syncifyTask(this);\n return this.result;\n }\n}\n\nclass _Syncifier {\n nextTaskId: Int32Array;\n signalBuffer: Int32Array;\n tasks: Map<number, SyncTask>;\n\n constructor() {\n this.nextTaskId = new Int32Array([1]);\n this.signalBuffer = new Int32Array(new SharedArrayBuffer(32 * 4 + 4));\n this.tasks = new Map();\n }\n\n scheduleTask(task: SyncTask) {\n task.taskId = this.nextTaskId[0];\n this.nextTaskId[0] += 2;\n task.signalBuffer = this.signalBuffer;\n this.tasks.set(task.taskId, task);\n }\n\n waitOnSignalBuffer() {\n const timeout = 50;\n for (;;) {\n const status = Atomics.wait(this.signalBuffer, 0, 0, timeout);\n switch (status) {\n case 'ok':\n case 'not-equal':\n return;\n case 'timed-out':\n if (interruptBuffer[0] !== 0) {\n handleInterrupt();\n }\n break;\n default:\n throw new Error('Unreachable');\n }\n }\n }\n\n *tasksIdsToWakeup() {\n const flag = Atomics.load(this.signalBuffer, 0);\n for (let i = 0; i < 32; i++) {\n const bit = 1 << i;\n if (flag & bit) {\n Atomics.and(this.signalBuffer, 0, ~bit);\n const wokenTask = Atomics.exchange(this.signalBuffer, i + 1, 0);\n yield wokenTask;\n }\n }\n }\n\n pollTasks(task?: SyncTask) {\n let result = false;\n for (const wokenTaskId of this.tasksIdsToWakeup()) {\n // console.log(\"poll task\", wokenTaskId, \"looking for\",task);\n const wokenTask = this.tasks.get(wokenTaskId);\n if (!wokenTask) {\n throw new Error(`Assertion error: unknown taskId ${wokenTaskId}.`);\n }\n if (wokenTask.poll()) {\n // console.log(\"completed task \", wokenTaskId, wokenTask, wokenTask._result);\n this.tasks.delete(wokenTaskId);\n if (wokenTask === task) {\n result = true;\n }\n }\n }\n return result;\n }\n\n syncifyTask(task: SyncTask) {\n for (;;) {\n this.waitOnSignalBuffer();\n // console.log(\"syncifyTask:: woke\");\n if (this.pollTasks(task)) {\n return;\n }\n }\n }\n}\n\nconst dataBuffers: Uint8Array[][] = [];\n\nfunction acquireDataBuffer(size: number): Uint8Array {\n const powerof2 = Math.ceil(Math.log2(size));\n if (!dataBuffers[powerof2]) {\n dataBuffers[powerof2] = [];\n }\n const result = dataBuffers[powerof2].pop();\n if (result) {\n result.fill(0);\n return result;\n }\n return new Uint8Array(new SharedArrayBuffer(2 ** powerof2));\n}\n\nfunction releaseDataBuffer(buffer: Uint8Array) {\n const powerof2 = Math.ceil(Math.log2(buffer.byteLength));\n dataBuffers[powerof2].push(buffer);\n}\n\nlet interruptBuffer = new Int32Array(new ArrayBuffer(4));\n\nlet handleInterrupt = (): void => {\n interruptBuffer[0] = 0;\n throw new Error('Interrupted!');\n};\n\n/**\n * Sets the interrupt handler. This is called when the computation is\n * interrupted. Should zero the interrupt buffer and throw an exception.\n * @function handler\n * @param {handler} handler\n */\nexport function setInterruptHandler(handler: () => void) {\n handleInterrupt = handler;\n}\n\n/**\n * Sets the interrupt buffer. Should be a shared array buffer. When element 0\n * is set non-zero it signals an interrupt.\n * @param {ArrayBufferLike} buffer\n */\nexport function setInterruptBuffer(buffer: ArrayBufferLike) {\n interruptBuffer = new Int32Array(buffer);\n}\n", "import { AsyncQueue } from './queue';\nimport { promiseHandles, ResolveFn, newCrossOriginWorker, isCrossOrigin } from '../utils';\nimport { Message, newRequest, Response, SyncRequest } from './message';\nimport { Endpoint } from './task-common';\nimport { syncResponse } from './task-main';\nimport { ChannelType, ChannelMain, ChannelWorker } from './channel';\nimport { WebROptions } from '../webr-main';\n\nimport { IN_NODE } from '../compat';\nimport type { Worker as NodeWorker } from 'worker_threads';\nif (IN_NODE) {\n (globalThis as any).Worker = require('worker_threads').Worker as NodeWorker;\n}\n\n// Main ----------------------------------------------------------------\n\nexport class SharedBufferChannelMain implements ChannelMain {\n inputQueue = new AsyncQueue<Message>();\n outputQueue = new AsyncQueue<Message>();\n #interruptBuffer?: Int32Array;\n\n initialised: Promise<unknown>;\n resolve: (_?: unknown) => void;\n close = () => {};\n\n #parked = new Map<string, ResolveFn>();\n\n constructor(config: Required<WebROptions>) {\n const initWorker = (worker: Worker) => {\n this.#handleEventsFromWorker(worker);\n this.close = () => worker.terminate();\n const msg = {\n type: 'init',\n data: { config, channelType: ChannelType.SharedArrayBuffer },\n } as Message;\n worker.postMessage(msg);\n };\n\n if (isCrossOrigin(config.WEBR_URL)) {\n newCrossOriginWorker(`${config.WEBR_URL}webr-worker.js`, (worker: Worker) =>\n initWorker(worker)\n );\n } else {\n const worker = new Worker(`${config.WEBR_URL}webr-worker.js`);\n initWorker(worker);\n }\n\n ({ resolve: this.resolve, promise: this.initialised } = promiseHandles());\n }\n\n async read() {\n return await this.outputQueue.get();\n }\n\n async flush() {\n const msg: Message[] = [];\n while (!this.outputQueue.isEmpty()) {\n msg.push(await this.read());\n }\n return msg;\n }\n\n interrupt() {\n if (!this.#interruptBuffer) {\n throw new Error('Failed attempt to interrupt before initialising interruptBuffer');\n }\n this.#interruptBuffer[0] = 1;\n }\n\n write(msg: Message) {\n this.inputQueue.put(msg);\n }\n\n async request(msg: Message, transferables?: [Transferable]): Promise<any> {\n const req = newRequest(msg, transferables);\n\n const { resolve: resolve, promise: prom } = promiseHandles();\n this.#parked.set(req.data.uuid, resolve);\n\n this.write(req);\n return prom;\n }\n\n #resolveResponse(msg: Response) {\n const uuid = msg.data.uuid;\n const resolve = this.#parked.get(uuid);\n\n if (resolve) {\n this.#parked.delete(uuid);\n resolve(msg.data.resp);\n } else {\n console.warn(\"Can't find request.\");\n }\n }\n\n #handleEventsFromWorker(worker: Worker) {\n if (IN_NODE) {\n (worker as unknown as NodeWorker).on('message', (message: Message) => {\n this.#onMessageFromWorker(worker, message);\n });\n } else {\n worker.onmessage = (ev: MessageEvent) =>\n this.#onMessageFromWorker(worker, ev.data as Message);\n }\n }\n\n #onMessageFromWorker = async (worker: Worker, message: Message) => {\n if (!message || !message.type) {\n return;\n }\n\n switch (message.type) {\n case 'resolve':\n this.#interruptBuffer = new Int32Array(message.data as SharedArrayBuffer);\n this.resolve();\n return;\n\n case 'response':\n this.#resolveResponse(message as Response);\n return;\n\n default:\n this.outputQueue.put(message);\n return;\n\n case 'sync-request': {\n const msg = message as SyncRequest;\n const payload = msg.data.msg;\n const reqData = msg.data.reqData;\n\n switch (payload.type) {\n case 'read': {\n const response = await this.inputQueue.get();\n await syncResponse(worker, reqData, response);\n break;\n }\n default:\n throw new TypeError(`Unsupported request type '${payload.type}'.`);\n }\n return;\n }\n case 'request':\n throw new TypeError(\n \"Can't send messages of type 'request' from a worker. Please Use 'sync-request' instead.\"\n );\n }\n };\n}\n\n// Worker --------------------------------------------------------------\n\nimport { SyncTask, setInterruptHandler, setInterruptBuffer } from './task-worker';\nimport { Module as _Module } from '../module';\n\ndeclare let Module: _Module;\n\nexport class SharedBufferChannelWorker implements ChannelWorker {\n #ep: Endpoint;\n #dispatch: (msg: Message) => void = () => 0;\n #interruptBuffer = new Int32Array(new SharedArrayBuffer(4));\n #interrupt = () => {};\n\n constructor() {\n this.#ep = (IN_NODE ? require('worker_threads').parentPort : globalThis) as Endpoint;\n setInterruptBuffer(this.#interruptBuffer.buffer);\n setInterruptHandler(() => this.handleInterrupt());\n }\n\n resolve() {\n this.write({ type: 'resolve', data: this.#interruptBuffer.buffer });\n }\n\n write(msg: Message, transfer?: [Transferable]) {\n this.#ep.postMessage(msg, transfer);\n }\n\n read(): Message {\n const msg = { type: 'read' } as Message;\n const task = new SyncTask(this.#ep, msg);\n return task.syncify() as Message;\n }\n\n inputOrDispatch(): number {\n for (;;) {\n const msg = this.read();\n if (msg.type === 'stdin') {\n return Module.allocateUTF8(msg.data as string);\n }\n this.#dispatch(msg);\n }\n }\n\n run(args: string[]) {\n Module.callMain(args);\n }\n\n setInterrupt(interrupt: () => void) {\n this.#interrupt = interrupt;\n }\n\n handleInterrupt() {\n if (this.#interruptBuffer[0] !== 0) {\n this.#interruptBuffer[0] = 0;\n this.#interrupt();\n }\n }\n\n setDispatchHandler(dispatch: (msg: Message) => void) {\n this.#dispatch = dispatch;\n }\n}\n", "import { AsyncQueue } from './queue';\nimport { promiseHandles, ResolveFn, newCrossOriginWorker, isCrossOrigin } from '../utils';\nimport {\n Message,\n newRequest,\n Response,\n Request,\n newResponse,\n encodeData,\n decodeData,\n} from './message';\nimport { Endpoint } from './task-common';\nimport { ChannelType, ChannelMain, ChannelWorker } from './channel';\nimport { WebROptions } from '../webr-main';\n\nimport { IN_NODE } from '../compat';\nimport type { Worker as NodeWorker } from 'worker_threads';\nif (IN_NODE) {\n (globalThis as any).Worker = require('worker_threads').Worker as NodeWorker;\n}\n\n// Main ----------------------------------------------------------------\n\nexport class ServiceWorkerChannelMain implements ChannelMain {\n inputQueue = new AsyncQueue<Message>();\n outputQueue = new AsyncQueue<Message>();\n\n initialised: Promise<unknown>;\n resolve: (_?: unknown) => void;\n close = () => {};\n\n #parked = new Map<string, ResolveFn>();\n #syncMessageCache = new Map<string, Message>();\n #registration?: ServiceWorkerRegistration;\n #interrupted = false;\n\n constructor(config: Required<WebROptions>) {\n const initWorker = (worker: Worker) => {\n this.#handleEventsFromWorker(worker);\n this.close = () => worker.terminate();\n this.#registerServiceWorker(`${config.SW_URL}webr-serviceworker.js`).then((clientId) => {\n const msg = {\n type: 'init',\n data: {\n config,\n channelType: ChannelType.ServiceWorker,\n clientId,\n location: window.location.href,\n },\n } as Message;\n worker.postMessage(msg);\n });\n };\n\n if (isCrossOrigin(config.SW_URL)) {\n newCrossOriginWorker(`${config.SW_URL}webr-worker.js`, (worker: Worker) =>\n initWorker(worker)\n );\n } else {\n const worker = new Worker(`${config.SW_URL}webr-worker.js`);\n initWorker(worker);\n }\n\n ({ resolve: this.resolve, promise: this.initialised } = promiseHandles());\n }\n\n activeRegistration(): ServiceWorker {\n if (!this.#registration?.active) {\n throw new Error('Attempted to obtain a non-existent active registration.');\n }\n return this.#registration.active;\n }\n\n async read() {\n return await this.outputQueue.get();\n }\n\n async flush() {\n const msg: Message[] = [];\n while (!this.outputQueue.isEmpty()) {\n msg.push(await this.read());\n }\n return msg;\n }\n\n interrupt() {\n this.#interrupted = true;\n }\n\n write(msg: Message) {\n this.inputQueue.put(msg);\n }\n\n async request(msg: Message, transferables?: [Transferable]): Promise<any> {\n const req = newRequest(msg, transferables);\n\n const { resolve: resolve, promise: prom } = promiseHandles();\n this.#parked.set(req.data.uuid, resolve);\n\n this.write(req);\n return prom;\n }\n\n async #registerServiceWorker(url: string): Promise<string> {\n // Register service worker\n this.#registration = await navigator.serviceWorker.register(url);\n await navigator.serviceWorker.ready;\n window.addEventListener('beforeunload', () => {\n this.#registration?.unregister();\n });\n\n // Ensure we can communicate with service worker and we have a client ID\n const clientId = await new Promise<string>((resolve) => {\n navigator.serviceWorker.addEventListener(\n 'message',\n function listener(event: MessageEvent<{ type: string; clientId: string }>) {\n if (event.data.type === 'registration-successful') {\n navigator.serviceWorker.removeEventListener('message', listener);\n resolve(event.data.clientId);\n }\n }\n );\n this.activeRegistration().postMessage({ type: 'register-client-main' });\n });\n\n // Setup listener for service worker messages\n navigator.serviceWorker.addEventListener('message', (event: MessageEvent<Request>) => {\n this.#onMessageFromServiceWorker(event);\n });\n return clientId;\n }\n\n async #onMessageFromServiceWorker(event: MessageEvent<Message>) {\n if (event.data.type === 'request') {\n const uuid = event.data.data as string;\n const message = this.#syncMessageCache.get(uuid);\n if (!message) {\n throw new Error('Request not found during service worker XHR request');\n }\n this.#syncMessageCache.delete(uuid);\n switch (message.type) {\n case 'read': {\n const response = await this.inputQueue.get();\n this.activeRegistration().postMessage({\n type: 'wasm-webr-fetch-response',\n uuid: uuid,\n response: newResponse(uuid, response),\n });\n break;\n }\n case 'interrupt': {\n const response = this.#interrupted;\n this.activeRegistration().postMessage({\n type: 'wasm-webr-fetch-response',\n uuid: uuid,\n response: newResponse(uuid, response),\n });\n this.#interrupted = false;\n break;\n }\n default:\n throw new TypeError(`Unsupported request type '${message.type}'.`);\n }\n return;\n }\n }\n\n #resolveResponse(msg: Response) {\n const uuid = msg.data.uuid;\n const resolve = this.#parked.get(uuid);\n\n if (resolve) {\n this.#parked.delete(uuid);\n resolve(msg.data.resp);\n } else {\n console.warn(\"Can't find request.\");\n }\n }\n\n #handleEventsFromWorker(worker: Worker) {\n if (IN_NODE) {\n (worker as unknown as NodeWorker).on('message', (message: Message) => {\n this.#onMessageFromWorker(worker, message);\n });\n } else {\n worker.onmessage = (ev: MessageEvent) =>\n this.#onMessageFromWorker(worker, ev.data as Message);\n }\n }\n\n #onMessageFromWorker = async (worker: Worker, message: Message) => {\n if (!message || !message.type) {\n return;\n }\n\n switch (message.type) {\n case 'resolve':\n this.resolve();\n return;\n\n case 'response':\n this.#resolveResponse(message as Response);\n return;\n\n default:\n this.outputQueue.put(message);\n return;\n\n case 'sync-request': {\n const request = message.data as Request;\n this.#syncMessageCache.set(request.data.uuid, request.data.msg);\n return;\n }\n\n case 'request':\n throw new TypeError(\n \"Can't send messages of type 'request' from a worker.\" +\n 'Use service worker fetch request instead.'\n );\n }\n };\n}\n\n// Worker --------------------------------------------------------------\n\nimport { Module as _Module } from '../module';\n\ndeclare let Module: _Module;\n\nexport class ServiceWorkerChannelWorker implements ChannelWorker {\n #ep: Endpoint;\n #mainThreadId: string;\n #location: string;\n #dispatch: (msg: Message) => void = () => 0;\n #interrupt = () => {};\n onMessageFromMainThread: (msg: Message) => void = () => {};\n\n constructor(data: { clientId?: string; location?: string }) {\n if (!data.clientId || !data.location) {\n throw Error('Unable to start service worker channel');\n }\n this.#mainThreadId = data.clientId;\n this.#location = data.location;\n this.#ep = (IN_NODE ? require('worker_threads').parentPort : globalThis) as Endpoint;\n }\n\n resolve() {\n this.write({ type: 'resolve' });\n }\n\n write(msg: Message, transfer?: [Transferable]) {\n this.#ep.postMessage(msg, transfer);\n }\n\n syncRequest(message: Message): Response {\n /*\n * Browsers timeout service workers after about 5 minutes on inactivity.\n * See e.g. service_worker_version.cc in Chromium.\n *\n * To avoid the service worker being shut down, we timeout our XHR after\n * 1 minute and then resend the request as a keep-alive. The service worker\n * uses the message UUID to identify the request and continue waiting for a\n * response from where it left off.\n */\n const request = newRequest(message);\n this.write({ type: 'sync-request', data: request });\n\n let retryCount = 0;\n for (;;) {\n try {\n const url = new URL('__wasm__/webr-fetch-request/', this.#location);\n const xhr = new XMLHttpRequest();\n xhr.timeout = 60000;\n xhr.responseType = 'arraybuffer';\n xhr.open('POST', url, false);\n const fetchReqBody = {\n clientId: this.#mainThreadId,\n uuid: request.data.uuid,\n };\n xhr.send(encodeData(fetchReqBody));\n return decodeData(new Uint8Array(xhr.response as ArrayBuffer)) as Response;\n } catch (e: any) {\n if (e instanceof DOMException && retryCount++ < 1000) {\n console.log('Service worker request failed - resending request');\n } else {\n throw e;\n }\n }\n }\n }\n\n read(): Message {\n const response = this.syncRequest({ type: 'read' });\n return response.data.resp as Message;\n }\n\n inputOrDispatch(): number {\n for (;;) {\n const msg = this.read();\n if (msg.type === 'stdin') {\n return Module.allocateUTF8(msg.data as string);\n }\n this.#dispatch(msg);\n }\n }\n\n run(args: string[]) {\n Module.callMain(args);\n }\n\n setInterrupt(interrupt: () => void) {\n this.#interrupt = interrupt;\n }\n\n handleInterrupt() {\n /* During R computation we have no way to directly interrupt the worker\n * thread. Instead, we hook into R's PolledEvents. Since we are not using\n * SharedArrayBuffer as a signal method, we instead send a message to the\n * main thread to ask if we should interrupt R.\n */\n const response = this.syncRequest({ type: 'interrupt' });\n const interrupted = response.data.resp as boolean;\n if (interrupted) {\n this.#interrupt();\n }\n }\n\n setDispatchHandler(dispatch: (msg: Message) => void) {\n this.#dispatch = dispatch;\n }\n}\n", "import { Message } from './message';\nimport { SharedBufferChannelMain, SharedBufferChannelWorker } from './channel-shared';\nimport { ServiceWorkerChannelMain, ServiceWorkerChannelWorker } from './channel-service';\nimport { WebROptions } from '../webr-main';\nimport { isCrossOrigin } from '../utils';\nimport { IN_NODE } from '../compat';\n\n// The channel structure is asymetric:\n//\n// - The main thread maintains the input and output queues. All\n// messages sent from main are stored in the input queue. The input\n// queue is pull-based, it's the worker that initiates a transfer\n// via a sync-request.\n//\n// The output queue is filled at the initiative of the worker. The\n// main thread asynchronously reads from this queue, typically in an\n// async infloop.\n//\n// - The worker synchronously reads from the input queue. Reading a\n// message blocks until an input is available. Writing a message to\n// the output queue is equivalent to calling `postMessage()` and\n// returns immediately.\n//\n// Note that the messages sent from main to worker need to be\n// serialised. There is no structured cloning involved, and\n// ArrayBuffers can't be transferred, only copied.\n\nexport interface ChannelMain {\n initialised: Promise<unknown>;\n close(): void;\n read(): Promise<Message>;\n flush(): Promise<Message[]>;\n write(msg: Message): void;\n request(msg: Message, transferables?: [Transferable]): Promise<any>;\n interrupt(): void;\n}\n\nexport interface ChannelWorker {\n resolve(): void;\n write(msg: Message, transfer?: [Transferable]): void;\n read(): Message;\n handleInterrupt(): void;\n setInterrupt(interrupt: () => void): void;\n run(args: string[]): void;\n inputOrDispatch: () => number;\n setDispatchHandler: (dispatch: (msg: Message) => void) => void;\n}\n\nexport const ChannelType = {\n Automatic: 0,\n SharedArrayBuffer: 1,\n ServiceWorker: 2,\n} as const;\n\nexport type ChannelInitMessage = {\n type: string;\n data: {\n config: Required<WebROptions>;\n channelType: Exclude<\n typeof ChannelType[keyof typeof ChannelType],\n typeof ChannelType.Automatic\n >;\n clientId?: string;\n location?: string;\n };\n};\n\nexport function newChannelMain(data: Required<WebROptions>) {\n switch (data.channelType) {\n case ChannelType.SharedArrayBuffer:\n return new SharedBufferChannelMain(data);\n case ChannelType.ServiceWorker:\n return new ServiceWorkerChannelMain(data);\n case ChannelType.Automatic:\n default:\n if (IN_NODE || crossOriginIsolated) {\n return new SharedBufferChannelMain(data);\n }\n /*\n * TODO: If we are not cross-origin isolated but we can still use service\n * workers, we could setup a service worker to inject the relevant headers\n * to enable cross-origin isolation.\n */\n if ('serviceWorker' in navigator && !isCrossOrigin(data.SW_URL)) {\n return new ServiceWorkerChannelMain(data);\n }\n throw new Error('Unable to initialise main thread communication channel');\n }\n}\n\nexport function newChannelWorker(msg: ChannelInitMessage) {\n switch (msg.data.channelType) {\n case ChannelType.SharedArrayBuffer:\n return new SharedBufferChannelWorker();\n case ChannelType.ServiceWorker:\n return new ServiceWorkerChannelWorker(msg.data);\n default:\n throw new Error('Unknown worker channel type recieved');\n }\n}\n", "export const BASE_URL = 'https://cdn.webr.workers.dev/latest/';\nexport const PKG_BASE_URL = 'https://repo.webr.workers.dev/';\n", "import type { Module } from './module';\nimport type { RProxy } from './proxy';\n\ndeclare let Module: Module;\n\nexport interface ToTreeOptions {\n depth: number;\n}\n\n// RProxy<RObjImpl> type aliases\nexport type RObject = RProxy<RObjImpl>;\nexport type RNull = RProxy<RObjNull>;\nexport type RSymbol = RProxy<RObjSymbol>;\nexport type RPairlist = RProxy<RObjPairlist>;\nexport type REnvironment = RProxy<RObjEnvironment>;\nexport type RString = RProxy<RObjString>;\nexport type RLogical = RProxy<RObjLogical>;\nexport type RInteger = RProxy<RObjInteger>;\nexport type RDouble = RProxy<RObjDouble>;\nexport type RComplex = RProxy<RObjComplex>;\nexport type RCharacter = RProxy<RObjCharacter>;\nexport type RList = RProxy<RObjList>;\nexport type RRaw = RProxy<RObjRaw>;\n// RFunction proxies are callable\nexport type RFunction = RProxy<RObjFunction> & ((...args: unknown[]) => Promise<unknown>);\n\nexport type RPtr = number;\n\nexport const RTypeMap = {\n null: 0,\n symbol: 1,\n pairlist: 2,\n closure: 3,\n environment: 4,\n promise: 5,\n call: 6,\n special: 7,\n builtin: 8,\n string: 9,\n logical: 10,\n integer: 13,\n double: 14,\n complex: 15,\n character: 16,\n dots: 17,\n any: 18,\n list: 19,\n expression: 20,\n bytecode: 21,\n pointer: 22,\n weakref: 23,\n raw: 24,\n s4: 25,\n new: 30,\n free: 31,\n function: 99,\n} as const;\nexport type RType = keyof typeof RTypeMap;\nexport type RTypeNumber = typeof RTypeMap[keyof typeof RTypeMap];\n\nexport type RTargetRaw = {\n obj: RawType;\n targetType: 'raw';\n};\n\nexport type RTargetPtr = {\n obj: {\n type?: RType;\n ptr: RPtr;\n methods?: string[];\n };\n targetType: 'ptr';\n};\n\nexport type RTargetError = {\n obj: {\n message: string;\n name: string;\n stack?: string;\n };\n targetType: 'err';\n};\nexport type RTargetType = 'raw' | 'ptr' | 'err';\nexport type RTargetObj = RTargetRaw | RTargetPtr | RTargetError;\n\ntype Nullable<T> = T | RObjNull;\n\ntype Complex = {\n re: number;\n im: number;\n};\n\nexport type RawType =\n | number\n | string\n | boolean\n | undefined\n | null\n | Complex\n | Error\n | ArrayBuffer\n | ArrayBufferView\n | Array<RawType>\n | Map<RawType, RawType>\n | Set<RawType>\n | { [key: string]: RawType };\n\nexport type NamedEntries<T> = [string | null, T][];\nexport type NamedObject<T> = { [key: string]: T };\n\nexport type RObjData = RObjImpl | RawType | RObjectTree<RObjImpl>;\nexport type RObjAtomicData<T> = T | (T | null)[] | RObjectTreeAtomic<T>;\nexport type RObjectTree<T> = RObjectTreeImpl<(RObjectTree<T> | RawType | T)[]>;\nexport type RObjectTreeAtomic<T> = RObjectTreeImpl<(T | null)[]>;\ntype RObjectTreeImpl<T> = {\n type: RType;\n names: (string | null)[] | null;\n values: T;\n missing?: boolean[];\n};\n\nfunction newRObjFromTarget(target: RTargetObj): RObjImpl {\n const obj = target.obj;\n\n // Conversion of RObjectTree type JS objects\n if (isRObjectTree(obj)) {\n return new (getRObjClass(RTypeMap[obj.type]))(obj);\n }\n\n // Conversion of explicit R NULL value\n if (obj && typeof obj === 'object' && 'type' in obj && obj.type === 'null') {\n return new RObjNull();\n }\n\n // Direct conversion of scalar JS values\n if (obj === null) {\n return new RObjLogical({ type: 'logical', names: null, values: [null] });\n }\n if (typeof obj === 'boolean') {\n return new RObjLogical(obj);\n }\n if (typeof obj === 'number') {\n return new RObjDouble(obj);\n }\n if (typeof obj === 'object' && 're' in obj && 'im' in obj) {\n return new RObjComplex(obj as Complex);\n }\n if (typeof obj === 'string') {\n return new RObjCharacter(obj);\n }\n\n // JS arrays are interpreted using R's c() function, so as to match\n // R's built in coercion rules\n if (Array.isArray(obj)) {\n const objs = obj.map((el) => newRObjFromTarget({ targetType: 'raw', obj: el }));\n const cString = Module.allocateUTF8('c');\n const call = RObjImpl.protect(\n RObjImpl.wrap(Module._Rf_allocVector(RTypeMap.call, objs.length + 1)) as RObjPairlist\n );\n call.setcar(RObjImpl.wrap(Module._Rf_install(cString)));\n let next = call.cdr();\n let i = 0;\n while (!next.isNull()) {\n next.setcar(objs[i++]);\n next = next.cdr();\n }\n const res = RObjImpl.wrap(Module._Rf_eval(call.ptr, RObjImpl.baseEnv.ptr));\n RObjImpl.unprotect(1);\n Module._free(cString);\n return res;\n }\n\n throw new Error('Robj construction for this JS object is not yet supported');\n}\n\nexport class RObjImpl {\n ptr: RPtr;\n\n constructor(target: RTargetObj | RawType) {\n this.ptr = 0;\n if (isRTargetObj(target)) {\n if (target.targetType === 'ptr') {\n this.ptr = target.obj.ptr;\n return this;\n }\n if (target.targetType === 'raw') {\n return newRObjFromTarget(target);\n }\n }\n return newRObjFromTarget({ targetType: 'raw', obj: target });\n }\n\n get [Symbol.toStringTag](): string {\n return `RObj:${this.type()}`;\n }\n\n type(): RType {\n const typeNumber = Module._TYPEOF(this.ptr) as RTypeNumber;\n const type = Object.keys(RTypeMap).find(\n (typeName) => RTypeMap[typeName as RType] === typeNumber\n );\n return type as RType;\n }\n\n protect(): void {\n this.ptr = Module._Rf_protect(this.ptr);\n }\n\n unprotect(): void {\n Module._Rf_unprotect_ptr(this.ptr);\n }\n\n preserve(): void {\n Module._R_PreserveObject(this.ptr);\n }\n\n release(): void {\n Module._R_ReleaseObject(this.ptr);\n }\n\n isNull(): this is RObjNull {\n return Module._TYPEOF(this.ptr) === RTypeMap.null;\n }\n\n isUnbound(): boolean {\n return this.ptr === RObjImpl.unboundValue.ptr;\n }\n\n attrs(): Nullable<RObjPairlist> {\n return RObjImpl.wrap(Module._ATTRIB(this.ptr)) as RObjPairlist;\n }\n\n setNames(values: (string | null)[] | null): this {\n let namesObj: RObjImpl;\n if (values === null) {\n namesObj = RObjImpl.null;\n RObjImpl.protect(namesObj);\n } else if (Array.isArray(values) && values.every((v) => typeof v === 'string' || v === null)) {\n namesObj = new RObjCharacter(values);\n } else {\n throw new Error('Argument to setNames must be null or an Array of strings or null');\n }\n Module._Rf_setAttrib(this.ptr, RObjImpl.namesSymbol.ptr, namesObj.ptr);\n RObjImpl.unprotect(1);\n return this;\n }\n\n names(): (string | null)[] | null {\n const names = RObjImpl.wrap(\n Module._Rf_protect(Module._Rf_getAttrib(this.ptr, RObjImpl.namesSymbol.ptr))\n ) as RObjCharacter;\n if (names.isNull()) {\n return null;\n }\n return names.toArray();\n }\n\n includes(name: string) {\n const names = this.names();\n return names && names.includes(name);\n }\n\n toTree(options: ToTreeOptions = { depth: 0 }, depth = 1): RawType | RObjectTree<RObjImpl> {\n throw new Error('This R object cannot be converted to JS');\n }\n\n toJs() {\n return this.toTree() as ReturnType<this['toTree']>;\n }\n\n subset(prop: number | string): RObjImpl {\n let idx: RPtr;\n let char: RPtr = 0;\n if (typeof prop === 'number') {\n idx = Module._Rf_protect(Module._Rf_ScalarInteger(prop));\n } else {\n char = Module.allocateUTF8(prop);\n idx = Module._Rf_protect(Module._Rf_mkString(char));\n }\n const call = Module._Rf_protect(Module._Rf_lang3(RObjImpl.bracketSymbol.ptr, this.ptr, idx));\n const sub = RObjImpl.wrap(Module._Rf_eval(call, RObjImpl.baseEnv.ptr));\n Module._Rf_unprotect(2);\n if (char) Module._free(char);\n return sub;\n }\n\n get(prop: number | string): RObjImpl {\n let idx: RPtr;\n let char: RPtr = 0;\n if (typeof prop === 'number') {\n idx = Module._Rf_protect(Module._Rf_ScalarInteger(prop));\n } else {\n char = Module.allocateUTF8(prop);\n idx = Module._Rf_protect(Module._Rf_mkString(char));\n }\n const call = Module._Rf_protect(Module._Rf_lang3(RObjImpl.bracket2Symbol.ptr, this.ptr, idx));\n const sub = RObjImpl.wrap(Module._Rf_eval(call, RObjImpl.baseEnv.ptr));\n Module._Rf_unprotect(2);\n if (char) Module._free(char);\n return sub;\n }\n\n getDollar(prop: string): RObjImpl {\n const char = Module.allocateUTF8(prop);\n const idx = Module._Rf_protect(Module._Rf_mkString(char));\n const call = Module._Rf_protect(Module._Rf_lang3(RObjImpl.dollarSymbol.ptr, this.ptr, idx));\n const sub = RObjImpl.wrap(Module._Rf_eval(call, RObjImpl.baseEnv.ptr));\n Module._Rf_unprotect(2);\n Module._free(char);\n return sub;\n }\n\n pluck(...path: (string | number)[]): RObjImpl | undefined {\n try {\n const result = path.reduce(\n (obj: RObjImpl, prop: string | number): RObjImpl => obj.get(prop),\n this\n );\n return result.isNull() ? undefined : result;\n } catch (err) {\n // Deal with subscript out of bounds error\n if (err === Infinity) {\n return undefined;\n }\n throw err;\n }\n }\n\n set(prop: string | number, value: RObjImpl | RawType) {\n let idx: RPtr;\n let char: RPtr = 0;\n if (typeof prop === 'number') {\n idx = Module._Rf_protect(Module._Rf_ScalarInteger(prop));\n } else {\n char = Module.allocateUTF8(prop);\n idx = Module._Rf_protect(Module._Rf_mkString(char));\n }\n\n const valueObj = isRObjImpl(value) ? value : new RObjImpl({ obj: value, targetType: 'raw' });\n\n const assign = Module.allocateUTF8('[[<-');\n const call = Module._Rf_protect(\n Module._Rf_lang4(Module._Rf_install(assign), this.ptr, idx, valueObj.ptr)\n );\n const val = RObjImpl.wrap(Module._Rf_eval(call, RObjImpl.baseEnv.ptr));\n\n Module._Rf_unprotect(2);\n if (char) Module._free(char);\n Module._free(assign);\n\n if (!isRObjImpl(value)) {\n valueObj.release();\n }\n\n return val;\n }\n\n static getMethods(obj: RObjImpl) {\n const props = new Set<string>();\n let cur: unknown = obj;\n do {\n Object.getOwnPropertyNames(cur).map((p) => props.add(p));\n } while ((cur = Object.getPrototypeOf(cur)));\n return [...props.keys()].filter((i) => typeof obj[i as keyof typeof obj] === 'function');\n }\n\n static get globalEnv(): RObjImpl {\n return RObjImpl.wrap(Module.getValue(Module._R_GlobalEnv, '*'));\n }\n\n static get emptyEnv(): RObjImpl {\n return RObjImpl.wrap(Module.getValue(Module._R_EmptyEnv, '*'));\n }\n\n static get baseEnv(): RObjImpl {\n return RObjImpl.wrap(Module.getValue(Module._R_BaseEnv, '*'));\n }\n\n static get null(): RObjNull {\n return RObjImpl.wrap(Module.getValue(Module._R_NilValue, '*')) as RObjNull;\n }\n\n static get naLogical(): number {\n return Module.getValue(Module._R_NaInt, 'i32');\n }\n\n static get naInteger(): number {\n return Module.getValue(Module._R_NaInt, 'i32');\n }\n\n static get naDouble(): number {\n return Module.getValue(Module._R_NaReal, 'double');\n }\n\n static get naString(): RObjImpl {\n return RObjImpl.wrap(Module.getValue(Module._R_NaString, '*'));\n }\n\n static get unboundValue(): RObjImpl {\n return RObjImpl.wrap(Module.getValue(Module._R_UnboundValue, '*'));\n }\n\n static get bracketSymbol(): RObjSymbol {\n return RObjImpl.wrap(Module.getValue(Module._