sexpt#
Declarative S-expression codecs for OCaml.
Overview#
Sexpt provides a bidirectional codec system for S-expressions, inspired by Daniel Bünzli's Jsont library. Codecs are defined once and used for both encoding and decoding, eliminating the common source of bugs where serialisation and deserialisation logic diverge.
The library consists of two packages:
- sexpt -- Core S-expression parsing and codec combinators
- sexpt.codecs -- Domain-specific codecs (Dune configuration files, and more to come)
Installation#
opam install sexpt
Basic Usage#
Defining a Codec#
open Sexpt
type config = { host : string; port : int; debug : bool }
let config_codec =
Record.(
obj (fun host port debug -> { host; port; debug })
|> mem "host" string ~enc:(fun c -> c.host)
|> mem "port" int ~enc:(fun c -> c.port)
|> mem "debug" bool ~enc:(fun c -> c.debug) ~dec_absent:false
|> finish
)
Decoding#
let config =
decode_string_exn config_codec
{|((host "localhost") (port 8080) (debug true))|}
Encoding#
let sexp = encode config_codec { host = "example.com"; port = 443; debug = false }
(* ((host "example.com") (port 443) (debug false)) *)
S-expression Format#
S-expressions are a minimal data format with two constructs: atoms and lists.
; Atoms
hello
"hello world"
123
; Lists
(a b c)
((x 1) (y 2))
; Records as association lists
((name "Alice")
(age 30)
(active true))
; Variants as tagged values
(Some 42)
None
Base Codecs#
| Codec | OCaml Type | S-expression |
|---|---|---|
string |
string |
foo, "hello" |
int |
int |
42, -17 |
float |
float |
3.14, -0.5 |
bool |
bool |
true, false |
unit |
unit |
() |
Combinators#
(* Optional values *)
let opt_int = option int
(* Lists *)
let int_list = list int
(* Pairs *)
let pair_codec = pair string int
Records#
type person = { name : string; age : int; email : string option }
let person_codec =
Record.(
obj (fun name age email -> { name; age; email })
|> mem "name" string ~enc:(fun p -> p.name)
|> mem "age" int ~enc:(fun p -> p.age)
|> opt_mem "email" string ~enc:(fun p -> p.email)
|> finish
)
Variants#
type colour = Red | Green | Blue | Rgb of int * int * int
let colour_codec =
Sexpt.Variant.variant [
Sexpt.Variant.case0 "Red" Red (function Red -> true | _ -> false);
Sexpt.Variant.case0 "Green" Green (function Green -> true | _ -> false);
Sexpt.Variant.case0 "Blue" Blue (function Blue -> true | _ -> false);
Sexpt.Variant.casev "Rgb" (Sexpt.triple Sexpt.int Sexpt.int Sexpt.int)
(fun (r, g, b) -> Rgb (r, g, b))
(function Rgb (r, g, b) -> Some (r, g, b) | _ -> None);
]
Dune File Support#
The sexpt.codecs package provides utilities for working with Dune
configuration files programmatically.
open Sexpt_codecs.Dune
(* Parse a dune-project file *)
let proj = Dune_project.parse sexps
let name = proj.name (* Some "my-project" *)
let version = proj.version (* Some "1.0.0" *)
(* Build stanzas programmatically *)
let lib = library ~public_name:"my-lib" ~libraries:["fmt"; "logs"] "foo"
(* Variable expansion *)
let expanded = expand ~env:(function
| "name" -> Some "Alice"
| _ -> None
) (Sexp.parse_string_exn "(hello %{name})")
(* (hello Alice) *)
Limitations#
The library does not currently support:
- Streaming parsing via bytesrw -- a future version could integrate with the bytesrw ecosystem for incremental parsing of large S-expressions
- Preserving comments or formatting during round-trips
- Custom atom quoting styles
Related Work#
- Jsont -- JSON codecs for OCaml. Primary inspiration for the API design.
- Sexplib -- Jane Street's S-expression library with PPX derivers.
- Csexp -- Canonical S-expressions used by Dune's RPC protocol.
Licence#
ISC