package corvus-json-schema

  1. Overview
  2. Docs
A fast JSON Schema evaluator (draft 4 to 2020-12) with no dependencies outside the OCaml distribution

Install

dune-project
 Dependency

Authors

Maintainers

Sources

v0.1.1.tar.gz
sha256=d0a084719770883384e488d42ff1a2c0f84a53e2b2898016183533b8393f2cab
sha512=d5875f234b316cd50a888e1a3acc055133d705eb6622e3856c22f6719fa3eae196f1f08cd08cc073cc0924d27e330ae2ea5388142dd5e91b173bdfd91ad997f0

doc/src/corvus-json-schema.ecmaregex/vm.ml.html

Source file vm.ml

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
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
(* The backtracking matcher. This is the port of vm.go of the ecmaregex package of the Go module.

   What differs from the Go source:

   The text is a range of a [Bytes.t] or of a [string], from [lo] up to [hi], where Go has a slice. The start of the
   text is [lo] and its end is [hi], and nothing outside them is read.

   Go keeps the working memory of a match in a pool of the program. Here it is a {!state} the caller gives, so that a
   program is a value nothing writes to. The interface of the library says who owns the state of each entry point.

   The loop of the Go source is two functions here that call each other in tail position, [step] and [backtrack].
   Every value they pass is an [int] or a pointer that already exists, so a match allocates only when the stack
   grows. A decoded character is one [int] (see utf8.ml), and an entry of the stack is four elements of an [int
   array], where Go has a slice of structures. *)

open Compile

(* The kinds of entry on the backtracking stack. A choice entry says where to resume. An undo entry restores a value
   that was overwritten after the entries below it were pushed. *)

(** Resumes at pc with the position pos. *)
let e_choice = 0

(** Belongs to a greedy single character loop that stopped at pos. Resuming gives back one character, down to the
    position a. *)
let e_rep = 1

(** Belongs to a lazy single character loop that has taken a characters and stands at pos. Resuming takes one more. *)
let e_rep_lazy = 2

(** [e_look] marks the start of a positive lookaround and [e_look_neg] of a negative one. Both hold the position to
    restore, and [e_look_neg] the instruction to continue at when its body has failed. *)
let e_look = 3

let e_look_neg = 4

(** Lies under the choices of a memo point. When it is reached, every way from the point has failed, and the bit pos of
    the memory is set (see [memo] of the state). *)
let e_mark = 5

(** Restores the entry position of group pc to pos. *)
let e_tmp = 6

(** Restores the start and end of group pc to pos and a. *)
let e_cap = 7

(** Restores the count and the iteration start of loop pc to pos and a. *)
let e_counter = 8

exception Match_time
(** A match was abandoned because it ran for longer than its limit. It is not an answer. *)

exception Match_memory
(** A match was abandoned because its backtracking stack reached [max_stack] entries. It is not an answer. *)

(** The number of steps the matcher takes between two looks at the clock and at the depth of its stack. A step is one
    instruction, and eight characters of a single character loop or of a backreference. A step takes a few nanoseconds,
    so the clock is read every few tens of microseconds, a match that ends before its first check never reads it, and a
    match goes past its limit by that much at most. *)
let check_every = 1 lsl 12

(** The number of entries the backtracking stack of a match with a limit may hold (four words each, which is 32 MB). A
    match that needs more is abandoned. A pattern that keeps a choice for every character of its text needs one entry or
    a few for each, so this is room for a text of some hundred thousand characters, and a pattern that pushes without
    consuming, such as [(?:a?){2147483647}], reaches it in a few milliseconds. It is looked at with the clock, so the
    stack can go past it by [check_every] entries. *)
let max_stack = 1 lsl 20

(** The largest memory of failed places, in words of 64 bits: 8 MB. A match that would need more goes without it. *)
let max_memo_words = 1 lsl 20

(** The number of steps for each position of the text that a match makes before it takes the memory into use. *)
let memo_steps_per_position = 64

(** The largest memory kept in the state between matches, in words of 64 bits. *)
let max_retained_memo = 1 lsl 16

(** The depth of the backtracking stack, in entries, beyond which a memo point is not marked. A mark is an entry of the
    stack, and a pattern that keeps no choice for a character still marks a place for each, so on a long text the marks
    alone would reach [max_stack]. A place that is not marked is not remembered, which loses time and nothing else, and
    the places below this depth are remembered when the stack unwinds, so a later start has the room to mark the next
    ones. *)
let max_mark_depth = max_stack / 2

(** Makes every match take its memory of failed places before its first step. The tests set it, to run every pattern
    they have with the memory in use, and so does the harness that runs the pattern datasets, to compare the answers
    with those of a match that takes the memory when it would by itself. Nothing else writes to it. *)
let memo_from_the_start = ref false

(* Why a match was abandoned. *)
let stop_none = 0
let stop_time = 1
let stop_memory = 2

