mte/unikernel/duniverse/ocaml-cstruct/ppx/ppx_cstruct.ml
2025-11-11 02:07:51 +01:00

662 lines
20 KiB
OCaml

(*
* Copyright (c) 2015 Nicolas Ojeda Bar <n.oje.bar@gmail.com>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*)
open Printf
open Ppxlib
open Ast_helper
module Ast = struct
include Ast_builder.Default
let econstr ~loc tag xs =
pexp_construct ~loc (Loc.make ~loc (lident tag)) (
match xs with
| [] -> None
| _ -> Some (pexp_tuple ~loc xs)
)
let pconstr ~loc tag xs =
ppat_construct ~loc (Loc.make ~loc (lident tag)) (
match xs with
| [] -> None
| _ -> Some (ppat_tuple ~loc xs)
)
let tconstr ~loc tag xs =
ptyp_constr ~loc (Loc.make ~loc (lident tag)) xs
end
type mode = Big_endian | Little_endian | Host_endian | Bi_endian
type prim =
| Char
| UInt8
| UInt16
| UInt32
| UInt64
type ty =
| Prim of prim
| Buffer of prim * int
type raw_field = {
name: string;
ty: ty;
definition_loc: Location.t;
}
type named_field = {
name: string;
ty: ty;
definition_loc: Location.t;
off: int;
}
type field =
| Named_field of named_field
| Ignored_field
let field_is_ignored name =
String.get name 0 = '_'
type t = {
name: string;
fields: field list;
len: int;
endian: mode;
}
let ty_of_string =
function
|"char_t" |"char" -> Some Char
|"uint8_t" |"uint8" |"int8" |"int8_t" -> Some UInt8
|"uint16_t"|"uint16"|"int16"|"int16_t" -> Some UInt16
|"uint32_t"|"uint32"|"int32"|"int32_t" -> Some UInt32
|"uint64_t"|"uint64"|"int64"|"int64_t" -> Some UInt64
|_ -> None
let width_of_prim = function
| Char -> 1
| UInt8 -> 1
| UInt16 -> 2
| UInt32 -> 4
| UInt64 -> 8
let width_of_ty = function
| Prim p -> width_of_prim p
| Buffer (p, len) -> width_of_prim p * len
let width_of_field f =
width_of_ty f.ty
let field_to_string f =
let rec string = function
|Prim Char -> "char_t"
|Prim UInt8 -> "uint8_t"
|Prim UInt16 -> "uint16_t"
|Prim UInt32 -> "uint32_t"
|Prim UInt64 -> "uint64_t"
|Buffer (prim, len) -> sprintf "%s[%d]" (string (Prim prim)) len
in
sprintf "%s %s" (string f.ty) f.name
let loc_err loc fmt = Location.raise_errorf ~loc ("ppx_cstruct: " ^^ fmt)
let parse_field loc name field_type sz =
match ty_of_string field_type with
|None -> loc_err loc "Unknown type %s" field_type
|Some ty -> begin
let ty = match ty,sz with
|_,None -> Prim ty
|prim,Some sz -> Buffer (prim, sz)
in
{ name; ty; definition_loc = loc }
end
let check_for_duplicates fields =
let module StringSet = Set.Make(String) in
let _ : StringSet.t =
List.fold_left (fun seen f ->
match f with
| Ignored_field -> seen
| Named_field {name; definition_loc; _} ->
if StringSet.mem name seen then
loc_err definition_loc "field %s is present several times in this type" name
else
StringSet.add name seen
)
StringSet.empty
fields
in
()
let create_struct loc endian name fields =
let endian = match endian with
|"little_endian" -> Little_endian
|"big_endian" -> Big_endian
|"host_endian" -> Host_endian
|"bi_endian" -> Bi_endian
|_ -> loc_err loc "unknown endian %s, should be little_endian, big_endian, host_endian or bi_endian" endian
in
let len, fields =
List.fold_left (fun (off,acc) ({name; ty; definition_loc}:raw_field) ->
let field =
if field_is_ignored name then
Ignored_field
else
Named_field { name; ty; off; definition_loc }
in
let off = width_of_ty ty + off in
let acc = acc @ [field] in
(off, acc)
) (0,[]) fields
in
check_for_duplicates fields;
{ fields; name = name.txt; len; endian }
let ($.) l x = Longident.Ldot (l, x)
let cstruct_id = Longident.Lident "Cstruct"
let mode_mod s = function
|Big_endian -> cstruct_id$."BE"$.s
|Little_endian -> cstruct_id$."LE"$.s
|Host_endian -> cstruct_id$."HE"$.s
|Bi_endian -> cstruct_id$."BL"$.s
let mode_mod loc x s =
Exp.ident ~loc {loc ; txt = mode_mod s x}
type op =
| Op_get of named_field
| Op_set of named_field
| Op_copy of named_field
| Op_blit of named_field
| Op_sizeof
| Op_hexdump
| Op_hexdump_to_buffer
let op_name s op =
let parts =
match op with
| Op_get f -> ["get"; s.name; f.name]
| Op_set f -> ["set"; s.name; f.name]
| Op_copy f -> ["copy"; s.name; f.name]
| Op_blit f -> ["blit"; s.name; f.name]
| Op_sizeof -> ["sizeof"; s.name]
| Op_hexdump -> ["hexdump"; s.name]
| Op_hexdump_to_buffer -> ["hexdump"; s.name; "to_buffer"]
in
String.concat "_" parts
let op_pvar ~loc s op = Ast.pvar ~loc (op_name s op)
let op_evar ~loc s op = Ast.evar ~loc (op_name s op)
let get_expr loc s f =
let m = mode_mod loc s.endian in
let num x = Ast.eint ~loc x in
match f.ty with
|Buffer (_, _) ->
let len = width_of_field f in
[%expr
fun src -> Cstruct.sub src [%e num f.off] [%e num len]
]
|Prim prim ->
[%expr
fun v ->
[%e match prim with
|Char -> [%expr Cstruct.get_char v [%e num f.off]]
|UInt8 -> [%expr Cstruct.get_uint8 v [%e num f.off]]
|UInt16 -> [%expr [%e m "get_uint16"] v [%e num f.off]]
|UInt32 -> [%expr [%e m "get_uint32"] v [%e num f.off]]
|UInt64 -> [%expr [%e m "get_uint64"] v [%e num f.off]]]]
let type_of_int_field ~loc = function
|Char -> [%type: char]
|UInt8 -> [%type: Cstruct.uint8]
|UInt16 -> [%type: Cstruct.uint16]
|UInt32 -> [%type: Cstruct.uint32]
|UInt64 -> [%type: Cstruct.uint64]
let set_expr loc s f =
let m = mode_mod loc s.endian in
let num x = Ast.eint ~loc x in
match f.ty with
|Buffer (_,_) ->
let len = width_of_field f in
[%expr
fun src srcoff dst ->
Cstruct.blit_from_string src srcoff dst [%e num f.off] [%e num len]]
|Prim prim ->
[%expr fun v x ->
[%e match prim with
|Char -> [%expr Cstruct.set_char v [%e num f.off] x]
|UInt8 -> [%expr Cstruct.set_uint8 v [%e num f.off] x]
|UInt16 -> [%expr [%e m "set_uint16"] v [%e num f.off] x]
|UInt32 -> [%expr [%e m "set_uint32"] v [%e num f.off] x]
|UInt64 -> [%expr [%e m "set_uint64"] v [%e num f.off] x]]]
let type_of_set ~loc f =
match f.ty with
|Buffer (_,_) ->
[%type: string -> int -> Cstruct.t -> unit]
|Prim prim ->
let retf = type_of_int_field ~loc prim in
[%type: Cstruct.t -> [%t retf] -> unit]
let hexdump_expr ~loc s =
[%expr fun v ->
let buf = Buffer.create 128 in
Buffer.add_string buf [%e Ast.estring ~loc (s.name ^ " = {\n")];
[%e op_evar ~loc s Op_hexdump_to_buffer] buf v;
print_endline (Buffer.contents buf);
print_endline "}"
]
let hexdump_to_buffer_expr ~loc s =
let prim_format_string = function
| Char -> [%expr "%c\n"]
| UInt8 | UInt16 -> [%expr "0x%x\n"]
| UInt32 -> [%expr "0x%lx\n"]
| UInt64 -> [%expr "0x%Lx\n"]
in
let hexdump_field = function
| Ignored_field ->
[%expr ()]
| Named_field f ->
let get_f = op_evar ~loc s (Op_get f) in
let expr =
match f.ty with
|Prim p ->
[%expr Printf.bprintf buf [%e prim_format_string p] ([%e get_f] v)]
|Buffer (_,_) ->
[%expr Printf.bprintf buf "<buffer %s>" [%e Ast.estring ~loc (field_to_string f)];
Cstruct.hexdump_to_buffer buf ([%e get_f] v)]
in
[%expr
Printf.bprintf buf " %s = " [%e Ast.estring ~loc f.name];
[%e expr]]
in
[%expr fun buf v -> [%e Ast.esequence ~loc (List.map hexdump_field s.fields)]]
let op_expr loc s = function
| Op_sizeof -> Ast.eint ~loc s.len
| Op_hexdump -> hexdump_expr ~loc s
| Op_hexdump_to_buffer -> hexdump_to_buffer_expr ~loc s
| Op_get f -> get_expr loc s f
| Op_set f -> set_expr loc s f
| Op_copy f ->
let len = width_of_field f in
[%expr fun src -> Cstruct.to_string src ~off:[%e Ast.eint ~loc f.off] ~len:[%e Ast.eint ~loc len] ]
| Op_blit f ->
let len = width_of_field f in
[%expr fun src srcoff dst ->
Cstruct.blit src srcoff dst [%e Ast.eint ~loc f.off] [%e Ast.eint ~loc len]]
let field_ops_for =
function
| Ignored_field ->
[]
| Named_field f ->
let if_buffer x =
match f.ty with
|Buffer (_,_) -> [x]
|Prim _ -> []
in
List.concat
[ [Op_get f]
; if_buffer (Op_copy f)
; [Op_set f]
; if_buffer (Op_blit f)
]
let ops_for s =
( [Op_sizeof]
@ List.concat (List.map field_ops_for s.fields)
@ [Op_hexdump_to_buffer;
Op_hexdump;
])
(** Generate functions of the form {get/set}_<struct>_<field> *)
let output_struct_one_endian loc s =
List.map
(fun op ->
[%stri let[@ocaml.warning "-32"] [%p op_pvar ~loc s op] =
[%e op_expr loc s op]])
(ops_for s)
let output_struct _loc s =
match s.endian with
| Bi_endian ->
(* In case of Bi-endian, create two modules - one for BE and one for LE *)
let expr_be = Mod.structure (output_struct_one_endian _loc {s with endian = Big_endian})
and expr_le = Mod.structure (output_struct_one_endian _loc {s with endian = Little_endian})
in [{pstr_desc = Pstr_module
{pmb_name = {txt = Some "BE"; loc = _loc}; pmb_expr = expr_be;
pmb_attributes = []; pmb_loc = _loc;}; pstr_loc = _loc;};
{pstr_desc = Pstr_module
{pmb_name = {txt = Some "LE"; loc = _loc}; pmb_expr = expr_le;
pmb_attributes = []; pmb_loc = _loc;}; pstr_loc = _loc;}
]
| _ -> output_struct_one_endian _loc s
let type_of_get ~loc f =
match f.ty with
|Buffer (_,_) ->
[%type: Cstruct.t -> Cstruct.t]
|Prim prim ->
let retf = type_of_int_field ~loc prim in
[%type: Cstruct.t -> [%t retf]]
let op_typ ~loc = function
| Op_sizeof -> [%type: int]
| Op_hexdump_to_buffer -> [%type: Buffer.t -> Cstruct.t -> unit]
| Op_hexdump -> [%type: Cstruct.t -> unit]
| Op_get f -> type_of_get ~loc f
| Op_set f -> type_of_set ~loc f
| Op_copy _ -> [%type: Cstruct.t -> string]
| Op_blit _ -> [%type: Cstruct.t -> int -> Cstruct.t -> unit]
(** Generate signatures of the form {get/set}_<struct>_<field> *)
let output_struct_sig loc s =
List.map
(fun op ->
Sig.value
(Val.mk
(Loc.make (op_name s op) ~loc)
(op_typ ~loc op)))
(ops_for s)
type enum_op =
| Enum_to_sexp
| Enum_of_sexp
| Enum_get
| Enum_set
| Enum_print
| Enum_parse
| Enum_compare
type cenum =
{ name : string Loc.t;
fields : (string Loc.t * int64) list;
prim : prim;
sexp : bool;
}
let enum_op_name cenum =
let s = cenum.name.txt in
function
| Enum_to_sexp -> sprintf "sexp_of_%s" s
| Enum_of_sexp -> sprintf "%s_of_sexp" s
| Enum_get -> sprintf "int_to_%s" s
| Enum_set -> sprintf "%s_to_int" s
| Enum_print -> sprintf "%s_to_string" s
| Enum_parse -> sprintf "string_to_%s" s
| Enum_compare -> sprintf "compare_%s" s
let enum_pattern ~loc {prim; _} =
let pat_integer f suffix i =
Pat.constant (Pconst_integer(f i, suffix))
in
match prim with
| Char ->
(fun i -> Ast.pchar ~loc (Char.chr (Int64.to_int i)))
| (UInt8 | UInt16) -> pat_integer Int64.to_string None
| UInt32 -> pat_integer (fun i -> Int32.to_string (Int64.to_int32 i)) (Some 'l')
| UInt64 -> pat_integer Int64.to_string (Some 'L')
let enum_integer ~loc {prim; _} =
let expr_integer f suffix i =
Exp.constant (Pconst_integer(f i, suffix))
in
match prim with
| Char -> (fun i -> Ast.echar ~loc (Char.chr (Int64.to_int i)))
| (UInt8 | UInt16) -> expr_integer Int64.to_string None
| UInt32 -> expr_integer (fun i -> Int32.to_string (Int64.to_int32 i)) (Some 'l')
| UInt64 -> expr_integer Int64.to_string (Some 'L')
let declare_enum_expr ~loc ({fields; _} as cenum) = function
| Enum_to_sexp ->
[%expr fun x -> Sexplib.Sexp.Atom ([%e Ast.evar ~loc (enum_op_name cenum Enum_print)] x) ]
| Enum_of_sexp ->
[%expr
fun x ->
match x with
| Sexplib.Sexp.List _ ->
raise (Sexplib.Pre_sexp.Of_sexp_error (Failure "expected Atom, got List", x))
| Sexplib.Sexp.Atom v ->
match [%e Ast.evar ~loc (enum_op_name cenum Enum_parse)] v with
| None ->
raise (Sexplib.Pre_sexp.Of_sexp_error (Failure "unable to parse enum string", x))
| Some r -> r
]
| Enum_get ->
let getters = (List.map (fun ({txt = f; _},i) ->
Exp.case (enum_pattern ~loc cenum i) [%expr Some [%e Ast.econstr ~loc f []]]
) fields) @ [Exp.case [%pat? _] [%expr None]]
in
Exp.function_ getters
| Enum_set ->
let setters = List.map (fun ({txt = f; _},i) ->
Exp.case (Ast.pconstr ~loc f []) (enum_integer ~loc cenum i)
) fields in
Exp.function_ setters
| Enum_print ->
let printers = List.map (fun ({txt = f; _},_) ->
Exp.case (Ast.pconstr ~loc f []) (Ast.estring ~loc f)
) fields in
Exp.function_ printers
| Enum_parse ->
let parsers = List.map (fun ({txt = f; _},_) ->
Exp.case (Ast.pstring ~loc f) [%expr Some [%e Ast.econstr ~loc f []]]
) fields in
Exp.function_ (parsers @ [Exp.case [%pat? _] [%expr None]])
| Enum_compare -> [%expr fun x y ->
let to_int = [%e Ast.evar ~loc (enum_op_name cenum Enum_set)] in
Stdlib.compare (to_int x) (to_int y)
]
let enum_ops_for {sexp; _} =
Enum_get ::
Enum_set ::
Enum_compare ::
Enum_print ::
Enum_parse ::
if sexp then
[ Enum_to_sexp
; Enum_of_sexp
]
else
[]
let enum_type_decl {name; fields; _} =
let decls = List.map (fun (f,_) -> Type.constructor f) fields in
Type.mk ~kind:(Ptype_variant decls) name
let output_enum ~loc cenum =
Str.type_ Recursive [enum_type_decl cenum] ::
List.map
(fun op ->
[%stri
let[@ocaml.warning "-32"] [%p Ast.pvar ~loc (enum_op_name cenum op)] =
[%e declare_enum_expr ~loc cenum op]
])
(enum_ops_for cenum)
let enum_op_type ~loc {name; prim; _} =
let cty = Ast.tconstr ~loc name.txt [] in
let oty = match prim with
| Char -> [%type: char]
| (UInt8|UInt16) -> [%type: int]
| UInt32 -> [%type: int32]
| UInt64 -> [%type: int64]
in
function
| Enum_get -> [%type: [%t oty] -> [%t cty] option]
| Enum_set -> [%type: [%t cty] -> [%t oty]]
| Enum_print -> [%type: [%t cty] -> string]
| Enum_parse -> [%type: string -> [%t cty] option]
| Enum_to_sexp -> [%type: [%t cty] -> Sexplib.Sexp.t]
| Enum_of_sexp -> [%type: Sexplib.Sexp.t -> [%t cty]]
| Enum_compare -> [%type: [%t cty] -> [%t cty] -> int]
let output_enum_sig loc (cenum:cenum) =
Sig.type_ Recursive [enum_type_decl cenum] ::
List.map
(fun op ->
let name = enum_op_name cenum op in
let typ = enum_op_type ~loc cenum op in
Sig.value (Val.mk (Loc.make name ~loc) typ))
(enum_ops_for cenum)
let constr_enum = function
| {pcd_name = f; pcd_args = Pcstr_tuple []; pcd_attributes = attrs; _} ->
let id = match attrs with
| [{attr_name = {txt = "id";_};
attr_payload =
PStr [{ pstr_desc =
Pstr_eval ({pexp_desc = Pexp_constant cst; pexp_loc = loc; _}, _); _}]
;_ }] ->
let cst = match cst with
| Pconst_integer(i, _) -> Int64.of_string i
| _ ->
loc_err loc "invalid id"
in
Some cst
| _ ->
None
in
(f, id)
| {pcd_loc = loc; _} ->
loc_err loc "invalid cenum variant"
let get_len = function
| [{attr_name = {txt = "len"; loc};
attr_payload = PStr
[{pstr_desc =
Pstr_eval ({pexp_desc = Pexp_constant (Pconst_integer (sz, None)); _}, _);
_}]
; _}]
->
let n = int_of_string sz in
if n > 0 then
Some n
else
loc_err loc "[@len] argument should be > 0"
| [{attr_name = {txt = "len"; loc}; _} ] ->
loc_err loc "[@len] argument should be an integer"
| _ ->
None
let constr_field {pld_name = fname; pld_type = fty; pld_loc = loc; pld_attributes = att; _} =
let sz = match get_len fty.ptyp_attributes, get_len att with
| Some sz, None
| None, Some sz -> Some sz
| Some _, Some _ -> loc_err loc "multiple field length attribute"
| None, None -> None
in
let fty = match fty.ptyp_desc with
| Ptyp_constr ({txt = Lident fty; _}, []) -> fty
| _ ->
loc_err fty.ptyp_loc "type identifier expected"
in
parse_field loc fname.txt fty sz
let cstruct decl =
let {ptype_name = name; ptype_kind = kind;
ptype_attributes = attrs; ptype_loc = loc; _} = decl in
let fields = match kind with
| Ptype_record fields -> List.map constr_field fields
| _ -> loc_err loc "record type declaration expected"
in
let endian = match attrs with
| [{attr_name = {txt = endian; _}; attr_payload = PStr []; _}] -> endian
| [_] -> loc_err loc "no attribute payload expected"
| _ -> loc_err loc "too many attributes"
in
create_struct loc endian name fields
let cenum decl =
let {ptype_name = name; ptype_kind = kind;
ptype_attributes = attrs; ptype_loc = loc; _} = decl in
let fields = match kind with
| Ptype_variant fields -> fields
| _ ->
loc_err loc "expected variant type"
in
let width, sexp =
match attrs with
| ({attr_name = {txt = width; _};attr_payload= PStr [];_})
:: ({attr_name = {txt = "sexp"; _};attr_payload = PStr []; _}) :: [] ->
width, true
| ({attr_name = {txt = width; _};attr_payload= PStr [];_}) :: [] ->
width, false
| _ ->
loc_err loc "invalid cenum attributes"
in
let n = ref Int64.minus_one in
let incr_n () = n := Int64.succ !n in
let fields = List.map constr_enum fields in
let fields =
List.map (function
| (f, None) -> incr_n (); (f, !n)
| (f, Some i) -> n := i; (f, i)
) fields in
let prim = match ty_of_string width with
| None -> loc_err loc "enum: unknown width specifier %s" width
| Some p -> p
in
{ name;
fields;
prim;
sexp;
}
let signature_item' mapper = function
| {psig_desc =
Psig_extension (({txt = "cstruct"; _}, PStr [{pstr_desc = Pstr_type(_, [decl]); _}]), _);
psig_loc = loc} ->
output_struct_sig loc (cstruct decl)
| {psig_desc =
Psig_extension (({txt = "cenum"; _}, PStr [{pstr_desc = Pstr_type(_, [decl]); _}]), _);
psig_loc = loc} ->
output_enum_sig loc (cenum decl)
| other -> [mapper other]
let structure_item' mapper = function
| {pstr_desc =
Pstr_extension (({txt = "cstruct"; _}, PStr [{pstr_desc = Pstr_type(_, [decl]); _}]), _);
pstr_loc = loc} ->
output_struct loc (cstruct decl)
| {pstr_desc =
Pstr_extension (({txt = "cenum"; _}, PStr [{pstr_desc = Pstr_type(_, [decl]); _}]), _);
pstr_loc = loc ;
_ } ->
output_enum ~loc (cenum decl)
| other -> [mapper other]
class mapper = object
inherit Ast_traverse.map as super
method! signature s =
List.concat (List.map (signature_item' super#signature_item) s)
method! structure s =
List.concat (List.map (structure_item' super#structure_item) s)
end
let () =
let mapper = new mapper in
Driver.register_transformation "ppx_cstruct" ~impl:mapper#structure ~intf:mapper#signature