package corvus-json-schema
sectionYPositions = computeSectionYPositions($el), 10)"
x-init="setTimeout(() => sectionYPositions = computeSectionYPositions($el), 10)"
>
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/dfa.ml.html
Source file dfa.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(* A deterministic automaton for the patterns that need no backtracking, over ASCII text. This is the port of dfa.go of the ecmaregex package of the Go module. Matching a pattern by walking its instructions costs tens of nanoseconds per byte of text. The strings a schema tests are short and nearly always ASCII, and most patterns in schemas are small. For such a pattern every state set the text can lead to is worked out once, and a match is then one table read per byte. The automaton reads bytes below 0x80 only. A text with any other byte is handed to the backtracking matcher from the start, so nothing here needs to know UTF-8 or the Unicode classes: a class is its ASCII members. A pattern with a word boundary, or whose automaton has more states than a byte can number, has no automaton and keeps to the backtracking matcher. What differs from the Go source, as in the Object Pascal port: Go hands the texts the automaton does not decide to the regexp package of its standard library, and builds the automaton on the first match, behind a sync.Once. Here those texts go to the backtracking matcher, and the automaton is built when the pattern is compiled, because a compiled pattern is a value that threads and domains share without a lock. *) open Parse (* The states every automaton has. dead: no match can follow. accept: a match was found. bail: the text is not ASCII. *) let dfa_dead = 0 let dfa_accept = 1 let dfa_bail = 2 let dfa_first = 3 (* An automaton has at most this many states (they are numbered in a byte), built from at most this many states of the pattern, in at most this many steps (a step is one state of the pattern looked at while a set is made). The steps bound what a pattern too large for an automaton costs before that is known, to about a millisecond. *) let max_dfa_states = 256 let max_nfa_states = 512 let max_dfa_steps = 400000 type nfa_kind = | Nfa_set (** Reads one byte of its set and goes to [out]. *) | Nfa_split (** Goes to [out] and to [alt], reading nothing. *) | Nfa_bol (** [Nfa_bol] and [Nfa_eol] go to [out] at the start and at the end of the text. *) | Nfa_eol | Nfa_match type nfa_state = { nkind : nfa_kind; bits : string; (** The 128 bits of the set, as in {!Charset.t}. *) mutable out : int; alt : int; } type nfa_builder = { mutable states : nfa_state array; (** The states, of which the first [count] are in use. *) mutable count : int; mutable failed : bool; (** The pattern has something an automaton over bytes cannot decide, or is too large. *) } let no_bits = String.make 16 '\000' let no_state = { nkind = Nfa_match; bits = no_bits; out = 0; alt = 0 } let add b nkind bits out alt = if b.count >= max_nfa_states then begin b.failed <- true; 0 end else begin if b.count = Array.length b.states then begin let grown = Array.make ((2 * b.count) + 16) no_state in Array.blit b.states 0 grown 0 b.count; b.states <- grown end; b.states.(b.count) <- { nkind; bits; out; alt }; b.count <- b.count + 1; b.count - 1 end (* Adds the states of a node that go on to next, and returns the state they start at. *) let rec compile b n next = if b.failed then 0 else match n.kind with | Empty -> next | Char -> let bits = Bytes.make 16 '\000' in if n.r < 128 then Bytes.set bits (n.r lsr 3) (Char.chr (1 lsl (n.r land 7))); add b Nfa_set (Bytes.to_string bits) next 0 | Set -> add b Nfa_set n.set.ascii next 0 | Cat -> let next = ref next in for i = Array.length n.subs - 1 downto 0 do next := compile b n.subs.(i) !next done; !next | Alt -> let start = ref (compile b n.subs.(Array.length n.subs - 1) next) in for i = Array.length n.subs - 2 downto 0 do let out = compile b n.subs.(i) next in start := add b Nfa_split no_bits out !start done; !start | Group -> compile b n.subs.(0) next | Repeat -> let sub = n.subs.(0) in let next = ref next in if n.max = unbounded then begin (* The loop, then the copies that must match before it. *) let loop = add b Nfa_split no_bits 0 !next in if not b.failed then begin b.states.(loop).out <- compile b sub loop; next := loop end end else begin (* The copies that may match, innermost last. *) let i = ref n.min in while !i < n.max && not b.failed do let out = compile b sub !next in next := add b Nfa_split no_bits out !next; incr i done end; if b.failed then 0 else begin let i = ref 0 in while !i < n.min && not b.failed do next := compile b sub !next; incr i done; !next end | Bol -> add b Nfa_bol no_bits next 0 | Eol -> add b Nfa_eol no_bits next 0 | Line_start | Line_end | Word_boundary | Not_word_boundary | Look | Backref -> (* A word boundary, or anything only the backtracking matcher can run. *) b.failed <- true; 0 type t = { byte_class : string; (** The class of each of the 256 bytes. *) trans : string; (** [trans.[(state lsl shift) lor class]] is the state after a byte of that class. *) shift : int; start : int; at_end : string; (** For each state: a byte that is not zero when the pattern matches if the text ends there. *) empty : bool; (** The pattern matches the empty text. *) } (** The automaton. A state is a byte, a byte of the text is one of a few classes (bytes the pattern does not tell apart), and a transition is one read of [trans]. Every part is a string, so nothing can write to it. *) (* The answers of a match. *) (** The pattern does not match the text. *) let no_match = 0 (** The pattern matches the text. *) let matched = 1 (** The text is not ASCII, which this automaton does not decide. *) let undecided = 2 let finish s = if s = dfa_accept then matched else if s = dfa_bail then undecided else no_match (* The loop of a match. The parts of the automaton are arguments so that each step reads them from registers. *) let rec run_string trans byte_class shift at_end text i hi s = if i = hi then if String.get at_end s <> '\000' then matched else no_match else let c = Char.code (String.get text i) in let s = Char.code (String.get trans ((s lsl shift) lor Char.code (String.get byte_class c))) in if s < dfa_first then finish s else run_string trans byte_class shift at_end text (i + 1) hi s let rec run_bytes trans byte_class shift at_end text i hi s = if i = hi then if String.get at_end s <> '\000' then matched else no_match else let c = Char.code (Bytes.get text i) in let s = Char.code (String.get trans ((s lsl shift) lor Char.code (String.get byte_class c))) in if s < dfa_first then finish s else run_bytes trans byte_class shift at_end text (i + 1) hi s (** [match_string d text lo hi] says whether the pattern matches the bytes [lo] up to [hi] of the text: {!matched}, {!no_match}, or {!undecided} when the text is not ASCII. It does not allocate. *) let match_string d text lo hi = if lo = hi then if d.empty then matched else no_match else if d.start = dfa_accept then matched else run_string d.trans d.byte_class d.shift d.at_end text lo hi d.start (** [match_bytes d text lo hi] is {!match_string} for bytes. The loop is written once for each kind of text so that each is one table read per byte with no test of the kind. *) let match_bytes d text lo hi = if lo = hi then if d.empty then matched else no_match else if d.start = dfa_accept then matched else run_bytes d.trans d.byte_class d.shift d.at_end text lo hi d.start type list_ = { mutable items : int array; mutable n : int } (** A list of states of the pattern, of which the first [n] elements of [items] are in use. *) let new_list () = { items = Array.make 16 0; n = 0 } let append list value = if list.n = Array.length list.items then begin let grown = Array.make ((2 * list.n) + 16) 0 in Array.blit list.items 0 grown 0 list.n; list.items <- grown end; list.items.(list.n) <- value; list.n <- list.n + 1 type dfa_builder = { nfa : nfa_state array; mark : int array; (** Scratch for closure: the states visited, by generation, and the stack. *) mutable gen : int; stack : list_; mutable steps : int; (** The steps left (see [max_dfa_steps]). *) } (** Makes the automaton's states from sets of the pattern's states. *) (* Adds to set every state that reading nothing reaches from the states in from, keeping the states that read a byte, the end anchors still to pass, and the match. A start anchor is passed only at_start, and an end anchor only at_end. *) let closure b set from at_start at_end = b.gen <- b.gen + 1; b.stack.n <- 0; for k = 0 to from.n - 1 do append b.stack from.items.(k) done; while b.stack.n > 0 do b.stack.n <- b.stack.n - 1; let i = b.stack.items.(b.stack.n) in b.steps <- b.steps - 1; if b.mark.(i) <> b.gen then begin b.mark.(i) <- b.gen; let s = b.nfa.(i) in match s.nkind with | Nfa_split -> append b.stack s.out; append b.stack s.alt | Nfa_bol -> if at_start then append b.stack s.out | Nfa_eol -> if at_end then append b.stack s.out else append set i | Nfa_set | Nfa_match -> append set i end done let has_match b set = let rec scan k = k < set.n && (b.nfa.(set.items.(k)).nkind = Nfa_match || scan (k + 1)) in scan 0 (* A set as the key of a table: its states in increasing order, two bytes each. It sorts the set. *) let set_key set = (* Insertion sort: the sets are small. *) for i = 1 to set.n - 1 do let j = ref i in while !j > 0 && set.items.(!j) < set.items.(!j - 1) do let swap = set.items.(!j) in set.items.(!j) <- set.items.(!j - 1); set.items.(!j - 1) <- swap; decr j done done; let key = Bytes.create (2 * set.n) in for k = 0 to set.n - 1 do Bytes.set key (2 * k) (Char.chr (set.items.(k) land 0xFF)); Bytes.set key ((2 * k) + 1) (Char.chr (set.items.(k) lsr 8)) done; Bytes.to_string key exception Too_large (** [build root] builds the automaton of a pattern for an unanchored search, from the tree the parser read. It returns [None] when the pattern has none. The caller must not ask for the automaton of a pattern with a counted quantifier above 1000 or with such quantifiers nested to a product above 1000: the number of states bounds the work here only when each repetition adds a state. *) let build root = let nb = { states = [||]; count = 0; failed = false } in let match_state = add nb Nfa_match no_bits 0 0 in let start = compile nb root match_state in if nb.failed then None else begin let nfa = Array.sub nb.states 0 nb.count in let b = { nfa; mark = Array.make (Array.length nfa) 0; gen = 0; stack = new_list (); steps = max_dfa_steps } in (* The classes of bytes: two bytes are in one class when every set of the pattern has both or neither. Class 0 is the bytes that are not ASCII. *) let byte_class = Bytes.make 256 '\000' in let classes = ref 1 in let signatures = Hashtbl.create 64 in let signature = Buffer.create ((Array.length nfa / 8) + 1) in for c = 0 to 127 do Buffer.clear signature; let acc = ref 0 and n = ref 0 in Array.iter (fun s -> if s.nkind = Nfa_set then begin acc := (!acc lsl 1) lor (if Charset.ascii_has s.bits c then 1 else 0) land 0xFF; incr n; if !n = 8 then begin Buffer.add_char signature (Char.chr !acc); acc := 0; n := 0 end end) nfa; Buffer.add_char signature (Char.chr !acc); let key = Buffer.contents signature in let class_ = match Hashtbl.find_opt signatures key with | Some class_ -> class_ | None -> let class_ = !classes in incr classes; Hashtbl.replace signatures key class_; class_ in Bytes.set byte_class c (Char.chr class_) done; let classes = !classes in let shift = ref 0 in while 1 lsl !shift < classes do incr shift done; let shift = !shift in (* One byte of each class, to move a set by. *) let sample = Array.make 129 0 in for c = 127 downto 0 do sample.(Char.code (Bytes.get byte_class c)) <- c done; let from_start = new_list () in append from_start start; (* The search is unanchored: a match may start at any position, so after every byte the states the pattern starts in (not at the start of the text) join the set. *) let restart = new_list () in closure b restart from_start false false; let at_both = new_list () in closure b at_both from_start true true; let empty = has_match b at_both in let ids = Hashtbl.create 64 in let sets = ref (Array.make 16 [||]) in let set_count = ref dfa_first in let stride = 1 lsl shift in (* The transitions, by state and class. The rows of the states an automaton may have are all there from the start, and the ones in use are cut out at the end. The row of the dead state stays zero. *) let table = Bytes.make (max_dfa_states * stride) '\000' in for class_ = 0 to stride - 1 do Bytes.set table ((dfa_accept * stride) + class_) (Char.chr dfa_accept); Bytes.set table ((dfa_bail * stride) + class_) (Char.chr dfa_bail) done; let at_end = Buffer.create 16 in Buffer.add_string at_end "\000\000\000"; let ends = new_list () and ends_closed = new_list () in (* Gives a set its state. It raises Too_large when there are too many. *) let intern set = if has_match b set then dfa_accept else if set.n = 0 then dfa_dead else let key = set_key set in match Hashtbl.find_opt ids key with | Some id -> id | None -> if !set_count >= max_dfa_states then raise Too_large; let id = !set_count in Hashtbl.replace ids key id; if id = Array.length !sets then begin let grown = Array.make (2 * id) [||] in Array.blit !sets 0 grown 0 id; sets := grown end; !sets.(id) <- Array.sub set.items 0 set.n; incr set_count; (* At the end of the text the end anchors are passed. *) ends.n <- 0; for k = 0 to set.n - 1 do let s = nfa.(set.items.(k)) in if s.nkind = Nfa_eol then append ends s.out done; ends_closed.n <- 0; if ends.n <> 0 then closure b ends_closed ends false true; Buffer.add_char at_end (if ends.n <> 0 && has_match b ends_closed then '\001' else '\000'); id in try let first = new_list () in closure b first from_start true false; let start = intern first in let moved = new_list () and next = new_list () in let id = ref dfa_first in while !id < !set_count do let set = !sets.(!id) in for class_ = 0 to stride - 1 do if class_ = 0 || class_ >= classes then (* Not ASCII (or no such class). *) Bytes.set table ((!id * stride) + class_) (Char.chr dfa_bail) else begin let c = sample.(class_) in moved.n <- 0; Array.iter (fun i -> let s = nfa.(i) in if s.nkind = Nfa_set && Charset.ascii_has s.bits c then append moved s.out) set; for k = 0 to restart.n - 1 do append moved restart.items.(k) done; next.n <- 0; closure b next moved false false; let target = intern next in if b.steps < 0 then raise Too_large; Bytes.set table ((!id * stride) + class_) (Char.chr target) end done; incr id done; Some { byte_class = Bytes.to_string byte_class; trans = Bytes.sub_string table 0 (!set_count * stride); shift; start; at_end = Buffer.contents at_end; empty; } with Too_large -> None end
sectionYPositions = computeSectionYPositions($el), 10)"
x-init="setTimeout(() => sectionYPositions = computeSectionYPositions($el), 10)"
>