type state = {
  text : Utf8.text;
  mutable stack : int array;
      (** The entries, four elements each: the kind, pc, pos and a. The first [sp] elements are in use. *)
  mutable sp : int;
  mutable caps : int array;  (** The start and end of each tracked group, or -1. *)
  mutable tmp : int array;  (** The position at which each tracked group was entered. *)
  mutable counts : int array;
      (** For each loop, the number of iterations started and the position at which the last one started. *)
  mutable units : bool;
      (** Says the program of the match in hand matches by UTF-16 code unit (see utf8.ml). It is set from the program
          when a match starts. *)
  clock : Float.Array.t;
      (** Four times in seconds, in a flat array of floats so that none is a block of its own and reading the clock
          allocates nothing. It is a [Float.Array.t] and not a [float array], which is an array of boxed floats where
          the compiler is configured with [-no-flat-float-array]. The first is the limit of a match, or zero for no
          limit: it belongs to the state and every match with the state has it. The second is the deadline of the match
          in hand, set at its first check, the third the time of its last check, and the fourth the reading of the clock
          in hand (see check). *)
  mutable timed : bool;  (** Says the deadline of the match in hand is set. *)
  mutable own_clock : (unit -> float) option;
      (** The clock the limit is counted on, in seconds, when the caller has given one. Without one it is
          {!default_clock}, the time that passes. *)
  mutable steps : int;  (** The number of steps left before the next check. *)
  mutable stop : int;  (** Why the match in hand was abandoned, or [stop_none]. *)
  mutable memo : Bytes.t;
      (** The memory of the places where a way has failed: one bit for each memo point of the program, each position of
          the text and each state of the loops around the point (see [m] of an instruction). A bit is set when every way
          from the place has failed, and a way that comes to a place whose bit is set fails at once. So no place is gone
          through twice, and a pattern of repetitions inside repetitions takes time proportional to its size times the
          text and not time that doubles with each character.

          The memory costs its size to clear, and a mark on the stack for each place gone through, so a match starts
          without it. It takes it at the check by which it has made as many steps as the memory has words and
          [memo_steps_per_position] more for each position of the text, which a match that goes through its text once or
          a few times never makes. A place whose way failed before then is not remembered, which loses time and nothing
          else. *)
  mutable memo_on : bool;  (** Says the memory is in use. *)
  mutable memo_off : bool;
      (** Says the memory never will be in use: the program has no memo points, or the memory would be larger than
          [max_memo_words]. *)
  mutable lo : int;  (** The start of the text of the match in hand. *)
  mutable width : int;  (** The number of positions of the text. *)
  mutable checks : int;  (** The checks the match in hand has made. *)
  mutable points : int;  (** The memo points of the program of the match in hand. *)
}
(** The working memory of one match. A match allocates only while the stack is still growing to the depth the pattern
    and the text need. *)

(** The largest stack kept between matches, in entries. *)
let max_retained_stack = 1 lsl 16

let new_state () =
  {
    text = Utf8.new_text ();
    stack = [||];
    sp = 0;
    caps = [||];
    tmp = [||];
    counts = [||];
    units = false;
    clock = Float.Array.make 4 0.;
    timed = false;
    own_clock = None;
    steps = 0;
    stop = stop_none;
    memo = Bytes.empty;
    memo_on = false;
    memo_off = true;
    lo = 0;
    width = 0;
    checks = 0;
    points = 0;
  }

(* The number of words of 64 bits of the memory of a match. *)
let memo_words st = ((st.points * (st.width lsl Compile.max_memo_loops)) + 63) lsr 6

(* Takes the memory of failed places into use when the match has made enough steps to pay for it. *)
let remember st =
  let words = memo_words st in
  if words > max_memo_words then st.memo_off <- true
  else if st.checks * check_every >= words + (memo_steps_per_position * st.width) || !memo_from_the_start then begin
    if Bytes.length st.memo < 8 * words then st.memo <- Bytes.make (8 * words) '\000'
    else Bytes.fill st.memo 0 (8 * words) '\000';
    st.memo_on <- true
  end

(* Returns the bit of the memory for the memo point of an instruction at a position, with the state of the loops around
   the point: for each, whether it would refuse an iteration that ended here as empty. *)
let memo_index st (i : inst) pos =
  let idx = (((i.m - 1) * st.width) + (pos - st.lo)) lsl Compile.max_memo_loops in
  let counts = st.counts in
  let idx = if i.ml0 >= 0 && counts.(2 * i.ml0) > i.mm0 && counts.((2 * i.ml0) + 1) = pos then idx lor 1 else idx in
  if i.ml1 >= 0 && counts.(2 * i.ml1) > i.mm1 && counts.((2 * i.ml1) + 1) = pos then idx lor 2 else idx

let failed st idx = (Char.code (Bytes.get st.memo (idx lsr 3)) lsr (idx land 7)) land 1 <> 0

let set_failed st idx =
  Bytes.set st.memo (idx lsr 3) (Char.chr (Char.code (Bytes.get st.memo (idx lsr 3)) lor (1 lsl (idx land 7))))

(** [set_limit st seconds] sets the time every match with the state may take from now on. Zero or less, or a value that
    is not a number, is no limit. *)
let set_limit st seconds = Float.Array.set st.clock 0 (if seconds > 0. then seconds else 0.)

(** [set_clock st clock] sets the clock the limit of every match with the state is counted on from now on: a function
    that returns the time in seconds, or [None] for {!default_clock}. *)
let set_clock st clock = st.own_clock <- clock

