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/charset.ml.html

Source file charset.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
(* The sets of code points of the engine, and case-insensitive matching. This is the port of charset.go and fold.go of
   the ecmaregex package of the Go module.

   Two things differ from the Go source, as they do in the Object Pascal port.

   Go keeps the sets already built for property expressions in a sync.Map, so that a property many patterns name is
   built once. Here each pattern builds the property sets it names, so there is no shared state to guard. That costs a
   pattern some microseconds when it is compiled and nothing when it matches.

   Go builds each of the two tables of case-insensitive matching on first use behind a sync.Once. Here a thread that
   finds no table builds one and publishes it through an [Atomic]. Two threads or domains that race both build the
   table, which is the same either way, and each uses the one it reads. *)

module Ucd = Corvus_json_schema_ucd

(** The last Unicode code point. *)
let max_rune = Ucd.max_rune

type t = { ranges : int array; ascii : string }
(** An immutable set of code points. The ranges are sorted, inclusive pairs that neither overlap nor touch. [ascii]
    repeats the membership of the code points below 128 as 128 bits in 16 bytes, so that the common case is one bit
    test. The Go source holds them in two words of 64 bits. An [int] has 63, so they are bytes here. *)

(** Reports whether the 128 bits hold the code point [c], which is below 128. *)
let ascii_has ascii c = (Char.code (String.get ascii (c lsr 3)) lsr (c land 7)) land 1 <> 0

let compare_int (a : int) (b : int) = compare a b

(** Builds a set from inclusive pairs in any order, which may overlap. With [negate] it builds the complement. *)
let make pairs negate =
  let n = Array.length pairs / 2 in
  (* A range is one number here, its first code point above its last, so that sorting the numbers sorts the ranges. A
     code point has 21 bits, so the number fits an int of 63 bits. *)
  let order = Array.init n (fun i -> (pairs.(2 * i) lsl 32) lor pairs.((2 * i) + 1)) in
  Array.sort compare_int order;
  let merged = Array.make (2 * n) 0 in
  let m = ref 0 in
  Array.iter
    (fun pair ->
      let lo = pair lsr 32 and hi = pair land 0xFFFFFFFF in
      if !m > 0 && lo <= merged.(!m - 1) + 1 then
        begin if hi > merged.(!m - 1) then merged.(!m - 1) <- hi
        end
      else begin
        merged.(!m) <- lo;
        merged.(!m + 1) <- hi;
        m := !m + 2
      end)
    order;
  let merged = Array.sub merged 0 !m in
  let ranges =
    if not negate then merged
    else begin
      let inverse = Array.make (Array.length merged + 2) 0 in
      let k = ref 0 and next = ref 0 and i = ref 0 in
      while !i < Array.length merged do
        if merged.(!i) > !next then begin
          inverse.(!k) <- !next;
          inverse.(!k + 1) <- merged.(!i) - 1;
          k := !k + 2
        end;
        next := merged.(!i + 1) + 1;
        i := !i + 2
      done;
      if !next <= max_rune then begin
        inverse.(!k) <- !next;
        inverse.(!k + 1) <- max_rune;
        k := !k + 2
      end;
      Array.sub inverse 0 !k
    end
  in
  let ascii = Bytes.make 16 '\000' in
  let i = ref 0 in
  while !i < Array.length ranges && ranges.(!i) < 128 do
    let c = ref ranges.(!i) in
    while !c <= ranges.(!i + 1) && !c < 128 do
      Bytes.set ascii (!c lsr 3) (Char.chr (Char.code (Bytes.get ascii (!c lsr 3)) lor (1 lsl (!c land 7))));
      incr c
    done;
    i := !i + 2
  done;
  { ranges; ascii = Bytes.to_string ascii }

let rec search ranges r lo hi =
  if lo >= hi then false
  else
    let mid = (lo + hi) lsr 1 in
    if r > ranges.((2 * mid) + 1) then search ranges r (mid + 1) hi
    else if r < ranges.(2 * mid) then search ranges r lo mid
    else true

(** Reports whether the set holds the code point. *)
let contains s r = if r < 128 then r >= 0 && ascii_has s.ascii r else search s.ranges r 0 (Array.length s.ranges / 2)

(** Reports whether the set holds no code point. *)
let is_empty s = Array.length s.ranges = 0

(** The set of no code point. It also stands where a set is wanted and none applies. *)
let none = make [||] false

