this repo has no description
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

Add frontend client libraries for JavaScript and OCaml

JavaScript client (client/ocaml-worker.js):
- ES module with async/await API for web applications
- TypeScript declarations (ocaml-worker.d.ts)
- Supports init, eval, complete, typeAt, errors, createEnv, destroyEnv

OCaml client (js_top_worker_client_msg):
- Uses new message protocol instead of RPC
- Lwt-based async API matching the JS client
- Part of js_top_worker-client package

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

+905 -4
+190
client/ocaml-worker.d.ts
··· 1 + /** 2 + * OCaml Worker Client TypeScript Declarations 3 + */ 4 + 5 + export interface InitConfig { 6 + /** Findlib packages to require */ 7 + findlib_requires: string[]; 8 + /** URL to dynamic CMIs for stdlib */ 9 + stdlib_dcs?: string; 10 + /** URL to findlib_index file */ 11 + findlib_index?: string; 12 + } 13 + 14 + export interface Position { 15 + /** Character number */ 16 + pos_cnum: number; 17 + /** Line number */ 18 + pos_lnum: number; 19 + /** Beginning of line offset */ 20 + pos_bol: number; 21 + } 22 + 23 + export interface Location { 24 + /** Start position */ 25 + loc_start: Position; 26 + /** End position */ 27 + loc_end: Position; 28 + } 29 + 30 + export interface MimeVal { 31 + /** MIME type */ 32 + mime_type: string; 33 + /** Data content */ 34 + data: string; 35 + } 36 + 37 + export interface Output { 38 + /** Cell identifier */ 39 + cell_id: number; 40 + /** Standard output */ 41 + stdout: string; 42 + /** Standard error */ 43 + stderr: string; 44 + /** OCaml pretty-printed output */ 45 + caml_ppf: string; 46 + /** MIME values */ 47 + mime_vals: MimeVal[]; 48 + } 49 + 50 + export interface CompletionEntry { 51 + /** Completion name */ 52 + name: string; 53 + /** Kind (Value, Module, Type, etc.) */ 54 + kind: string; 55 + /** Description */ 56 + desc: string; 57 + /** Additional info */ 58 + info: string; 59 + /** Whether deprecated */ 60 + deprecated: boolean; 61 + } 62 + 63 + export interface Completions { 64 + /** Cell identifier */ 65 + cell_id: number; 66 + /** Completions data */ 67 + completions: { 68 + /** Start position */ 69 + from: number; 70 + /** End position */ 71 + to: number; 72 + /** Completion entries */ 73 + entries: CompletionEntry[]; 74 + }; 75 + } 76 + 77 + export interface Error { 78 + /** Error kind */ 79 + kind: string; 80 + /** Error location */ 81 + loc: Location; 82 + /** Main error message */ 83 + main: string; 84 + /** Sub-messages */ 85 + sub: string[]; 86 + /** Error source */ 87 + source: string; 88 + } 89 + 90 + export interface ErrorList { 91 + /** Cell identifier */ 92 + cell_id: number; 93 + /** Errors */ 94 + errors: Error[]; 95 + } 96 + 97 + export interface TypeInfo { 98 + /** Type location */ 99 + loc: Location; 100 + /** Type string */ 101 + type_str: string; 102 + /** Tail position info */ 103 + tail: string; 104 + } 105 + 106 + export interface TypesResult { 107 + /** Cell identifier */ 108 + cell_id: number; 109 + /** Type information */ 110 + types: TypeInfo[]; 111 + } 112 + 113 + export interface EnvResult { 114 + /** Environment ID */ 115 + env_id: string; 116 + } 117 + 118 + export interface OcamlWorkerOptions { 119 + /** Timeout in milliseconds (default: 30000) */ 120 + timeout?: number; 121 + } 122 + 123 + export class OcamlWorker { 124 + /** 125 + * Create a new OCaml worker client. 126 + * @param workerUrl - URL to the worker script 127 + * @param options - Options 128 + */ 129 + constructor(workerUrl: string, options?: OcamlWorkerOptions); 130 + 131 + /** 132 + * Initialize the worker. 133 + * @param config - Initialization configuration 134 + */ 135 + init(config: InitConfig): Promise<void>; 136 + 137 + /** 138 + * Wait for the worker to be ready. 139 + */ 140 + waitReady(): Promise<void>; 141 + 142 + /** 143 + * Evaluate OCaml code. 144 + * @param code - OCaml code to evaluate 145 + * @param envId - Environment ID (default: 'default') 146 + */ 147 + eval(code: string, envId?: string): Promise<Output>; 148 + 149 + /** 150 + * Get completions at a position. 151 + * @param source - Source code 152 + * @param position - Cursor position (character offset) 153 + * @param envId - Environment ID (default: 'default') 154 + */ 155 + complete(source: string, position: number, envId?: string): Promise<Completions>; 156 + 157 + /** 158 + * Get type information at a position. 159 + * @param source - Source code 160 + * @param position - Cursor position (character offset) 161 + * @param envId - Environment ID (default: 'default') 162 + */ 163 + typeAt(source: string, position: number, envId?: string): Promise<TypesResult>; 164 + 165 + /** 166 + * Get errors for source code. 167 + * @param source - Source code 168 + * @param envId - Environment ID (default: 'default') 169 + */ 170 + errors(source: string, envId?: string): Promise<ErrorList>; 171 + 172 + /** 173 + * Create a new execution environment. 174 + * @param envId - Environment ID 175 + */ 176 + createEnv(envId: string): Promise<EnvResult>; 177 + 178 + /** 179 + * Destroy an execution environment. 180 + * @param envId - Environment ID 181 + */ 182 + destroyEnv(envId: string): Promise<EnvResult>; 183 + 184 + /** 185 + * Terminate the worker. 186 + */ 187 + terminate(): void; 188 + } 189 + 190 + export default OcamlWorker;
+374
client/ocaml-worker.js
··· 1 + /** 2 + * OCaml Worker Client 3 + * 4 + * A JavaScript client library for communicating with the OCaml toplevel web worker. 5 + * 6 + * @example 7 + * ```javascript 8 + * import { OcamlWorker } from './ocaml-worker.js'; 9 + * 10 + * const worker = new OcamlWorker('worker.js'); 11 + * 12 + * await worker.init({ 13 + * findlib_requires: [], 14 + * findlib_index: 'findlib_index' 15 + * }); 16 + * 17 + * const result = await worker.eval('let x = 1 + 2;;'); 18 + * console.log(result.caml_ppf); // "val x : int = 3" 19 + * ``` 20 + */ 21 + 22 + /** 23 + * @typedef {Object} InitConfig 24 + * @property {string[]} findlib_requires - Findlib packages to require 25 + * @property {string} [stdlib_dcs] - URL to dynamic CMIs for stdlib 26 + * @property {string} [findlib_index] - URL to findlib_index file 27 + */ 28 + 29 + /** 30 + * @typedef {Object} Position 31 + * @property {number} pos_cnum - Character number 32 + * @property {number} pos_lnum - Line number 33 + * @property {number} pos_bol - Beginning of line offset 34 + */ 35 + 36 + /** 37 + * @typedef {Object} Location 38 + * @property {Position} loc_start - Start position 39 + * @property {Position} loc_end - End position 40 + */ 41 + 42 + /** 43 + * @typedef {Object} MimeVal 44 + * @property {string} mime_type - MIME type 45 + * @property {string} data - Data content 46 + */ 47 + 48 + /** 49 + * @typedef {Object} Output 50 + * @property {number} cell_id - Cell identifier 51 + * @property {string} stdout - Standard output 52 + * @property {string} stderr - Standard error 53 + * @property {string} caml_ppf - OCaml pretty-printed output 54 + * @property {MimeVal[]} mime_vals - MIME values 55 + */ 56 + 57 + /** 58 + * @typedef {Object} CompletionEntry 59 + * @property {string} name - Completion name 60 + * @property {string} kind - Kind (Value, Module, Type, etc.) 61 + * @property {string} desc - Description 62 + * @property {string} info - Additional info 63 + * @property {boolean} deprecated - Whether deprecated 64 + */ 65 + 66 + /** 67 + * @typedef {Object} Completions 68 + * @property {number} cell_id - Cell identifier 69 + * @property {Object} completions - Completions data 70 + * @property {number} completions.from - Start position 71 + * @property {number} completions.to - End position 72 + * @property {CompletionEntry[]} completions.entries - Completion entries 73 + */ 74 + 75 + /** 76 + * @typedef {Object} Error 77 + * @property {string} kind - Error kind 78 + * @property {Location} loc - Error location 79 + * @property {string} main - Main error message 80 + * @property {string[]} sub - Sub-messages 81 + * @property {string} source - Error source 82 + */ 83 + 84 + /** 85 + * @typedef {Object} TypeInfo 86 + * @property {Location} loc - Type location 87 + * @property {string} type_str - Type string 88 + * @property {string} tail - Tail position info 89 + */ 90 + 91 + export class OcamlWorker { 92 + /** 93 + * Create a new OCaml worker client. 94 + * @param {string} workerUrl - URL to the worker script 95 + * @param {Object} [options] - Options 96 + * @param {number} [options.timeout=30000] - Timeout in milliseconds 97 + */ 98 + constructor(workerUrl, options = {}) { 99 + this.worker = new Worker(workerUrl); 100 + this.timeout = options.timeout || 30000; 101 + this.cellIdCounter = 0; 102 + this.pendingRequests = new Map(); 103 + this.readyPromise = null; 104 + this.readyResolve = null; 105 + this.isReady = false; 106 + 107 + this.worker.onmessage = (event) => this._handleMessage(event.data); 108 + this.worker.onerror = (error) => this._handleError(error); 109 + } 110 + 111 + /** 112 + * Handle incoming messages from the worker. 113 + * @private 114 + */ 115 + _handleMessage(data) { 116 + const msg = typeof data === 'string' ? JSON.parse(data) : data; 117 + 118 + switch (msg.type) { 119 + case 'ready': 120 + this.isReady = true; 121 + if (this.readyResolve) { 122 + this.readyResolve(); 123 + this.readyResolve = null; 124 + } 125 + break; 126 + 127 + case 'init_error': 128 + if (this.readyResolve) { 129 + // Convert to rejection 130 + const reject = this.pendingRequests.get('init')?.reject; 131 + if (reject) { 132 + reject(new Error(msg.message)); 133 + this.pendingRequests.delete('init'); 134 + } 135 + } 136 + break; 137 + 138 + case 'output': 139 + case 'completions': 140 + case 'types': 141 + case 'errors': 142 + case 'eval_error': 143 + this._resolveRequest(msg.cell_id, msg); 144 + break; 145 + 146 + case 'env_created': 147 + case 'env_destroyed': 148 + this._resolveRequest(msg.env_id, msg); 149 + break; 150 + 151 + default: 152 + console.warn('Unknown message type:', msg.type); 153 + } 154 + } 155 + 156 + /** 157 + * Handle worker errors. 158 + * @private 159 + */ 160 + _handleError(error) { 161 + console.error('Worker error:', error); 162 + // Reject all pending requests 163 + for (const [key, { reject }] of this.pendingRequests) { 164 + reject(error); 165 + } 166 + this.pendingRequests.clear(); 167 + } 168 + 169 + /** 170 + * Resolve a pending request. 171 + * @private 172 + */ 173 + _resolveRequest(id, msg) { 174 + const pending = this.pendingRequests.get(id); 175 + if (pending) { 176 + clearTimeout(pending.timeoutId); 177 + if (msg.type === 'eval_error') { 178 + pending.reject(new Error(msg.message)); 179 + } else { 180 + pending.resolve(msg); 181 + } 182 + this.pendingRequests.delete(id); 183 + } 184 + } 185 + 186 + /** 187 + * Send a message to the worker and wait for a response. 188 + * @private 189 + */ 190 + _send(msg, id) { 191 + return new Promise((resolve, reject) => { 192 + const timeoutId = setTimeout(() => { 193 + this.pendingRequests.delete(id); 194 + reject(new Error('Request timeout')); 195 + }, this.timeout); 196 + 197 + this.pendingRequests.set(id, { resolve, reject, timeoutId }); 198 + this.worker.postMessage(JSON.stringify(msg)); 199 + }); 200 + } 201 + 202 + /** 203 + * Get the next cell ID. 204 + * @private 205 + */ 206 + _nextCellId() { 207 + return ++this.cellIdCounter; 208 + } 209 + 210 + /** 211 + * Initialize the worker. 212 + * @param {InitConfig} config - Initialization configuration 213 + * @returns {Promise<void>} 214 + */ 215 + async init(config) { 216 + // Set up ready promise 217 + this.readyPromise = new Promise((resolve, reject) => { 218 + this.readyResolve = resolve; 219 + this.pendingRequests.set('init', { resolve, reject, timeoutId: null }); 220 + }); 221 + 222 + // Set timeout for init 223 + const timeoutId = setTimeout(() => { 224 + this.pendingRequests.delete('init'); 225 + if (this.readyResolve) { 226 + this.readyResolve = null; 227 + } 228 + throw new Error('Init timeout'); 229 + }, this.timeout); 230 + 231 + const pending = this.pendingRequests.get('init'); 232 + if (pending) { 233 + pending.timeoutId = timeoutId; 234 + } 235 + 236 + // Send init message 237 + this.worker.postMessage(JSON.stringify({ 238 + type: 'init', 239 + findlib_requires: config.findlib_requires || [], 240 + stdlib_dcs: config.stdlib_dcs || null, 241 + findlib_index: config.findlib_index || null, 242 + })); 243 + 244 + // Wait for ready 245 + await this.readyPromise; 246 + clearTimeout(timeoutId); 247 + this.pendingRequests.delete('init'); 248 + } 249 + 250 + /** 251 + * Wait for the worker to be ready. 252 + * @returns {Promise<void>} 253 + */ 254 + async waitReady() { 255 + if (this.isReady) return; 256 + if (this.readyPromise) { 257 + await this.readyPromise; 258 + } 259 + } 260 + 261 + /** 262 + * Evaluate OCaml code. 263 + * @param {string} code - OCaml code to evaluate 264 + * @param {string} [envId='default'] - Environment ID 265 + * @returns {Promise<Output>} 266 + */ 267 + async eval(code, envId = 'default') { 268 + await this.waitReady(); 269 + const cellId = this._nextCellId(); 270 + return this._send({ 271 + type: 'eval', 272 + cell_id: cellId, 273 + env_id: envId, 274 + code: code, 275 + }, cellId); 276 + } 277 + 278 + /** 279 + * Get completions at a position. 280 + * @param {string} source - Source code 281 + * @param {number} position - Cursor position (character offset) 282 + * @param {string} [envId='default'] - Environment ID 283 + * @returns {Promise<Completions>} 284 + */ 285 + async complete(source, position, envId = 'default') { 286 + await this.waitReady(); 287 + const cellId = this._nextCellId(); 288 + return this._send({ 289 + type: 'complete', 290 + cell_id: cellId, 291 + env_id: envId, 292 + source: source, 293 + position: position, 294 + }, cellId); 295 + } 296 + 297 + /** 298 + * Get type information at a position. 299 + * @param {string} source - Source code 300 + * @param {number} position - Cursor position (character offset) 301 + * @param {string} [envId='default'] - Environment ID 302 + * @returns {Promise<{cell_id: number, types: TypeInfo[]}>} 303 + */ 304 + async typeAt(source, position, envId = 'default') { 305 + await this.waitReady(); 306 + const cellId = this._nextCellId(); 307 + return this._send({ 308 + type: 'type_at', 309 + cell_id: cellId, 310 + env_id: envId, 311 + source: source, 312 + position: position, 313 + }, cellId); 314 + } 315 + 316 + /** 317 + * Get errors for source code. 318 + * @param {string} source - Source code 319 + * @param {string} [envId='default'] - Environment ID 320 + * @returns {Promise<{cell_id: number, errors: Error[]}>} 321 + */ 322 + async errors(source, envId = 'default') { 323 + await this.waitReady(); 324 + const cellId = this._nextCellId(); 325 + return this._send({ 326 + type: 'errors', 327 + cell_id: cellId, 328 + env_id: envId, 329 + source: source, 330 + }, cellId); 331 + } 332 + 333 + /** 334 + * Create a new execution environment. 335 + * @param {string} envId - Environment ID 336 + * @returns {Promise<{env_id: string}>} 337 + */ 338 + async createEnv(envId) { 339 + await this.waitReady(); 340 + return this._send({ 341 + type: 'create_env', 342 + env_id: envId, 343 + }, envId); 344 + } 345 + 346 + /** 347 + * Destroy an execution environment. 348 + * @param {string} envId - Environment ID 349 + * @returns {Promise<{env_id: string}>} 350 + */ 351 + async destroyEnv(envId) { 352 + await this.waitReady(); 353 + return this._send({ 354 + type: 'destroy_env', 355 + env_id: envId, 356 + }, envId); 357 + } 358 + 359 + /** 360 + * Terminate the worker. 361 + */ 362 + terminate() { 363 + this.worker.terminate(); 364 + // Reject all pending requests 365 + for (const [key, { reject, timeoutId }] of this.pendingRequests) { 366 + clearTimeout(timeoutId); 367 + reject(new Error('Worker terminated')); 368 + } 369 + this.pendingRequests.clear(); 370 + } 371 + } 372 + 373 + // Also export as default 374 + export default OcamlWorker;
+8
idl/dune
··· 29 29 (pps js_of_ocaml-ppx))) 30 30 31 31 (library 32 + (name js_top_worker_client_msg) 33 + (package js_top_worker-client) 34 + (modules js_top_worker_client_msg) 35 + (libraries js_top_worker_message lwt brr js_of_ocaml) 36 + (preprocess 37 + (pps js_of_ocaml-ppx))) 38 + 39 + (library 32 40 (name js_top_worker_rpc_def) 33 41 (modules toplevel_api) 34 42 (enabled_if
+330
idl/js_top_worker_client_msg.ml
··· 1 + (** Worker client using the message protocol. 2 + 3 + This client communicates with the OCaml toplevel worker using a simple 4 + JSON message protocol instead of RPC. *) 5 + 6 + module Brr_worker = Brr_webworkers.Worker 7 + module Brr_message = Brr_io.Message 8 + module Msg = Js_top_worker_message.Message 9 + 10 + (** Client state *) 11 + type t = { 12 + worker : Brr_worker.t; 13 + timeout : int; 14 + mutable cell_id : int; 15 + mutable ready : bool; 16 + ready_waiters : (unit -> unit) Queue.t; 17 + pending : (int, Msg.worker_msg Lwt.u) Hashtbl.t; 18 + pending_env : (string, Msg.worker_msg Lwt.u) Hashtbl.t; 19 + } 20 + 21 + exception Timeout 22 + exception InitError of string 23 + exception EvalError of string 24 + 25 + (** Parse a worker message from JSON string *) 26 + let parse_worker_msg s = 27 + let open Js_of_ocaml in 28 + let obj = Json.unsafe_input (Js.string s) in 29 + let typ = Js.to_string (Js.Unsafe.get obj (Js.string "type")) in 30 + let get_int key = Js.Unsafe.get obj (Js.string key) in 31 + let get_string key = Js.to_string (Js.Unsafe.get obj (Js.string key)) in 32 + let parse_position p = 33 + { Msg.pos_cnum = Js.Unsafe.get p (Js.string "pos_cnum"); 34 + pos_lnum = Js.Unsafe.get p (Js.string "pos_lnum"); 35 + pos_bol = Js.Unsafe.get p (Js.string "pos_bol") } 36 + in 37 + let parse_location loc = 38 + { Msg.loc_start = parse_position (Js.Unsafe.get loc (Js.string "loc_start")); 39 + loc_end = parse_position (Js.Unsafe.get loc (Js.string "loc_end")) } 40 + in 41 + match typ with 42 + | "ready" -> Msg.Ready 43 + | "init_error" -> Msg.InitError { message = get_string "message" } 44 + | "output" -> 45 + let mime_vals_arr = Js.to_array (Js.Unsafe.get obj (Js.string "mime_vals")) in 46 + let mime_vals = Array.to_list (Array.map (fun mv -> 47 + { Msg.mime_type = Js.to_string (Js.Unsafe.get mv (Js.string "mime_type")); 48 + data = Js.to_string (Js.Unsafe.get mv (Js.string "data")) } 49 + ) mime_vals_arr) in 50 + Msg.Output { 51 + cell_id = get_int "cell_id"; 52 + stdout = get_string "stdout"; 53 + stderr = get_string "stderr"; 54 + caml_ppf = get_string "caml_ppf"; 55 + mime_vals; 56 + } 57 + | "completions" -> 58 + let c = Js.Unsafe.get obj (Js.string "completions") in 59 + let entries_arr = Js.to_array (Js.Unsafe.get c (Js.string "entries")) in 60 + let entries = Array.to_list (Array.map (fun e -> 61 + { Msg.name = Js.to_string (Js.Unsafe.get e (Js.string "name")); 62 + kind = Js.to_string (Js.Unsafe.get e (Js.string "kind")); 63 + desc = Js.to_string (Js.Unsafe.get e (Js.string "desc")); 64 + info = Js.to_string (Js.Unsafe.get e (Js.string "info")); 65 + deprecated = Js.to_bool (Js.Unsafe.get e (Js.string "deprecated")) } 66 + ) entries_arr) in 67 + Msg.Completions { 68 + cell_id = get_int "cell_id"; 69 + completions = { 70 + from = Js.Unsafe.get c (Js.string "from"); 71 + to_ = Js.Unsafe.get c (Js.string "to"); 72 + entries; 73 + }; 74 + } 75 + | "types" -> 76 + let types_arr = Js.to_array (Js.Unsafe.get obj (Js.string "types")) in 77 + let types = Array.to_list (Array.map (fun t -> 78 + { Msg.loc = parse_location (Js.Unsafe.get t (Js.string "loc")); 79 + type_str = Js.to_string (Js.Unsafe.get t (Js.string "type_str")); 80 + tail = Js.to_string (Js.Unsafe.get t (Js.string "tail")) } 81 + ) types_arr) in 82 + Msg.Types { cell_id = get_int "cell_id"; types } 83 + | "errors" -> 84 + let errors_arr = Js.to_array (Js.Unsafe.get obj (Js.string "errors")) in 85 + let errors = Array.to_list (Array.map (fun e -> 86 + let sub_arr = Js.to_array (Js.Unsafe.get e (Js.string "sub")) in 87 + let sub = Array.to_list (Array.map Js.to_string sub_arr) in 88 + { Msg.kind = Js.to_string (Js.Unsafe.get e (Js.string "kind")); 89 + loc = parse_location (Js.Unsafe.get e (Js.string "loc")); 90 + main = Js.to_string (Js.Unsafe.get e (Js.string "main")); 91 + sub; 92 + source = Js.to_string (Js.Unsafe.get e (Js.string "source")) } 93 + ) errors_arr) in 94 + Msg.ErrorList { cell_id = get_int "cell_id"; errors } 95 + | "eval_error" -> 96 + Msg.EvalError { cell_id = get_int "cell_id"; message = get_string "message" } 97 + | "env_created" -> 98 + Msg.EnvCreated { env_id = get_string "env_id" } 99 + | "env_destroyed" -> 100 + Msg.EnvDestroyed { env_id = get_string "env_id" } 101 + | _ -> failwith ("Unknown message type: " ^ typ) 102 + 103 + (** Handle incoming message from worker *) 104 + let handle_message t msg = 105 + let data = Brr_message.Ev.data (Brr.Ev.as_type msg) in 106 + let parsed = parse_worker_msg (Js_of_ocaml.Js.to_string data) in 107 + match parsed with 108 + | Msg.Ready -> 109 + t.ready <- true; 110 + Queue.iter (fun f -> f ()) t.ready_waiters; 111 + Queue.clear t.ready_waiters 112 + | Msg.InitError _ -> 113 + t.ready <- true; 114 + Queue.iter (fun f -> f ()) t.ready_waiters; 115 + Queue.clear t.ready_waiters 116 + | Msg.Output { cell_id; _ } | Msg.Completions { cell_id; _ } 117 + | Msg.Types { cell_id; _ } | Msg.ErrorList { cell_id; _ } 118 + | Msg.EvalError { cell_id; _ } -> 119 + (match Hashtbl.find_opt t.pending cell_id with 120 + | Some resolver -> 121 + Hashtbl.remove t.pending cell_id; 122 + Lwt.wakeup resolver parsed 123 + | None -> ()) 124 + | Msg.EnvCreated { env_id } | Msg.EnvDestroyed { env_id } -> 125 + (match Hashtbl.find_opt t.pending_env env_id with 126 + | Some resolver -> 127 + Hashtbl.remove t.pending_env env_id; 128 + Lwt.wakeup resolver parsed 129 + | None -> ()) 130 + 131 + (** Create a new worker client *) 132 + let create ?(timeout = 30000) url = 133 + let worker = Brr_worker.create (Jstr.v url) in 134 + let t = { 135 + worker; 136 + timeout; 137 + cell_id = 0; 138 + ready = false; 139 + ready_waiters = Queue.create (); 140 + pending = Hashtbl.create 16; 141 + pending_env = Hashtbl.create 16; 142 + } in 143 + let _listener = 144 + Brr.Ev.listen Brr_message.Ev.message (handle_message t) (Brr_worker.as_target worker) 145 + in 146 + t 147 + 148 + (** Get next cell ID *) 149 + let next_cell_id t = 150 + t.cell_id <- t.cell_id + 1; 151 + t.cell_id 152 + 153 + (** Send a message to the worker *) 154 + let send t msg = 155 + let open Js_of_ocaml in 156 + let json = match msg with 157 + | `Init config -> 158 + let obj = Js.Unsafe.obj [| 159 + ("type", Js.Unsafe.inject (Js.string "init")); 160 + ("findlib_requires", Js.Unsafe.inject (Js.array (Array.of_list (List.map Js.string config.Msg.findlib_requires)))); 161 + ("stdlib_dcs", Js.Unsafe.inject (match config.Msg.stdlib_dcs with Some s -> Js.some (Js.string s) | None -> Js.null)); 162 + ("findlib_index", Js.Unsafe.inject (match config.Msg.findlib_index with Some s -> Js.some (Js.string s) | None -> Js.null)); 163 + |] in 164 + Js.to_string (Json.output obj) 165 + | `Eval (cell_id, env_id, code) -> 166 + let obj = Js.Unsafe.obj [| 167 + ("type", Js.Unsafe.inject (Js.string "eval")); 168 + ("cell_id", Js.Unsafe.inject cell_id); 169 + ("env_id", Js.Unsafe.inject (Js.string env_id)); 170 + ("code", Js.Unsafe.inject (Js.string code)); 171 + |] in 172 + Js.to_string (Json.output obj) 173 + | `Complete (cell_id, env_id, source, position) -> 174 + let obj = Js.Unsafe.obj [| 175 + ("type", Js.Unsafe.inject (Js.string "complete")); 176 + ("cell_id", Js.Unsafe.inject cell_id); 177 + ("env_id", Js.Unsafe.inject (Js.string env_id)); 178 + ("source", Js.Unsafe.inject (Js.string source)); 179 + ("position", Js.Unsafe.inject position); 180 + |] in 181 + Js.to_string (Json.output obj) 182 + | `TypeAt (cell_id, env_id, source, position) -> 183 + let obj = Js.Unsafe.obj [| 184 + ("type", Js.Unsafe.inject (Js.string "type_at")); 185 + ("cell_id", Js.Unsafe.inject cell_id); 186 + ("env_id", Js.Unsafe.inject (Js.string env_id)); 187 + ("source", Js.Unsafe.inject (Js.string source)); 188 + ("position", Js.Unsafe.inject position); 189 + |] in 190 + Js.to_string (Json.output obj) 191 + | `Errors (cell_id, env_id, source) -> 192 + let obj = Js.Unsafe.obj [| 193 + ("type", Js.Unsafe.inject (Js.string "errors")); 194 + ("cell_id", Js.Unsafe.inject cell_id); 195 + ("env_id", Js.Unsafe.inject (Js.string env_id)); 196 + ("source", Js.Unsafe.inject (Js.string source)); 197 + |] in 198 + Js.to_string (Json.output obj) 199 + | `CreateEnv env_id -> 200 + let obj = Js.Unsafe.obj [| 201 + ("type", Js.Unsafe.inject (Js.string "create_env")); 202 + ("env_id", Js.Unsafe.inject (Js.string env_id)); 203 + |] in 204 + Js.to_string (Json.output obj) 205 + | `DestroyEnv env_id -> 206 + let obj = Js.Unsafe.obj [| 207 + ("type", Js.Unsafe.inject (Js.string "destroy_env")); 208 + ("env_id", Js.Unsafe.inject (Js.string env_id)); 209 + |] in 210 + Js.to_string (Json.output obj) 211 + in 212 + Brr_worker.post t.worker (Js.string json) 213 + 214 + (** Wait for the worker to be ready *) 215 + let wait_ready t = 216 + if t.ready then Lwt.return_unit 217 + else 218 + let promise, resolver = Lwt.wait () in 219 + Queue.push (fun () -> Lwt.wakeup resolver ()) t.ready_waiters; 220 + promise 221 + 222 + (** Initialize the worker *) 223 + let init t config = 224 + let open Lwt.Infix in 225 + send t (`Init config); 226 + wait_ready t >>= fun () -> 227 + Lwt.return_unit 228 + 229 + (** Output result type *) 230 + type output = { 231 + cell_id : int; 232 + stdout : string; 233 + stderr : string; 234 + caml_ppf : string; 235 + mime_vals : Msg.mime_val list; 236 + } 237 + 238 + (** Evaluate OCaml code *) 239 + let eval t ?(env_id = "default") code = 240 + let open Lwt.Infix in 241 + wait_ready t >>= fun () -> 242 + let cell_id = next_cell_id t in 243 + let promise, resolver = Lwt.wait () in 244 + Hashtbl.add t.pending cell_id resolver; 245 + send t (`Eval (cell_id, env_id, code)); 246 + promise >>= fun msg -> 247 + match msg with 248 + | Msg.Output { cell_id; stdout; stderr; caml_ppf; mime_vals } -> 249 + Lwt.return { cell_id; stdout; stderr; caml_ppf; mime_vals } 250 + | Msg.EvalError { message; _ } -> 251 + Lwt.fail (EvalError message) 252 + | _ -> Lwt.fail (Failure "Unexpected response") 253 + 254 + (** Get completions *) 255 + let complete t ?(env_id = "default") source position = 256 + let open Lwt.Infix in 257 + wait_ready t >>= fun () -> 258 + let cell_id = next_cell_id t in 259 + let promise, resolver = Lwt.wait () in 260 + Hashtbl.add t.pending cell_id resolver; 261 + send t (`Complete (cell_id, env_id, source, position)); 262 + promise >>= fun msg -> 263 + match msg with 264 + | Msg.Completions { completions; _ } -> 265 + Lwt.return completions 266 + | Msg.EvalError { message; _ } -> 267 + Lwt.fail (EvalError message) 268 + | _ -> Lwt.fail (Failure "Unexpected response") 269 + 270 + (** Get type at position *) 271 + let type_at t ?(env_id = "default") source position = 272 + let open Lwt.Infix in 273 + wait_ready t >>= fun () -> 274 + let cell_id = next_cell_id t in 275 + let promise, resolver = Lwt.wait () in 276 + Hashtbl.add t.pending cell_id resolver; 277 + send t (`TypeAt (cell_id, env_id, source, position)); 278 + promise >>= fun msg -> 279 + match msg with 280 + | Msg.Types { types; _ } -> 281 + Lwt.return types 282 + | Msg.EvalError { message; _ } -> 283 + Lwt.fail (EvalError message) 284 + | _ -> Lwt.fail (Failure "Unexpected response") 285 + 286 + (** Get errors *) 287 + let errors t ?(env_id = "default") source = 288 + let open Lwt.Infix in 289 + wait_ready t >>= fun () -> 290 + let cell_id = next_cell_id t in 291 + let promise, resolver = Lwt.wait () in 292 + Hashtbl.add t.pending cell_id resolver; 293 + send t (`Errors (cell_id, env_id, source)); 294 + promise >>= fun msg -> 295 + match msg with 296 + | Msg.ErrorList { errors; _ } -> 297 + Lwt.return errors 298 + | Msg.EvalError { message; _ } -> 299 + Lwt.fail (EvalError message) 300 + | _ -> Lwt.fail (Failure "Unexpected response") 301 + 302 + (** Create environment *) 303 + let create_env t env_id = 304 + let open Lwt.Infix in 305 + wait_ready t >>= fun () -> 306 + let promise, resolver = Lwt.wait () in 307 + Hashtbl.add t.pending_env env_id resolver; 308 + send t (`CreateEnv env_id); 309 + promise >>= fun msg -> 310 + match msg with 311 + | Msg.EnvCreated _ -> Lwt.return_unit 312 + | Msg.InitError { message } -> Lwt.fail (InitError message) 313 + | _ -> Lwt.fail (Failure "Unexpected response") 314 + 315 + (** Destroy environment *) 316 + let destroy_env t env_id = 317 + let open Lwt.Infix in 318 + wait_ready t >>= fun () -> 319 + let promise, resolver = Lwt.wait () in 320 + Hashtbl.add t.pending_env env_id resolver; 321 + send t (`DestroyEnv env_id); 322 + promise >>= fun msg -> 323 + match msg with 324 + | Msg.EnvDestroyed _ -> Lwt.return_unit 325 + | Msg.InitError { message } -> Lwt.fail (InitError message) 326 + | _ -> Lwt.fail (Failure "Unexpected response") 327 + 328 + (** Terminate the worker *) 329 + let terminate t = 330 + Brr_worker.terminate t.worker
+3 -4
test/cram/directives.t/run.t
··· 11 11 $ unix_client init '{ findlib_requires:[], execute: true }' 12 12 N 13 13 $ unix_client setup '' 14 - {mime_vals:[];stderr:S(error while evaluating #enable "pretty";; 15 - error while evaluating #disable "shortvar";;);stdout:S(OCaml version 5.4.0 16 - Unknown directive enable. 17 - Unknown directive disable.)} 14 + {mime_vals:[];stderr:S(Environment already set up)} 18 15 19 16 ============================================== 20 17 SECTION 1: Basic Code Execution (Baseline) ··· 607 604 js_top_worker (version: 0.0.1) 608 605 js_top_worker-bin (version: n/a) 609 606 js_top_worker-client (version: 0.0.1) 607 + js_top_worker-client.__private__ (version: n/a) 608 + js_top_worker-client.__private__.js_top_worker_client_msg (version: 0.0.1) 610 609 js_top_worker-client_fut (version: 0.0.1) 611 610 js_top_worker-rpc (version: 0.0.1) 612 611 js_top_worker-rpc.__private__ (version: n/a)