(** [default_clock ()] is the time in seconds on the clock a limit is counted on when the caller has given none: the
    time of day, from [Unix.gettimeofday]. [check] reads it through this function, which is inlined there. *)
let[@inline] default_clock () = Unix.gettimeofday ()

(** [limit st] is the limit of the state, or zero for none. *)
let limit st = Float.Array.get st.clock 0

(* Without a clock of the caller's, the clock is the time that passes: the time of day, from Unix.gettimeofday of the
   unix library of the OCaml distribution, which returns an unboxed float and allocates nothing. The standard library
   has one clock of its own, Sys.time, which is the processor time of the whole process. It does not move while the
   process waits, and in a program that computes on several domains at once it runs that many times faster than the
   time that passes, so a limit of one second would be a fraction of one there. The limit was counted on it at first,
   and it was measured 4 to 8 % ahead of the time that passed on one domain. The OCaml distribution has no monotonic
   clock, in 4.14 or in 5.5: one needs a package from outside it or a C stub, and this library has neither.

   The time of day can be set, and so can a clock of the caller's. Set forward, a match is abandoned early, which is an
   error and not a wrong answer. Set back, the deadline is counted again from the new time, so a match never gets more
   than its limit after a change. A clock of the caller's is a closure that returns a float, which is a block of its
   own for each read.

   Each branch writes its reading of the clock to the state, and the reading is read back from there, so that the float
   is never the value of the match on the clock. That value would be boxed by flambda, whose default clock otherwise
   allocates nothing, because one of the branches returns the boxed float of a closure.

   check is called every check_every steps. It reports whether the match may go on. The clock starts at the first
   check. *)
let check st =
  st.steps <- check_every;
  if (not st.memo_on) && not st.memo_off then begin
    st.checks <- st.checks + 1;
    remember st
  end;
  let limit = Float.Array.get st.clock 0 in
  if limit <= 0. then true
  else if st.sp > 4 * max_stack then begin
    st.stop <- stop_memory;
    false
  end
  else
    let () =
      match st.own_clock with
      | None -> Float.Array.set st.clock 3 (default_clock ())
      | Some clock -> Float.Array.set st.clock 3 (clock ())
    in
    let now = Float.Array.get st.clock 3 in
    if (not st.timed) || now < Float.Array.get st.clock 2 then begin
      st.timed <- true;
      Float.Array.set st.clock 1 (now +. limit);
      Float.Array.set st.clock 2 now;
      true
    end
    else if now > Float.Array.get st.clock 1 then begin
      st.stop <- stop_time;
      false
    end
    else begin
      Float.Array.set st.clock 2 now;
      true
    end

let byte (t : Utf8.text) i = if t.is_string then Char.code (String.get t.s i) else Char.code (Bytes.get t.b i)

(* The stack grows to twice its size. A match with a limit is abandoned at the first check after its stack has passed
   max_stack entries, so its stack is not grown far beyond what it can use before then: the entries of max_stack, of
   the steps between two checks, and of the groups one instruction can clear. *)
let grow st =
  let length = Array.length st.stack in
  let doubled = (2 * length) + 256 in
  let enough = 4 * (max_stack + check_every + (1 lsl 16)) in
  let size = if Float.Array.get st.clock 0 > 0. && length < enough && doubled > enough then enough else doubled in
  let grown = Array.make size 0 in
  Array.blit st.stack 0 grown 0 st.sp;
  st.stack <- grown

let push st kind pc pos a =
  if st.sp + 4 > Array.length st.stack then grow st;
  let stack = st.stack and sp = st.sp in
  stack.(sp) <- kind;
  stack.(sp + 1) <- pc;
  stack.(sp + 2) <- pos;
  stack.(sp + 3) <- a;
  st.sp <- sp + 4

(* Applies the undo entry at the element [e] of the stack. *)
let undo st e =
  let stack = st.stack in
  let kind = stack.(e) and pc = stack.(e + 1) and pos = stack.(e + 2) and a = stack.(e + 3) in
  if kind = e_tmp then st.tmp.(pc) <- pos
  else if kind = e_cap then begin
    st.caps.(2 * pc) <- pos;
    st.caps.((2 * pc) + 1) <- a
  end
  else if kind = e_counter then begin
    st.counts.(2 * pc) <- pos;
    st.counts.((2 * pc) + 1) <- a
  end

(* Reports whether the character before the position is an ECMA-262 line terminator, and line_terminator_at whether
   the character at the position is one. U+2028 and U+2029 are E2 80 A8 and E2 80 A9. *)
let line_terminator_before t lo pos =
  let c = byte t (pos - 1) in
  if c = 0x0A || c = 0x0D then true
  else if c = 0xA8 || c = 0xA9 then pos - lo >= 3 && byte t (pos - 3) = 0xE2 && byte t (pos - 2) = 0x80
  else false

let line_terminator_at t pos hi =
  let c = byte t pos in
  if c = 0x0A || c = 0x0D then true
  else if c = 0xE2 then pos + 2 < hi && byte t (pos + 1) = 0x80 && (byte t (pos + 2) = 0xA8 || byte t (pos + 2) = 0xA9)
  else false

