···11+# Dolt database (managed by Dolt, not git)
22+dolt/
33+dolt-access.lock
44+55+# Runtime files
66+bd.sock
77+bd.sock.startlock
88+sync-state.json
99+last-touched
1010+.exclusive-lock
1111+1212+# Daemon runtime (lock, log, pid)
1313+daemon.*
1414+1515+# Interactions log (runtime, not versioned)
1616+interactions.jsonl
1717+1818+# Push state (runtime, per-machine)
1919+push-state.json
2020+2121+# Lock files (various runtime locks)
2222+*.lock
2323+2424+# Local version tracking (prevents upgrade notification spam after git ops)
2525+.local_version
2626+2727+# Worktree redirect file (contains relative path to main repo's .beads/)
2828+# Must not be committed as paths would be wrong in other clones
2929+redirect
3030+3131+# Sync state (local-only, per-machine)
3232+# These files are machine-specific and should not be shared across clones
3333+.sync.lock
3434+export-state/
3535+3636+# Ephemeral store (SQLite - wisps/molecules, intentionally not versioned)
3737+ephemeral.sqlite3
3838+ephemeral.sqlite3-journal
3939+ephemeral.sqlite3-wal
4040+ephemeral.sqlite3-shm
4141+4242+# Dolt server management (auto-started by bd)
4343+dolt-server.pid
4444+dolt-server.log
4545+dolt-server.lock
4646+dolt-server.port
4747+4848+# Corrupt backup directories (created by bd doctor --fix recovery)
4949+*.corrupt.backup/
5050+5151+# Backup data (auto-exported JSONL, local-only)
5252+backup/
5353+5454+# Legacy files (from pre-Dolt versions)
5555+*.db
5656+*.db?*
5757+*.db-journal
5858+*.db-wal
5959+*.db-shm
6060+db.sqlite
6161+bd.db
6262+# NOTE: Do NOT add negation patterns here.
6363+# They would override fork protection in .git/info/exclude.
6464+# Config files (metadata.json, config.yaml) are tracked by git by default
6565+# since no pattern above ignores them.
+538
README.md
···11+# chibi-ocaml
22+33+OCaml bindings for [chibi-scheme](https://github.com/ashinn/chibi-scheme), a
44+small, embeddable R7RS Scheme implementation.
55+66+chibi-ocaml lets you embed one or more sandboxed Scheme virtual machines in your
77+OCaml programs. The chibi-scheme runtime is vendored and compiled automatically
88+-- no system-level installation of chibi-scheme is required. But a C compiler capable of building chibi-scheme is required.
99+1010+## Features
1111+1212+- **Full R7RS Scheme** -- the complete chibi-scheme 0.12.0 interpreter
1313+- **Multiple independent VMs** -- each `Context.t` has its own heap
1414+- **Configurable memory limits** -- set initial and maximum heap sizes
1515+- **Capability-based sandboxing** -- whitelist file, network, process, and I/O access
1616+- **Foreign functions** -- register OCaml functions callable from Scheme (arity 0--6)
1717+- **Seq-based streaming** -- feed OCaml `Seq.t` data into Scheme processors; produce streams with OCaml 5 effects via `Stream.from_producer`
1818+- **Bidirectional value conversion** -- construct and extract all Scheme types
1919+- **Output capture** -- redirect and capture Scheme I/O from OCaml
2020+- **External dependencies** -- C compiler, Ocaml > 5, and dune
2121+2222+## Installation
2323+2424+```
2525+opam install chibi-ocaml
2626+```
2727+2828+Or from source:
2929+3030+```sh
3131+git clone https://github.com/username/chibi-ocaml.git
3232+cd chibi-ocaml
3333+dune build
3434+dune runtest # 97 binding tests + chibi's own 8000+ tests
3535+```
3636+3737+## Quick Start
3838+3939+Add to your `dune` file:
4040+4141+```sexp
4242+(executable
4343+ (name my_program)
4444+ (libraries chibi-ocaml))
4545+```
4646+4747+### Hello World
4848+4949+```ocaml
5050+open Chibi_ocaml.Chibi
5151+5252+let () =
5353+ with_context (fun ctx ->
5454+ let result = Eval.to_int ctx "(+ 2 3)" in
5555+ Printf.printf "2 + 3 = %d\n" result)
5656+```
5757+5858+### Define and Call Functions
5959+6060+```ocaml
6161+open Chibi_ocaml.Chibi
6262+6363+let () =
6464+ with_context (fun ctx ->
6565+ (* Define a Scheme function *)
6666+ let _ = Eval.string ctx "(define (factorial n)
6767+ (if (<= n 1) 1 (* n (factorial (- n 1)))))" in
6868+ let result = Eval.to_int ctx "(factorial 10)" in
6969+ Printf.printf "10! = %d\n" result)
7070+```
7171+7272+### Register OCaml Functions in Scheme
7373+7474+```ocaml
7575+open Chibi_ocaml.Chibi
7676+7777+let () =
7878+ with_context (fun ctx ->
7979+ (* OCaml function callable from Scheme *)
8080+ Env.define_fn2 ctx "ocaml-add" (fun a b ->
8181+ Value.of_int ctx (Sexp.to_int a + Sexp.to_int b));
8282+8383+ let result = Eval.to_int ctx "(ocaml-add 100 200)" in
8484+ Printf.printf "Result: %d\n" result) (* 300 *)
8585+```
8686+8787+### Multiple Independent VMs
8888+8989+```ocaml
9090+open Chibi_ocaml.Chibi
9191+9292+let () =
9393+ let vm1 = Context.create () in
9494+ let vm2 = Context.create () in
9595+ let _ = Eval.string vm1 "(define x 42)" in
9696+ let _ = Eval.string vm2 "(define x 99)" in
9797+ Printf.printf "VM1: x = %d\n" (Eval.to_int vm1 "x"); (* 42 *)
9898+ Printf.printf "VM2: x = %d\n" (Eval.to_int vm2 "x"); (* 99 *)
9999+ Context.destroy vm1;
100100+ Context.destroy vm2
101101+```
102102+103103+### Memory-Limited Sandboxed VM
104104+105105+```ocaml
106106+open Chibi_ocaml.Chibi
107107+108108+let () =
109109+ let config = Context.sandboxed_config
110110+ ~heap_size:(1024 * 1024) (* 1 MB initial *)
111111+ ~max_heap_size:(8 * 1024 * 1024) (* 8 MB maximum *)
112112+ ~capabilities:[Sandbox.Module_import] (* only allow module imports *)
113113+ () in
114114+ with_context ~config (fun ctx ->
115115+ (* Pure computation works *)
116116+ let result = Eval.to_int ctx "(apply + '(1 2 3 4 5))" in
117117+ Printf.printf "Sum: %d\n" result;
118118+119119+ (* File/network/process access is blocked *)
120120+ let v = Eval.string ctx "open-input-file" in
121121+ assert (Sexp.is_void v)) (* overridden with void *)
122122+```
123123+124124+### Streaming Data from OCaml to Scheme
125125+126126+```ocaml
127127+open Chibi_ocaml.Chibi
128128+129129+let () =
130130+ with_context (fun ctx ->
131131+ (* Stream integers from OCaml into a Scheme fold *)
132132+ let numbers = List.to_seq [1; 2; 3; 4; 5; 6; 7; 8; 9; 10] in
133133+ let stream = Stream.of_int_seq ctx numbers in
134134+ let sum = Stream.fold ctx
135135+ ~expr:"(lambda (x acc) (+ acc x))"
136136+ ~init:(Value.of_int ctx 0)
137137+ stream in
138138+ Printf.printf "Sum: %d\n" (Sexp.to_int sum); (* 55 *)
139139+140140+ (* Map a Scheme function over an OCaml sequence *)
141141+ let words = List.to_seq ["hello"; "world"] in
142142+ let stream = Stream.of_string_seq ctx words in
143143+ let proc = Eval.string ctx "(lambda (s) (string-length s))" in
144144+ let lengths = Stream.map ctx ~proc stream in
145145+ let results = Stream.to_int_list lengths in
146146+ Printf.printf "Lengths: %d, %d\n" (List.nth results 0) (List.nth results 1))
147147+```
148148+149149+### Producing a Stream with Effects
150150+151151+`Stream.from_producer` is the one place effects are used. It lets you write an
152152+imperative producer that calls `Effect.perform (Stream.Yield v)` and converts it
153153+into a lazy `Seq.t`:
154154+155155+```ocaml
156156+open Chibi_ocaml.Chibi
157157+158158+let () =
159159+ with_context (fun ctx ->
160160+ let producer = Stream.from_producer (fun () ->
161161+ for i = 1 to 5 do
162162+ Effect.perform (Stream.Yield (Value.of_int ctx i))
163163+ done) in
164164+ let results = Stream.to_int_list producer in
165165+ Printf.printf "Produced: %s\n"
166166+ (String.concat ", " (List.map string_of_int results)))
167167+```
168168+169169+### Capturing Scheme Output
170170+171171+```ocaml
172172+open Chibi_ocaml.Chibi
173173+174174+let () =
175175+ with_context (fun ctx ->
176176+ let (result, stdout, stderr) = Io.capture ctx (fun () ->
177177+ Eval.string ctx {|(begin
178178+ (display "Hello from Scheme!")
179179+ (newline)
180180+ 42)|}) in
181181+ Printf.printf "stdout: %s" stdout; (* "Hello from Scheme!\n" *)
182182+ Printf.printf "result: %d\n"
183183+ (match result with Ok v -> Sexp.to_int v | Error _ -> -1))
184184+```
185185+186186+### Pattern Matching on Scheme Values
187187+188188+```ocaml
189189+open Chibi_ocaml.Chibi
190190+191191+let rec scheme_to_string ctx v =
192192+ match Sexp.classify v with
193193+ | Sexp.Null -> "()"
194194+ | Sexp.Boolean b -> if b then "#t" else "#f"
195195+ | Sexp.Fixnum n -> string_of_int n
196196+ | Sexp.Flonum f -> string_of_float f
197197+ | Sexp.String s -> Printf.sprintf "%S" s
198198+ | Sexp.Symbol s -> s
199199+ | Sexp.Pair ->
200200+ let items = Sexp.to_list v in
201201+ "(" ^ String.concat " " (List.map (scheme_to_string ctx) items) ^ ")"
202202+ | Sexp.Vector ->
203203+ let items = Array.to_list (Sexp.to_array v) in
204204+ "#(" ^ String.concat " " (List.map (scheme_to_string ctx) items) ^ ")"
205205+ | Sexp.Procedure -> "#<procedure>"
206206+ | _ -> Eval.write ctx v
207207+```
208208+209209+## API Reference
210210+211211+The library is accessed through the `Chibi_ocaml.Chibi` module, which contains
212212+the following submodules:
213213+214214+### Exceptions
215215+216216+| Exception | Raised when |
217217+|---|---|
218218+| `Chibi_error of string` | Scheme evaluation fails, or a chibi API operation errors |
219219+| `Context_destroyed` | Operating on a context that has been destroyed |
220220+| `Type_error of string` | Extracting a value of the wrong type from an sexp |
221221+222222+### `Sexp` -- Scheme Values
223223+224224+`Sexp.t` is the opaque type representing any Scheme value.
225225+226226+**Classification:**
227227+228228+```ocaml
229229+val classify : t -> tag
230230+```
231231+232232+Returns a `tag` variant for pattern matching:
233233+234234+```ocaml
235235+type tag =
236236+ | Null | Boolean of bool | Fixnum of int | Flonum of float
237237+ | Char of int | String of string | Symbol of string
238238+ | Pair | Vector | Bytevector | Procedure | Port | Void | Eof | Other
239239+```
240240+241241+**Predicates:** `is_null`, `is_pair`, `is_symbol`, `is_string`, `is_fixnum`,
242242+`is_flonum`, `is_number`, `is_boolean`, `is_char`, `is_vector`,
243243+`is_bytevector`, `is_procedure`, `is_port`, `is_void`, `is_eof`, `is_true`
244244+245245+**Pair access:** `car`, `cdr`, `caar`, `cadr`, `cdar`, `cddr`
246246+247247+**Extraction** (raise `Type_error` on type mismatch):
248248+`to_int`, `to_float`, `to_string`, `to_symbol`, `to_bool`, `to_char`, `to_bytes`
249249+250250+**Collection conversion:**
251251+- `to_list : t -> t list` -- Scheme list to OCaml list
252252+- `to_array : t -> t array` -- Scheme vector to OCaml array
253253+- `list_length : t -> int`
254254+- `equal : t -> t -> bool` -- pointer equality (`eq?`)
255255+256256+### `Context` -- Scheme VM Instance
257257+258258+Each context is an independent Scheme VM with its own heap.
259259+260260+```ocaml
261261+type config = {
262262+ heap_size : int; (* initial heap bytes, 0 = default ~2MB *)
263263+ max_heap_size : int; (* max heap bytes, 0 = unlimited *)
264264+ sandbox : Sandbox.t; (* capability restrictions *)
265265+ module_paths : string list; (* additional module search paths *)
266266+}
267267+```
268268+269269+| Function | Description |
270270+|---|---|
271271+| `create ?config ()` | Create a new VM. Default: full access, default heap. |
272272+| `destroy t` | Free all resources. Safe to call multiple times. |
273273+| `is_alive t` | Check if not destroyed. |
274274+| `heap_size t` | Current heap size in bytes. |
275275+| `heap_max_size t` | Max allowed heap (0 = unlimited). |
276276+| `gc t` | Trigger garbage collection. Returns approx. bytes freed. |
277277+| `default_config` | Full access, default heap, no limits. |
278278+| `sandboxed_config ?heap_size ?max_heap_size ?capabilities ()` | Restricted config. |
279279+280280+Contexts are also cleaned up by the OCaml GC finalizer if you forget to call
281281+`destroy`, but explicit cleanup is recommended.
282282+283283+### `Value` -- Constructing Scheme Values
284284+285285+All functions take `Context.t` as the first argument.
286286+287287+| Function | Creates |
288288+|---|---|
289289+| `void ctx` | Scheme void |
290290+| `null ctx` | Empty list `'()` |
291291+| `eof ctx` | EOF object |
292292+| `of_bool ctx b` | `#t` or `#f` |
293293+| `of_int ctx n` | Fixnum |
294294+| `of_float ctx f` | Flonum |
295295+| `of_string ctx s` | Scheme string |
296296+| `of_symbol ctx s` | Symbol |
297297+| `of_char ctx c` | Character (from OCaml `char`) |
298298+| `of_char_code ctx n` | Character (from Unicode code point) |
299299+| `of_bytes ctx b` | Bytevector |
300300+| `cons ctx a b` | Pair |
301301+| `of_list ctx items` | Proper list from OCaml list of sexp |
302302+| `of_array ctx arr` | Vector from OCaml array of sexp |
303303+| `of_int_list ctx ns` | List of fixnums |
304304+| `of_string_list ctx ss` | List of strings |
305305+306306+### `Eval` -- Expression Evaluation
307307+308308+| Function | Description |
309309+|---|---|
310310+| `string ctx code` | Evaluate a single Scheme expression from a string. |
311311+| `sexp ctx s` | Evaluate a parsed s-expression. |
312312+| `apply ctx proc args` | Apply a procedure to a list of arguments. |
313313+| `load ctx path` | Load a file (searches module path). |
314314+| `load_direct ctx path` | Load a file by absolute path. Handles top-level `import`. |
315315+| `read ctx code` | Parse a string into an s-expression without evaluating. |
316316+| `to_string ctx code` | Eval and return `write` representation. |
317317+| `to_int ctx code` | Eval and extract integer. |
318318+| `to_float ctx code` | Eval and extract float. |
319319+| `to_bool ctx code` | Eval and extract boolean. |
320320+| `write ctx sexp` | Convert sexp to machine-readable string. |
321321+| `display ctx sexp` | Convert sexp to human-readable string. |
322322+323323+### `Env` -- Environment Bindings
324324+325325+| Function | Description |
326326+|---|---|
327327+| `define ctx name value` | Define a binding in the current environment. |
328328+| `lookup ctx name` | Look up a binding. Returns `Sexp.t option`. |
329329+| `lookup_exn ctx name` | Look up a binding. Raises `Chibi_error` if unbound. |
330330+| `define_function ctx name arity f` | Register an OCaml function callable from Scheme. |
331331+| `define_fn0` .. `define_fn6` | Convenience: typed wrappers for fixed arity. |
332332+333333+Foreign functions receive arguments as `Sexp.t list` (for `define_function`) or
334334+as positional `Sexp.t` arguments (for `define_fnN`). They must return a
335335+`Sexp.t`. Maximum arity is 6.
336336+337337+### `Sandbox` -- Capability-Based Security
338338+339339+The sandbox uses a whitelist model: you specify which capabilities to **grant**.
340340+Everything not explicitly granted is denied.
341341+342342+```ocaml
343343+type capability =
344344+ | File_read (* reading files from the filesystem *)
345345+ | File_write (* writing/modifying files and directories *)
346346+ | Net_access (* sockets, HTTP, DNS *)
347347+ | Process_exec (* fork, exec, system, kill, signals, shell *)
348348+ | Env_access (* environment variables, user/host info, PIDs *)
349349+ | Module_import (* importing Scheme modules via (import ...) *)
350350+ | Standard_io (* access to stdin/stdout/stderr *)
351351+```
352352+353353+| Function | Description |
354354+|---|---|
355355+| `none` | Maximum sandbox: all capabilities denied. |
356356+| `full` | No restrictions: all capabilities granted. |
357357+| `allow caps` | Grant only the listed capabilities. |
358358+| `has_capability sandbox cap` | Query a capability. |
359359+360360+#### What each capability controls
361361+362362+**`File_read`** -- When denied, blocks ~50 bindings including:
363363+`open-input-file`, `file-exists?`, `file->string`, `directory-files`,
364364+`file-size`, `file-regular?`, `file-directory?`, `current-directory`,
365365+`include`, `load`, and all `(chibi filesystem)` stat/query operations.
366366+367367+**`File_write`** -- When denied, blocks ~30 bindings including:
368368+`open-output-file`, `delete-file`, `rename-file`, `create-directory`,
369369+`delete-directory`, `chmod`, `chown`, `link-file`, `symbolic-link-file`,
370370+`change-directory`, and all POSIX file descriptor write operations.
371371+372372+**`Net_access`** -- When denied, blocks ~45 bindings including:
373373+`socket`, `connect`, `bind`, `accept`, `listen`, `send`, `receive`,
374374+`open-net-io`, `http-get`, `http-post`, `http-put`, `http-delete`,
375375+`run-net-server`, `run-http-server`, and all `(chibi net)` operations.
376376+377377+**`Process_exec`** -- When denied, blocks ~45 bindings including:
378378+`fork`, `execute`, `kill`, `waitpid`, `system`, `exit`, `emergency-exit`,
379379+`sleep`, `alarm`, `call-with-process-io`, `process->string`, all
380380+`(chibi shell)` operations, signal handling, and privilege escalation
381381+(`set-current-user-id!`, `set-root-directory!`, etc.).
382382+383383+**`Env_access`** -- When denied, blocks ~25 bindings including:
384384+`get-environment-variable`, `get-environment-variables`, `command-line`,
385385+`get-host-name`, `user-information`, `current-user-id`, `current-process-id`,
386386+and all `(chibi system)` user/group query operations.
387387+388388+**`Standard_io`** -- When denied, redirects `current-input-port`,
389389+`current-output-port`, and `current-error-port` to null string ports.
390390+Scheme code can still use `display`/`write`/`read` but the data goes nowhere.
391391+392392+**`Module_import`** -- When denied, the standard R7RS environment is not
393393+loaded at all. The context starts with only chibi-scheme primitives.
394394+395395+#### Example: computation-only sandbox
396396+397397+```ocaml
398398+let config = Context.sandboxed_config
399399+ ~heap_size:(2 * 1024 * 1024)
400400+ ~max_heap_size:(16 * 1024 * 1024)
401401+ ~capabilities:[Sandbox.Module_import] (* only allow importing modules *)
402402+ () in
403403+with_context ~config (fun ctx ->
404404+ (* Pure computation works *)
405405+ let _ = Eval.string ctx "(define (fib n) (if (< n 2) n (+ (fib (- n 1)) (fib (- n 2)))))" in
406406+ let result = Eval.to_int ctx "(fib 20)" in
407407+ Printf.printf "fib(20) = %d\n" result;
408408+409409+ (* File access is blocked *)
410410+ let v = Eval.string ctx "open-input-file" in
411411+ assert (Sexp.is_void v);
412412+413413+ (* Network access is blocked *)
414414+ let v = Eval.string ctx "socket" in
415415+ assert (Sexp.is_void v))
416416+```
417417+418418+#### Security considerations
419419+420420+The sandbox enforces restrictions by overriding dangerous bindings in the
421421+interaction environment with `void` after loading the standard library. This
422422+provides strong defense-in-depth:
423423+424424+- **190+ dangerous bindings** are blocked across filesystem, network, process,
425425+ environment, and privilege escalation categories.
426426+- **Memory limits** are enforced by chibi-scheme's heap allocator at the C level
427427+ -- Scheme code cannot bypass `max_heap_size`.
428428+- **Standard I/O** is physically redirected to null ports when denied.
429429+430430+However, the sandbox is **not a security boundary** in the formal sense:
431431+432432+- If `Module_import` is enabled, Scheme code could potentially re-import
433433+ blocked bindings from their source modules. For maximum security, grant only
434434+ `Module_import` if you need the standard library, and avoid granting it if
435435+ pure computation suffices.
436436+- For the strongest isolation, create a context with no capabilities at all,
437437+ then use `Env.define` and `Env.define_function` from OCaml to provide only
438438+ the specific operations your application needs.
439439+440440+### `Stream` -- Seq-Based Streaming
441441+442442+The `Stream` module bridges OCaml `Seq.t` lazy sequences with Scheme processing
443443+functions. Most operations are plain sequence transforms; `from_producer` is the
444444+one function that uses OCaml 5 effects to turn an imperative yield-style
445445+producer into a lazy `Seq.t`.
446446+447447+| Function | Description |
448448+|---|---|
449449+| `from_producer f` | Create a `Sexp.t Seq.t` from a function using `Effect.perform (Yield v)`. |
450450+| `feed ctx ~proc ~init seq` | Fold a Scheme procedure over a sequence. |
451451+| `fold ctx ~expr ~init seq` | Like `feed` but takes the procedure as a Scheme expression string. |
452452+| `map ctx ~proc seq` | Map a Scheme procedure over a sequence. |
453453+| `filter ctx ~pred seq` | Filter a sequence with a Scheme predicate. |
454454+| `of_channel ctx ic` | Read lines as Scheme strings. |
455455+| `of_int_seq ctx seq` | Convert `int Seq.t` to `Sexp.t Seq.t`. |
456456+| `of_string_seq ctx seq` | Convert `string Seq.t` to `Sexp.t Seq.t`. |
457457+| `to_scheme_list ctx seq` | Collect into a Scheme list. |
458458+| `to_int_list seq` | Collect and extract integers. |
459459+| `to_string_list seq` | Collect and extract strings. |
460460+461461+### `Io` -- Output Capture and Port Redirection
462462+463463+| Function | Description |
464464+|---|---|
465465+| `capture ctx f` | Redirect stdout/stderr, run `f`, return `(result, stdout, stderr)`. Ports are restored afterward. |
466466+| `set_input_string ctx s` | Set current input port to read from a string. |
467467+| `redirect_output ctx` | Redirect stdout to string port. Returns thunk to get output. |
468468+469469+### `with_context`
470470+471471+```ocaml
472472+val with_context : ?config:Context.config -> (Context.t -> 'a) -> 'a
473473+```
474474+475475+Creates a context, runs the function, and destroys the context when done. The
476476+context is always cleaned up, even if the function raises an exception.
477477+478478+## Memory Management
479479+480480+chibi-ocaml coordinates two garbage collectors -- OCaml's GC and chibi-scheme's
481481+mark-and-sweep GC:
482482+483483+- **Scheme values held by OCaml** are protected from chibi's GC via
484484+ `sexp_preserve_object`. When the OCaml wrapper is finalized, the protection is
485485+ released.
486486+- **Contexts** are reference-counted via the `alive` flag. Destroying a context
487487+ invalidates all associated sexp wrappers. Using a sexp from a destroyed
488488+ context raises `Context_destroyed`.
489489+- **`with_context`** provides bracket-style cleanup. Explicit `destroy` is also
490490+ supported and is idempotent.
491491+492492+You do not need to manually manage Scheme value lifetimes. Values are
493493+automatically released when they become unreachable from OCaml.
494494+495495+## Thread Safety
496496+497497+Each `Context.t` is an independent VM with its own heap. Different contexts can
498498+be used from different OCaml domains (or OS threads) without synchronization.
499499+500500+However, **a single context must not be accessed concurrently** from multiple
501501+threads or domains. chibi-scheme's internal state is not thread-safe.
502502+503503+## Building from Source
504504+505505+Requirements:
506506+- OCaml >= 5.1
507507+- dune >= 3.22
508508+- A C compiler (gcc or clang)
509509+- `-lm` and `-ldl` (standard on Linux; macOS provides these by default)
510510+511511+```sh
512512+dune build # compile
513513+dune runtest # run OCaml binding tests (97 tests)
514514+dune exec bin/main.exe # run demo
515515+dune exec test_runner/run_chibi_tests.exe # run chibi's own test suite
516516+```
517517+518518+## Test Results
519519+520520+The chibi-scheme test suite passes through our bindings with the same results as
521521+the native chibi-scheme binary:
522522+523523+| Suite | Result |
524524+|---|---|
525525+| Basic tests (11) | 11/11 |
526526+| R5RS | 189/189 |
527527+| R7RS | 1224/1225 (1 embedding-specific: `command-line`) |
528528+| Syntax | 12/12 |
529529+| Unicode | 18/18 |
530530+| Division | 294/304 (chibi bignum bug, same as native) |
531531+| Library tests (all SRFIs + chibi libs) | 6455/6455 |
532532+533533+**Total: 8197/8204** (99.9%). All failures are chibi-scheme's own bugs or
534534+inherent embedding differences, not binding issues.
535535+536536+## License
537537+538538+BSD-3-Clause. chibi-scheme is also BSD-licensed.