diff --git a/src/ppx/astmisc.ml b/src/ppx/astmisc.ml index bfb282c..7bba8fd 100644 --- a/src/ppx/astmisc.ml +++ b/src/ppx/astmisc.ml @@ -30,12 +30,19 @@ let pint ?loc ?suffix x = Pat.constant ?loc (Const.int ?suffix x) let eint ?loc ?suffix x = Exp.constant ?loc (Const.int ?suffix x) let estring ?loc x = Exp.constant ?loc (Const.string x) -let attr_base_type ~deriver ~loc attrs = +let attr_base_type ~deriver attrs = let open Ppx_deriving in - match attr ~deriver "t" attrs with - | Some { attr_payload = PTyp core_type; _ } -> core_type - | _ -> Ppx_deriving.raise_errorf ~loc - "ppx_deriving_binary requires [@t: base_type] for variants or polymorphic variants" + match attr ~deriver "base_type" attrs with + | Some { attr_payload = PTyp core_type; _ } -> Some core_type + | _ -> None + +let attr_base_type_exn ~deriver ~loc attrs = + let open Ppx_deriving in + match attr_base_type ~deriver attrs with + | Some core_type -> core_type + | None -> + Ppx_deriving.raise_errorf ~loc + "ppx_deriving_binary requires [@base_type: t] for variants or polymorphic variants" let attr_length ~deriver attrs = Ppx_deriving.(attrs |> attr ~deriver "length" |> Arg.(get_attr ~deriver int)) @@ -47,6 +54,9 @@ let attr_length_exn ~deriver ~loc attrs = Ppx_deriving.raise_errorf ~loc "ppx_deriving_binary requires [@length] for string, bytes, list and array" +let attr_offset ~deriver attrs = + Ppx_deriving.(attrs |> attr ~deriver "offset" |> Arg.(get_attr ~deriver int)) + (** Collect labelled arguments from an expression of form [fun ~lb1 ~lb2 ... -> ...]. *) let get_labelled_args_from_fun efun = diff --git a/src/ppx/bitfield.ml b/src/ppx/bitfield.ml new file mode 100644 index 0000000..968bb0b --- /dev/null +++ b/src/ppx/bitfield.ml @@ -0,0 +1,56 @@ +(* ppx_deriving_cstruct + + Copyright (c) 2021 Akinori Abe + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. *) + +open Ppx_deriving.Ast_convenience +open Ppxlib +open Ppxlib.Ast_helper + +type t = + { + rbf_name : string; + rbf_offset : int; + rbf_length : int; + rbf_mask : int; + rbf_loc : Location.t; + } + +let of_ocaml_label_declarations ~deriver label_decls = + let aux (default_ofs, acc) ld = + let ofs = + match Astmisc.attr_offset ~deriver ld.pld_attributes with + | Some n -> n + | None -> default_ofs in + let len = + match Astmisc.attr_length ~deriver ld.pld_attributes with + | Some n -> n + | None -> 1 in + let field = { + rbf_name = ld.pld_name.txt; + rbf_offset = ofs; + rbf_length = len; + rbf_mask = 1 lsl len - 1; + rbf_loc = ld.pld_loc; + } in + (ofs + len, field :: acc) + in + let _, rev_fields = List.fold_left aux (0, []) label_decls in + List.rev rev_fields diff --git a/src/ppx/decoder.ml b/src/ppx/decoder.ml index eaa4a6b..a4a6c78 100644 --- a/src/ppx/decoder.ml +++ b/src/ppx/decoder.ml @@ -110,11 +110,11 @@ and decoder_of_polymorphic_variant ~loc row_fields attrs = - let base_type = Astmisc.attr_base_type ~deriver ~loc attrs in + let base_type = Astmisc.attr_base_type_exn ~deriver ~loc attrs in Variant.constructors_of_ocaml_row_fields ~deriver row_fields |> decoder_of_constructors ~deriver ~path ~base_type ~loc - ~constructor:Exp.variant + ~constructor:(fun name -> Exp.variant name) and decoder_of_constructors ~deriver @@ -174,3 +174,85 @@ and decoder_of_record ~deriver ~path ~constructor ~loc labels = labels [%expr ([%e record], _i)] in [%expr fun _b _i -> [%e body]] + +let decoder_of_variant ~deriver ~path ~loc type_decl constrs attrs = + Variant.assert_not_GADT type_decl constrs ; + let base_type = Astmisc.attr_base_type_exn ~deriver ~loc attrs in + Variant.constructors_of_ocaml_constructors ~deriver constrs + |> decoder_of_constructors + ~deriver + ~path + ~base_type + ~loc + ~constructor:(fun name -> Exp.construct (Astmisc.mklid name)) + +let mk_record labels = + let label_bindings = + List.map + (fun ld -> (Astmisc.mklid ld.pld_name.txt, evar ld.pld_name.txt)) + labels in + Exp.record label_bindings None + +let decoder_of_record_bitfield ~deriver ~path ~loc base_type labels = + let open Bitfield in + let mk_rec = mk_record labels in + let fields = Bitfield.of_ocaml_label_declarations ~deriver labels in + let dec = + List.fold_right + (fun field acc -> + let name = pvar field.rbf_name in + let loc = field.rbf_loc in + let ofs = Exp.constant (Const.int field.rbf_offset) in + let mask = Exp.constant (Const.int field.rbf_mask) in + let rhs = [%expr (_x lsr [%e ofs]) land [%e mask]] in + [%expr let [%p name] = [%e rhs] in [%e acc]]) + fields mk_rec in + let base_decoder = decoder_of_core_type ~deriver ~path base_type in + [%expr + fun _cs _i -> + let _x, _i = [%e base_decoder] _cs _i in + ([%e dec], _i)] + +let str_decoder_of_type_decl ~deriver ~path type_decl = + let loc = type_decl.ptype_loc in + let path = String.concat "." path ^ "." ^ type_decl.ptype_name.txt in + let decoder = match type_decl.ptype_kind with + (* Record type declaration: *) + | Ptype_record labels -> + begin + match Astmisc.attr_base_type ~deriver type_decl.ptype_attributes with + | None -> + decoder_of_record + ~deriver ~path ~loc:type_decl.ptype_loc labels + ~constructor:(fun x -> x) + | Some base_type -> + decoder_of_record_bitfield ~deriver ~path ~loc base_type labels + end + (* (Non-polymorphics) varia nt type declarations: *) + | Ptype_variant constrs -> + decoder_of_variant ~deriver ~path ~loc type_decl constrs type_decl.ptype_attributes + (* Other types: *) + | Ptype_abstract | Ptype_open -> + match type_decl.ptype_manifest with + | Some typ -> decoder_of_core_type ~deriver ~path typ + | None -> + Ppx_deriving.raise_errorf + "ppx_deriving_cstruct does not support empty types: %s" + type_decl.ptype_name.txt in + (* Converts type parameters into function parameters *) + Astmisc.parametrize_expression type_decl.ptype_params decoder + +let type_decl_str ~deriver ~options:_ ~path type_decls = + [Astmisc.create_str_value + ~mkexp:(str_decoder_of_type_decl ~deriver ~path) + affix type_decls] + +let sig_decoder_of_type_decl type_decl = + Astmisc.create_sig_value + affix type_decl + ~mktype:(fun t -> + let loc = t.ptyp_loc in + [%type: Bytes.t -> int -> [%t t] * int]) + +let type_decl_sig ~options:_ ~path:_ type_decls = + List.map (sig_decoder_of_type_decl) type_decls diff --git a/src/ppx/dune b/src/ppx/dune index 54fbcec..900ac49 100644 --- a/src/ppx/dune +++ b/src/ppx/dune @@ -16,4 +16,5 @@ (modules Ppx_deriving_binary Decoder Variant + Bitfield Astmisc)) diff --git a/src/ppx/ppx_deriving_binary.ml b/src/ppx/ppx_deriving_binary.ml index 08cd076..28a2525 100644 --- a/src/ppx/ppx_deriving_binary.ml +++ b/src/ppx/ppx_deriving_binary.ml @@ -22,7 +22,9 @@ let () = let open Ppx_deriving in + let deriver = "of_binary_bytes" in register (create "of_binary_bytes" () ~core_type:(Decoder.decoder_of_core_type - ~deriver:"of_binary_bytes" - ~path:"")) + ~deriver ~path:"") + ~type_decl_str:(Decoder.type_decl_str ~deriver) + ~type_decl_sig:Decoder.type_decl_sig) diff --git a/tests/ppx/test_of_binary_bytes.ml b/tests/ppx/test_of_binary_bytes.ml index 28f8962..334073f 100644 --- a/tests/ppx/test_of_binary_bytes.ml +++ b/tests/ppx/test_of_binary_bytes.ml @@ -84,15 +84,195 @@ let test_of_binary_bytes_list ctxt = let test_of_binary_bytes_polymorphic_variant ctxt = let b = b_ "\x00\x1c" in - let actual = [%of_binary_bytes: [ `A [@value 0x1c] | `B of uint8 * uint16le ] [@t: uint8]] b 1 in + let actual = [%of_binary_bytes: [ `A [@value 0x1c] | `B of uint8 * uint16le ] [@base_type: uint8]] b 1 in let expected = (`A, 2) in assert_equal ~ctxt ~printer:[%show: [ `A | `B of uint8 * uint16le ] * int] expected actual ; let b = b_ "\x00\x01\x11\x22\x33" in - let actual = [%of_binary_bytes: [ `A [@value 0x1c] | `B of uint8 * uint16le ] [@t: uint8]] b 1 in + let actual = [%of_binary_bytes: [ `A [@value 0x1c] | `B of uint8 * uint16le ] [@base_type: uint8]] b 1 in let expected = (`B (0x11, 0x3322), 5) in assert_equal ~ctxt ~printer:[%show: [ `A | `B of uint8 * uint16le ] * int] expected actual +type t1 = + { + a : uint16le; + b : uint8; + } +[@@deriving of_binary_bytes, show] + +let test_of_binary_bytes_str_record ctxt = + let b = b_ "\x00\x12\x34\x56" in + let actual = [%of_binary_bytes: t1] b 1 in + let expected = ({ a = 0x3412; b = 0x56 }, 4) in + assert_equal ~ctxt ~printer:[%show: t1 * int] expected actual + +type t2 = + | Foo [@value 0x42] + | Bar +[@@base_type: int32lei] +[@@deriving of_binary_bytes, show] + +let test_of_binary_bytes_str_variant_noargs ctxt = + let b = b_ "\x00\x42\x00\x00\x00" in + let actual = [%of_binary_bytes: t2] b 1 in + let expected = (Foo, 5) in + assert_equal ~ctxt ~printer:[%show: t2 * int] expected actual + ; + let b = b_ "\x00\x01\x00\x00\x00" in + let actual = [%of_binary_bytes: t2] b 1 in + let expected = (Bar, 5) in + assert_equal ~ctxt ~printer:[%show: t2 * int] expected actual + ; + let b = b_ "\x00\x02\x00\x00\x00" in + assert_raises (Parse_error "Test_of_binary_bytes.t2") (fun () -> [%of_binary_bytes: t2] b 1) + +type t3 = + | Foo of uint8 + | Bar of uint8 * uint16le +[@@base_type: uint16le] +[@@deriving of_binary_bytes, show] + +let test_of_binary_bytes_str_variant_tuple ctxt = + let b = b_ "\x00\x00\x00\x42" in + let actual = [%of_binary_bytes: t3] b 1 in + let expected = (Foo 0x42, 4) in + assert_equal ~ctxt ~printer:[%show: t3 * int] expected actual + ; + let b = b_ "\x00\x01\x00\x11\x22\x33" in + let actual = [%of_binary_bytes: t3] b 1 in + let expected = (Bar (0x11, 0x3322), 6) in + assert_equal ~ctxt ~printer:[%show: t3 * int] expected actual + +type t4 = + | Foo of { a : uint8; b : uint16le; } +[@@base_type: uint16le] +[@@deriving of_binary_bytes, show] + +let test_of_binary_bytes_str_variant_record ctxt = + let b = b_ "\x00\x00\x00\x11\x22\x33" in + let actual = [%of_binary_bytes: t4] b 1 in + let expected = (Foo { a = 0x11; b = 0x3322 }, 6) in + assert_equal ~ctxt ~printer:[%show: t4 * int] expected actual + +type ('a, 'b) t5 = 'a * 'b +[@@deriving of_binary_bytes, show] + +let test_of_binary_bytes_str_parametrized_type ctxt = + let b = b_ "\x00\x11\x22\x33\x44" in + let actual = [%of_binary_bytes: (uint16le, uint16le) t5] b 1 in + let expected = ((0x2211, 0x4433), 5) in + assert_equal ~ctxt ~printer:[%show: (uint16le, uint16le) t5 * int] expected actual + +type t6 = [ `Foo | `Bar of uint16le ] [@base_type: uint16le] +[@@deriving of_binary_bytes, show] + +let test_of_binary_bytes_str_poly_variant ctxt = + let b = b_ "\x00\x00\x00" in + let actual = [%of_binary_bytes: t6] b 1 in + let expected = (`Foo, 3) in + assert_equal ~ctxt ~printer:[%show: t6 * int] expected actual + ; + let b = b_ "\x00\x01\x00\x11\x22" in + let actual = [%of_binary_bytes: t6] b 1 in + let expected = (`Bar 0x2211, 5) in + assert_equal ~ctxt ~printer:[%show: t6 * int] expected actual + ; + let b = b_ "\x00\x02\x00" in + assert_raises + (Parse_error "Test_of_binary_bytes.t6") + (fun () -> [%of_binary_bytes: t6] b 1) + +type t9 = + { + foo : uint8; + baz : uint8 option [@of_binary_bytes + fun ~foo b i -> + if foo = 0 then (None, i) + else let x, i = uint8_of_binary_bytes b i in (Some x, i)]; + hoge : uint8; + } +[@@deriving of_binary_bytes, show] + +let test_of_binary_bytes_str_conditional_field ctxt = + let b = b_ "\x00\x00\x00" in + let actual = [%of_binary_bytes: t9] b 1 in + let expected = ({ foo = 0; baz = None; hoge = 0; }, 3) in + assert_equal ~ctxt ~printer:[%show: t9 * int] expected actual + ; + let b = b_ "\x00\x01\x02\x03" in + let actual = [%of_binary_bytes: t9] b 1 in + let expected = ({ foo = 1; baz = Some 2; hoge = 3; }, 4) in + assert_equal ~ctxt ~printer:[%show: t9 * int] expected actual + +type t10 = + { + b0 : int; + b1 : int [@length 2]; + b2 : int; + b3 : int [@offset 8] [@length 4]; + } +[@@base_type: uint16be] +[@@deriving of_binary_bytes, show] + +let test_of_binary_bytes_str_bitfield ctxt = + let b = b_ "\x00\x00\x01" in + let actual = [%of_binary_bytes: t10] b 1 in + let expected = ({ + b0 = 1; + b1 = 0; + b2 = 0; + b3 = 0; + }, 3) in + assert_equal ~ctxt ~printer:[%show: t10 * int] expected actual + ; + let b = b_ "\x00\x00\x06" in + let actual = [%of_binary_bytes: t10] b 1 in + let expected = ({ + b0 = 0; + b1 = 3; + b2 = 0; + b3 = 0; + }, 3) in + assert_equal ~ctxt ~printer:[%show: t10 * int] expected actual + ; + let b = b_ "\x00\x00\x08" in + let actual = [%of_binary_bytes: t10] b 1 in + let expected = ({ + b0 = 0; + b1 = 0; + b2 = 1; + b3 = 0; + }, 3) in + assert_equal ~ctxt ~printer:[%show: t10 * int] expected actual + ; + let b = b_ "\x00\x00\xf0" in + let actual = [%of_binary_bytes: t10] b 1 in + let expected = ({ + b0 = 0; + b1 = 0; + b2 = 0; + b3 = 0; + }, 3) in + assert_equal ~ctxt ~printer:[%show: t10 * int] expected actual + ; + let b = b_ "\x00\x0f\x00" in + let actual = [%of_binary_bytes: t10] b 1 in + let expected = ({ + b0 = 0; + b1 = 0; + b2 = 0; + b3 = 0xf; + }, 3) in + assert_equal ~ctxt ~printer:[%show: t10 * int] expected actual + +(* Checks that [@@deriving of_binary_bytes] in signature generates a type consistent + with one in structure by the OCaml type checker. *) +module M : sig + type 'a t [@@deriving of_binary_bytes] +end = struct + type 'a t = 'a * uint8 [@@deriving of_binary_bytes] +end + let suite = "of_binary_bytes driver" >::: [ "[%of_binary_bytes: core-type]" >::: [ @@ -108,5 +288,15 @@ let suite = "string" >:: test_of_binary_bytes_string; "list" >:: test_of_binary_bytes_list; "polymorphic_variant" >:: test_of_binary_bytes_polymorphic_variant; - ] + ]; + "[@@deriving of_binary_bytes] in structure" >::: [ + "record" >:: test_of_binary_bytes_str_record; + "variant without arguments" >:: test_of_binary_bytes_str_variant_noargs; + "variant with tuple arguments" >:: test_of_binary_bytes_str_variant_tuple; + "variant with record arguments" >:: test_of_binary_bytes_str_variant_record; + "parametrized type" >:: test_of_binary_bytes_str_parametrized_type; + "polymorphic variant" >:: test_of_binary_bytes_str_poly_variant; + "record (conditional field)" >:: test_of_binary_bytes_str_conditional_field; + "record (bitfield)" >:: test_of_binary_bytes_str_bitfield; + ]; ]