(* Reports whether the bytes of lit are in the text at the position at. The caller has checked that they fit. *)
let rec equal_lit t at lit k =
  k = String.length lit || (byte t (at + k) = Char.code (String.get lit k) && equal_lit t at lit (k + 1))

(* Reports whether the n bytes at a and the n bytes at b are the same. *)
let rec equal_spans t a b n = n = 0 || (byte t a = byte t b && equal_spans t (a + 1) (b + 1) (n - 1))

(* Returns the first position at or after at where the bytes of lit are in the text, or -1. *)
let rec index_of t at hi lit =
  if at > hi - String.length lit then -1
  else if byte t at = Char.code (String.get lit 0) && equal_lit t at lit 1 then at
  else index_of t (at + 1) hi lit

(* Reads the character at the position at, which is not ASCII: a code unit when the match is by code unit, and a code
   point otherwise. decode_before reads the character that ends at the position. *)
let decode_at st t lo at hi = if st.units then Utf8.unit_at t lo at hi else Utf8.decode t at hi
let decode_before st t lo at hi = if st.units then Utf8.unit_before t lo at hi else Utf8.decode_last t lo at

(* Returns the width of the character at the position at when the instruction's character or class matches it, and 0
   otherwise. The character is a code unit when the match is by code unit. *)
let one st i t lo at hi =
  if at >= hi then 0
  else
    let c = byte t at in
    let packed = if c < Utf8.rune_self then (c lsl 3) lor 1 else decode_at st t lo at hi in
    let r = Utf8.rune packed in
    if i.has_set then if Charset.contains i.set r then Utf8.width packed else 0
    else if r = i.r then Utf8.width packed
    else 0

(* Reports whether a way with the quick q can go on from the position. *)
let allows (q : quick) t pos hi =
  if pos >= hi then q.at_end
  else if q.mid then true
  else
    let c = byte t pos in
    if c < Utf8.rune_self then (Char.code (Bytes.get q.ascii (c lsr 3)) lsr (c land 7)) land 1 <> 0 else q.other

(* The greedy single character loop of Op_rep. It takes characters from at while the instruction matches and the max
   allows, and then pushes the choice of giving some back. n counts the characters taken and floor is the position
   once the minimum was met. It returns the position reached, or -1 when the minimum was not met. *)
let rec rep st i t lo hi pc at n floor =
  if n <> i.max && at < hi then
    let c = byte t at in
    let w =
      if c < Utf8.rune_self then if if i.has_set then Charset.ascii_has i.set.ascii c else c = i.r then 1 else 0
      else
        let packed = decode_at st t lo at hi in
        let r = Utf8.rune packed in
        if if i.has_set then Charset.contains i.set r else r = i.r then Utf8.width packed else 0
    in
    if w = 0 then rep_done st i pc at n floor
    else
      let at = at + w and n = n + 1 in
      rep st i t lo hi pc at n (if n = i.min then at else floor)
  else rep_done st i pc at n floor

and rep_done st i pc at n floor =
  st.steps <- st.steps - (n lsr 3);
  if n >= i.min then
    if st.memo_on && i.m <> 0 then begin
      let idx = memo_index st i at in
      if failed st idx then begin
        (* What follows the loop has failed from here before. The loop gives a character back. *)
        if at > floor then push st e_rep (pc + 1) at floor;
        -1
      end
      else begin
        if at > floor then push st e_rep (pc + 1) at floor
        else if st.sp < 4 * max_mark_depth then push st e_mark 0 idx 0;
        at
      end
    end
    else begin
      if at > floor then push st e_rep (pc + 1) at floor;
      at
    end
  else -1

(* Compares the group from..until with the text at the position at, going forward, a character at a time: to fold
   case (with fold), or because a group matched by code unit can begin or end between the halves of a character,
   where its bytes do not say what it holds. It returns the position after the text that matched, or -1. (The second
   half of a character lies beyond the end of a group that ends between the halves, so a code unit of the group is
   read from the whole text.) *)
let rec backref_chars st t lo hi fold flag from until at =
  if from >= until then at
  else if at >= hi then -1
  else
    let want = if st.units then Utf8.unit_at t lo from hi else Utf8.decode t from until in
    let got = decode_at st t lo at hi in
    let w = Utf8.rune want and g = Utf8.rune got in
    if g = w || (fold && Charset.canonicalize g flag = Charset.canonicalize w flag) then
      backref_chars st t lo hi fold flag (from + Utf8.width want) until (at + Utf8.width got)
    else -1

(* The same going backward: the group is read from its end and the text before the position at. *)
let rec backref_chars_back st t lo hi fold flag from until at =
  if from >= until then at
  else if at <= lo then -1
  else
    let want = if st.units then Utf8.unit_before t lo until hi else Utf8.decode_last t from until in
    let got = decode_before st t lo at hi in
    let w = Utf8.rune want and g = Utf8.rune got in
    if g = w || (fold && Charset.canonicalize g flag = Charset.canonicalize w flag) then
      backref_chars_back st t lo hi fold flag from (until - Utf8.width want) (at - Utf8.width got)
    else -1

(* Reports whether a code point before or at a position is a word character of a case-insensitive word boundary. *)
let fold_word_before t lo pos = pos > lo && Charset.is_fold_word_rune (Utf8.rune (Utf8.decode_last t lo pos))
let fold_word_at t pos hi = pos < hi && Charset.is_fold_word_rune (Utf8.rune (Utf8.decode t pos hi))

