1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
module Libregexp = Quickjs_c.Libregexp
type bytecode =
(char, Bigarray.int8_unsigned_elt, Bigarray.c_layout) Bigarray.Array1.t
type t = {
bc_storage : bytecode; [@warning "-69"]
bc : Unsigned.uint8 Ctypes_static.ptr;
source : string;
flags : int;
mutable last_index : int;
}
type match_indices = {
ranges : (int * int) option array;
groups : (string * (int * int) option) list;
}
type match_result = {
captures : string option array;
index : int;
input : string;
groups : (string * string option) list;
indices : match_indices option;
}
type source_range = { utf16 : int * int; bytes : (int * int) option }
type prepared_match = { result : match_result; range : source_range }
type compile_error =
[ `Unexpected_end
| `Malformed_unicode_char
| `Invalid_escape_sequence
| `Nothing_to_repeat
| `Stack_overflow
| `Invalid_flags of string
| `Unknown of string ]
exception Timeout
let compile_error_to_string = function
| `Unexpected_end -> "unexpected end"
| `Malformed_unicode_char -> "malformed unicode char"
| `Invalid_escape_sequence -> "invalid escape sequence"
| `Nothing_to_repeat -> "nothing to repeat"
| `Stack_overflow -> "stack overflow"
| `Invalid_flags msg -> msg
| `Unknown s -> s
let lre_flag_global = 1 lsl 0
let lre_flag_ignorecase = 1 lsl 1
let lre_flag_multiline = 1 lsl 2
let lre_flag_dotall = 1 lsl 3
let lre_flag_unicode = 1 lsl 4
let lre_flag_sticky = 1 lsl 5
let lre_flag_indices = 1 lsl 6
let lre_flag_unicode_sets = 1 lsl 8
let has_flag flags flag = flags land flag != 0
let global regexp = has_flag regexp.flags lre_flag_global
let ignorecase regexp = has_flag regexp.flags lre_flag_ignorecase
let multiline regexp = has_flag regexp.flags lre_flag_multiline
let dotall regexp = has_flag regexp.flags lre_flag_dotall
let sticky regexp = has_flag regexp.flags lre_flag_sticky
let unicode regexp = has_flag regexp.flags lre_flag_unicode
let unicode_sets regexp = has_flag regexp.flags lre_flag_unicode_sets
let indices regexp = has_flag regexp.flags lre_flag_indices
let known_flags =
[
('d', lre_flag_indices);
('g', lre_flag_global);
('i', lre_flag_ignorecase);
('m', lre_flag_multiline);
('s', lre_flag_dotall);
('u', lre_flag_unicode);
('v', lre_flag_unicode_sets);
('y', lre_flag_sticky);
]
let parse_flags flags =
let rec loop i acc =
if i >= Stdlib.String.length flags then Ok acc
else
let c = Stdlib.String.get flags i in
match List.assoc_opt c known_flags with
| None ->
Error (`Invalid_flags (Printf.sprintf "unknown regexp flag '%c'" c))
| Some mask when has_flag acc mask ->
Error
(`Invalid_flags (Printf.sprintf "duplicated regexp flag '%c'" c))
| Some mask -> loop (i + 1) (acc lor mask)
in
match loop 0 0 with
| Error _ as error -> error
| Ok acc
when has_flag acc lre_flag_unicode && has_flag acc lre_flag_unicode_sets ->
Error (`Invalid_flags "regexp flags 'u' and 'v' cannot be combined")
| Ok acc -> Ok acc
let flags_to_string flags =
Stdlib.String.concat ""
(List.filter_map
(fun (c, mask) ->
if has_flag flags mask then Some (Stdlib.String.make 1 c) else None)
known_flags)
let strlen ptr =
let rec aux ptr len =
let c = Ctypes.( !@ ) ptr in
if c = char_of_int 0 then len else aux (Ctypes.( +@ ) ptr 1) (len + 1)
in
aux ptr 0
let has_astral_chars s =
let len = Stdlib.String.length s in
let rec check i =
if i >= len then false
else if Char.code (Stdlib.String.get s i) >= 0xF0 then true
else check (i + 1)
in
check 0
let is_ascii s =
let len = Stdlib.String.length s in
let rec check i =
if i >= len then true
else if Char.code (Stdlib.String.get s i) >= 0x80 then false
else check (i + 1)
in
check 0
let to_cesu8 s =
let len = Stdlib.String.length s in
let buf = Stdlib.Buffer.create (len + 8) in
let add_code_unit code =
if code < 0x80 then Stdlib.Buffer.add_char buf (Char.chr code)
else if code < 0x800 then begin
Stdlib.Buffer.add_char buf (Char.chr (0xC0 lor (code lsr 6)));
Stdlib.Buffer.add_char buf (Char.chr (0x80 lor (code land 0x3F)))
end
else begin
Stdlib.Buffer.add_char buf (Char.chr (0xE0 lor (code lsr 12)));
Stdlib.Buffer.add_char buf (Char.chr (0x80 lor ((code lsr 6) land 0x3F)));
Stdlib.Buffer.add_char buf (Char.chr (0x80 lor (code land 0x3F)))
end
in
let rec loop i =
if i >= len then Stdlib.Buffer.contents buf
else
let d = Stdlib.String.get_utf_8_uchar s i in
let code = Uchar.to_int (Uchar.utf_decode_uchar d) in
if code < 0x10000 then add_code_unit code
else begin
let code' = code - 0x10000 in
add_code_unit (0xD800 lor (code' lsr 10));
add_code_unit (0xDC00 lor (code' land 0x3FF))
end;
loop (i + Uchar.utf_decode_length d)
in
loop 0
let compile ~flags:flags_string source =
match parse_flags flags_string with
| Error _ as error -> error
| Ok flags -> (
let compiled_byte_code_len = Ctypes.allocate Ctypes.int 0 in
let size_of_error_msg = 64 in
let error_msg = Ctypes.allocate_n ~count:size_of_error_msg Ctypes.char in
let is_unicode_mode =
has_flag flags lre_flag_unicode || has_flag flags lre_flag_unicode_sets
in
let pattern =
if (not is_unicode_mode) && has_astral_chars source then to_cesu8 source
else source
in
let input = Ctypes.ocaml_string_start pattern in
let input_length =
Stdlib.String.length pattern |> Unsigned.Size_t.of_int
in
let compiled_byte_code =
Libregexp.compile compiled_byte_code_len error_msg size_of_error_msg
input input_length flags Ctypes.null
in
match Ctypes.is_null compiled_byte_code with
| false ->
let bc_len = Ctypes.( !@ ) compiled_byte_code_len in
let bc_storage =
Bigarray.Array1.create Bigarray.char Bigarray.c_layout bc_len
in
let src_chars =
Ctypes.coerce
(Ctypes.ptr Ctypes.uint8_t)
(Ctypes.ptr Ctypes.char) compiled_byte_code
in
for i = 0 to bc_len - 1 do
Bigarray.Array1.unsafe_set bc_storage i
(Ctypes.( !@ ) (Ctypes.( +@ ) src_chars i))
done;
Libregexp.bytecode_free compiled_byte_code;
let bc =
Ctypes.coerce (Ctypes.ptr Ctypes.char)
(Ctypes.ptr Ctypes.uint8_t)
(Ctypes.bigarray_start Ctypes.array1 bc_storage)
in
Ok { bc_storage; bc; flags; last_index = 0; source }
| true ->
let length = strlen error_msg in
let error = Ctypes.string_from_ptr ~length error_msg in
Error
(match error with
| "unexpected end" -> `Unexpected_end
| "malformed unicode char" -> `Malformed_unicode_char
| "nothing to repeat" -> `Nothing_to_repeat
| "stack overflow" -> `Stack_overflow
| "invalid escape sequence in regular expression" ->
`Invalid_escape_sequence
| unknown -> `Unknown unknown))
let last_index regexp = regexp.last_index
let set_last_index regexp idx = regexp.last_index <- max 0 idx
let source regexp = regexp.source
let flags regexp = flags_to_string regexp.flags
let group name (result : match_result) =
match List.assoc_opt name result.groups with
| Some value -> value
| None -> None
let group_indices name (result : match_result) =
match result.indices with
| None -> None
| Some indices -> (
match List.assoc_opt name indices.groups with
| Some value -> value
| None -> None)
let utf8_to_utf16_bytes s =
let len = Stdlib.String.length s in
let buf = Stdlib.Buffer.create (len * 2) in
let add_u16 code =
let lo = Char.chr (code land 0xFF) in
let hi = Char.chr ((code lsr 8) land 0xFF) in
if Sys.big_endian then begin
Stdlib.Buffer.add_char buf hi;
Stdlib.Buffer.add_char buf lo
end
else begin
Stdlib.Buffer.add_char buf lo;
Stdlib.Buffer.add_char buf hi
end
in
let rec loop i =
if i >= len then Stdlib.Buffer.contents buf
else
let d = Stdlib.String.get_utf_8_uchar s i in
let code = Uchar.to_int (Uchar.utf_decode_uchar d) in
(if code < 0x10000 then add_u16 code
else
let code' = code - 0x10000 in
add_u16 (0xD800 lor (code' lsr 10));
add_u16 (0xDC00 lor (code' land 0x3FF)));
loop (i + Uchar.utf_decode_length d)
in
loop 0
type utf16_map = { u8_by_u16 : int array; valid_utf8 : bool }
let build_utf16_map s =
let len = Stdlib.String.length s in
let rec count i units =
if i >= len then units
else
let d = Stdlib.String.get_utf_8_uchar s i in
let code = Uchar.to_int (Uchar.utf_decode_uchar d) in
count
(i + Uchar.utf_decode_length d)
(units + if code >= 0x10000 then 2 else 1)
in
let u8_by_u16 = Array.make (count 0 0 + 1) (-1) in
let valid_utf8 = ref true in
let rec fill i u16_idx =
if i >= len then begin
u8_by_u16.(u16_idx) <- i
end
else begin
let d = Stdlib.String.get_utf_8_uchar s i in
if not (Uchar.utf_decode_is_valid d) then valid_utf8 := false;
let code = Uchar.to_int (Uchar.utf_decode_uchar d) in
u8_by_u16.(u16_idx) <- i;
let units = if code >= 0x10000 then 2 else 1 in
fill (i + Uchar.utf_decode_length d) (u16_idx + units)
end
in
fill 0 0;
{ u8_by_u16; valid_utf8 = !valid_utf8 }
let utf16_length_of_map map = Array.length map.u8_by_u16 - 1
let utf16_to_utf8_exact map target =
if target < 0 || target > utf16_length_of_map map then None
else
let byte_index = map.u8_by_u16.(target) in
if byte_index < 0 then None else Some byte_index
let fill_carray_of_string s =
let len = Stdlib.String.length s in
let arr = Ctypes.CArray.make Ctypes.uint8_t len in
for i = 0 to len - 1 do
Ctypes.CArray.set arr i
(Unsigned.UInt8.of_int (Char.code (Stdlib.String.get s i)))
done;
arr
type prepared_input = {
input : string;
buffer_storage : Unsigned.uint8 Ctypes.CArray.t;
matching_length : int;
buffer_type : int;
utf16_map : utf16_map option;
}
let prepare_input input =
if is_ascii input then
{
input;
buffer_storage = fill_carray_of_string input;
matching_length = Stdlib.String.length input;
buffer_type = 0;
utf16_map = None;
}
else
let map = build_utf16_map input in
let utf16_str = utf8_to_utf16_bytes input in
{
input;
buffer_storage = fill_carray_of_string utf16_str;
matching_length = utf16_length_of_map map;
buffer_type = 1;
utf16_map = Some map;
}
let prepared_byte_range prepared ~start ~end_ =
if start < 0 || end_ < start || end_ > prepared.matching_length then None
else
match prepared.utf16_map with
| None -> Some (start, end_)
| Some map when not map.valid_utf8 -> None
| Some map -> (
match (utf16_to_utf8_exact map start, utf16_to_utf8_exact map end_) with
| Some start_byte, Some end_byte -> Some (start_byte, end_byte)
| _ -> None)
let utf16_unit prepared index =
let offset = index * 2 in
let first =
Ctypes.CArray.get prepared.buffer_storage offset |> Unsigned.UInt8.to_int
in
let second =
Ctypes.CArray.get prepared.buffer_storage (offset + 1)
|> Unsigned.UInt8.to_int
in
if Sys.big_endian then (first lsl 8) lor second else first lor (second lsl 8)
let prepared_substring prepared ~start ~end_ =
let start = Stdlib.max 0 (Stdlib.min start prepared.matching_length) in
let end_ = Stdlib.max start (Stdlib.min end_ prepared.matching_length) in
match prepared_byte_range prepared ~start ~end_ with
| Some (start_byte, end_byte) ->
Stdlib.String.sub prepared.input start_byte (end_byte - start_byte)
| None ->
let buf = Stdlib.Buffer.create ((end_ - start) * 3) in
let rec loop index =
if index < end_ then (
let code = utf16_unit prepared index in
if code >= 0xD800 && code <= 0xDBFF && index + 1 < end_ then
let low = utf16_unit prepared (index + 1) in
if low >= 0xDC00 && low <= 0xDFFF then (
let code_point =
0x10000 + ((code - 0xD800) * 0x400) + (low - 0xDC00)
in
Stdlib.Buffer.add_utf_8_uchar buf (Uchar.of_int code_point);
loop (index + 2))
else (
Stdlib.Buffer.add_utf_8_uchar buf Uchar.rep;
loop (index + 1))
else
let uchar =
if code >= 0xD800 && code <= 0xDFFF then Uchar.rep
else Uchar.of_int code
in
Stdlib.Buffer.add_utf_8_uchar buf uchar;
loop (index + 1))
in
loop start;
Stdlib.Buffer.contents buf
let prepared_advance_index prepared ~unicode index =
let index = Stdlib.max 0 index in
let next = if index = Stdlib.max_int then index else index + 1 in
if
(not unicode) || prepared.buffer_type = 0
|| next >= prepared.matching_length
then next
else
let high = utf16_unit prepared index in
if high < 0xD800 || high > 0xDBFF then index + 1
else
let low = utf16_unit prepared (index + 1) in
if low >= 0xDC00 && low <= 0xDFFF then index + 2 else index + 1
let prepared_start_index prepared regexp =
let index = if global regexp || sticky regexp then regexp.last_index else 0 in
if
prepared.buffer_type = 1 && index > 0
&& index < prepared.matching_length
&& unicode regexp
then
let current = utf16_unit prepared index in
let previous = utf16_unit prepared (index - 1) in
if
current >= 0xDC00 && current <= 0xDFFF && previous >= 0xD800
&& previous <= 0xDBFF
then index - 1
else index
else index
let exec_prepared ?timeout_ms regexp prepared =
let input = prepared.input in
let capture_count = Libregexp.get_capture_count regexp.bc in
let capture_size = capture_count * 2 in
let capture = Ctypes.CArray.make (Ctypes.ptr Ctypes.uint8_t) capture_size in
let start_capture = Ctypes.CArray.start capture in
let buffer = Ctypes.CArray.start prepared.buffer_storage in
let start_index = prepared_start_index prepared regexp in
if start_index > prepared.matching_length then begin
if global regexp || sticky regexp then regexp.last_index <- 0;
None
end
else begin
let deadline =
match timeout_ms with
| None -> None
| Some ms ->
Some (Ctypes.allocate Ctypes.double (Libregexp.now_ms () +. ms))
in
let exec_result =
Libregexp.exec start_capture regexp.bc buffer start_index
prepared.matching_length prepared.buffer_type deadline
in
match exec_result with
| 1 ->
let to_utf16_index raw_bytes =
match prepared.utf16_map with
| Some _ -> raw_bytes / 2
| None -> raw_bytes
in
let ranges =
Array.init capture_count (fun group ->
let start_ptr = Ctypes.CArray.get capture (group * 2) in
let end_ptr = Ctypes.CArray.get capture ((group * 2) + 1) in
if Ctypes.is_null start_ptr || Ctypes.is_null end_ptr then None
else
let u16_start =
to_utf16_index (Ctypes.ptr_diff buffer start_ptr)
in
let u16_end = to_utf16_index (Ctypes.ptr_diff buffer end_ptr) in
Some (u16_start, u16_end))
in
let captures =
Array.map
(function
| None -> None
| Some (u16_start, u16_end) ->
Some
(prepared_substring prepared ~start:u16_start ~end_:u16_end))
ranges
in
let match_start, match_end =
match ranges.(0) with
| Some range -> range
| None -> assert false
in
if global regexp || sticky regexp then regexp.last_index <- match_end;
let named_groups =
match Libregexp.get_groupnames regexp.bc with
| None -> []
| Some first_name ->
let rec walk ptr group acc =
if group >= capture_count then List.rev acc
else
let name_len = strlen ptr in
let acc =
if name_len > 0 then
let name = Ctypes.string_from_ptr ~length:name_len ptr in
(name, group) :: acc
else acc
in
walk (Ctypes.( +@ ) ptr (name_len + 1)) (group + 1) acc
in
walk first_name 1 []
in
let groups =
List.map (fun (name, group) -> (name, captures.(group))) named_groups
in
let indices =
if has_flag regexp.flags lre_flag_indices then
Some
{
ranges;
groups =
List.map
(fun (name, group) -> (name, ranges.(group)))
named_groups;
}
else None
in
let result =
{ captures; index = match_start; input; groups; indices }
in
let range =
{
utf16 = (match_start, match_end);
bytes =
prepared_byte_range prepared ~start:match_start ~end_:match_end;
}
in
Some { result; range }
| 0 ->
if global regexp || sticky regexp then regexp.last_index <- 0;
None
| -2 -> raise Timeout
| _ -> raise Out_of_memory
end
let exec ?timeout_ms regexp input =
match exec_prepared ?timeout_ms regexp (prepare_input input) with
| Some prepared_match -> Some prepared_match.result
| None -> None
let test ?timeout_ms regexp input =
match exec ?timeout_ms regexp input with Some _ -> true | None -> false