Closed kentookura closed 7 months ago
Very cool to see Forester experimenting with Irmin!
Here's a complete example of Repr.abstract
and some tests of the different functions:
open Lwt.Syntax
type foo = { v : string } [@@deriving repr]
module Abstract : Irmin.Contents.S with type t = foo = struct
type t = foo
let t : foo Repr.ty =
let pp h foo =
Format.fprintf h "{ v = %S }" foo.v
in
let of_string str =
(* Must be able to decode the output of [pp] *)
try Scanf.sscanf str "{ v = %S }" (fun str -> Ok { v = str })
with _ -> Error (`Msg "of_string")
in
let encode_json encoder foo =
let (`Ok | _) = Jsonm.encode encoder (`Lexeme `Os) in
let (`Ok | _) = Jsonm.encode encoder (`Lexeme (`Name "v")) in
let (`Ok | _) = Jsonm.encode encoder (`Lexeme (`String foo.v)) in
let (`Ok | _) = Jsonm.encode encoder (`Lexeme `Oe) in
()
in
let decode_json decoder =
(* Must be able to decode the output of [encode_json] *)
let (let*) = Result.bind in
let* () = Repr.Json.decode decoder |> function `Lexeme `Os -> Ok () | _ -> Error (`Msg "decode_json") in
let* () = Repr.Json.decode decoder |> function `Lexeme (`Name "v") -> Ok () | _ -> Error (`Msg "decode_json") in
let* v = Repr.Json.decode decoder |> function `Lexeme (`String v) -> Ok v | _ -> Error (`Msg "decode_json") in
let* () = Repr.Json.decode decoder |> function `Lexeme `Oe -> Ok () | _ -> Error (`Msg "decode_json") in
Ok { v = v }
in
let encode_bin foo write =
Repr.Binary.Varint.encode (String.length foo.v) write ;
write foo.v
in
let decode_bin buffer at =
(* Must be able to decode the output of [encode_bin] *)
let len = Repr.Binary.Varint.decode buffer at in
let v = String.sub buffer !at len in
at := !at + len ;
{ v }
in
let size =
Repr.Size.custom_dynamic
~of_value:(fun foo ->
(* Optional, precompute the size that will be used by [encode_bin] *)
let len = String.length foo.v in
let varint_len_size = ref 0 in
Repr.Binary.Varint.encode len
(fun s -> varint_len_size := !varint_len_size + String.length s) ;
!varint_len_size + len)
~of_encoding:(fun str offset ->
(* You can skip this function, it's unused nowadays *)
let at = ref offset in
let len = Repr.Binary.Varint.decode str at in
let varint_len_size = !at - offset in
varint_len_size + len)
()
in
let pre_hash =
(* Same as [encode_bin], unless the binary serialization has changed
but we want to preserve the previous cryptographic hash for
backward compatibility. *)
encode_bin
in
let equal a b = String.equal a.v b.v in
let compare a b = String.compare a.v b.v in
let short_hash ?seed foo =
match seed with
| None -> Hashtbl.hash foo.v
| Some seed -> Hashtbl.seeded_hash seed foo.v
in
Repr.abstract
~pp ~of_string
~json:(encode_json, decode_json)
~bin:(encode_bin, decode_bin, size)
~pre_hash
~equal ~compare ~short_hash
()
let merge = Irmin.Merge.(option (idempotent t))
end
module Store = Irmin_git_unix.FS.KV (Abstract)
module Info = Irmin_unix.Info (Store.Info)
let info message = Info.v ~author:"Example" "%s" message
let main_branch config =
let* repo = Store.Repo.v config in
Store.main repo
let test schema foo =
let pretty_string = Format.asprintf "%a" (Repr.pp schema) foo in
assert (pretty_string = Repr.to_string schema foo) ;
Format.printf "pp: %s@." pretty_string;
let r = Repr.of_string schema pretty_string in
assert (r = Ok foo) ;
let buf = Buffer.create 16 in
let encoder = Jsonm.encoder ~minify:true (`Buffer buf) in
Repr.encode_json schema encoder foo ;
let (`Ok | _) = Jsonm.encode encoder `End in
let json_encoded = Buffer.contents buf in
Format.printf "json: %S@." json_encoded ;
assert (json_encoded = Printf.sprintf {|{"v":%S}|} foo.v) ;
let decoder = Jsonm.decoder (`String json_encoded) in
let r = Repr.decode_json schema decoder in
assert (r = Ok foo) ;
let binstr = Repr.(unstage (to_bin_string schema)) foo in
Format.printf "binstring = %S@." binstr ;
let r = Repr.(unstage (of_bin_string schema)) binstr in
assert (r = Ok foo) ;
let expected_size = match Repr.(unstage (size_of schema)) foo with
| None -> failwith "unable to precompute binstring length"
| Some s -> s
in
Format.printf "expected_size = %i@." expected_size ;
assert (expected_size = String.length binstr) ;
let short_hash = Repr.(unstage (short_hash schema)) foo in
Format.printf "short_hash = %#i@." short_hash ;
()
let main =
let config = Irmin_git.config ~bare:true "/tmp/irmin" in
let* t = main_branch config in
let* () = Store.set_exn t ["a"] { v = "foo" } ~info:(info "first commit") in
let+ s = Store.get t ["a"] in
assert (s = { v = "foo" } ) ;
Format.printf "# Abstract test:@." ;
test Abstract.t s ;
Format.printf "@." ;
Format.printf "# PPX test:@." ;
test foo_t s
let () = Lwt_main.run main
For the irmin-git
backend, the most important functions to implement are pp
, of_string
and equal
(and the rest is likely unused by this specific backend so you could skip it with assert false
, although the other functions like the binary encoding are used by e.g. the irmin-pack
backend).
Regarding the stack overflow, it's likely coming from https://github.com/kentookura/ocaml-forester/blob/irmin/lib/core/Rep.ml#L336 as recursive types must be explicitly created with Repr.mu
(or the repr definition doesn't terminate!)
I haven't tested it thoroughly but for example:
let t : Sem.tree ty =
let open Sem in
Repr.mu (fun tree ->
let t = Repr.mu (fun t -> list (located_sem_node t tree)) in
record "tree" (fun fm body : Sem.tree -> { fm; body })
|+ field "fm" (frontmatter t) (fun t -> t.fm)
(* without annotation compiler thinks that t is obj_method due to `body` field *)
|+ field "body" t (fun (t : Sem.tree) -> t.body)
|> sealr)
Thanks a lot! I've applied your suggestions and it works well.
The following code compiles but results in an error (not surprising since we are asserting false):
I encountered this when trying to track down the following issue:
I have some representations of types, but when adapting the above code it results in a stack overflow:
https://github.com/kentookura/ocaml-forester/blob/irmin/bin/forest-manager/main.ml
Could the reason for this be the unimplemented size functions for the representation of Range?
https://github.com/kentookura/ocaml-forester/blob/4fe2a99ad9869e155d8b8c124846e0ecd2380f73/lib/core/Rep.ml#L83
I would like to request a fully worked example of representing abstract types that works with irmin. It would help me track down where the error lies in my code. Thanks!