(* Runs the instruction at pc with the text at pos. It reports whether the program matches. It keeps its choices on
   an explicit stack, so the depth of the search is bounded by memory and not by the stack of the thread. When it
   fails, every group and counter is back to the value it had on entry. *)
let rec step insts st t lo hi pc pos =
  let left = st.steps - 1 in
  st.steps <- left;
  if left < 0 && not (check st) then false else exec insts st t lo hi pc pos

and exec insts st t lo hi pc pos =
  let i = insts.(pc) in
  match i.op with
  | Op_char ->
      if pos < hi then
        let c = byte t pos in
        if c < Utf8.rune_self then
          if c = i.r then step insts st t lo hi (pc + 1) (pos + 1) else backtrack insts st t lo hi
        else
          let packed = decode_at st t lo pos hi in
          if Utf8.rune packed = i.r then step insts st t lo hi (pc + 1) (pos + Utf8.width packed)
          else backtrack insts st t lo hi
      else backtrack insts st t lo hi
  | Op_set ->
      if pos < hi then
        let c = byte t pos in
        if c < Utf8.rune_self then
          if Charset.ascii_has i.set.ascii c then step insts st t lo hi (pc + 1) (pos + 1)
          else backtrack insts st t lo hi
        else
          let packed = decode_at st t lo pos hi in
          if Charset.contains i.set (Utf8.rune packed) then step insts st t lo hi (pc + 1) (pos + Utf8.width packed)
          else backtrack insts st t lo hi
      else backtrack insts st t lo hi
  | Op_lit ->
      let n = String.length i.lit in
      st.steps <- st.steps - (n lsr 3);
      if hi - pos >= n && equal_lit t pos i.lit 0 then step insts st t lo hi (pc + 1) (pos + n)
      else backtrack insts st t lo hi
  | Op_char_back ->
      if pos > lo then
        let c = byte t (pos - 1) in
        let packed = if c < Utf8.rune_self then (c lsl 3) lor 1 else decode_before st t lo pos hi in
        if Utf8.rune packed = i.r then step insts st t lo hi (pc + 1) (pos - Utf8.width packed)
        else backtrack insts st t lo hi
      else backtrack insts st t lo hi
  | Op_set_back ->
      if pos > lo then
        let c = byte t (pos - 1) in
        let packed = if c < Utf8.rune_self then (c lsl 3) lor 1 else decode_before st t lo pos hi in
        if Charset.contains i.set (Utf8.rune packed) then step insts st t lo hi (pc + 1) (pos - Utf8.width packed)
        else backtrack insts st t lo hi
      else backtrack insts st t lo hi
  | Op_fail -> backtrack insts st t lo hi
  | Op_bol -> if pos = lo then step insts st t lo hi (pc + 1) pos else backtrack insts st t lo hi
  | Op_eol -> if pos = hi then step insts st t lo hi (pc + 1) pos else backtrack insts st t lo hi
  | Op_word_boundary ->
      let before = pos > lo && Charset.is_word_byte (byte t (pos - 1)) in
      let after = pos < hi && Charset.is_word_byte (byte t pos) in
      if before <> after then step insts st t lo hi (pc + 1) pos else backtrack insts st t lo hi
  | Op_not_word_boundary ->
      let before = pos > lo && Charset.is_word_byte (byte t (pos - 1)) in
      let after = pos < hi && Charset.is_word_byte (byte t pos) in
      if before = after then step insts st t lo hi (pc + 1) pos else backtrack insts st t lo hi
  | Op_line_start ->
      if pos = lo || line_terminator_before t lo pos then step insts st t lo hi (pc + 1) pos
      else backtrack insts st t lo hi
  | Op_line_end ->
      if pos = hi || line_terminator_at t pos hi then step insts st t lo hi (pc + 1) pos else backtrack insts st t lo hi
  | Op_word_boundary_fold ->
      if fold_word_before t lo pos <> fold_word_at t pos hi then step insts st t lo hi (pc + 1) pos
      else backtrack insts st t lo hi
  | Op_not_word_boundary_fold ->
      if fold_word_before t lo pos = fold_word_at t pos hi then step insts st t lo hi (pc + 1) pos
      else backtrack insts st t lo hi
  | Op_jmp -> step insts st t lo hi i.x pos
  | Op_split ->
      (* A place from which every way has failed before fails at once. The mark is under whatever comes of this place,
         a choice or not, so that the place is remembered when it has all failed. *)
      if st.memo_on && i.m <> 0 && failed st (memo_index st i pos) then backtrack insts st t lo hi
      else begin
        if st.memo_on && i.m <> 0 && st.sp < 4 * max_mark_depth then push st e_mark 0 (memo_index st i pos) 0;
        (* A way the next character rules out is not taken, and no choice is kept for it. *)
        if not (allows insts.(i.y).q t pos hi) then
          if not (allows insts.(i.x).q t pos hi) then backtrack insts st t lo hi else step insts st t lo hi i.x pos
        else if not (allows insts.(i.x).q t pos hi) then step insts st t lo hi i.y pos
        else begin
          push st e_choice i.y pos 0;
          step insts st t lo hi i.x pos
        end
      end
  | Op_save_tmp ->
      push st e_tmp i.a st.tmp.(i.a) 0;
      st.tmp.(i.a) <- pos;
      step insts st t lo hi (pc + 1) pos
  | Op_capture ->
      let caps = st.caps in
      push st e_cap i.a caps.(2 * i.a) caps.((2 * i.a) + 1);
      if i.flag then begin
        caps.(2 * i.a) <- pos;
        caps.((2 * i.a) + 1) <- st.tmp.(i.a)
      end
      else begin
        caps.(2 * i.a) <- st.tmp.(i.a);
        caps.((2 * i.a) + 1) <- pos
      end;
      step insts st t lo hi (pc + 1) pos
  | Op_clear ->
      let caps = st.caps in
      st.steps <- st.steps - (i.b - i.a);
      for g = i.a to i.b do
        if caps.(2 * g) >= 0 then begin
          push st e_cap g caps.(2 * g) caps.((2 * g) + 1);
          caps.(2 * g) <- -1;
          caps.((2 * g) + 1) <- -1
        end
      done;
      step insts st t lo hi (pc + 1) pos
  | Op_backref ->
      (* A group that has not taken part matches the empty string. *)
      let from = st.caps.(2 * i.a) and until = st.caps.((2 * i.a) + 1) in
      if from < 0 then step insts st t lo hi (pc + 1) pos
      else
        let n = until - from in
        st.steps <- st.steps - (n lsr 3);
        if hi - pos >= n && equal_spans t pos from n then step insts st t lo hi (pc + 1) (pos + n)
        else backtrack insts st t lo hi
  | Op_backref_back ->
      let from = st.caps.(2 * i.a) and until = st.caps.((2 * i.a) + 1) in
      if from < 0 then step insts st t lo hi (pc + 1) pos
      else
        let n = until - from in
        st.steps <- st.steps - (n lsr 3);
        if pos - lo >= n && equal_spans t (pos - n) from n then step insts st t lo hi (pc + 1) (pos - n)
        else backtrack insts st t lo hi
  | Op_backref_fold | Op_backref_units ->
      let from = st.caps.(2 * i.a) and until = st.caps.((2 * i.a) + 1) in
      if from < 0 then step insts st t lo hi (pc + 1) pos
      else
        let () = st.steps <- st.steps - ((until - from) lsr 3) in
        let at = backref_chars st t lo hi (i.op = Op_backref_fold) i.flag from until pos in
        if at >= 0 then step insts st t lo hi (pc + 1) at else backtrack insts st t lo hi
  | Op_backref_fold_back | Op_backref_units_back ->
      let from = st.caps.(2 * i.a) and until = st.caps.((2 * i.a) + 1) in
      if from < 0 then step insts st t lo hi (pc + 1) pos
      else
        let () = st.steps <- st.steps - ((until - from) lsr 3) in
        let at = backref_chars_back st t lo hi (i.op = Op_backref_fold_back) i.flag from until pos in
        if at >= 0 then step insts st t lo hi (pc + 1) at else backtrack insts st t lo hi
  | Op_look ->
      (* What follows a lookaround starts where the lookaround does, and so does the body of a lookahead. *)
      if not (allows i.q t pos hi) then backtrack insts st t lo hi
      else begin
        push st (if i.flag then e_look_neg else e_look) i.x pos 0;
        step insts st t lo hi (pc + 1) pos
      end
  | Op_look_ok ->
      (* The body matched. A lookaround is atomic, so the choices made inside it are dropped. The undo entries stay,
         because the groups it set remain set until something below this point is retried. *)
      let stack = st.stack in
      let f = ref (st.sp - 4) in
      while stack.(!f) <> e_look do
        f := !f - 4
      done;
      let pos = stack.(!f + 2) in
      let keep = ref !f in
      let k = ref (!f + 4) in
      while !k < st.sp do
        if stack.(!k) >= e_tmp then begin
          stack.(!keep) <- stack.(!k);
          stack.(!keep + 1) <- stack.(!k + 1);
          stack.(!keep + 2) <- stack.(!k + 2);
          stack.(!keep + 3) <- stack.(!k + 3);
          keep := !keep + 4
        end;
        k := !k + 4
      done;
      st.sp <- !keep;
      step insts st t lo hi (pc + 1) pos
  | Op_look_fail ->
      (* The body of a negative lookaround matched, so the lookaround fails and everything it did is undone. *)
      let stack = st.stack in
      let e = ref (st.sp - 4) in
      while stack.(!e) <> e_look_neg do
        undo st !e;
        e := !e - 4
      done;
      st.sp <- !e;
      backtrack insts st t lo hi
  | Op_rep ->
      let at = rep st i t lo hi pc pos 0 pos in
      if at >= 0 then step insts st t lo hi (pc + 1) at else backtrack insts st t lo hi
  | Op_rep_lazy ->
      let at = ref pos and n = ref 0 and stuck = ref false in
      while !n < i.min && not !stuck do
        let w = one st i t lo !at hi in
        if w = 0 then stuck := true
        else begin
          at := !at + w;
          incr n
        end
      done;
      st.steps <- st.steps - (!n lsr 3);
      if !n = i.min then
        if st.memo_on && i.m <> 0 then begin
          let idx = memo_index st i !at in
          if failed st idx then begin
            (* What follows the loop has failed from here before. The loop takes one more character. *)
            if !n <> i.max then push st e_rep_lazy pc !at !n;
            backtrack insts st t lo hi
          end
          else begin
            if !n <> i.max then push st e_rep_lazy pc !at !n
            else if st.sp < 4 * max_mark_depth then push st e_mark 0 idx 0;
            step insts st t lo hi (pc + 1) !at
          end
        end
        else begin
          if !n <> i.max then push st e_rep_lazy pc !at !n;
          step insts st t lo hi (pc + 1) !at
        end
      else backtrack insts st t lo hi
  | Op_loop_init ->
      let counts = st.counts in
      push st e_counter i.a counts.(2 * i.a) counts.((2 * i.a) + 1);
      counts.(2 * i.a) <- 0;
      counts.((2 * i.a) + 1) <- -1;
      step insts st t lo hi (pc + 1) pos
  | Op_loop ->
      let n = st.counts.(2 * i.a) in
      (* Once the minimum is met, ECMA-262 rejects an iteration that consumed nothing. That is what stops a loop over
         a body that can be empty. The loop also gives up when fewer bytes are left than any way from here to a match
         consumes: what follows the loop, and the iterations it has still to make. A count is below 2^31 and a need at
         most 2^30, so their product fits an int. *)
      let left = hi - pos in
      if
        left < i.need
        || (n < i.min && i.iter <> 0 && left - i.need < (i.min - n) * i.iter)
        || (n > i.min && pos = st.counts.((2 * i.a) + 1))
      then backtrack insts st t lo hi
      else if n < i.min then step insts st t lo hi (pc + 1) pos
      else if i.max >= 0 && n >= i.max then step insts st t lo hi i.x pos
      else if st.memo_on && i.m <> 0 && failed st (memo_index st i pos) then
        (* The loop may be left or entered from here, and both have failed from here before. *)
        backtrack insts st t lo hi
      else begin
        if st.memo_on && i.m <> 0 && st.sp < 4 * max_mark_depth then push st e_mark 0 (memo_index st i pos) 0;
        if not (allows insts.(i.x).q t pos hi) then
          (* The next character rules out leaving the loop, so there is no choice. *)
          step insts st t lo hi (pc + 1) pos
        else if not (allows insts.(pc + 1).q t pos hi) then step insts st t lo hi i.x pos
        else if i.flag then begin
          push st e_choice (pc + 1) pos 0;
          step insts st t lo hi i.x pos
        end
        else begin
          push st e_choice i.x pos 0;
          step insts st t lo hi (pc + 1) pos
        end
      end
  | Op_loop_iter ->
      let counts = st.counts in
      push st e_counter i.a counts.(2 * i.a) counts.((2 * i.a) + 1);
      counts.(2 * i.a) <- counts.(2 * i.a) + 1;
      counts.((2 * i.a) + 1) <- pos;
      step insts st t lo hi (pc + 1) pos
  | Op_match -> true

