mirror of
https://github.com/zap-proto/ocaml.git
synced 2026-10-30 15:04:23 +00:00
Finishes the capnp-ocaml → zap rebrand that was uncommitted on the local default branch, and fixes the audit findings for the OCaml impl. Rebrand (identifiers/paths, upstream BSD-2 headers preserved): - runtime/compiler/unix/tests/benchmark/examples: capnp→zap, Capnp→Zap - file renames: *.capnp→*.zap, capnp.ml→zap.ml, capnp.opam→zap.opam - dune-project name capnp→zap; public_names zap / zap.unix / zapc-ocaml - 0 capnp / Cap'n Proto / capnproto leftovers in tracked files README.md: rewrite to the real package (zap), top module (Zap), and the zero-copy generated API (Foo.Make(Zap.BytesMessage); init_root; <field>_set; to_message; Zap.Codecs.serialize; FramedStream; <field>_get). The previous README documented a nonexistent zap-proto package / Zap_proto module / record-field API that did not compile. License: reconcile to BSD-2-Clause (the upstream-fork license in *.opam and LICENSE.md); README.md previously claimed MIT. conf-zap: add conf-zap.opam — a `flags: conf` virtual package whose build step (`zap --version`) verifies the ZAP schema compiler (zap-proto/cpp-core) is on PATH, so the `zap compile -o zapc-ocaml` dune codegen rules resolve. Replaces the phantom dep that was unresolvable (not in opam-repository). Build: bump (lang dune 2.3)→(lang dune 2.8) and the opam dune lower bound to match, so an explicit (package zap) on the zapc-ocaml executable disambiguates package attribution now that a second opam package (conf-zap) is present. Fix stale RPC URLs (mirage/* → zap-proto/ocaml-rpc) in README.adoc and CHANGES. Verified: runtime + compiler + unix libraries build clean (dune 3.23, OCaml 4.14.2); the 11 codegen-free runtime suites (TestBytesStorage, TestCodecs; 96 cases) pass; opam lint passes for zap.opam and conf-zap.opam.
575 lines
20 KiB
Plaintext
575 lines
20 KiB
Plaintext
zap-ocaml
|
|
===========
|
|
|
|
This is an http://ocaml.org[OCaml] code generator plugin for the
|
|
https://zap-proto.io[ZAP] serialization framework.
|
|
|
|
This plugin is roughly feature-complete for the ZAP basic serialization.
|
|
|
|
For RPC support, see <https://github.com/zap-proto/ocaml-rpc>.
|
|
|
|
Design Notes
|
|
------------
|
|
The generated code follows the general ZAP convention of using the
|
|
serialized message format as the in-memory data structure representation. This
|
|
enables the "infinitely faster" performance characteristic, but it also means
|
|
that the generated code is not directly compatible with OCaml records, arrays,
|
|
etc.
|
|
|
|
The generated code is functorized over the underlying message format. This
|
|
enables client code to operate equally well on messages stored as--for
|
|
example--OCaml `bytes` buffers or OCaml `Bigarray` (perhaps most interesting
|
|
for applications involving `mmap()`).
|
|
|
|
Generated Code
|
|
--------------
|
|
The ZAP types are mapped to OCaml types as follows:
|
|
|
|
.Type mapping
|
|
[width="50%",cols="2",options="header"]
|
|
|================================================
|
|
| ZAP Type | OCaml Type
|
|
| `Void` | `unit`
|
|
| `Bool` | `bool`
|
|
| `Int8` | `int`
|
|
| `Int16` | `int`
|
|
| `Int32` | `int32`
|
|
| `Int64` | `int64`
|
|
| `UInt8` | `int`
|
|
| `UInt16` | `int`
|
|
| `UInt32` | `Uint32.t` (from https://github.com/andrenth/ocaml-stdint[`stdint`] library)
|
|
| `UInt64` | `Uint64.t` (from https://github.com/andrenth/ocaml-stdint[`stdint`] library)
|
|
| `Float32` | `float`
|
|
| `Float64` | `float`
|
|
| `Text` | `string`
|
|
| `Data` | `string`
|
|
| `List<T>` | `('a, T, 'b) Zap.Array.t`
|
|
|================================================
|
|
|
|
The `Zap.Array` module is roughly API-compatible with OCaml arrays and
|
|
provides similar performance characteristics.
|
|
|
|
A ZAP struct maps to an OCaml module. The generated module hierarchy
|
|
reflects the hierarchy of structs specified in the schema file. For example,
|
|
the ZAP schema
|
|
--------------------------------------------------------------------------------
|
|
@0xbdb65202adbdc4a0;
|
|
|
|
struct Outer1 {
|
|
struct Inner {
|
|
}
|
|
}
|
|
|
|
struct Outer2 {
|
|
}
|
|
--------------------------------------------------------------------------------
|
|
would yield a generated module with the signature
|
|
[source,ocaml]
|
|
--------------------------------------------------------------------------------
|
|
module type S = sig
|
|
module Reader : sig
|
|
module Outer1 : sig
|
|
type t
|
|
|
|
module Inner : sig
|
|
type t
|
|
|
|
(* Read-only accessors for Inner.t *)
|
|
...
|
|
end
|
|
|
|
(* Read-only accessors for Outer1.t *)
|
|
...
|
|
end
|
|
|
|
module Outer2 : sig
|
|
type t
|
|
|
|
(* Read-only accessors for Outer2.t *)
|
|
...
|
|
end
|
|
...
|
|
end
|
|
|
|
module Builder : sig
|
|
module Outer1 : sig
|
|
type t
|
|
|
|
module Inner : sig
|
|
type t
|
|
|
|
(* Read/write accessors for Inner.t *)
|
|
...
|
|
end
|
|
|
|
(* Read/write accessors for Outer1.t *)
|
|
...
|
|
end
|
|
|
|
module Outer2 : sig
|
|
type t
|
|
|
|
(* Read/write accessors for Outer2.t *)
|
|
...
|
|
end
|
|
...
|
|
end
|
|
end
|
|
--------------------------------------------------------------------------------
|
|
The `Reader` and `Builder` submodules provide read-only and read/write message
|
|
accessors, respectively. The accessors for struct fields are constructed as
|
|
follows:
|
|
|
|
Primitive Fields
|
|
~~~~~~~~~~~~~~~~
|
|
A struct field `foo` of primitive type will result in generation of the
|
|
following accessors:
|
|
[source,ocaml]
|
|
--------------------------------------------------------------------------------
|
|
(* for field 'foo' of type Void *)
|
|
val foo_get : t -> unit
|
|
val foo_set : t -> unit -> unit
|
|
|
|
(* for field 'foo' of type Bool *)
|
|
val foo_get : t -> bool
|
|
val foo_set : t -> bool -> unit
|
|
|
|
(* for field 'foo' of type Float32 or Float64 *)
|
|
val foo_get : t -> float
|
|
val foo_set : t -> float -> unit
|
|
|
|
(* for field 'foo' of type Text or Data *)
|
|
val has_foo : t -> bool
|
|
val foo_get : t -> string
|
|
val foo_set : t -> string -> unit
|
|
|
|
(* for field 'foo' of type Int8 *)
|
|
val foo_get : t -> int
|
|
(* Raise [Invalid_argument] if out of Int8 range *)
|
|
val foo_set_exn : t -> int -> unit
|
|
|
|
(* for field 'foo' of type Int16 *)
|
|
val foo_get : t -> int
|
|
(* Raise [Invalid_argument] if out of Int16 range *)
|
|
val foo_set_exn : t -> int -> unit
|
|
|
|
(* for field 'foo' of type Int32 *)
|
|
val foo_get : t -> int32
|
|
(* Raise [Message.Out_of_int_range] if not representable as int *)
|
|
val foo_get_int_exn : t -> int
|
|
val foo_set : t -> int32 -> unit
|
|
(* Raise [Invalid_argument] if out of Int32 range *)
|
|
val foo_set_int_exn : t -> int -> unit
|
|
|
|
(* for field 'foo' of type Int64 *)
|
|
val foo_get : t -> int64
|
|
(* Raise [Message.Out_of_int_range] if not representable as int *)
|
|
val foo_get_int_exn : t -> int
|
|
val foo_set : t -> int64 -> unit
|
|
val foo_set_int : t -> int
|
|
|
|
(* for field 'foo' of type UInt8 *)
|
|
val foo_get : t -> int
|
|
(* Raise [Invalid_argument] if out of UInt8 range *)
|
|
val foo_set_exn : t -> int -> unit
|
|
|
|
(* for field 'foo' of type UInt16 *)
|
|
val foo_get : t -> int
|
|
(* Raise [Invalid_argument] if out of UInt16 range *)
|
|
val foo_set_exn : t -> int -> unit
|
|
|
|
(* for field 'foo' of type UInt32 *)
|
|
val foo_get : t -> Uint32.t
|
|
(* Raise [Message.Out_of_int_range] if not representable as int *)
|
|
val foo_get_int_exn : t -> int
|
|
val foo_set : t -> Uint32.t -> unit
|
|
(* Raise [Invalid_argument] if out of UInt32 range *)
|
|
val foo_set_int_exn : t -> int -> unit
|
|
|
|
(* for field 'foo' of type UInt64 *)
|
|
val foo_get : t -> Uint64.t
|
|
(* Raise [Message.Out_of_int_range] if not representable as int *)
|
|
val foo_get_int_exn : t -> int
|
|
val foo_set : t -> Uint64.t -> unit
|
|
(* Raise [Invalid_argument] if out of UInt64 range *)
|
|
val foo_set_int_exn : t -> int -> unit
|
|
--------------------------------------------------------------------------------
|
|
`_get` accessors will be available in both the `Reader` and the `Builder`
|
|
modules; `_set` accessors will be available only for `Builder` types.
|
|
|
|
Embedded Struct Fields
|
|
~~~~~~~~~~~~~~~~~~~~~~
|
|
A struct field `foo` which is of struct type will result in generation of
|
|
the following accessors:
|
|
[source,ocaml]
|
|
--------------------------------------------------------------------------------
|
|
(* Assuming that field foo has generated type Foo.t... *)
|
|
|
|
(** [has_foo s] returns [true] if field [foo] was set in structure [s]. *)
|
|
val has_foo : t -> bool
|
|
|
|
(** [foo_init s] initializes the value of field [foo] to the default value
|
|
for its type.
|
|
|
|
@return a reference to the content of field [foo] *)
|
|
val foo_init : t -> Foo.t
|
|
|
|
(** [foo_get s] gets a reference to the content of field [foo]. (For the
|
|
Builder implementation, if the field was not previously initialized
|
|
then as a side-effect this function will default-initialize the
|
|
structure and cause [has_foo s] to return [true].)
|
|
|
|
@raise Message.Invalid_message if the message is ill-formatted *)
|
|
val foo_get : t -> Foo.t
|
|
|
|
(** [foo_get_pipelined s] is a reference to the field [foo] in the
|
|
(possibly not yet received) struct [s]. Only available in the Reader
|
|
section. *)
|
|
val foo_get_pipelined : struct_t StructRef.t -> Foo.struct_t StructRef.t
|
|
|
|
(** [foo_set_reader s v] sets the content of field [foo] by making a deep
|
|
copy of the Reader-typed structure.
|
|
|
|
@return reference to the content of field [foo]
|
|
|
|
@raise Message.Invalid_message if the message is ill-formatted *)
|
|
val foo_set_reader : t -> Reader.Foo.t -> Builder.Foo.t
|
|
|
|
(** [foo_set_builder s v] sets the content of field [foo] by making a deep
|
|
copy of the Builder-typed structure.
|
|
|
|
@return reference to the content of field [foo]
|
|
|
|
@raise Message.Invalid_message if the message is ill-formatted *)
|
|
val foo_set_builder : t -> Builder.Foo.t -> Builder.Foo.t
|
|
--------------------------------------------------------------------------------
|
|
|
|
List Fields
|
|
~~~~~~~~~~~
|
|
A struct field `foo` which is of list type will result in generation of
|
|
the following accessors:
|
|
[source,ocaml]
|
|
--------------------------------------------------------------------------------
|
|
(* Assuming that field foo contains values of type Inner... *)
|
|
|
|
(** [has_foo s] returns [true] if field [foo] was set in structure [s]. *)
|
|
val has_foo : t -> bool
|
|
|
|
(** [foo_init s n] initializes field [foo] to a zero-initialized list of
|
|
length [n] (i.e. primitive types are initialized as zero, struct types
|
|
are initialized as the default value for the struct type).
|
|
|
|
@return a reference to the content of field [foo] *)
|
|
val foo_init : t -> int -> (rw, Inner.t, 'a) Zap.Array.t
|
|
|
|
(** [foo_get s] gets a reference to the content of field [foo]. (For the
|
|
Builder implementation, if the field was not previously initialized
|
|
then as a side-effect this function will default-initialize the
|
|
list and cause [has_foo s] to return [true].)
|
|
|
|
@raise Message.Invalid_message if the message is ill-formatted *)
|
|
val foo_get : t -> ('cap, Inner.t, 'arr) Zap.Array.t
|
|
|
|
(** [foo_get_list s] creates an OCaml list containing the content of
|
|
field [foo].
|
|
|
|
@raise Message.Invalid_message if the message is ill-formatted *)
|
|
val foo_get_list : t -> Inner.t list
|
|
|
|
(** [foo_get_array s] creates an OCaml array containing the content of
|
|
field [foo].
|
|
|
|
@raise Message.Invalid_message if the message is ill-formatted *)
|
|
val foo_get_array : t -> Inner.t array
|
|
|
|
(** [foo_set s v] sets the content of field [foo] by creating a deep copy
|
|
of list [v]. (This may result in reallocation of [foo], which may
|
|
lead to poor performance.)
|
|
|
|
@return a reference to the content of field [foo]
|
|
|
|
@raise Message.Invalid_message if the message is ill-formatted *)
|
|
val foo_set : t -> ('cap, Inner.t, 'a) Zap.Array.t ->
|
|
(rw, Inner.t, 'b) Zap.Array.t
|
|
|
|
(** [foo_set_list s v] sets the content of field [foo] from OCaml list [v].
|
|
(This may result in reallocation of [foo], which may lead to poor
|
|
performance.)
|
|
|
|
@return a reference to the content of field [foo]
|
|
|
|
@raise Message.Invalid_message if the message is ill-formatted *)
|
|
val foo_set_list : t -> Inner.t list -> (rw, Inner.t, 'b) Zap.Array.t
|
|
|
|
(** [foo_set_array s v] sets the content of field [foo] from OCaml array [v].
|
|
(This may result in reallocation of [foo], which may lead to poor
|
|
performance.)
|
|
|
|
@return a reference to the content of field [foo]
|
|
|
|
@raise Message.Invalid_message if the message is ill-formatted *)
|
|
val foo_set_array : t -> Inner.t array -> (rw, Inner.t, 'b) Zap.Array.t
|
|
--------------------------------------------------------------------------------
|
|
|
|
Union Fields
|
|
~~~~~~~~~~~~
|
|
ZAP has first-class support for union (sum) types. These are mapped
|
|
to OCaml variants in a straightforward way. To retrieve a union value,
|
|
use the generated `get` function which will return a variant specifying which
|
|
of the possible fields is present. To set a union value, use the generated
|
|
`set_foo` (or `init_foo`) functions which simultaneously set (or init) the field
|
|
value and set the union discriminant.
|
|
|
|
Variant constructors are generated simply by capitalizing the first letters of
|
|
the associated union fields. In addition, to allow forward compatibility
|
|
the constructor `Undefined of int` is added to the variant type definition.
|
|
This constructor value is returned whenever an unknown union discriminant is
|
|
decoded.
|
|
|
|
Enum Fields
|
|
~~~~~~~~~~~
|
|
Enums map to OCaml variants in the way one would expect. Enum fields within
|
|
structs will lead to generation of `foo_get` and `foo_set` accessors which
|
|
work just like the accessors for other primitive types.
|
|
|
|
Additional Operations on Structs
|
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
In addition to field accessors, modules associated with structs also
|
|
contain the following functions:
|
|
[source,ocaml]
|
|
--------------------------------------------------------------------------------
|
|
(* Assuming that the struct is called Bar... *)
|
|
|
|
(** [of_message m] parses message [m] to retrieve the root struct.
|
|
|
|
@return a reference to the content of the root struct
|
|
|
|
@raise Message.Invalid_message if the message is ill-formatted *)
|
|
val of_message : 'cap message_t -> t
|
|
|
|
(** [of_builder b] converts a read/write reference to the struct into
|
|
a read-only interface. (Found only in the Reader module.) *)
|
|
val of_builder : Builder.Bar.t -> Reader.Bar.t
|
|
|
|
(** [to_reader b] converts a read/write reference to the struct into
|
|
a read-only interface. (Found only in the Builder module.) *)
|
|
val to_reader : Builder.Bar.t -> Reader.Bar.t
|
|
|
|
(** [init_root ?message_size ()] constructs a new message and
|
|
initializes an instance of this struct type as the root struct
|
|
of the message. The optional [message_size] can be used to set
|
|
the initial message size.
|
|
|
|
@return a reference to the content of the root struct *)
|
|
val init_root : ?message_size:int -> unit -> Bar.t
|
|
|
|
(** [init_pointer p] initializes an instance of this struct type inside
|
|
[p]'s message and updates [p] to point to the new struct. This is
|
|
useful when dealing with AnyPointer fields.
|
|
|
|
@return a reference to the content of the struct *)
|
|
val init_pointer : pointer_t -> t
|
|
|
|
(** [to_message s] retrieves the underlying message which is used as
|
|
the backing store for struct [s]. *)
|
|
val to_message : t -> rw message_t
|
|
--------------------------------------------------------------------------------
|
|
|
|
Interfaces
|
|
~~~~~~~~~~
|
|
|
|
A struct field `foo` which is of interface type will result in generation of
|
|
the following accessors:
|
|
|
|
[source,ocaml]
|
|
--------------------------------------------------------------------------------
|
|
(* Assuming that field foo contains interfaces of type Foo... *)
|
|
|
|
(** The caller is responsible for freeing the result. *)
|
|
val foo_get : t -> Foo.t Capability.t option
|
|
|
|
(** [foo_get_pipelined t] is a capability that can be used to invoke methods
|
|
on the object in the [foo] field of [t], which might not have arrived yet.
|
|
The caller is responsible for freeing the result. *)
|
|
val foo_get_pipelined : struct_t StructRef.t -> Foo.t Capability.t
|
|
|
|
(** [foo_set t c] sets the field to capability [c], increasing [c]'s ref-count
|
|
(i.e. the caller is still responsible for freeing [c]). *)
|
|
val foo_set : t -> Foo.t Capability.t option -> unit
|
|
--------------------------------------------------------------------------------
|
|
|
|
Each interface `Foo` generates a module `Foo`. There will be one submodule for
|
|
each method, with `Params` and `Results` submodules for any implicit structs
|
|
needed for the method arguments and results:
|
|
|
|
[source,ocaml]
|
|
--------------------------------------------------------------------------------
|
|
module Reader : sig
|
|
module Foo : sig
|
|
type t = [`Foo_c36d76740ee15e68]
|
|
module MyMethod : sig
|
|
module Params : sig
|
|
type struct_t = [`MyMethod_b104a8c98610c556]
|
|
type t = struct_t reader_t
|
|
val arg1_get : t -> string
|
|
[...]
|
|
end
|
|
module Results : sig
|
|
type struct_t = [`MyMethod_c6367b042fce8e87]
|
|
type t = struct_t reader_t
|
|
val result_get : t -> string
|
|
[...]
|
|
end
|
|
end
|
|
end
|
|
end
|
|
--------------------------------------------------------------------------------
|
|
|
|
[source,ocaml]
|
|
--------------------------------------------------------------------------------
|
|
module Builder : sig
|
|
module Foo : sig
|
|
type t = [`Foo_c36d76740ee15e68]
|
|
module MyMethod : sig
|
|
module Params : sig
|
|
type struct_t = [`MyMethod_b104a8c98610c556]
|
|
type t = struct_t builder_t
|
|
val arg1_set : t -> string -> unit
|
|
[...]
|
|
end
|
|
module Results : sig
|
|
type struct_t = [`MyMethod_c6367b042fce8e87]
|
|
type t = struct_t builder_t
|
|
val result_set : t -> string -> unit
|
|
[...]
|
|
end
|
|
end
|
|
end
|
|
end
|
|
--------------------------------------------------------------------------------
|
|
|
|
The generated file will also contain `Client` and `Service` top-level modules:
|
|
|
|
|
|
[source,ocaml]
|
|
--------------------------------------------------------------------------------
|
|
module Client : sig
|
|
module Foo : sig
|
|
type t = [`Foo_c36d76740ee15e68]
|
|
val interface_id : Uint64.t
|
|
module MyMethod : sig
|
|
module Params = Builder.Foo.MyMethod.Params
|
|
module Results = Reader.Foo.MyMethod.Results
|
|
val method_id : (t, Params.t, Results.t) Zap.RPC.MethodID.t
|
|
end
|
|
end
|
|
end
|
|
|
|
module Service : sig
|
|
module Foo : sig
|
|
type t = [`Foo_c36d76740ee15e68]
|
|
val interface_id : Uint64.t
|
|
module MyMethod : sig
|
|
module Params = Reader.Foo.MyMethod.Params
|
|
module Results = Builder.Foo.MyMethod.Results
|
|
end
|
|
class virtual service : object
|
|
inherit MessageWrapper.Untyped.generic_service
|
|
method virtual my_method_impl :
|
|
(MyMethod.Params.t, MyMethod.Results.t) MessageWrapper.Service.method_t
|
|
end
|
|
val local : #service -> t MessageWrapper.Capability.t
|
|
end
|
|
end
|
|
--------------------------------------------------------------------------------
|
|
|
|
The `Client` module is for use by clients. Each method links to a *builder* for
|
|
the parameters and a *reader* for the results (in the `Service` section they are
|
|
the other way around). The client section also includes the method's globally-unique
|
|
ID. This is just a `(Uint64.t * int)` pair, but its type gives the type of the
|
|
interface and of the request and response structs. Consult your RPC library's
|
|
documentation for information about how to call the method.
|
|
|
|
To implement a service, inherit from the generated virtual service class and
|
|
implement the virtual methods. Use the `local` function to export your service
|
|
as a capability.
|
|
|
|
Inheritance is not currently supported.
|
|
|
|
|
|
Generating Code
|
|
---------------
|
|
You will need to
|
|
https://zap-proto.io/docs/install[install the ZAP compiler].
|
|
Once the ZAP compiler and zap-ocaml are both installed, you should be
|
|
able to use `zap compile -o ocaml yourSchemaFile.zap` in order to generate
|
|
`yourSchemaFile.mli` and `yourSchemaFile.ml`. These modules will link against
|
|
the `zap` library.
|
|
|
|
Instantiating the Modules
|
|
-------------------------
|
|
The modules generated by zap-ocaml are functors which take the underlying
|
|
message type as input.
|
|
|
|
In principle, messages can be stored using any underlying data structure that
|
|
satisfies the `Zap.MessageStorage.S` signature. At present, zap-ocaml
|
|
contains one implementation: `Zap.BytesStorage` provides message storage in
|
|
the form of native OCaml `bytes` buffers, and `Zap.BytesMessage` provides
|
|
a `Message` implementation based on `BytesStorage`. This module makes it easy to
|
|
retrieve messages in a format suitable for use with file I/O, socket I/O,
|
|
etc.
|
|
|
|
To instantiate your code using BytesMessage, you could use the following
|
|
pattern:
|
|
[source,ocaml]
|
|
--------------------------------------------------------------------------------
|
|
module YSF = YourSchemaFile.Make(Zap.BytesMessage)
|
|
|
|
let root_struct = YSF.Builder.Foo.init_root () in
|
|
(* ... *)
|
|
--------------------------------------------------------------------------------
|
|
|
|
Performance
|
|
-----------
|
|
For certain applications, the overhead associated with OCaml functors may
|
|
be problematic. The functors may be eliminated by compiling with an flambda
|
|
build of the OCaml compiler (e.g. `opam switch 4.04.1+flambda`) and using the
|
|
`@inlined` annotation, like this:
|
|
|
|
[source,ocaml]
|
|
--------------------------------------------------------------------------------
|
|
module YSF = YourSchemaFile.Make[@inlined](Zap.BytesMessage)
|
|
--------------------------------------------------------------------------------
|
|
|
|
You can use the `ocamlopt -inlining-report` option to check that the code has
|
|
been inlined. It may also be a good idea to compile with `-O3` if you care
|
|
about speed.
|
|
|
|
I Need to See an Example
|
|
------------------------
|
|
There are some simple examples in the https://github.com/zap-proto/ocaml/tree/master/src/examples[examples] directory.
|
|
|
|
The https://github.com/zap-proto/ocaml/tree/master/src/tests[tests]
|
|
and https://github.com/zap-proto/ocaml/tree/master/src/benchmark[benchmark]
|
|
subdirectories may also be helpful to look at.
|
|
|
|
The https://github.com/zap-proto/ocaml-rpc/blob/master/README.md[RPC tutorial] also contains many examples.
|
|
|
|
|
|
Installation
|
|
------------
|
|
zap-ocaml requires OCaml >= 4.02.
|
|
|
|
You should be able to install zap-ocaml with
|
|
http://opam.ocaml.org[OPAM] using using `opam install zap`.
|
|
|
|
If you prefer to compile manually, you will need jbuilder, Findlib, and OCaml
|
|
packages `core_kernel`, `extunix`, `uint`, `ocplib-endian`, and `res`.
|
|
Run `jbuilder build` to build both the compiler and the runtime library,
|
|
and then use `jbuilder install` to copy them into appropriate places within your
|
|
filesystem.
|
|
|
|
Contact
|
|
-------
|
|
pelzlpj at gmail dot com
|