Bitmatch syntax extension, working on bits and bitstrings.
authorRichard W.M. Jones <rich@annexia.org>
Mon, 31 Mar 2008 22:52:17 +0000 (22:52 +0000)
committerRichard W.M. Jones <rich@annexia.org>
Mon, 31 Mar 2008 22:52:17 +0000 (22:52 +0000)
12 files changed:
.cvsignore [new file with mode: 0644]
.depend [new file with mode: 0644]
Makefile [new file with mode: 0644]
bitmatch.ml [new file with mode: 0644]
bitmatch.mli [new file with mode: 0644]
examples/.cvsignore [new file with mode: 0644]
examples/ipv4_header.ml [new file with mode: 0644]
examples/make_ipv4_header.ml [new file with mode: 0644]
pa_bitmatch.ml [new file with mode: 0644]
tests/.cvsignore [new file with mode: 0644]
tests/01_load.ml [new file with mode: 0644]
tests/05_bits.ml [new file with mode: 0644]

diff --git a/.cvsignore b/.cvsignore
new file mode 100644 (file)
index 0000000..6ccf209
--- /dev/null
@@ -0,0 +1,5 @@
+*.cmi
+*.cmo
+*.cmx
+*.cma
+*.cmxa
\ No newline at end of file
diff --git a/.depend b/.depend
new file mode 100644 (file)
index 0000000..4797d7b
--- /dev/null
+++ b/.depend
@@ -0,0 +1,2 @@
+bitmatch.cmo: bitmatch.cmi 
+bitmatch.cmx: bitmatch.cmi 
diff --git a/Makefile b/Makefile
new file mode 100644 (file)
index 0000000..348e320
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,66 @@
+# $Id: Makefile,v 1.1 2008-03-31 22:52:17 rjones Exp $
+
+OCAMLFIND = ocamlfind
+OCAMLMKLIB = ocamlmklib
+
+
+OCAMLCFLAGS = -g
+OCAMLCPACKAGES =
+OCAMLOPTFLAGS =
+OCAMLOPTPACKAGES =
+
+EXAMPLES := $(wildcard examples/*.ml)
+
+TESTS  := $(patsubst %.ml,%,$(wildcard tests/*.ml))
+
+all:   pa_bitmatch.cmo bitmatch.cma bitmatch.cmxa
+
+pa_bitmatch.cmo: pa_bitmatch.ml
+       ocamlfind ocamlc -I +camlp4 camlp4lib.cma -pp camlp4of.opt -c $< -o $@
+
+bitmatch.cma: bitmatch.cmo
+       $(OCAMLFIND) ocamlc -a -o $@ $^
+
+bitmatch.cmxa: bitmatch.cmx
+       $(OCAMLFIND) ocamlopt -a -o $@ $^
+
+test:
+       @for f in $(TESTS); do \
+         echo Test: $$f; \
+         $(OCAMLFIND) ocamlc -pp "camlp4o pa_bitmatch.cmo" \
+           -I . bitmatch.cma $$f.ml -o $$f; \
+         $$f; \
+       done
+
+print-tests: pa_bitmatch.cmo
+       @for f in $(TESTS); do \
+         echo Test: $$f.ml; \
+         camlp4o pa_bitmatch.cmo -printer pr_o.cmo $$f.ml; \
+       done
+
+print-examples: pa_bitmatch.cmo
+       @for f in $(EXAMPLES); do \
+         echo Example: $$f; \
+         camlp4o pa_bitmatch.cmo -printer pr_o.cmo $$f; \
+       done
+
+.mli.cmi:
+       $(OCAMLFIND) ocamlc $(OCAMLCFLAGS) $(OCAMLCPACKAGES) -c $<
+.ml.cmo:
+       $(OCAMLFIND) ocamlc $(OCAMLCFLAGS) $(OCAMLCPACKAGES) -c $<
+.ml.cmx:
+       $(OCAMLFIND) ocamlopt $(OCAMLOPTFLAGS) $(OCAMLOPTPACKAGES) -c $<
+
+depend: .depend
+
+.depend: bitmatch.ml bitmatch.mli
+       rm -f .depend
+       $(OCAMLFIND) ocamldep $(OCAMLCPACKAGES) $^ > $@
+
+ifeq ($(wildcard .depend),.depend)
+include .depend
+endif
+
+.PHONY: depend dist check-manifest dpkg doc print-examples print-tests test
+
+.SUFFIXES:      .cmo .cmi .cmx .ml .mli .mll
diff --git a/bitmatch.ml b/bitmatch.ml
new file mode 100644 (file)
index 0000000..3135c89
--- /dev/null
@@ -0,0 +1,48 @@
+(* Bitmatch library.
+ * $Id: bitmatch.ml,v 1.1 2008-03-31 22:52:17 rjones Exp $
+ *)
+
+(* A bitstring is simply the data itself (as a string), and the
+ * bitoffset and the bitlength within the string.  Note offset/length
+ * are counted in bits, not bytes.
+ *)
+type bitstring = string * int * int
+
+(* Functions to create and load bitstrings. *)
+let empty_bitstring = "", 0, 0
+
+let make_bitstring len c = String.make ((len+7) lsr 3) c, 0, len
+
+let create_bitstring len = make_bitstring len '\000'
+
+let bitstring_of_chan chan =
+  let tmpsize = 16384 in
+  let buf = Buffer.create tmpsize in
+  let tmp = String.create tmpsize in
+  let n = ref 0 in
+  while n := input chan tmp 0 tmpsize; !n > 0 do
+    Buffer.add_substring buf tmp 0 !n;
+  done;
+  Buffer.contents buf, 0, Buffer.length buf lsl 3
+
+let bitstring_of_file fname =
+  let chan = open_in_bin fname in
+  let bs = bitstring_of_chan chan in
+  close_in chan;
+  bs
+
+(* Extraction functions (internal: called from the generated macros,
+ * and the parameters should have been checked for sanity already).
+ *)
+let extract_bitstring data off len flen =
+  (data, off, flen), off+flen, len-flen
+
+let extract_remainder data off len =
+  (data, off, len), off+len, 0
+
+(* Extract and convert to numeric. *)
+let extract_bit data off len _ =       (* final param is always 1 *)
+  let byteoff = off lsr 3 in
+  let bitmask = 1 lsl (7 - (off land 7)) in
+  let b = Char.code data.[byteoff] land bitmask <> 0 in
+  b, off+1, len-1
diff --git a/bitmatch.mli b/bitmatch.mli
new file mode 100644 (file)
index 0000000..7760d5a
--- /dev/null
@@ -0,0 +1,23 @@
+(* Bitmatch library.
+ * $Id: bitmatch.mli,v 1.1 2008-03-31 22:52:17 rjones Exp $
+ *)
+
+type bitstring = string * int * int
+
+val empty_bitstring : bitstring
+
+val create_bitstring : int -> bitstring
+
+val make_bitstring : int -> char -> bitstring
+
+val bitstring_of_chan : in_channel -> bitstring
+
+val bitstring_of_file : string -> bitstring
+
+(**/**)
+
+val extract_bitstring : string -> int -> int -> int -> bitstring * int * int
+
+val extract_remainder : string -> int -> int -> bitstring * int * int
+
+val extract_bit : string -> int -> int -> int -> bool * int * int
diff --git a/examples/.cvsignore b/examples/.cvsignore
new file mode 100644 (file)
index 0000000..6ccf209
--- /dev/null
@@ -0,0 +1,5 @@
+*.cmi
+*.cmo
+*.cmx
+*.cma
+*.cmxa
\ No newline at end of file
diff --git a/examples/ipv4_header.ml b/examples/ipv4_header.ml
new file mode 100644 (file)
index 0000000..c5d6945
--- /dev/null
@@ -0,0 +1,78 @@
+(* Parse and display an IPv4 header from a file.
+ * $Id: ipv4_header.ml,v 1.1 2008-03-31 22:52:17 rjones Exp $
+ *)
+
+open Printf
+
+let header = Bitmatch.bitstring_of_file "ipv4_header.dat"
+
+let () =
+  bitmatch header with
+  | version : 4; hdrlen : 4; tos : 8; length : 16;
+    identification : 16; flags : 3; fragoffset : 13;
+    ttl : 8; protocol : 8; checksum : 16;
+    source : 32;
+    dest : 32;
+    options : (hdrlen-5)*32 : bitstring;
+    payload : -1 : bitstring
+      when version = 4 ->
+
+    printf "IPv%d:\n" version;
+    printf "  header length: %d * 32 bit words\n" hdrlen;
+    printf "  type of service: %d\n" tos;
+    printf "  packet length: %d bytes\n" length;
+    printf "  identification: %d\n" identification;
+    printf "  flags: %d\n" flags;
+    printf "  fragment offset: %d\n" fragoffset;
+    printf "  ttl: %d\n" ttl;
+    printf "  protocol: %d\n" protocol;
+    printf "  checksum: %d\n" checksum;
+    printf "  source: %lx  dest: %lx\n" source dest;
+    printf "  header options + padding:\n";
+    Bitmatch.hexdump_bitstring stdout options;
+    printf "  packet payload:\n";
+    Bitmatch.hexdump_bitstring stdout payload
+
+  | version : 4 ->
+    eprintf "cannot parse IP version %d\n" version
+
+  | _ as header ->
+    eprintf "data is smaller than one nibble:\n";
+    Bitmatch.hexdump_bitstring stderr header
+
+
+(* converted into:
+
+   let (data, off, len) = header in
+   let result = ref None in
+   try
+   if len >= 4 then (
+     let version, off, len = Bitmatch.extract_unsigned_be data off len 4 in
+     if len >= 4 then (
+       let hdrlen, off, len = Bitmatch.extract_unsigned_be data off len 4 in
+       (* ... *)
+       if (hdrlen-5)*32 >= 0 && len >= (hdrlen-5)*32 then (
+         let options, off, len =
+           Bitmatch.extract_bitstring data off len ((hdrlen-5)*32) in
+         let payload, off, len =
+           Bitmatch.extract_remainder data off len in
+
+         if version = 4 then (
+           ...
+           raise Exit
+         )
+       )
+     )
+   )
+   if len >= 4 then (
+     let version, off, len = Bitmatch.extract_unsigned_be data off len 4 in
+     ...;
+     raise Exit
+   )
+   ...
+   with Exit -> ();
+   match !result with
+   | Some x -> x
+   | None -> raise Match_failure _loc
+
+*)
diff --git a/examples/make_ipv4_header.ml b/examples/make_ipv4_header.ml
new file mode 100644 (file)
index 0000000..55ba2e1
--- /dev/null
@@ -0,0 +1,45 @@
+(* Create an IPv4 header.
+ * $Id: make_ipv4_header.ml,v 1.1 2008-03-31 22:52:17 rjones Exp $
+ *)
+
+open Printf
+
+let version = 4
+let hdrlen = 5                         (* no options *)
+let tos = 16
+let length = 64                                (* total packet length *)
+let identification = 0
+let flags = 0
+let fragoffset = 0
+let ttl = 255
+let protocol = 17                      (* UDP *)
+let checksum = 0
+let source = 0xc0a80202                        (* 192.168.2.2 *)
+let dest = 0xc0a80201                  (* 192.168.2.1 *)
+let options = Bitmatch.empty_bitstring
+let payload_length = (length - hdrlen*4) * 8
+let payload = Bitmatch.create_bitstring payload_length
+
+let header =
+  <| version : 4; hdrlen : 4; tos : 8; length : 16;
+     identification : 16; flags : 3; fragoffset : 13;
+     ttl : 8; protocol : 8; checksum : 16;
+     source : 32;
+     dest : 32;
+     options : -1, bitstring;
+     payload : payload_length, bitstring |>
+
+(*
+  generates:
+
+  let header = Bitmatch.join_bitstrings [
+    Bitmatch.create_unsigned_be version 4;
+    Bitmatch.create_unsigned_be hdrlen 4; (* etc. *)
+    options;
+    Bitmatch.check_bitstring_length payload payload_length
+  ]
+
+  which can throw an exception if values are out of range.
+*)
+
+let () = Bitmatch.file_of_bitstring header "ipv4_header_out.dat"
diff --git a/pa_bitmatch.ml b/pa_bitmatch.ml
new file mode 100644 (file)
index 0000000..2c5a6de
--- /dev/null
@@ -0,0 +1,373 @@
+(* Bitmatch syntax extension.
+ * $Id: pa_bitmatch.ml,v 1.1 2008-03-31 22:52:17 rjones Exp $
+ *)
+
+open Printf
+
+open Camlp4.PreCast
+open Syntax
+open Ast
+
+type m = Fields of f list              (* field ; field -> ... *)
+       | Bind of string option         (* _ -> ... *)
+and f = {
+  ident : string;                      (* field name *)
+  flen : expr;                         (* length in bits, may be non-const *)
+  endian : endian;                     (* endianness *)
+  signed : bool;                       (* true if signed, false if unsigned *)
+  t : t;                               (* type *)
+}
+and endian = BigEndian | LittleEndian | NativeEndian
+and t = Int | Bitstring
+
+(* Generate a fresh, unique symbol each time called. *)
+let gensym =
+  let i = ref 1000 in
+  fun name ->
+    incr i; let i = !i in
+    sprintf "__pabitmatch_%s_%d" name i
+
+(* Deal with the qualifiers which appear for a field. *)
+let output_field _loc name flen qs =
+  let endian, signed, t =
+    match qs with
+    | None -> (None, None, None)
+    | Some qs ->
+       List.fold_left (
+         fun (endian, signed, t) q ->
+           match q with
+           | "bigendian" ->
+               if endian <> None then
+                 Loc.raise _loc (Failure "an endian flag has been set already")
+               else (
+                 let endian = Some BigEndian in
+                 (endian, signed, t)
+               )
+           | "littleendian" ->
+               if endian <> None then
+                 Loc.raise _loc (Failure "an endian flag has been set already")
+               else (
+                 let endian = Some LittleEndian in
+                 (endian, signed, t)
+               )
+           | "nativeendian" ->
+               if endian <> None then
+                 Loc.raise _loc (Failure "an endian flag has been set already")
+               else (
+                 let endian = Some NativeEndian in
+                 (endian, signed, t)
+               )
+           | "signed" ->
+               if signed <> None then
+                 Loc.raise _loc (Failure "a signed flag has been set already")
+               else (
+                 let signed = Some true in
+                 (endian, signed, t)
+               )
+           | "unsigned" ->
+               if signed <> None then
+                 Loc.raise _loc (Failure "a signed flag has been set already")
+               else (
+                 let signed = Some false in
+                 (endian, signed, t)
+               )
+           | "int" ->
+               if t <> None then
+                 Loc.raise _loc (Failure "a type flag has been set already")
+               else (
+                 let t = Some Int in
+                 (endian, signed, t)
+               )
+           | "bitstring" ->
+               if t <> None then
+                 Loc.raise _loc (Failure "a type flag has been set already")
+               else (
+                 let t = Some Bitstring in
+                 (endian, signed, t)
+               )
+           | s ->
+               Loc.raise _loc (Failure (s ^ ": unknown qualifier"))
+       ) (None, None, None) qs in
+
+  (* If type is set to bitstring then endianness and signedness
+   * qualifiers are meaningless and must not be set.
+   *)
+  if t = Some Bitstring && (endian <> None || signed <> None) then
+    Loc.raise _loc (
+      Failure "bitstring type and endian or signed qualifiers cannot be mixed"
+    );
+
+  (* Default endianness, signedness, type. *)
+  let endian = match endian with None -> BigEndian | Some e -> e in
+  let signed = match signed with None -> false | Some s -> s in
+  let t = match t with None -> Int | Some t -> t in
+
+  {
+    ident = name;
+    flen = flen;
+    endian = endian;
+    signed = signed;
+    t = t;
+  }
+
+(* Generate the code for a bitmatch statement.  '_loc' is the
+ * location, 'bs' is the bitstring parameter, 'cases' are
+ * the list of cases to test against.
+ *)
+let output_bitmatch _loc bs cases =
+  let data = gensym "data" and off = gensym "off" and len = gensym "len" in
+  let result = gensym "result" in
+
+  (* This generates the field extraction code for each
+   * field a single case.  Each field must be wider than
+   * the minimum permitted for the type and there must be
+   * enough remaining data in the bitstring to satisfy it.
+   * As we go through the fields, symbols 'data', 'off' and 'len'
+   * track our position and remaining length in the bitstring.
+   *
+   * The whole thing is a lot of nested 'if' statements. Code
+   * is generated from the inner-most (last) field outwards.
+   *)
+  let rec output_field_extraction inner = function
+    | [] -> inner
+    | {ident=ident; flen=flen; endian=endian; signed=signed; t=t} :: fields ->
+       (* If length an integer constant?  If so, what is it?  This
+        * is very simple-minded and only detects simple constants.
+        *)
+       let flen_is_const =
+         match flen with
+         | <:expr< $int:i$ >> -> Some (int_of_string i)
+         | _ -> None in
+
+       let name_of_int_extract_const = function
+         | (1, _, _) -> "extract_bit"
+         | ((2|3|4|5|6|7), _, false) -> "extract_char_unsigned"
+         | ((2|3|4|5|6|7), _, true) -> "extract_char_signed"
+         | (i, BigEndian, false) when i <= 31 -> "extract_int_be_unsigned"
+         | (i, BigEndian, true) when i <= 31 -> "extract_int_be_signed"
+         | (i, LittleEndian, false) when i <= 31 -> "extract_int_le_unsigned"
+         | (i, LittleEndian, true) when i <= 31 -> "extract_int_le_signed"
+         | (i, NativeEndian, false) when i <= 31 -> "extract_int_ne_unsigned"
+         | (i, NativeEndian, true) when i <= 31 -> "extract_int_ne_signed"
+         | (32, BigEndian, false) -> "extract_int32_be_unsigned"
+         | (32, BigEndian, true) -> "extract_int32_be_signed"
+         | (32, LittleEndian, false) -> "extract_int32_le_unsigned"
+         | (32, LittleEndian, true) -> "extract_int32_le_signed"
+         | (32, NativeEndian, false) -> "extract_int32_ne_unsigned"
+         | (32, NativeEndian, true) -> "extract_int32_ne_signed"
+         | (_, BigEndian, false) -> "extract_int64_be_unsigned"
+         | (_, BigEndian, true) -> "extract_int64_be_signed"
+         | (_, LittleEndian, false) -> "extract_int64_le_unsigned"
+         | (_, LittleEndian, true) -> "extract_int64_le_signed"
+         | (_, NativeEndian, false) -> "extract_int64_ne_unsigned"
+         | (_, NativeEndian, true) -> "extract_int64_ne_signed"
+       in
+       let name_of_int_extract = function
+           (* XXX As an enhancement we should allow users to
+            * specify that a field length can fit into a char/int/int32
+            * (of course, this would have to be checked at runtime).
+            *)
+         | (BigEndian, false) -> "extract_int64_be_unsigned"
+         | (BigEndian, true) -> "extract_int64_be_signed"
+         | (LittleEndian, false) -> "extract_int64_le_unsigned"
+         | (LittleEndian, true) -> "extract_int64_le_signed"
+         | (NativeEndian, false) -> "extract_int64_ne_unsigned"
+         | (NativeEndian, true) -> "extract_int64_ne_signed"
+       in
+
+       let expr =
+         match t, flen_is_const with
+         (* Common case: int field, constant flen *)
+         | Int, Some i when i > 0 && i <= 64 ->
+             let extract_func = name_of_int_extract_const (i,endian,signed) in
+             <:expr<
+               if $lid:len$ >= $flen$ then (
+                 let $lid:ident$, $lid:off$, $lid:len$ =
+                   Bitmatch.$lid:extract_func$ $lid:data$ $lid:off$ $lid:len$
+                     $flen$ in
+                 $inner$
+               )
+             >>
+
+         | Int, Some _ ->
+             Loc.raise _loc (Failure "length of int field must be [1..64]")
+
+         (* Int field, non-const flen.  We have to test the range of
+          * the field at runtime.  If outside the range it's a no-match
+          * (not an error).
+          *)
+         | Int, None ->
+             let extract_func = name_of_int_extract (endian,signed) in
+             <:expr<
+               if $flen$ >= 1 && $flen$ <= 64 && $flen$ >= $lid:len$ then (
+                 let $lid:ident$, $lid:off$, $lid:len$ =
+                   Bitmatch.$lid:extract_func$ $lid:data$ $lid:off$ $lid:len$
+                     $flen$ in
+                 $inner$
+               )
+             >>
+
+          (* Bitstring, constant flen >= 0. *)
+         | Bitstring, Some i when i >= 0 ->
+             <:expr<
+               if $lid:len$ >= $flen$ then (
+                 let $lid:ident$, $lid:off$, $lid:len$ =
+                   Bitmatch.extract_bitstring $lid:data$ $lid:off$ $lid:len$
+                     $flen$ in
+                 $inner$
+               )
+             >>
+
+          (* Bitstring, constant flen = -1, means consume all the
+          * rest of the input.
+          *)
+         | Bitstring, Some i when i = -1 ->
+             <:expr<
+               let $lid:ident$, $lid:off$, $lid:len$ =
+                 Bitmatch.extract_remainder $lid:data$ $lid:off$ $lid:len$ in
+                 $inner$
+             >>
+
+         | Bitstring, Some _ ->
+             Loc.raise _loc (Failure "length of bitstring must be >= 0 or the special value -1")
+
+         (* Bitstring field, non-const flen.  We check the flen is >= 0
+          * (-1 is not allowed here) at runtime.
+          *)
+         | Bitstring, None ->
+             <:expr<
+               if $flen$ >= 0 && $lid:len$ >= $flen$ then (
+                 let $lid:ident$, $lid:off$, $lid:len$ =
+                   Bitmatch.extract_bitstring $lid:data$ $lid:off$ $lid:len$
+                     $flen$ in
+                 $inner$
+               )
+             >>
+       in
+
+       output_field_extraction expr fields
+  in
+
+  (* Convert each case in the match. *)
+  let cases = List.map (
+    function
+    (* field : len ; field : len when .. -> ..*)
+    | (Fields fields, Some whenclause, code) ->
+       let inner =
+         <:expr<
+           if $whenclause$ then (
+             $lid:result$ := Some ($code$);
+             raise Exit
+            )
+         >> in
+       output_field_extraction inner (List.rev fields)
+
+    (* field : len ; field : len -> ... *)
+    | (Fields fields, None, code) ->
+       let inner =
+         <:expr<
+           $lid:result$ := Some ($code$);
+           raise Exit
+         >> in
+       output_field_extraction inner (List.rev fields)
+
+    (* _ as name when ... -> ... *)
+    | (Bind (Some name), Some whenclause, code) ->
+       <:expr<
+         let $lid:name$ = ($lid:data$, $lid:off$, $lid:len$) in
+         if $whenclause$ then (
+           $lid:result$ := Some ($code$);
+           raise Exit
+         )
+       >>
+
+    (* _ as name -> ... *)
+    | (Bind (Some name), None, code) ->
+       <:expr<
+         let $lid:name$ = ($lid:data$, $lid:off$, $lid:len$) in
+         $lid:result$ := Some ($code$);
+         raise Exit
+       >>
+
+    (* _ when ... -> ... *)
+    | (Bind None, Some whenclause, code) ->
+       <:expr<
+         if $whenclause$ then (
+           $lid:result$ := Some ($code$);
+           raise Exit
+         )
+       >>
+
+    (* _ -> ... *)
+    | (Bind None, None, code) ->
+       <:expr<
+         $lid:result$ := Some ($code$);
+         raise Exit
+       >>
+
+  ) cases in
+
+  let cases =
+    List.fold_right (fun case base -> <:expr< $case$ ; $base$ >>)
+      cases <:expr< () >> in
+
+  (* The final code just wraps the list of cases in a
+   * try/with construct so that each case is tried in
+   * turn until one case matches (that case sets 'result'
+   * and raises 'Exit' to leave the whole statement).
+   * If result isn't set by the end then we will raise
+   * Match_failure with the location of the bitmatch
+   * statement in the original code.
+   *)
+  let loc_fname = Loc.file_name _loc in
+  let loc_line = string_of_int (Loc.start_line _loc) in
+  let loc_char = string_of_int (Loc.start_off _loc - Loc.start_bol _loc) in
+
+  <:expr<
+    let ($lid:data$, $lid:off$, $lid:len$) = $bs$ in
+    let $lid:result$ = ref None in
+    (try
+      $cases$
+    with Exit -> ());
+    match ! $lid:result$ with
+    | Some x -> x
+    | None -> raise (Match_failure ($str:loc_fname$,
+                                   $int:loc_line$, $int:loc_char$))
+  >>
+
+EXTEND Gram
+  GLOBAL: expr;
+
+  qualifiers: [
+    [ LIST0 [ q = LIDENT -> q ] SEP "," ]
+  ];
+
+  field: [
+    [ name = LIDENT; ":"; len = expr LEVEL "top";
+      qs = OPT [ ":"; qs = qualifiers -> qs ] ->
+       output_field _loc name len qs
+    ]
+  ];
+
+  match_case: [
+    [ fields = LIST0 field SEP ";";
+      w = OPT [ "when"; e = expr -> e ]; "->";
+      code = expr ->
+       (Fields fields, w, code)
+    ]
+  | [ "_";
+      bind = OPT [ "as"; name = LIDENT -> name ];
+      w = OPT [ "when"; e = expr -> e ]; "->";
+      code = expr ->
+       (Bind bind, w, code)
+    ]
+  ];
+
+  expr: LEVEL ";" [
+    [ "bitmatch"; bs = expr; "with"; OPT "|";
+      cases = LIST1 match_case SEP "|" ->
+       output_bitmatch _loc bs cases
+    ]
+  ];
+
+END
diff --git a/tests/.cvsignore b/tests/.cvsignore
new file mode 100644 (file)
index 0000000..af0d569
--- /dev/null
@@ -0,0 +1,7 @@
+*.cmi
+*.cmo
+*.cmx
+*.cma
+*.cmxa
+01_load
+05_bits
diff --git a/tests/01_load.ml b/tests/01_load.ml
new file mode 100644 (file)
index 0000000..64374bf
--- /dev/null
@@ -0,0 +1,5 @@
+(* Just check that the extension and library load.
+ * $Id: 01_load.ml,v 1.1 2008-03-31 22:52:17 rjones Exp $
+ *)
+
+let _ = Bitmatch.extract_bit
diff --git a/tests/05_bits.ml b/tests/05_bits.ml
new file mode 100644 (file)
index 0000000..df59527
--- /dev/null
@@ -0,0 +1,22 @@
+(* Extract bits.
+ * $Id: 05_bits.ml,v 1.1 2008-03-31 22:52:17 rjones Exp $
+ *)
+
+open Printf
+
+let bits = Bitmatch.make_bitstring 24 '\x5a' (* makes the string 0x5a5a5a *)
+
+let () =
+  bitmatch bits with
+  | b0  : 1; b1  : 1; b2  : 1; b3  : 1; b4  : 1; b5  : 1; b6  : 1; b7  : 1;
+    b8  : 1; b9  : 1; b10 : 1; b11 : 1; b12 : 1; b13 : 1; b14 : 1; b15 : 1;
+    b16 : 1; b17 : 1; b18 : 1; b19 : 1; b20 : 1; b21 : 1; b22 : 1; b23 : 1;
+    rest : -1 : bitstring ->
+      assert (not b0 && b1 && not b2 && b3 && b4 && not b5 && b6 && not b7);
+      assert (not b8 && b9 && not b10 && b11 && b12 && not b13 && b14 && not b15);
+      assert (not b16 && b17 && not b18 && b19 && b20 && not b21 && b22 && not b23);
+      let _, off, len = rest in
+      assert (off = 24 && len = 0)
+
+  | _ ->
+      failwith "error: did not match\n"