(* The instruction failed. Takes up the most recent choice, undoing what was recorded since. *)
and backtrack insts st t lo hi =
  if st.sp = 0 then false
  else
    let stack = st.stack in
    let e = st.sp - 4 in
    let kind = stack.(e) in
    if kind = e_choice then begin
      st.sp <- e;
      step insts st t lo hi stack.(e + 1) stack.(e + 2)
    end
    else if kind = e_rep then begin
      let pc = stack.(e + 1) and pos = stack.(e + 2) and floor = stack.(e + 3) in
      (* What followed the loop from the position it stood at has failed. *)
      let rep = insts.(pc - 1) in
      let marked = st.memo_on && rep.m <> 0 in
      if marked then set_failed st (memo_index st rep pos);
      (* Give back the last character taken. The characters between a and pos were decoded going forward, and
         decoding the last of them going backward finds the same boundary. *)
      let w =
        if byte t (pos - 1) < Utf8.rune_self then 1
        else if st.units then Utf8.width (Utf8.unit_before t lo pos hi)
        else Utf8.width (Utf8.decode_last t floor pos)
      in
      let pos = pos - w in
      stack.(e + 2) <- pos;
      let last = pos <= floor in
      (* A position from which what follows the loop cannot go on is passed over, and so is one from which it has
         failed before. *)
      if not (allows insts.(pc).q t pos hi) then begin
        if last then st.sp <- e;
        st.steps <- st.steps - 1;
        backtrack insts st t lo hi
      end
      else if marked && failed st (memo_index st rep pos) then begin
        if last then st.sp <- e;
        st.steps <- st.steps - 1;
        backtrack insts st t lo hi
      end
      else begin
        if last then
          if marked then begin
            (* The loop has nothing more to give back. The entry stays to note this last way. *)
            stack.(e) <- e_mark;
            stack.(e + 2) <- memo_index st rep pos
          end
          else st.sp <- e;
        step insts st t lo hi pc pos
      end
    end
    else if kind = e_rep_lazy then begin
      let pc = stack.(e + 1) in
      let rep = insts.(pc) in
      (* What followed the loop from the position it stood at has failed. *)
      let marked = st.memo_on && rep.m <> 0 in
      if marked then set_failed st (memo_index st rep stack.(e + 2));
      let w = one st rep t lo stack.(e + 2) hi in
      if w = 0 then begin
        st.sp <- e;
        backtrack insts st t lo hi
      end
      else begin
        let pos = stack.(e + 2) + w and taken = stack.(e + 3) + 1 in
        stack.(e + 2) <- pos;
        stack.(e + 3) <- taken;
        let last = taken = rep.max in
        if marked && failed st (memo_index st rep pos) then begin
          if last then st.sp <- e;
          st.steps <- st.steps - 1;
          backtrack insts st t lo hi
        end
        else begin
          if last then
            if marked then begin
              stack.(e) <- e_mark;
              stack.(e + 2) <- memo_index st rep pos
            end
            else st.sp <- e;
          step insts st t lo hi (pc + 1) pos
        end
      end
    end
    else if kind = e_mark then begin
      (* Every way from a memo point has failed. *)
      set_failed st stack.(e + 2);
      st.sp <- e;
      backtrack insts st t lo hi
    end
    else if kind = e_look then begin
      (* The body of a positive lookaround has no way left to match. *)
      st.sp <- e;
      backtrack insts st t lo hi
    end
    else if kind = e_look_neg then begin
      (* The body of a negative lookaround has no way to match, so the lookaround holds. *)
      st.sp <- e;
      step insts st t lo hi stack.(e + 1) stack.(e + 2)
    end
    else begin
      undo st e;
      st.sp <- e;
      backtrack insts st t lo hi
    end