(* The classes ECMA-262 defines. \d and \w are ASCII only, \s is WhiteSpace and LineTerminator, and . is everything
   but the four line terminators. *)
let digit_pairs = [| 0x30; 0x39 |]
let word_pairs = [| 0x30; 0x39; 0x41; 0x5A; 0x5F; 0x5F; 0x61; 0x7A |]

let space_pairs =
  [|
    0x9; 0xD; 0x20; 0x20; 0xA0; 0xA0; 0x1680; 0x1680; 0x2000; 0x200A; 0x2028; 0x2029; 0x202F; 0x202F; 0x205F; 0x205F;
    0x3000; 0x3000; 0xFEFF; 0xFEFF;
  |]
[@@ocamlformat "disable"]

let line_terminator_pairs = [| 0x0A; 0x0A; 0x0D; 0x0D; 0x2028; 0x2029 |]
let dot_set = make line_terminator_pairs true
let any_set = make [| 0; max_rune |] false
let digit_set = make digit_pairs false
let not_digit_set = make digit_pairs true
let word_set = make word_pairs false
let not_word_set = make word_pairs true
let space_set = make space_pairs false
let not_space_set = make space_pairs true

(** Reports whether a byte is an ECMA-262 word character. Every word character is ASCII, so a word boundary can be
    decided from the bytes on either side of a position. *)
let is_word_byte b = (b >= 0x61 && b <= 0x7A) || (b >= 0x41 && b <= 0x5A) || (b >= 0x30 && b <= 0x39) || b = 0x5F

(* The long names and the aliases of the General_Category values, each with its short name. *)
let category_names =
  [|
    ("Letter", "L"); ("Lowercase_Letter", "Ll"); ("Uppercase_Letter", "Lu"); ("Titlecase_Letter", "Lt");
    ("Cased_Letter", "LC"); ("Modifier_Letter", "Lm"); ("Other_Letter", "Lo"); ("Mark", "M"); ("Combining_Mark", "M");
    ("Nonspacing_Mark", "Mn"); ("Spacing_Mark", "Mc"); ("Enclosing_Mark", "Me"); ("Number", "N");
    ("Decimal_Number", "Nd"); ("digit", "Nd"); ("Letter_Number", "Nl"); ("Other_Number", "No"); ("Punctuation", "P");
    ("punct", "P"); ("Connector_Punctuation", "Pc"); ("Dash_Punctuation", "Pd"); ("Open_Punctuation", "Ps");
    ("Close_Punctuation", "Pe"); ("Initial_Punctuation", "Pi"); ("Final_Punctuation", "Pf");
    ("Other_Punctuation", "Po"); ("Symbol", "S"); ("Math_Symbol", "Sm"); ("Currency_Symbol", "Sc");
    ("Modifier_Symbol", "Sk"); ("Other_Symbol", "So"); ("Separator", "Z"); ("Space_Separator", "Zs");
    ("Line_Separator", "Zl"); ("Paragraph_Separator", "Zp"); ("Other", "C"); ("Control", "Cc"); ("cntrl", "Cc");
    ("Format", "Cf"); ("Surrogate", "Cs"); ("Private_Use", "Co"); ("Unassigned", "Cn");
    ("L", "L"); ("Ll", "Ll"); ("Lu", "Lu"); ("Lt", "Lt"); ("LC", "LC"); ("Lm", "Lm"); ("Lo", "Lo"); ("M", "M");
    ("Mn", "Mn"); ("Mc", "Mc"); ("Me", "Me"); ("N", "N"); ("Nd", "Nd"); ("Nl", "Nl"); ("No", "No"); ("P", "P");
    ("Pc", "Pc"); ("Pd", "Pd"); ("Ps", "Ps"); ("Pe", "Pe"); ("Pi", "Pi"); ("Pf", "Pf"); ("Po", "Po"); ("S", "S");
    ("Sm", "Sm"); ("Sc", "Sc"); ("Sk", "Sk"); ("So", "So"); ("Z", "Z"); ("Zs", "Zs"); ("Zl", "Zl"); ("Zp", "Zp");
    ("C", "C"); ("Cc", "Cc"); ("Cf", "Cf"); ("Cs", "Cs"); ("Co", "Co"); ("Cn", "Cn");
  |]
[@@ocamlformat "disable"]

