(** {1 Code generators}

    The code generator library is designed to be used from a build system
    (for example, from [dune]) to generate efficient code for features
    that are harder to provide at runtime.

    The idea is that the build system should invoke some OCaml script
    that depends on [containers.codegen]; the script uses the DSL below
    to describe what code to generate (e.g. a description of a bitfield type)
    and emits a [.ml] file (and possibly a [.mli] file).

    For example, the build script might contain:

{[
module CG = Containers_codegen
let () =
  let module B = CG.Bitfield in
  let b = B.make ~name:"t" () in
  B.field_bit b "x";
  B.field_bit b "y";
  B.field_bit b "z";
  B.field_int b ~width:5 "foo";

  CG.emit_file "foo.mli" [B.gen_mli b];
  CG.emit_file "foo.ml" [B.gen_ml b];
  ()
]}

    and this will produce [foo.ml] and [foo.mli] with a bitfield containing
    [x], [y], and [z].

*)

module Fmt = CCFormat

type code

(** {2 Representation of OCaml code} *)
module Code : sig
  type t = code

  val pp : t Fmt.printer
  val to_string : t -> string
  val mk_pp : unit Fmt.printer -> t
  val mk_str : string -> t
  val in_struct : string -> t list -> t
  val in_sig : string -> t list -> t
end

(** {2 Generate efficient bitfields that fit in an integer} *)
module Bitfield : sig
  type t

  val make : ?emit_failure_if_too_wide:bool -> name:string -> unit -> t
  (** Make a new bitfield with the given name.
      @param name the name of the generated type
      @param emit_failure_if_too_wide if true, generated code includes a runtime
      assertion that {!Sys.int_size} is wide enough to support this type *)

  val field_bit : t -> string -> unit
  (** [field_bit ty name] adds a field of size [1] to the bitfield [ty],
      with name [name]. The generate code will provide get/set for
      a boolean. *)

  val field_int : t -> width:int -> string -> unit
  (** [field_int ty name ~width] adds a field of size [width] to
    the bitfield with name [name].
    The accessors will be for integers of [width] bits, and the
    setter might assert that the provided integer fits. *)

  val total_width : t -> int
  (** Total width in bits of the given bitfield. *)

  val gen_mli : t -> code
  (** Generate code for the type signature for the given bitfield *)

  val gen_ml : t -> code
  (** Generate code for the implementation for the given bitfield *)
end

val emit_file : string -> code list -> unit
(** [emit_file file cs] emits code fragments [cs] into the given file
    at path [file] *)

val emit_chan : out_channel -> code list -> unit
val emit_string : code list -> string
