ocaml bindings for chibi-scheme VM
0
fork

Configure Feed

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

Add README and .beads config

+603
+65
.beads/.gitignore
··· 1 + # Dolt database (managed by Dolt, not git) 2 + dolt/ 3 + dolt-access.lock 4 + 5 + # Runtime files 6 + bd.sock 7 + bd.sock.startlock 8 + sync-state.json 9 + last-touched 10 + .exclusive-lock 11 + 12 + # Daemon runtime (lock, log, pid) 13 + daemon.* 14 + 15 + # Interactions log (runtime, not versioned) 16 + interactions.jsonl 17 + 18 + # Push state (runtime, per-machine) 19 + push-state.json 20 + 21 + # Lock files (various runtime locks) 22 + *.lock 23 + 24 + # Local version tracking (prevents upgrade notification spam after git ops) 25 + .local_version 26 + 27 + # Worktree redirect file (contains relative path to main repo's .beads/) 28 + # Must not be committed as paths would be wrong in other clones 29 + redirect 30 + 31 + # Sync state (local-only, per-machine) 32 + # These files are machine-specific and should not be shared across clones 33 + .sync.lock 34 + export-state/ 35 + 36 + # Ephemeral store (SQLite - wisps/molecules, intentionally not versioned) 37 + ephemeral.sqlite3 38 + ephemeral.sqlite3-journal 39 + ephemeral.sqlite3-wal 40 + ephemeral.sqlite3-shm 41 + 42 + # Dolt server management (auto-started by bd) 43 + dolt-server.pid 44 + dolt-server.log 45 + dolt-server.lock 46 + dolt-server.port 47 + 48 + # Corrupt backup directories (created by bd doctor --fix recovery) 49 + *.corrupt.backup/ 50 + 51 + # Backup data (auto-exported JSONL, local-only) 52 + backup/ 53 + 54 + # Legacy files (from pre-Dolt versions) 55 + *.db 56 + *.db?* 57 + *.db-journal 58 + *.db-wal 59 + *.db-shm 60 + db.sqlite 61 + bd.db 62 + # NOTE: Do NOT add negation patterns here. 63 + # They would override fork protection in .git/info/exclude. 64 + # Config files (metadata.json, config.yaml) are tracked by git by default 65 + # since no pattern above ignores them.
+538
README.md
··· 1 + # chibi-ocaml 2 + 3 + OCaml bindings for [chibi-scheme](https://github.com/ashinn/chibi-scheme), a 4 + small, embeddable R7RS Scheme implementation. 5 + 6 + chibi-ocaml lets you embed one or more sandboxed Scheme virtual machines in your 7 + OCaml programs. The chibi-scheme runtime is vendored and compiled automatically 8 + -- no system-level installation of chibi-scheme is required. But a C compiler capable of building chibi-scheme is required. 9 + 10 + ## Features 11 + 12 + - **Full R7RS Scheme** -- the complete chibi-scheme 0.12.0 interpreter 13 + - **Multiple independent VMs** -- each `Context.t` has its own heap 14 + - **Configurable memory limits** -- set initial and maximum heap sizes 15 + - **Capability-based sandboxing** -- whitelist file, network, process, and I/O access 16 + - **Foreign functions** -- register OCaml functions callable from Scheme (arity 0--6) 17 + - **Seq-based streaming** -- feed OCaml `Seq.t` data into Scheme processors; produce streams with OCaml 5 effects via `Stream.from_producer` 18 + - **Bidirectional value conversion** -- construct and extract all Scheme types 19 + - **Output capture** -- redirect and capture Scheme I/O from OCaml 20 + - **External dependencies** -- C compiler, Ocaml > 5, and dune 21 + 22 + ## Installation 23 + 24 + ``` 25 + opam install chibi-ocaml 26 + ``` 27 + 28 + Or from source: 29 + 30 + ```sh 31 + git clone https://github.com/username/chibi-ocaml.git 32 + cd chibi-ocaml 33 + dune build 34 + dune runtest # 97 binding tests + chibi's own 8000+ tests 35 + ``` 36 + 37 + ## Quick Start 38 + 39 + Add to your `dune` file: 40 + 41 + ```sexp 42 + (executable 43 + (name my_program) 44 + (libraries chibi-ocaml)) 45 + ``` 46 + 47 + ### Hello World 48 + 49 + ```ocaml 50 + open Chibi_ocaml.Chibi 51 + 52 + let () = 53 + with_context (fun ctx -> 54 + let result = Eval.to_int ctx "(+ 2 3)" in 55 + Printf.printf "2 + 3 = %d\n" result) 56 + ``` 57 + 58 + ### Define and Call Functions 59 + 60 + ```ocaml 61 + open Chibi_ocaml.Chibi 62 + 63 + let () = 64 + with_context (fun ctx -> 65 + (* Define a Scheme function *) 66 + let _ = Eval.string ctx "(define (factorial n) 67 + (if (<= n 1) 1 (* n (factorial (- n 1)))))" in 68 + let result = Eval.to_int ctx "(factorial 10)" in 69 + Printf.printf "10! = %d\n" result) 70 + ``` 71 + 72 + ### Register OCaml Functions in Scheme 73 + 74 + ```ocaml 75 + open Chibi_ocaml.Chibi 76 + 77 + let () = 78 + with_context (fun ctx -> 79 + (* OCaml function callable from Scheme *) 80 + Env.define_fn2 ctx "ocaml-add" (fun a b -> 81 + Value.of_int ctx (Sexp.to_int a + Sexp.to_int b)); 82 + 83 + let result = Eval.to_int ctx "(ocaml-add 100 200)" in 84 + Printf.printf "Result: %d\n" result) (* 300 *) 85 + ``` 86 + 87 + ### Multiple Independent VMs 88 + 89 + ```ocaml 90 + open Chibi_ocaml.Chibi 91 + 92 + let () = 93 + let vm1 = Context.create () in 94 + let vm2 = Context.create () in 95 + let _ = Eval.string vm1 "(define x 42)" in 96 + let _ = Eval.string vm2 "(define x 99)" in 97 + Printf.printf "VM1: x = %d\n" (Eval.to_int vm1 "x"); (* 42 *) 98 + Printf.printf "VM2: x = %d\n" (Eval.to_int vm2 "x"); (* 99 *) 99 + Context.destroy vm1; 100 + Context.destroy vm2 101 + ``` 102 + 103 + ### Memory-Limited Sandboxed VM 104 + 105 + ```ocaml 106 + open Chibi_ocaml.Chibi 107 + 108 + let () = 109 + let config = Context.sandboxed_config 110 + ~heap_size:(1024 * 1024) (* 1 MB initial *) 111 + ~max_heap_size:(8 * 1024 * 1024) (* 8 MB maximum *) 112 + ~capabilities:[Sandbox.Module_import] (* only allow module imports *) 113 + () in 114 + with_context ~config (fun ctx -> 115 + (* Pure computation works *) 116 + let result = Eval.to_int ctx "(apply + '(1 2 3 4 5))" in 117 + Printf.printf "Sum: %d\n" result; 118 + 119 + (* File/network/process access is blocked *) 120 + let v = Eval.string ctx "open-input-file" in 121 + assert (Sexp.is_void v)) (* overridden with void *) 122 + ``` 123 + 124 + ### Streaming Data from OCaml to Scheme 125 + 126 + ```ocaml 127 + open Chibi_ocaml.Chibi 128 + 129 + let () = 130 + with_context (fun ctx -> 131 + (* Stream integers from OCaml into a Scheme fold *) 132 + let numbers = List.to_seq [1; 2; 3; 4; 5; 6; 7; 8; 9; 10] in 133 + let stream = Stream.of_int_seq ctx numbers in 134 + let sum = Stream.fold ctx 135 + ~expr:"(lambda (x acc) (+ acc x))" 136 + ~init:(Value.of_int ctx 0) 137 + stream in 138 + Printf.printf "Sum: %d\n" (Sexp.to_int sum); (* 55 *) 139 + 140 + (* Map a Scheme function over an OCaml sequence *) 141 + let words = List.to_seq ["hello"; "world"] in 142 + let stream = Stream.of_string_seq ctx words in 143 + let proc = Eval.string ctx "(lambda (s) (string-length s))" in 144 + let lengths = Stream.map ctx ~proc stream in 145 + let results = Stream.to_int_list lengths in 146 + Printf.printf "Lengths: %d, %d\n" (List.nth results 0) (List.nth results 1)) 147 + ``` 148 + 149 + ### Producing a Stream with Effects 150 + 151 + `Stream.from_producer` is the one place effects are used. It lets you write an 152 + imperative producer that calls `Effect.perform (Stream.Yield v)` and converts it 153 + into a lazy `Seq.t`: 154 + 155 + ```ocaml 156 + open Chibi_ocaml.Chibi 157 + 158 + let () = 159 + with_context (fun ctx -> 160 + let producer = Stream.from_producer (fun () -> 161 + for i = 1 to 5 do 162 + Effect.perform (Stream.Yield (Value.of_int ctx i)) 163 + done) in 164 + let results = Stream.to_int_list producer in 165 + Printf.printf "Produced: %s\n" 166 + (String.concat ", " (List.map string_of_int results))) 167 + ``` 168 + 169 + ### Capturing Scheme Output 170 + 171 + ```ocaml 172 + open Chibi_ocaml.Chibi 173 + 174 + let () = 175 + with_context (fun ctx -> 176 + let (result, stdout, stderr) = Io.capture ctx (fun () -> 177 + Eval.string ctx {|(begin 178 + (display "Hello from Scheme!") 179 + (newline) 180 + 42)|}) in 181 + Printf.printf "stdout: %s" stdout; (* "Hello from Scheme!\n" *) 182 + Printf.printf "result: %d\n" 183 + (match result with Ok v -> Sexp.to_int v | Error _ -> -1)) 184 + ``` 185 + 186 + ### Pattern Matching on Scheme Values 187 + 188 + ```ocaml 189 + open Chibi_ocaml.Chibi 190 + 191 + let rec scheme_to_string ctx v = 192 + match Sexp.classify v with 193 + | Sexp.Null -> "()" 194 + | Sexp.Boolean b -> if b then "#t" else "#f" 195 + | Sexp.Fixnum n -> string_of_int n 196 + | Sexp.Flonum f -> string_of_float f 197 + | Sexp.String s -> Printf.sprintf "%S" s 198 + | Sexp.Symbol s -> s 199 + | Sexp.Pair -> 200 + let items = Sexp.to_list v in 201 + "(" ^ String.concat " " (List.map (scheme_to_string ctx) items) ^ ")" 202 + | Sexp.Vector -> 203 + let items = Array.to_list (Sexp.to_array v) in 204 + "#(" ^ String.concat " " (List.map (scheme_to_string ctx) items) ^ ")" 205 + | Sexp.Procedure -> "#<procedure>" 206 + | _ -> Eval.write ctx v 207 + ``` 208 + 209 + ## API Reference 210 + 211 + The library is accessed through the `Chibi_ocaml.Chibi` module, which contains 212 + the following submodules: 213 + 214 + ### Exceptions 215 + 216 + | Exception | Raised when | 217 + |---|---| 218 + | `Chibi_error of string` | Scheme evaluation fails, or a chibi API operation errors | 219 + | `Context_destroyed` | Operating on a context that has been destroyed | 220 + | `Type_error of string` | Extracting a value of the wrong type from an sexp | 221 + 222 + ### `Sexp` -- Scheme Values 223 + 224 + `Sexp.t` is the opaque type representing any Scheme value. 225 + 226 + **Classification:** 227 + 228 + ```ocaml 229 + val classify : t -> tag 230 + ``` 231 + 232 + Returns a `tag` variant for pattern matching: 233 + 234 + ```ocaml 235 + type tag = 236 + | Null | Boolean of bool | Fixnum of int | Flonum of float 237 + | Char of int | String of string | Symbol of string 238 + | Pair | Vector | Bytevector | Procedure | Port | Void | Eof | Other 239 + ``` 240 + 241 + **Predicates:** `is_null`, `is_pair`, `is_symbol`, `is_string`, `is_fixnum`, 242 + `is_flonum`, `is_number`, `is_boolean`, `is_char`, `is_vector`, 243 + `is_bytevector`, `is_procedure`, `is_port`, `is_void`, `is_eof`, `is_true` 244 + 245 + **Pair access:** `car`, `cdr`, `caar`, `cadr`, `cdar`, `cddr` 246 + 247 + **Extraction** (raise `Type_error` on type mismatch): 248 + `to_int`, `to_float`, `to_string`, `to_symbol`, `to_bool`, `to_char`, `to_bytes` 249 + 250 + **Collection conversion:** 251 + - `to_list : t -> t list` -- Scheme list to OCaml list 252 + - `to_array : t -> t array` -- Scheme vector to OCaml array 253 + - `list_length : t -> int` 254 + - `equal : t -> t -> bool` -- pointer equality (`eq?`) 255 + 256 + ### `Context` -- Scheme VM Instance 257 + 258 + Each context is an independent Scheme VM with its own heap. 259 + 260 + ```ocaml 261 + type config = { 262 + heap_size : int; (* initial heap bytes, 0 = default ~2MB *) 263 + max_heap_size : int; (* max heap bytes, 0 = unlimited *) 264 + sandbox : Sandbox.t; (* capability restrictions *) 265 + module_paths : string list; (* additional module search paths *) 266 + } 267 + ``` 268 + 269 + | Function | Description | 270 + |---|---| 271 + | `create ?config ()` | Create a new VM. Default: full access, default heap. | 272 + | `destroy t` | Free all resources. Safe to call multiple times. | 273 + | `is_alive t` | Check if not destroyed. | 274 + | `heap_size t` | Current heap size in bytes. | 275 + | `heap_max_size t` | Max allowed heap (0 = unlimited). | 276 + | `gc t` | Trigger garbage collection. Returns approx. bytes freed. | 277 + | `default_config` | Full access, default heap, no limits. | 278 + | `sandboxed_config ?heap_size ?max_heap_size ?capabilities ()` | Restricted config. | 279 + 280 + Contexts are also cleaned up by the OCaml GC finalizer if you forget to call 281 + `destroy`, but explicit cleanup is recommended. 282 + 283 + ### `Value` -- Constructing Scheme Values 284 + 285 + All functions take `Context.t` as the first argument. 286 + 287 + | Function | Creates | 288 + |---|---| 289 + | `void ctx` | Scheme void | 290 + | `null ctx` | Empty list `'()` | 291 + | `eof ctx` | EOF object | 292 + | `of_bool ctx b` | `#t` or `#f` | 293 + | `of_int ctx n` | Fixnum | 294 + | `of_float ctx f` | Flonum | 295 + | `of_string ctx s` | Scheme string | 296 + | `of_symbol ctx s` | Symbol | 297 + | `of_char ctx c` | Character (from OCaml `char`) | 298 + | `of_char_code ctx n` | Character (from Unicode code point) | 299 + | `of_bytes ctx b` | Bytevector | 300 + | `cons ctx a b` | Pair | 301 + | `of_list ctx items` | Proper list from OCaml list of sexp | 302 + | `of_array ctx arr` | Vector from OCaml array of sexp | 303 + | `of_int_list ctx ns` | List of fixnums | 304 + | `of_string_list ctx ss` | List of strings | 305 + 306 + ### `Eval` -- Expression Evaluation 307 + 308 + | Function | Description | 309 + |---|---| 310 + | `string ctx code` | Evaluate a single Scheme expression from a string. | 311 + | `sexp ctx s` | Evaluate a parsed s-expression. | 312 + | `apply ctx proc args` | Apply a procedure to a list of arguments. | 313 + | `load ctx path` | Load a file (searches module path). | 314 + | `load_direct ctx path` | Load a file by absolute path. Handles top-level `import`. | 315 + | `read ctx code` | Parse a string into an s-expression without evaluating. | 316 + | `to_string ctx code` | Eval and return `write` representation. | 317 + | `to_int ctx code` | Eval and extract integer. | 318 + | `to_float ctx code` | Eval and extract float. | 319 + | `to_bool ctx code` | Eval and extract boolean. | 320 + | `write ctx sexp` | Convert sexp to machine-readable string. | 321 + | `display ctx sexp` | Convert sexp to human-readable string. | 322 + 323 + ### `Env` -- Environment Bindings 324 + 325 + | Function | Description | 326 + |---|---| 327 + | `define ctx name value` | Define a binding in the current environment. | 328 + | `lookup ctx name` | Look up a binding. Returns `Sexp.t option`. | 329 + | `lookup_exn ctx name` | Look up a binding. Raises `Chibi_error` if unbound. | 330 + | `define_function ctx name arity f` | Register an OCaml function callable from Scheme. | 331 + | `define_fn0` .. `define_fn6` | Convenience: typed wrappers for fixed arity. | 332 + 333 + Foreign functions receive arguments as `Sexp.t list` (for `define_function`) or 334 + as positional `Sexp.t` arguments (for `define_fnN`). They must return a 335 + `Sexp.t`. Maximum arity is 6. 336 + 337 + ### `Sandbox` -- Capability-Based Security 338 + 339 + The sandbox uses a whitelist model: you specify which capabilities to **grant**. 340 + Everything not explicitly granted is denied. 341 + 342 + ```ocaml 343 + type capability = 344 + | File_read (* reading files from the filesystem *) 345 + | File_write (* writing/modifying files and directories *) 346 + | Net_access (* sockets, HTTP, DNS *) 347 + | Process_exec (* fork, exec, system, kill, signals, shell *) 348 + | Env_access (* environment variables, user/host info, PIDs *) 349 + | Module_import (* importing Scheme modules via (import ...) *) 350 + | Standard_io (* access to stdin/stdout/stderr *) 351 + ``` 352 + 353 + | Function | Description | 354 + |---|---| 355 + | `none` | Maximum sandbox: all capabilities denied. | 356 + | `full` | No restrictions: all capabilities granted. | 357 + | `allow caps` | Grant only the listed capabilities. | 358 + | `has_capability sandbox cap` | Query a capability. | 359 + 360 + #### What each capability controls 361 + 362 + **`File_read`** -- When denied, blocks ~50 bindings including: 363 + `open-input-file`, `file-exists?`, `file->string`, `directory-files`, 364 + `file-size`, `file-regular?`, `file-directory?`, `current-directory`, 365 + `include`, `load`, and all `(chibi filesystem)` stat/query operations. 366 + 367 + **`File_write`** -- When denied, blocks ~30 bindings including: 368 + `open-output-file`, `delete-file`, `rename-file`, `create-directory`, 369 + `delete-directory`, `chmod`, `chown`, `link-file`, `symbolic-link-file`, 370 + `change-directory`, and all POSIX file descriptor write operations. 371 + 372 + **`Net_access`** -- When denied, blocks ~45 bindings including: 373 + `socket`, `connect`, `bind`, `accept`, `listen`, `send`, `receive`, 374 + `open-net-io`, `http-get`, `http-post`, `http-put`, `http-delete`, 375 + `run-net-server`, `run-http-server`, and all `(chibi net)` operations. 376 + 377 + **`Process_exec`** -- When denied, blocks ~45 bindings including: 378 + `fork`, `execute`, `kill`, `waitpid`, `system`, `exit`, `emergency-exit`, 379 + `sleep`, `alarm`, `call-with-process-io`, `process->string`, all 380 + `(chibi shell)` operations, signal handling, and privilege escalation 381 + (`set-current-user-id!`, `set-root-directory!`, etc.). 382 + 383 + **`Env_access`** -- When denied, blocks ~25 bindings including: 384 + `get-environment-variable`, `get-environment-variables`, `command-line`, 385 + `get-host-name`, `user-information`, `current-user-id`, `current-process-id`, 386 + and all `(chibi system)` user/group query operations. 387 + 388 + **`Standard_io`** -- When denied, redirects `current-input-port`, 389 + `current-output-port`, and `current-error-port` to null string ports. 390 + Scheme code can still use `display`/`write`/`read` but the data goes nowhere. 391 + 392 + **`Module_import`** -- When denied, the standard R7RS environment is not 393 + loaded at all. The context starts with only chibi-scheme primitives. 394 + 395 + #### Example: computation-only sandbox 396 + 397 + ```ocaml 398 + let config = Context.sandboxed_config 399 + ~heap_size:(2 * 1024 * 1024) 400 + ~max_heap_size:(16 * 1024 * 1024) 401 + ~capabilities:[Sandbox.Module_import] (* only allow importing modules *) 402 + () in 403 + with_context ~config (fun ctx -> 404 + (* Pure computation works *) 405 + let _ = Eval.string ctx "(define (fib n) (if (< n 2) n (+ (fib (- n 1)) (fib (- n 2)))))" in 406 + let result = Eval.to_int ctx "(fib 20)" in 407 + Printf.printf "fib(20) = %d\n" result; 408 + 409 + (* File access is blocked *) 410 + let v = Eval.string ctx "open-input-file" in 411 + assert (Sexp.is_void v); 412 + 413 + (* Network access is blocked *) 414 + let v = Eval.string ctx "socket" in 415 + assert (Sexp.is_void v)) 416 + ``` 417 + 418 + #### Security considerations 419 + 420 + The sandbox enforces restrictions by overriding dangerous bindings in the 421 + interaction environment with `void` after loading the standard library. This 422 + provides strong defense-in-depth: 423 + 424 + - **190+ dangerous bindings** are blocked across filesystem, network, process, 425 + environment, and privilege escalation categories. 426 + - **Memory limits** are enforced by chibi-scheme's heap allocator at the C level 427 + -- Scheme code cannot bypass `max_heap_size`. 428 + - **Standard I/O** is physically redirected to null ports when denied. 429 + 430 + However, the sandbox is **not a security boundary** in the formal sense: 431 + 432 + - If `Module_import` is enabled, Scheme code could potentially re-import 433 + blocked bindings from their source modules. For maximum security, grant only 434 + `Module_import` if you need the standard library, and avoid granting it if 435 + pure computation suffices. 436 + - For the strongest isolation, create a context with no capabilities at all, 437 + then use `Env.define` and `Env.define_function` from OCaml to provide only 438 + the specific operations your application needs. 439 + 440 + ### `Stream` -- Seq-Based Streaming 441 + 442 + The `Stream` module bridges OCaml `Seq.t` lazy sequences with Scheme processing 443 + functions. Most operations are plain sequence transforms; `from_producer` is the 444 + one function that uses OCaml 5 effects to turn an imperative yield-style 445 + producer into a lazy `Seq.t`. 446 + 447 + | Function | Description | 448 + |---|---| 449 + | `from_producer f` | Create a `Sexp.t Seq.t` from a function using `Effect.perform (Yield v)`. | 450 + | `feed ctx ~proc ~init seq` | Fold a Scheme procedure over a sequence. | 451 + | `fold ctx ~expr ~init seq` | Like `feed` but takes the procedure as a Scheme expression string. | 452 + | `map ctx ~proc seq` | Map a Scheme procedure over a sequence. | 453 + | `filter ctx ~pred seq` | Filter a sequence with a Scheme predicate. | 454 + | `of_channel ctx ic` | Read lines as Scheme strings. | 455 + | `of_int_seq ctx seq` | Convert `int Seq.t` to `Sexp.t Seq.t`. | 456 + | `of_string_seq ctx seq` | Convert `string Seq.t` to `Sexp.t Seq.t`. | 457 + | `to_scheme_list ctx seq` | Collect into a Scheme list. | 458 + | `to_int_list seq` | Collect and extract integers. | 459 + | `to_string_list seq` | Collect and extract strings. | 460 + 461 + ### `Io` -- Output Capture and Port Redirection 462 + 463 + | Function | Description | 464 + |---|---| 465 + | `capture ctx f` | Redirect stdout/stderr, run `f`, return `(result, stdout, stderr)`. Ports are restored afterward. | 466 + | `set_input_string ctx s` | Set current input port to read from a string. | 467 + | `redirect_output ctx` | Redirect stdout to string port. Returns thunk to get output. | 468 + 469 + ### `with_context` 470 + 471 + ```ocaml 472 + val with_context : ?config:Context.config -> (Context.t -> 'a) -> 'a 473 + ``` 474 + 475 + Creates a context, runs the function, and destroys the context when done. The 476 + context is always cleaned up, even if the function raises an exception. 477 + 478 + ## Memory Management 479 + 480 + chibi-ocaml coordinates two garbage collectors -- OCaml's GC and chibi-scheme's 481 + mark-and-sweep GC: 482 + 483 + - **Scheme values held by OCaml** are protected from chibi's GC via 484 + `sexp_preserve_object`. When the OCaml wrapper is finalized, the protection is 485 + released. 486 + - **Contexts** are reference-counted via the `alive` flag. Destroying a context 487 + invalidates all associated sexp wrappers. Using a sexp from a destroyed 488 + context raises `Context_destroyed`. 489 + - **`with_context`** provides bracket-style cleanup. Explicit `destroy` is also 490 + supported and is idempotent. 491 + 492 + You do not need to manually manage Scheme value lifetimes. Values are 493 + automatically released when they become unreachable from OCaml. 494 + 495 + ## Thread Safety 496 + 497 + Each `Context.t` is an independent VM with its own heap. Different contexts can 498 + be used from different OCaml domains (or OS threads) without synchronization. 499 + 500 + However, **a single context must not be accessed concurrently** from multiple 501 + threads or domains. chibi-scheme's internal state is not thread-safe. 502 + 503 + ## Building from Source 504 + 505 + Requirements: 506 + - OCaml >= 5.1 507 + - dune >= 3.22 508 + - A C compiler (gcc or clang) 509 + - `-lm` and `-ldl` (standard on Linux; macOS provides these by default) 510 + 511 + ```sh 512 + dune build # compile 513 + dune runtest # run OCaml binding tests (97 tests) 514 + dune exec bin/main.exe # run demo 515 + dune exec test_runner/run_chibi_tests.exe # run chibi's own test suite 516 + ``` 517 + 518 + ## Test Results 519 + 520 + The chibi-scheme test suite passes through our bindings with the same results as 521 + the native chibi-scheme binary: 522 + 523 + | Suite | Result | 524 + |---|---| 525 + | Basic tests (11) | 11/11 | 526 + | R5RS | 189/189 | 527 + | R7RS | 1224/1225 (1 embedding-specific: `command-line`) | 528 + | Syntax | 12/12 | 529 + | Unicode | 18/18 | 530 + | Division | 294/304 (chibi bignum bug, same as native) | 531 + | Library tests (all SRFIs + chibi libs) | 6455/6455 | 532 + 533 + **Total: 8197/8204** (99.9%). All failures are chibi-scheme's own bugs or 534 + inherent embedding differences, not binding issues. 535 + 536 + ## License 537 + 538 + BSD-3-Clause. chibi-scheme is also BSD-licensed.