(* The names and the aliases of the binary properties ECMA-262 lists, each with its canonical name. *)
let binary_names =
  [|
    ("ASCII", "ASCII"); ("ASCII_Hex_Digit", "ASCII_Hex_Digit"); ("AHex", "ASCII_Hex_Digit");
    ("Alphabetic", "Alphabetic"); ("Alpha", "Alphabetic"); ("Any", "Any"); ("Assigned", "Assigned");
    ("Bidi_Control", "Bidi_Control"); ("Bidi_C", "Bidi_Control"); ("Bidi_Mirrored", "Bidi_Mirrored");
    ("Bidi_M", "Bidi_Mirrored"); ("Case_Ignorable", "Case_Ignorable"); ("CI", "Case_Ignorable"); ("Cased", "Cased");
    ("Changes_When_Casefolded", "Changes_When_Casefolded"); ("CWCF", "Changes_When_Casefolded");
    ("Changes_When_Casemapped", "Changes_When_Casemapped"); ("CWCM", "Changes_When_Casemapped");
    ("Changes_When_Lowercased", "Changes_When_Lowercased"); ("CWL", "Changes_When_Lowercased");
    ("Changes_When_NFKC_Casefolded", "Changes_When_NFKC_Casefolded"); ("CWKCF", "Changes_When_NFKC_Casefolded");
    ("Changes_When_Titlecased", "Changes_When_Titlecased"); ("CWT", "Changes_When_Titlecased");
    ("Changes_When_Uppercased", "Changes_When_Uppercased"); ("CWU", "Changes_When_Uppercased"); ("Dash", "Dash");
    ("Default_Ignorable_Code_Point", "Default_Ignorable_Code_Point"); ("DI", "Default_Ignorable_Code_Point");
    ("Deprecated", "Deprecated"); ("Dep", "Deprecated"); ("Diacritic", "Diacritic"); ("Dia", "Diacritic");
    ("Emoji", "Emoji"); ("Emoji_Component", "Emoji_Component"); ("EComp", "Emoji_Component");
    ("Emoji_Modifier", "Emoji_Modifier"); ("EMod", "Emoji_Modifier"); ("Emoji_Modifier_Base", "Emoji_Modifier_Base");
    ("EBase", "Emoji_Modifier_Base"); ("Emoji_Presentation", "Emoji_Presentation"); ("EPres", "Emoji_Presentation");
    ("Extended_Pictographic", "Extended_Pictographic"); ("ExtPict", "Extended_Pictographic");
    ("Extender", "Extender"); ("Ext", "Extender"); ("Grapheme_Base", "Grapheme_Base"); ("Gr_Base", "Grapheme_Base");
    ("Grapheme_Extend", "Grapheme_Extend"); ("Gr_Ext", "Grapheme_Extend"); ("Hex_Digit", "Hex_Digit");
    ("Hex", "Hex_Digit"); ("IDS_Binary_Operator", "IDS_Binary_Operator"); ("IDSB", "IDS_Binary_Operator");
    ("IDS_Trinary_Operator", "IDS_Trinary_Operator"); ("IDST", "IDS_Trinary_Operator");
    ("ID_Continue", "ID_Continue"); ("IDC", "ID_Continue"); ("ID_Start", "ID_Start"); ("IDS", "ID_Start");
    ("Ideographic", "Ideographic"); ("Ideo", "Ideographic"); ("Join_Control", "Join_Control");
    ("Join_C", "Join_Control"); ("Logical_Order_Exception", "Logical_Order_Exception");
    ("LOE", "Logical_Order_Exception"); ("Lowercase", "Lowercase"); ("Lower", "Lowercase"); ("Math", "Math");
    ("Noncharacter_Code_Point", "Noncharacter_Code_Point"); ("NChar", "Noncharacter_Code_Point");
    ("Pattern_Syntax", "Pattern_Syntax"); ("Pat_Syn", "Pattern_Syntax");
    ("Pattern_White_Space", "Pattern_White_Space"); ("Pat_WS", "Pattern_White_Space");
    ("Quotation_Mark", "Quotation_Mark"); ("QMark", "Quotation_Mark"); ("Radical", "Radical");
    ("Regional_Indicator", "Regional_Indicator"); ("RI", "Regional_Indicator");
    ("Sentence_Terminal", "Sentence_Terminal"); ("STerm", "Sentence_Terminal"); ("Soft_Dotted", "Soft_Dotted");
    ("SD", "Soft_Dotted"); ("Terminal_Punctuation", "Terminal_Punctuation"); ("Term", "Terminal_Punctuation");
    ("Unified_Ideograph", "Unified_Ideograph"); ("UIdeo", "Unified_Ideograph"); ("Uppercase", "Uppercase");
    ("Upper", "Uppercase"); ("Variation_Selector", "Variation_Selector"); ("VS", "Variation_Selector");
    ("White_Space", "White_Space"); ("space", "White_Space"); ("XID_Continue", "XID_Continue");
    ("XIDC", "XID_Continue"); ("XID_Start", "XID_Start"); ("XIDS", "XID_Start");
  |]