(* Reports whether the program matches at the position start. *)
let run p st t lo hi start =
  st.sp <- 0;
  step p.insts st t lo hi 0 start

let rec search_prefix p st t lo hi at =
  let at = index_of t at hi p.prefix in
  if at < 0 || hi - at < p.need then false
  else if run p st t lo hi at then true
  else if st.stop <> stop_none then false
  else search_prefix p st t lo hi (at + 1)

let rec search_first p st t lo hi at =
  if hi - at < p.need then false
  else if at = hi then (not p.has_first) && run p st t lo hi at
  else
    let c = byte t at in
    let packed = if c < Utf8.rune_self then (c lsl 3) lor 1 else decode_at st t lo at hi in
    if ((not p.has_first) || Charset.contains p.first (Utf8.rune packed)) && run p st t lo hi at then true
    else if st.stop <> stop_none then false
    else search_first p st t lo hi (at + Utf8.width packed)

let search p st t lo hi =
  if hi - lo < p.need then false
  else if p.anchored then run p st t lo hi lo
  else if String.length p.prefix > 0 then search_prefix p st t lo hi lo
  else search_first p st t lo hi lo

(* Makes the state ready for a match of the program, searches, and lets go of the text. With a limit in the state the
   match is abandoned when it has run for longer than that, or when its stack has reached max_stack, and the exception
   says which. *)
