1 (* Bitmatch syntax extension.
2 * $Id: pa_bitmatch.ml,v 1.3 2008-04-01 10:05:14 rjones Exp $
11 type m = Fields of f list (* field ; field -> ... *)
12 | Bind of string option (* _ -> ... *)
14 ident : string; (* field name *)
15 flen : expr; (* length in bits, may be non-const *)
16 endian : endian; (* endianness *)
17 signed : bool; (* true if signed, false if unsigned *)
20 and endian = BigEndian | LittleEndian | NativeEndian
21 and t = Int | Bitstring
23 (* Generate a fresh, unique symbol each time called. *)
28 sprintf "__pabitmatch_%s_%d" name i
30 (* Deal with the qualifiers which appear for a field. *)
31 let output_field _loc name flen qs =
32 let endian, signed, t =
34 | None -> (None, None, None)
37 fun (endian, signed, t) q ->
40 if endian <> None then
41 Loc.raise _loc (Failure "an endian flag has been set already")
43 let endian = Some BigEndian in
47 if endian <> None then
48 Loc.raise _loc (Failure "an endian flag has been set already")
50 let endian = Some LittleEndian in
54 if endian <> None then
55 Loc.raise _loc (Failure "an endian flag has been set already")
57 let endian = Some NativeEndian in
61 if signed <> None then
62 Loc.raise _loc (Failure "a signed flag has been set already")
64 let signed = Some true in
68 if signed <> None then
69 Loc.raise _loc (Failure "a signed flag has been set already")
71 let signed = Some false in
76 Loc.raise _loc (Failure "a type flag has been set already")
83 Loc.raise _loc (Failure "a type flag has been set already")
85 let t = Some Bitstring in
89 Loc.raise _loc (Failure (s ^ ": unknown qualifier"))
90 ) (None, None, None) qs in
92 (* If type is set to bitstring then endianness and signedness
93 * qualifiers are meaningless and must not be set.
95 if t = Some Bitstring && (endian <> None || signed <> None) then
97 Failure "bitstring type and endian or signed qualifiers cannot be mixed"
100 (* Default endianness, signedness, type. *)
101 let endian = match endian with None -> BigEndian | Some e -> e in
102 let signed = match signed with None -> false | Some s -> s in
103 let t = match t with None -> Int | Some t -> t in
113 (* Generate the code for a bitmatch statement. '_loc' is the
114 * location, 'bs' is the bitstring parameter, 'cases' are
115 * the list of cases to test against.
117 let output_bitmatch _loc bs cases =
118 let data = gensym "data" and off = gensym "off" and len = gensym "len" in
119 let result = gensym "result" in
121 (* This generates the field extraction code for each
122 * field a single case. Each field must be wider than
123 * the minimum permitted for the type and there must be
124 * enough remaining data in the bitstring to satisfy it.
125 * As we go through the fields, symbols 'data', 'off' and 'len'
126 * track our position and remaining length in the bitstring.
128 * The whole thing is a lot of nested 'if' statements. Code
129 * is generated from the inner-most (last) field outwards.
131 let rec output_field_extraction inner = function
133 | {ident=ident; flen=flen; endian=endian; signed=signed; t=t} :: fields ->
134 (* If length an integer constant? If so, what is it? This
135 * is very simple-minded and only detects simple constants.
139 | <:expr< $int:i$ >> -> Some (int_of_string i)
142 let name_of_int_extract_const = function
143 (* XXX As an enhancement we should allow a 64-bit-only
144 * mode which lets us use 'int' up to 63 bits and won't
145 * compile on 32-bit platforms.
147 (* XXX The meaning of signed/unsigned breaks down at
148 * 31, 32, 63 and 64 bits.
150 | (1, _, _) -> "extract_bit"
151 | ((2|3|4|5|6|7|8), _, false) -> "extract_char_unsigned"
152 | ((2|3|4|5|6|7|8), _, true) -> "extract_char_signed"
153 | (i, BigEndian, false) when i <= 31 -> "extract_int_be_unsigned"
154 | (i, BigEndian, true) when i <= 31 -> "extract_int_be_signed"
155 | (i, LittleEndian, false) when i <= 31 -> "extract_int_le_unsigned"
156 | (i, LittleEndian, true) when i <= 31 -> "extract_int_le_signed"
157 | (i, NativeEndian, false) when i <= 31 -> "extract_int_ne_unsigned"
158 | (i, NativeEndian, true) when i <= 31 -> "extract_int_ne_signed"
159 | (32, BigEndian, false) -> "extract_int32_be_unsigned"
160 | (32, BigEndian, true) -> "extract_int32_be_signed"
161 | (32, LittleEndian, false) -> "extract_int32_le_unsigned"
162 | (32, LittleEndian, true) -> "extract_int32_le_signed"
163 | (32, NativeEndian, false) -> "extract_int32_ne_unsigned"
164 | (32, NativeEndian, true) -> "extract_int32_ne_signed"
165 | (_, BigEndian, false) -> "extract_int64_be_unsigned"
166 | (_, BigEndian, true) -> "extract_int64_be_signed"
167 | (_, LittleEndian, false) -> "extract_int64_le_unsigned"
168 | (_, LittleEndian, true) -> "extract_int64_le_signed"
169 | (_, NativeEndian, false) -> "extract_int64_ne_unsigned"
170 | (_, NativeEndian, true) -> "extract_int64_ne_signed"
172 let name_of_int_extract = function
173 (* XXX As an enhancement we should allow users to
174 * specify that a field length can fit into a char/int/int32
175 * (of course, this would have to be checked at runtime).
177 | (BigEndian, false) -> "extract_int64_be_unsigned"
178 | (BigEndian, true) -> "extract_int64_be_signed"
179 | (LittleEndian, false) -> "extract_int64_le_unsigned"
180 | (LittleEndian, true) -> "extract_int64_le_signed"
181 | (NativeEndian, false) -> "extract_int64_ne_unsigned"
182 | (NativeEndian, true) -> "extract_int64_ne_signed"
186 match t, flen_is_const with
187 (* Common case: int field, constant flen *)
188 | Int, Some i when i > 0 && i <= 64 ->
189 let extract_func = name_of_int_extract_const (i,endian,signed) in
191 if $lid:len$ >= $flen$ then (
192 let $lid:ident$, $lid:off$, $lid:len$ =
193 Bitmatch.$lid:extract_func$ $lid:data$ $lid:off$ $lid:len$
200 Loc.raise _loc (Failure "length of int field must be [1..64]")
202 (* Int field, non-const flen. We have to test the range of
203 * the field at runtime. If outside the range it's a no-match
207 let extract_func = name_of_int_extract (endian,signed) in
209 if $flen$ >= 1 && $flen$ <= 64 && $flen$ >= $lid:len$ then (
210 let $lid:ident$, $lid:off$, $lid:len$ =
211 Bitmatch.$lid:extract_func$ $lid:data$ $lid:off$ $lid:len$
217 (* Bitstring, constant flen >= 0. *)
218 | Bitstring, Some i when i >= 0 ->
220 if $lid:len$ >= $flen$ then (
221 let $lid:ident$, $lid:off$, $lid:len$ =
222 Bitmatch.extract_bitstring $lid:data$ $lid:off$ $lid:len$
228 (* Bitstring, constant flen = -1, means consume all the
231 | Bitstring, Some i when i = -1 ->
233 let $lid:ident$, $lid:off$, $lid:len$ =
234 Bitmatch.extract_remainder $lid:data$ $lid:off$ $lid:len$ in
238 | Bitstring, Some _ ->
239 Loc.raise _loc (Failure "length of bitstring must be >= 0 or the special value -1")
241 (* Bitstring field, non-const flen. We check the flen is >= 0
242 * (-1 is not allowed here) at runtime.
246 if $flen$ >= 0 && $lid:len$ >= $flen$ then (
247 let $lid:ident$, $lid:off$, $lid:len$ =
248 Bitmatch.extract_bitstring $lid:data$ $lid:off$ $lid:len$
255 output_field_extraction expr fields
258 (* Convert each case in the match. *)
259 let cases = List.map (
261 (* field : len ; field : len when .. -> ..*)
262 | (Fields fields, Some whenclause, code) ->
265 if $whenclause$ then (
266 $lid:result$ := Some ($code$);
270 output_field_extraction inner (List.rev fields)
272 (* field : len ; field : len -> ... *)
273 | (Fields fields, None, code) ->
276 $lid:result$ := Some ($code$);
279 output_field_extraction inner (List.rev fields)
281 (* _ as name when ... -> ... *)
282 | (Bind (Some name), Some whenclause, code) ->
284 let $lid:name$ = ($lid:data$, $lid:off$, $lid:len$) in
285 if $whenclause$ then (
286 $lid:result$ := Some ($code$);
291 (* _ as name -> ... *)
292 | (Bind (Some name), None, code) ->
294 let $lid:name$ = ($lid:data$, $lid:off$, $lid:len$) in
295 $lid:result$ := Some ($code$);
299 (* _ when ... -> ... *)
300 | (Bind None, Some whenclause, code) ->
302 if $whenclause$ then (
303 $lid:result$ := Some ($code$);
309 | (Bind None, None, code) ->
311 $lid:result$ := Some ($code$);
317 (* Join them into a single expression.
319 * Don't do it with a normal fold_right because that leaves
320 * 'raise Exit; ()' at the end which causes a compiler warning.
321 * Hence a bit of complexity here.
323 * Note that the number of cases is always >= 1 so List.hd is safe.
325 let cases = List.rev cases in
327 List.fold_left (fun base case -> <:expr< $case$ ; $base$ >>)
328 (List.hd cases) (List.tl cases) in
330 (* The final code just wraps the list of cases in a
331 * try/with construct so that each case is tried in
332 * turn until one case matches (that case sets 'result'
333 * and raises 'Exit' to leave the whole statement).
334 * If result isn't set by the end then we will raise
335 * Match_failure with the location of the bitmatch
336 * statement in the original code.
338 let loc_fname = Loc.file_name _loc in
339 let loc_line = string_of_int (Loc.start_line _loc) in
340 let loc_char = string_of_int (Loc.start_off _loc - Loc.start_bol _loc) in
343 let ($lid:data$, $lid:off$, $lid:len$) = $bs$ in
344 let $lid:result$ = ref None in
348 match ! $lid:result$ with
350 | None -> raise (Match_failure ($str:loc_fname$,
351 $int:loc_line$, $int:loc_char$))
358 [ LIST0 [ q = LIDENT -> q ] SEP "," ]
362 [ name = LIDENT; ":"; len = expr LEVEL "top";
363 qs = OPT [ ":"; qs = qualifiers -> qs ] ->
364 output_field _loc name len qs
369 [ fields = LIST0 field SEP ";";
370 w = OPT [ "when"; e = expr -> e ]; "->";
372 (Fields fields, w, code)
375 bind = OPT [ "as"; name = LIDENT -> name ];
376 w = OPT [ "when"; e = expr -> e ]; "->";
383 [ "bitmatch"; bs = expr; "with"; OPT "|";
384 cases = LIST1 match_case SEP "|" ->
385 output_bitmatch _loc bs cases