[@@ocamlformat "disable"]

(* The two lists as tables. Nothing writes to them once the module is initialised, so reading them needs no lock. *)
let table_of pairs =
  let table = Hashtbl.create (2 * Array.length pairs) in
  Array.iter (fun (name, canonical) -> Hashtbl.replace table name canonical) pairs;
  table

let category_table : (string, string) Hashtbl.t = table_of category_names
let binary_table : (string, string) Hashtbl.t = table_of binary_names

(* Returns the ranges of a table as inclusive pairs, or [None] for no table. *)
let table_pairs = function None -> None | Some table -> Some (Ucd.ranges table)

let category_pairs value =
  match Hashtbl.find_opt category_table value with None -> None | Some short -> table_pairs (Ucd.category short)

(** Returns the ranges of a property expression, or [None] for a name or value that ECMA-262 does not define. Every
    range comes from the tables of the ucd library, which hold one fixed version of Unicode. *)
let property_pairs expr =
  match String.index_opt expr '=' with
  | Some i -> (
      let name = String.sub expr 0 i and value = String.sub expr (i + 1) (String.length expr - i - 1) in
      match name with
      | "General_Category" | "gc" -> category_pairs value
      | "Script" | "sc" -> table_pairs (Ucd.script value)
      | "Script_Extensions" | "scx" -> table_pairs (Ucd.script_extensions value)
      | _ -> None)
  | None -> (
      match category_pairs expr with
      | Some pairs -> Some pairs
      | None -> (
          match Hashtbl.find_opt binary_table expr with
          | None -> None
          | Some "ASCII" -> Some [| 0; 0x7F |]
          | Some "Any" -> Some [| 0; max_rune |]
          | Some canonical -> table_pairs (Ucd.binary canonical)))

(** Resolves the expression between the braces of [\p{...}] to its set, or [None] for a name or value that ECMA-262 does
    not define. *)
let property_set expr = match property_pairs expr with None -> None | Some pairs -> Some (make pairs false)

(* Case-insensitive matching exists only inside a modifier group such as (?i:...). ECMA-262 defines it through a
   Canonicalize function. Two characters match when they canonicalize to the same character. The function differs
   between the two grammars. With the u flag it is Unicode simple case folding. With no flag it is the single
   character uppercase mapping of a UTF-16 code unit, which never maps a character outside ASCII into ASCII.

   The parser applies the equivalence to every literal and class as it reads them, so both back ends match a
   case-insensitive group with plain classes. Only a backreference needs the function when matching.

   The folding and the uppercase mapping come from the tables of the ucd library. *)

(* The characters of the Basic Multilingual Plane whose uppercase form is more than one character. canonicalize
    leaves them alone. *)
let multi_upper =
  make
    [|
      0xDF; 0xDF; 0x149; 0x149; 0x1F0; 0x1F0; 0x390; 0x390; 0x3B0; 0x3B0; 0x587; 0x587; 0x1E96; 0x1E9A; 0x1F50; 0x1F50;
      0x1F52; 0x1F52; 0x1F54; 0x1F54; 0x1F56; 0x1F56; 0x1F80; 0x1FAF; 0x1FB2; 0x1FB4; 0x1FB6; 0x1FB7; 0x1FBC; 0x1FBC;
      0x1FC2; 0x1FC4; 0x1FC6; 0x1FC7; 0x1FCC; 0x1FCC; 0x1FD2; 0x1FD3; 0x1FD6; 0x1FD7; 0x1FE2; 0x1FE4; 0x1FE6; 0x1FE7;
      0x1FF2; 0x1FF4; 0x1FF6; 0x1FF7; 0x1FFC; 0x1FFC; 0xFB00; 0xFB06; 0xFB13; 0xFB17;
    |]
    false