let match_text p st lo hi =
  (* The memory grows to what this program needs. The groups start out not having taken part. The entry positions and
     the counters need no start value: a match writes each before it uses it, and until then only saves and restores
     what it finds there. *)
  if Array.length st.caps < 2 * p.groups then begin
    st.caps <- Array.make (2 * p.groups) (-1);
    st.tmp <- Array.make p.groups 0
  end;
  if Array.length st.counts < 2 * p.loops then st.counts <- Array.make (2 * p.loops) 0;
  for k = 0 to (2 * p.groups) - 1 do
    st.caps.(k) <- -1
  done;
  st.units <- p.units;
  st.timed <- false;
  st.steps <- check_every;
  st.stop <- stop_none;
  st.memo_on <- false;
  st.memo_off <- p.points = 0;
  st.checks <- 0;
  st.lo <- lo;
  st.width <- hi - lo + 1;
  st.points <- p.points;
  if !memo_from_the_start && not st.memo_off then remember st;
  let t = st.text in
  match search p st t lo hi with
  | matched ->
      if Array.length st.stack > 4 * max_retained_stack then st.stack <- [||];
      if Bytes.length st.memo > 8 * max_retained_memo then st.memo <- Bytes.empty;
      t.b <- Bytes.empty;
      t.s <- "";
      (* A match that was abandoned has no answer. The exceptions carry nothing, so raising one allocates nothing. *)
      if st.stop = stop_time then raise Match_time else if st.stop = stop_memory then raise Match_memory else matched
  | exception e ->
      st.stack <- [||];
      st.memo <- Bytes.empty;
      t.b <- Bytes.empty;
      t.s <- "";
      raise e

(** [match_bytes p st text lo hi] reports whether the program matches anywhere in the bytes [lo] up to [hi] of the text,
    with [st] as its working memory. The caller has checked the range. *)
let match_bytes p st text lo hi =
  let t = st.text in
  t.b <- text;
  t.is_string <- false;
  match_text p st lo hi

(** [match_string p st text lo hi] is {!match_bytes} for a string. *)
let match_string p st text lo hi =
  let t = st.text in
  t.s <- text;
  t.is_string <- true;
  match_text p st lo hi