[@@ocamlformat "disable"]

(** The Canonicalize function of ECMA-262 for case-insensitive matching. It does not allocate. *)
let canonicalize r unicode_mode =
  if unicode_mode then (* Every character of a simple case folding class folds to the same character. *)
    Ucd.fold r
  else if
    (* A character beyond the Basic Multilingual Plane is two code units, and neither has an uppercase form. *)
    r > 0xFFFF || contains multi_upper r
  then r
  else
    let upper = Ucd.to_upper r in
    if (r >= 128 && upper < 128) || upper > 0xFFFF then r else upper

type fold_table = { points : int array; classes : int array array }
(** The characters that are equivalent to some other character, in ascending order, each with all the members of its
    equivalence class. *)

(* Builds the table from the classes, each the list of its members by the character they canonicalize to. *)
let new_fold_table (classes : (int, int list) Hashtbl.t) =
  let by_member = Hashtbl.create 4096 in
  let points = ref [] in
  Hashtbl.iter
    (fun _ members ->
      match members with
      | [] | [ _ ] -> ()
      | _ ->
          let class_ = Array.of_list (List.rev members) in
          Array.iter
            (fun member ->
              points := member :: !points;
              Hashtbl.replace by_member member class_)
            class_)
    classes;
  let points = Array.of_list !points in
  Array.sort compare_int points;
  { points; classes = Array.map (Hashtbl.find by_member) points }

let add_member classes key member =
  Hashtbl.replace classes key
    (member :: (match Hashtbl.find_opt classes key with Some members -> members | None -> []))

let build_fold_table unicode_mode =
  let classes = Hashtbl.create 4096 in
  if unicode_mode then
    (* A class is a character that others fold to, with those others. *)
    Array.iter
      (fun r ->
        let key = Ucd.fold r in
        if not (Hashtbl.mem classes key) then Hashtbl.replace classes key [ key ];
        add_member classes key r)
      (Ucd.folding ())
  else
    for r = 0 to 0xFFFF do
      add_member classes (canonicalize r false) r
    done;
  new_fold_table classes

let unicode_fold_table : fold_table option Atomic.t = Atomic.make None
let legacy_fold_table : fold_table option Atomic.t = Atomic.make None

(** Returns the table of the grammar in use. It is built on first use, which only a pattern with a case-insensitive
    group reaches. *)
let fold_classes unicode_mode =
  let slot = if unicode_mode then unicode_fold_table else legacy_fold_table in
  match Atomic.get slot with
  | Some table -> table
  | None ->
      let table = build_fold_table unicode_mode in
      Atomic.set slot (Some table);
      table

(* The first index of [points] whose element is at least [lo]. *)
let rec first_at_least points lo from until =
  if from >= until then from
  else
    let mid = (from + until) lsr 1 in
    if points.(mid) >= lo then first_at_least points lo from mid else first_at_least points lo (mid + 1) until

(** Returns the set of every character equivalent to some member of the set. *)
let fold_closure_counted set unicode_mode =
  let table = fold_classes unicode_mode in
  let extra = ref [] in
  let variants = ref 0 in
  let i = ref 0 in
  while !i < Array.length set.ranges do
    let lo = set.ranges.(!i) and hi = set.ranges.(!i + 1) in
    let k = ref (first_at_least table.points lo 0 (Array.length table.points)) in
    while !k < Array.length table.points && table.points.(!k) <= hi do
      incr variants;
      Array.iter
        (fun member -> if not (contains set member) then extra := member :: member :: !extra)
        table.classes.(!k);
      incr k
    done;
    i := !i + 2
  done;
  ((if !extra = [] then set else make (Array.append (Array.of_list !extra) set.ranges) false), !variants)

let fold_closure set unicode_mode = fst (fold_closure_counted set unicode_mode)

(* With the u flag and case-insensitive matching, \w and \b also count the two characters that fold to an ASCII
   letter (U+017F, the long s, and U+212A, the Kelvin sign) as word characters. *)
let fold_word_pairs = [| 0x30; 0x39; 0x41; 0x5A; 0x5F; 0x5F; 0x61; 0x7A; 0x17F; 0x17F; 0x212A; 0x212A |]
let fold_word_set = make fold_word_pairs false
let not_fold_word_set = make fold_word_pairs true
let is_fold_word_rune r = (r < 128 && is_word_byte r) || r = 0x17F || r = 0x212A