corvus_json_schema 0.1.3 → 0.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. checksums.yaml +4 -4
  2. data/Cargo.lock +2 -12
  3. data/README.md +10 -1
  4. data/THIRD-PARTY-NOTICES.txt +95 -0
  5. data/VERSIONHISTORY.md +24 -0
  6. data/ext/corvus_json_schema/Cargo.toml +2 -2
  7. data/ext/corvus_json_schema/clippy.toml +3 -4
  8. data/ext/corvus_json_schema/src/lib.rs +42 -19
  9. data/ext/corvus_json_schema/vendor/corvus-json-schema/Cargo.toml +9 -4
  10. data/ext/corvus_json_schema/vendor/corvus-json-schema/README.md +87 -4
  11. data/ext/corvus_json_schema/vendor/corvus-json-schema/THIRD-PARTY-NOTICES.txt +93 -0
  12. data/ext/corvus_json_schema/vendor/corvus-json-schema/src/compiler.rs +88 -96
  13. data/ext/corvus_json_schema/vendor/corvus-json-schema/src/document.rs +2 -2
  14. data/ext/corvus_json_schema/vendor/corvus-json-schema/src/ecmaregex/charset.rs +271 -0
  15. data/ext/corvus_json_schema/vendor/corvus-json-schema/src/ecmaregex/compile.rs +941 -0
  16. data/ext/corvus_json_schema/vendor/corvus-json-schema/src/ecmaregex/dfa.rs +366 -0
  17. data/ext/corvus_json_schema/vendor/corvus-json-schema/src/ecmaregex/fold.rs +138 -0
  18. data/ext/corvus_json_schema/vendor/corvus-json-schema/src/ecmaregex/mod.rs +329 -0
  19. data/ext/corvus_json_schema/vendor/corvus-json-schema/src/ecmaregex/parse.rs +1128 -0
  20. data/ext/corvus_json_schema/vendor/corvus-json-schema/src/ecmaregex/re2.rs +129 -0
  21. data/ext/corvus_json_schema/vendor/corvus-json-schema/src/ecmaregex/tables_gen.rs +6685 -0
  22. data/ext/corvus_json_schema/vendor/corvus-json-schema/src/ecmaregex/tests.rs +1672 -0
  23. data/ext/corvus_json_schema/vendor/corvus-json-schema/src/ecmaregex/ucd.rs +309 -0
  24. data/ext/corvus_json_schema/vendor/corvus-json-schema/src/ecmaregex/units.rs +165 -0
  25. data/ext/corvus_json_schema/vendor/corvus-json-schema/src/ecmaregex/vm.rs +981 -0
  26. data/ext/corvus_json_schema/vendor/corvus-json-schema/src/eval/plan/fused.rs +13 -9
  27. data/ext/corvus_json_schema/vendor/corvus-json-schema/src/eval/plan.rs +12 -11
  28. data/ext/corvus_json_schema/vendor/corvus-json-schema/src/eval.rs +105 -73
  29. data/ext/corvus_json_schema/vendor/corvus-json-schema/src/formats.rs +95 -1
  30. data/ext/corvus_json_schema/vendor/corvus-json-schema/src/lib.rs +165 -30
  31. data/ext/corvus_json_schema/vendor/corvus-json-schema/src/loader.rs +19 -21
  32. data/ext/corvus_json_schema/vendor/corvus-json-schema/src/numbers.rs +1 -1
  33. data/ext/corvus_json_schema/vendor/corvus-json-schema/src/options.rs +199 -4
  34. data/ext/corvus_json_schema/vendor/corvus-json-schema/src/pattern.rs +758 -79
  35. data/ext/corvus_json_schema/vendor/corvus-json-schema/src/uri.rs +4 -4
  36. data/lib/corvus_json_schema/version.rb +1 -1
  37. data/lib/corvus_json_schema.rb +13 -3
  38. metadata +16 -2
@@ -272,11 +272,9 @@ impl<'l, 'o> SchemaCompiler<'l, 'o> {
272
272
  };
273
273
  let legacy = dialect.is_legacy();
274
274
 
275
- if legacy {
276
- if let Some(Value::String(r)) = e.get("$ref") {
277
- // In draft 7 and earlier, $ref replaces every sibling keyword.
278
- return self.compile_ref(id, target, r);
279
- }
275
+ if legacy && let Some(Value::String(r)) = e.get("$ref") {
276
+ // In draft 7 and earlier, $ref replaces every sibling keyword.
277
+ return self.compile_ref(id, target, r);
280
278
  }
281
279
 
282
280
  let applicator = legacy || voc & vocab::APPLICATOR != 0;
@@ -295,15 +293,15 @@ impl<'l, 'o> SchemaCompiler<'l, 'o> {
295
293
  if let Some(Value::String(r)) = e.get("$ref") {
296
294
  self.compile_ref(id, target, r)?;
297
295
  }
298
- if dialect >= Dialect::Draft202012 {
299
- if let Some(Value::String(r)) = e.get("$dynamicRef") {
300
- self.compile_dynamic_ref(id, target, r, false)?;
301
- }
296
+ if dialect >= Dialect::Draft202012
297
+ && let Some(Value::String(r)) = e.get("$dynamicRef")
298
+ {
299
+ self.compile_dynamic_ref(id, target, r, false)?;
302
300
  }
303
- if dialect == Dialect::Draft201909 {
304
- if let Some(Value::String(r)) = e.get("$recursiveRef") {
305
- self.compile_dynamic_ref(id, target, r, true)?;
306
- }
301
+ if dialect == Dialect::Draft201909
302
+ && let Some(Value::String(r)) = e.get("$recursiveRef")
303
+ {
304
+ self.compile_dynamic_ref(id, target, r, true)?;
307
305
  }
308
306
 
309
307
  // Applicators.
@@ -321,18 +319,18 @@ impl<'l, 'o> SchemaCompiler<'l, 'o> {
321
319
  let c = self.child(target, v, "/not");
322
320
  self.nodes[id as usize].not = Some(c);
323
321
  }
324
- if dialect >= Dialect::Draft7 {
325
- if let Some(v) = e.get("if") {
326
- let c = self.child(target, v, "/if");
327
- self.nodes[id as usize].if_ = Some(c);
328
- if let Some(v) = e.get("then") {
329
- let c = self.child(target, v, "/then");
330
- self.nodes[id as usize].then = Some(c);
331
- }
332
- if let Some(v) = e.get("else") {
333
- let c = self.child(target, v, "/else");
334
- self.nodes[id as usize].else_ = Some(c);
335
- }
322
+ if dialect >= Dialect::Draft7
323
+ && let Some(v) = e.get("if")
324
+ {
325
+ let c = self.child(target, v, "/if");
326
+ self.nodes[id as usize].if_ = Some(c);
327
+ if let Some(v) = e.get("then") {
328
+ let c = self.child(target, v, "/then");
329
+ self.nodes[id as usize].then = Some(c);
330
+ }
331
+ if let Some(v) = e.get("else") {
332
+ let c = self.child(target, v, "/else");
333
+ self.nodes[id as usize].else_ = Some(c);
336
334
  }
337
335
  }
338
336
  if let Some(Value::Object(props)) = e.get("properties") {
@@ -346,11 +344,7 @@ impl<'l, 'o> SchemaCompiler<'l, 'o> {
346
344
  if let Some(Value::Object(pp)) = e.get("patternProperties") {
347
345
  let mut list = Vec::with_capacity(pp.len());
348
346
  for (pattern, v) in pp {
349
- let compiled = crate::pattern::compile(pattern).ok_or_else(|| {
350
- SchemaCompilationError::new(format!(
351
- "Invalid regular expression '{pattern}' in patternProperties."
352
- ))
353
- })?;
347
+ let compiled = crate::pattern::compile(pattern).map_err(|e| e.of(pattern, "patternProperties"))?;
354
348
  let c = self.child(target, v, &format!("/patternProperties/{}", escape_pointer_token(pattern)));
355
349
  list.push(PatternProperty { pattern: compiled, node: c });
356
350
  }
@@ -384,11 +378,11 @@ impl<'l, 'o> SchemaCompiler<'l, 'o> {
384
378
  self.nodes[id as usize].prefix_items =
385
379
  self.child_array(target, e.get("prefixItems"), "prefixItems");
386
380
  }
387
- if let Some(v) = e.get("items") {
388
- if !v.is_array() {
389
- let c = self.child(target, v, &kw("items"));
390
- self.nodes[id as usize].items = Some(c);
391
- }
381
+ if let Some(v) = e.get("items")
382
+ && !v.is_array()
383
+ {
384
+ let c = self.child(target, v, &kw("items"));
385
+ self.nodes[id as usize].items = Some(c);
392
386
  }
393
387
  } else if let Some(v) = e.get("items") {
394
388
  if v.is_array() {
@@ -431,10 +425,10 @@ impl<'l, 'o> SchemaCompiler<'l, 'o> {
431
425
  n.type_mask = mask;
432
426
  n.has_type = true;
433
427
  }
434
- if dialect >= Dialect::Draft6 {
435
- if let Some(c) = e.get("const") {
436
- n.const_value = Some(c.clone());
437
- }
428
+ if dialect >= Dialect::Draft6
429
+ && let Some(c) = e.get("const")
430
+ {
431
+ n.const_value = Some(c.clone());
438
432
  }
439
433
  if let Some(Value::Array(values)) = e.get("enum") {
440
434
  n.enum_values = Some(values.clone());
@@ -450,18 +444,18 @@ impl<'l, 'o> SchemaCompiler<'l, 'o> {
450
444
  n.required_list = Some(list);
451
445
  n.required = Some(dedup);
452
446
  }
453
- if dialect >= Dialect::Draft201909 {
454
- if let Some(Value::Object(dr)) = e.get("dependentRequired") {
455
- let deps = dependencies.get_or_insert_with(Vec::new);
456
- for (name, v) in dr {
457
- if let Value::Array(list) = v {
458
- deps.push(DependencyEntry {
459
- keyword: DependencyKeyword::DependentRequired,
460
- name: name.clone(),
461
- required: Some(strings(list)),
462
- schema: None,
463
- });
464
- }
447
+ if dialect >= Dialect::Draft201909
448
+ && let Some(Value::Object(dr)) = e.get("dependentRequired")
449
+ {
450
+ let deps = dependencies.get_or_insert_with(Vec::new);
451
+ for (name, v) in dr {
452
+ if let Value::Array(list) = v {
453
+ deps.push(DependencyEntry {
454
+ keyword: DependencyKeyword::DependentRequired,
455
+ name: name.clone(),
456
+ required: Some(strings(list)),
457
+ schema: None,
458
+ });
465
459
  }
466
460
  }
467
461
  }
@@ -473,9 +467,7 @@ impl<'l, 'o> SchemaCompiler<'l, 'o> {
473
467
  n.min_length = get_uint(e.get("minLength"));
474
468
  n.max_length = get_uint(e.get("maxLength"));
475
469
  if let Some(Value::String(p)) = e.get("pattern") {
476
- n.pattern = Some(crate::pattern::compile(p).ok_or_else(|| {
477
- SchemaCompilationError::new(format!("Invalid regular expression '{p}' in pattern."))
478
- })?);
470
+ n.pattern = Some(crate::pattern::compile(p).map_err(|e| e.of(p, "pattern"))?);
479
471
  }
480
472
  if let Some(Value::Number(m)) = e.get("multipleOf") {
481
473
  n.multiple_of = Some(m.clone());
@@ -577,17 +569,17 @@ impl<'l, 'o> SchemaCompiler<'l, 'o> {
577
569
  }
578
570
  }
579
571
  }
580
- if dialect >= Dialect::Draft201909 {
581
- if let Some(Value::Object(ds)) = e.get("dependentSchemas") {
582
- for (name, v) in ds {
583
- let c = self.child(target, v, &format!("/dependentSchemas/{}", escape_pointer_token(name)));
584
- out.push(DependencyEntry {
585
- keyword: DependencyKeyword::DependentSchemas,
586
- name: name.clone(),
587
- required: None,
588
- schema: Some(c),
589
- });
590
- }
572
+ if dialect >= Dialect::Draft201909
573
+ && let Some(Value::Object(ds)) = e.get("dependentSchemas")
574
+ {
575
+ for (name, v) in ds {
576
+ let c = self.child(target, v, &format!("/dependentSchemas/{}", escape_pointer_token(name)));
577
+ out.push(DependencyEntry {
578
+ keyword: DependencyKeyword::DependentSchemas,
579
+ name: name.clone(),
580
+ required: None,
581
+ schema: Some(c),
582
+ });
591
583
  }
592
584
  }
593
585
  out
@@ -906,15 +898,15 @@ impl<'l, 'o> SchemaCompiler<'l, 'o> {
906
898
 
907
899
  fn compute_discriminators(&mut self) {
908
900
  for i in 0..self.nodes.len() {
909
- if let Some(branches) = self.nodes[i].one_of.clone() {
910
- if branches.len() > 1 {
911
- self.nodes[i].one_of_discriminator = self.build_discriminator(&branches).map(Box::new);
912
- }
901
+ if let Some(branches) = self.nodes[i].one_of.clone()
902
+ && branches.len() > 1
903
+ {
904
+ self.nodes[i].one_of_discriminator = self.build_discriminator(&branches).map(Box::new);
913
905
  }
914
- if let Some(branches) = self.nodes[i].any_of.clone() {
915
- if branches.len() > 1 {
916
- self.nodes[i].any_of_discriminator = self.build_discriminator(&branches).map(Box::new);
917
- }
906
+ if let Some(branches) = self.nodes[i].any_of.clone()
907
+ && branches.len() > 1
908
+ {
909
+ self.nodes[i].any_of_discriminator = self.build_discriminator(&branches).map(Box::new);
918
910
  }
919
911
  }
920
912
  }
@@ -986,32 +978,32 @@ impl<'l, 'o> SchemaCompiler<'l, 'o> {
986
978
  return Class::Wildcard;
987
979
  };
988
980
  let p = self.effective_node(child);
989
- if let Some(c) = &p.const_value {
990
- if let Some(v) = primitive(c) {
991
- return Class::Positive(vec![v]);
992
- }
981
+ if let Some(c) = &p.const_value
982
+ && let Some(v) = primitive(c)
983
+ {
984
+ return Class::Positive(vec![v]);
993
985
  }
994
- if let Some(values) = &p.enum_values {
995
- if !values.is_empty() {
996
- let prims: Vec<DiscriminatorValue> = values.iter().filter_map(primitive).collect();
997
- if prims.len() == values.len() {
998
- return Class::Positive(prims);
999
- }
986
+ if let Some(values) = &p.enum_values
987
+ && !values.is_empty()
988
+ {
989
+ let prims: Vec<DiscriminatorValue> = values.iter().filter_map(primitive).collect();
990
+ if prims.len() == values.len() {
991
+ return Class::Positive(prims);
1000
992
  }
1001
993
  }
1002
- if let Some(not) = p.not {
1003
- if p.has_type && p.type_mask == type_mask::STRING {
1004
- let not = self.effective_node(not);
1005
- if let Some(values) = &not.enum_values {
1006
- if values.iter().all(Value::is_string)
1007
- && !not.has_type
1008
- && not.const_value.is_none()
1009
- && !not.has_string_keywords()
1010
- && !not.has_in_place_applicators()
1011
- {
1012
- return Class::Negative(values.iter().filter_map(primitive).collect());
1013
- }
1014
- }
994
+ if let Some(not) = p.not
995
+ && p.has_type
996
+ && p.type_mask == type_mask::STRING
997
+ {
998
+ let not = self.effective_node(not);
999
+ if let Some(values) = &not.enum_values
1000
+ && values.iter().all(Value::is_string)
1001
+ && !not.has_type
1002
+ && not.const_value.is_none()
1003
+ && !not.has_string_keywords()
1004
+ && !not.has_in_place_applicators()
1005
+ {
1006
+ return Class::Negative(values.iter().filter_map(primitive).collect());
1015
1007
  }
1016
1008
  }
1017
1009
  Class::Wildcard
@@ -552,9 +552,9 @@ impl<'s> Parser<'s> {
552
552
  return;
553
553
  }
554
554
  let mut kept: Vec<Node> = Vec::with_capacity(pairs.len());
555
- for p in pairs.chunks_exact(2) {
555
+ for p in pairs.as_chunks::<2>().0 {
556
556
  let name = self.key_str(&p[0]);
557
- match kept.chunks_exact(2).position(|q| self.key_str(&q[0]) == name) {
557
+ match kept.as_chunks::<2>().0.iter().position(|q| self.key_str(&q[0]) == name) {
558
558
  Some(at) => kept[2 * at + 1] = p[1],
559
559
  None => kept.extend_from_slice(p),
560
560
  }
@@ -0,0 +1,271 @@
1
+ //! Sets of code points, the classes ECMA-262 defines, and the sets of the Unicode properties (`charset.go`).
2
+
3
+ use std::collections::HashMap;
4
+ use std::sync::{Arc, LazyLock, Mutex};
5
+
6
+ use super::Rune;
7
+ use super::ucd;
8
+
9
+ /// The last Unicode code point.
10
+ pub(crate) const MAX_RUNE: Rune = ucd::MAX_RUNE;
11
+
12
+ /// An immutable set of code points. The ranges are sorted, inclusive, and neither overlap nor touch. The two words
13
+ /// of `ascii` repeat the membership of the code points below 128 so that the common case is one bit test.
14
+ #[derive(Debug, PartialEq, Eq)]
15
+ pub(crate) struct CharSet {
16
+ pub(crate) ranges: Vec<Rune>,
17
+ pub(crate) ascii: [u64; 2],
18
+ }
19
+
20
+ impl CharSet {
21
+ /// Builds a set from inclusive pairs in any order, which may overlap. With `negate` it builds the complement.
22
+ pub(crate) fn new(pairs: &[Rune], negate: bool) -> CharSet {
23
+ let mut idx: Vec<usize> = (0..pairs.len() / 2).collect();
24
+ idx.sort_by_key(|&i| pairs[2 * i]);
25
+ let mut merged: Vec<Rune> = Vec::with_capacity(pairs.len());
26
+ for i in idx {
27
+ let (lo, hi) = (pairs[2 * i], pairs[2 * i + 1]);
28
+ if let Some(last) = merged.last_mut()
29
+ && lo <= *last + 1
30
+ {
31
+ if hi > *last {
32
+ *last = hi;
33
+ }
34
+ continue;
35
+ }
36
+ merged.push(lo);
37
+ merged.push(hi);
38
+ }
39
+ if negate {
40
+ let mut inverse = Vec::with_capacity(merged.len() + 2);
41
+ let mut next: Rune = 0;
42
+ for &[lo, hi] in merged.as_chunks::<2>().0 {
43
+ if lo > next {
44
+ inverse.push(next);
45
+ inverse.push(lo - 1);
46
+ }
47
+ next = hi + 1;
48
+ }
49
+ if next <= MAX_RUNE {
50
+ inverse.push(next);
51
+ inverse.push(MAX_RUNE);
52
+ }
53
+ merged = inverse;
54
+ }
55
+ let mut ascii = [0u64; 2];
56
+ for &[lo, hi] in merged.as_chunks::<2>().0 {
57
+ if lo >= 128 {
58
+ break;
59
+ }
60
+ let mut c = lo;
61
+ while c <= hi && c < 128 {
62
+ ascii[(c >> 6) as usize] |= 1 << (c & 63);
63
+ c += 1;
64
+ }
65
+ }
66
+ CharSet { ranges: merged, ascii }
67
+ }
68
+
69
+ /// [`CharSet::new`], shared.
70
+ pub(crate) fn shared(pairs: &[Rune], negate: bool) -> Arc<CharSet> {
71
+ Arc::new(CharSet::new(pairs, negate))
72
+ }
73
+
74
+ /// Whether the set holds the ASCII character.
75
+ #[inline(always)]
76
+ pub(crate) fn has_ascii(&self, c: u8) -> bool {
77
+ (self.ascii[(c >> 6) as usize & 1] >> (c & 63)) & 1 != 0
78
+ }
79
+
80
+ /// Whether the set holds the code point.
81
+ #[inline]
82
+ pub(crate) fn contains(&self, r: Rune) -> bool {
83
+ if (0..128).contains(&r) {
84
+ return (self.ascii[(r >> 6) as usize] >> (r & 63)) & 1 != 0;
85
+ }
86
+ let ranges = &self.ranges[..];
87
+ let (mut lo, mut hi) = (0, ranges.len() / 2);
88
+ while lo < hi {
89
+ let mid = (lo + hi) >> 1;
90
+ if r > ranges[2 * mid + 1] {
91
+ lo = mid + 1;
92
+ } else if r < ranges[2 * mid] {
93
+ hi = mid;
94
+ } else {
95
+ return true;
96
+ }
97
+ }
98
+ false
99
+ }
100
+
101
+ /// Whether the set holds no code point.
102
+ pub(crate) fn is_empty(&self) -> bool {
103
+ self.ranges.is_empty()
104
+ }
105
+ }
106
+
107
+ // The classes ECMA-262 defines. \d and \w are ASCII only, \s is WhiteSpace and LineTerminator, and . is everything
108
+ // but the four line terminators.
109
+ pub(crate) const DIGIT_PAIRS: &[Rune] = &[0x30, 0x39];
110
+ pub(crate) const WORD_PAIRS: &[Rune] = &[0x30, 0x39, 0x41, 0x5A, 0x5F, 0x5F, 0x61, 0x7A];
111
+ pub(crate) const SPACE_PAIRS: &[Rune] = &[
112
+ 0x9, 0xD, 0x20, 0x20, 0xA0, 0xA0, 0x1680, 0x1680, 0x2000, 0x200A, 0x2028, 0x2029, 0x202F, 0x202F, 0x205F, 0x205F,
113
+ 0x3000, 0x3000, 0xFEFF, 0xFEFF,
114
+ ];
115
+ pub(crate) const LINE_TERMINATOR_PAIRS: &[Rune] = &[0xA, 0xA, 0xD, 0xD, 0x2028, 0x2029];
116
+
117
+ pub(crate) static DOT_SET: LazyLock<Arc<CharSet>> = LazyLock::new(|| CharSet::shared(LINE_TERMINATOR_PAIRS, true));
118
+
119
+ /// Whether a byte is an ECMA-262 word character. Every word character is ASCII, so a word boundary can be decided
120
+ /// from the bytes on either side of a position.
121
+ #[inline(always)]
122
+ pub(crate) fn is_word_byte(b: u8) -> bool {
123
+ b.is_ascii_alphanumeric() || b == b'_'
124
+ }
125
+
126
+ /// The short name of a General_Category value, from its long name, an alias or the short name itself.
127
+ fn category_name(value: &str) -> Option<&'static str> {
128
+ Some(match value {
129
+ "Letter" | "L" => "L",
130
+ "Lowercase_Letter" | "Ll" => "Ll",
131
+ "Uppercase_Letter" | "Lu" => "Lu",
132
+ "Titlecase_Letter" | "Lt" => "Lt",
133
+ "Cased_Letter" | "LC" => "LC",
134
+ "Modifier_Letter" | "Lm" => "Lm",
135
+ "Other_Letter" | "Lo" => "Lo",
136
+ "Mark" | "Combining_Mark" | "M" => "M",
137
+ "Nonspacing_Mark" | "Mn" => "Mn",
138
+ "Spacing_Mark" | "Mc" => "Mc",
139
+ "Enclosing_Mark" | "Me" => "Me",
140
+ "Number" | "N" => "N",
141
+ "Decimal_Number" | "digit" | "Nd" => "Nd",
142
+ "Letter_Number" | "Nl" => "Nl",
143
+ "Other_Number" | "No" => "No",
144
+ "Punctuation" | "punct" | "P" => "P",
145
+ "Connector_Punctuation" | "Pc" => "Pc",
146
+ "Dash_Punctuation" | "Pd" => "Pd",
147
+ "Open_Punctuation" | "Ps" => "Ps",
148
+ "Close_Punctuation" | "Pe" => "Pe",
149
+ "Initial_Punctuation" | "Pi" => "Pi",
150
+ "Final_Punctuation" | "Pf" => "Pf",
151
+ "Other_Punctuation" | "Po" => "Po",
152
+ "Symbol" | "S" => "S",
153
+ "Math_Symbol" | "Sm" => "Sm",
154
+ "Currency_Symbol" | "Sc" => "Sc",
155
+ "Modifier_Symbol" | "Sk" => "Sk",
156
+ "Other_Symbol" | "So" => "So",
157
+ "Separator" | "Z" => "Z",
158
+ "Space_Separator" | "Zs" => "Zs",
159
+ "Line_Separator" | "Zl" => "Zl",
160
+ "Paragraph_Separator" | "Zp" => "Zp",
161
+ "Other" | "C" => "C",
162
+ "Control" | "cntrl" | "Cc" => "Cc",
163
+ "Format" | "Cf" => "Cf",
164
+ "Surrogate" | "Cs" => "Cs",
165
+ "Private_Use" | "Co" => "Co",
166
+ "Unassigned" | "Cn" => "Cn",
167
+ _ => return None,
168
+ })
169
+ }
170
+
171
+ /// The canonical name of a binary property ECMA-262 lists, from the name or an alias.
172
+ fn binary_name(name: &str) -> Option<&'static str> {
173
+ Some(match name {
174
+ "ASCII" => "ASCII",
175
+ "ASCII_Hex_Digit" | "AHex" => "ASCII_Hex_Digit",
176
+ "Alphabetic" | "Alpha" => "Alphabetic",
177
+ "Any" => "Any",
178
+ "Assigned" => "Assigned",
179
+ "Bidi_Control" | "Bidi_C" => "Bidi_Control",
180
+ "Bidi_Mirrored" | "Bidi_M" => "Bidi_Mirrored",
181
+ "Case_Ignorable" | "CI" => "Case_Ignorable",
182
+ "Cased" => "Cased",
183
+ "Changes_When_Casefolded" | "CWCF" => "Changes_When_Casefolded",
184
+ "Changes_When_Casemapped" | "CWCM" => "Changes_When_Casemapped",
185
+ "Changes_When_Lowercased" | "CWL" => "Changes_When_Lowercased",
186
+ "Changes_When_NFKC_Casefolded" | "CWKCF" => "Changes_When_NFKC_Casefolded",
187
+ "Changes_When_Titlecased" | "CWT" => "Changes_When_Titlecased",
188
+ "Changes_When_Uppercased" | "CWU" => "Changes_When_Uppercased",
189
+ "Dash" => "Dash",
190
+ "Default_Ignorable_Code_Point" | "DI" => "Default_Ignorable_Code_Point",
191
+ "Deprecated" | "Dep" => "Deprecated",
192
+ "Diacritic" | "Dia" => "Diacritic",
193
+ "Emoji" => "Emoji",
194
+ "Emoji_Component" | "EComp" => "Emoji_Component",
195
+ "Emoji_Modifier" | "EMod" => "Emoji_Modifier",
196
+ "Emoji_Modifier_Base" | "EBase" => "Emoji_Modifier_Base",
197
+ "Emoji_Presentation" | "EPres" => "Emoji_Presentation",
198
+ "Extended_Pictographic" | "ExtPict" => "Extended_Pictographic",
199
+ "Extender" | "Ext" => "Extender",
200
+ "Grapheme_Base" | "Gr_Base" => "Grapheme_Base",
201
+ "Grapheme_Extend" | "Gr_Ext" => "Grapheme_Extend",
202
+ "Hex_Digit" | "Hex" => "Hex_Digit",
203
+ "IDS_Binary_Operator" | "IDSB" => "IDS_Binary_Operator",
204
+ "IDS_Trinary_Operator" | "IDST" => "IDS_Trinary_Operator",
205
+ "ID_Continue" | "IDC" => "ID_Continue",
206
+ "ID_Start" | "IDS" => "ID_Start",
207
+ "Ideographic" | "Ideo" => "Ideographic",
208
+ "Join_Control" | "Join_C" => "Join_Control",
209
+ "Logical_Order_Exception" | "LOE" => "Logical_Order_Exception",
210
+ "Lowercase" | "Lower" => "Lowercase",
211
+ "Math" => "Math",
212
+ "Noncharacter_Code_Point" | "NChar" => "Noncharacter_Code_Point",
213
+ "Pattern_Syntax" | "Pat_Syn" => "Pattern_Syntax",
214
+ "Pattern_White_Space" | "Pat_WS" => "Pattern_White_Space",
215
+ "Quotation_Mark" | "QMark" => "Quotation_Mark",
216
+ "Radical" => "Radical",
217
+ "Regional_Indicator" | "RI" => "Regional_Indicator",
218
+ "Sentence_Terminal" | "STerm" => "Sentence_Terminal",
219
+ "Soft_Dotted" | "SD" => "Soft_Dotted",
220
+ "Terminal_Punctuation" | "Term" => "Terminal_Punctuation",
221
+ "Unified_Ideograph" | "UIdeo" => "Unified_Ideograph",
222
+ "Uppercase" | "Upper" => "Uppercase",
223
+ "Variation_Selector" | "VS" => "Variation_Selector",
224
+ "White_Space" | "space" => "White_Space",
225
+ "XID_Continue" | "XIDC" => "XID_Continue",
226
+ "XID_Start" | "XIDS" => "XID_Start",
227
+ _ => return None,
228
+ })
229
+ }
230
+
231
+ /// The sets already built for property expressions. A property set can have hundreds of ranges, and schemas tend to
232
+ /// repeat the same few. Only an expression ECMA-262 defines is kept, and those are a fixed few hundred, so the
233
+ /// cache is bounded.
234
+ static PROPERTY_CACHE: LazyLock<Mutex<HashMap<String, Arc<CharSet>>>> = LazyLock::new(|| Mutex::new(HashMap::new()));
235
+
236
+ /// Resolves the expression between the braces of `\p{...}` to its set. `None` for a name or value that ECMA-262
237
+ /// does not define.
238
+ pub(crate) fn property_set(expr: &str) -> Option<Arc<CharSet>> {
239
+ if let Some(cached) = PROPERTY_CACHE.lock().unwrap_or_else(|e| e.into_inner()).get(expr) {
240
+ return Some(cached.clone());
241
+ }
242
+ let pairs = property_pairs(expr)?;
243
+ let set = CharSet::shared(&pairs, false);
244
+ PROPERTY_CACHE.lock().unwrap_or_else(|e| e.into_inner()).insert(expr.to_string(), set.clone());
245
+ Some(set)
246
+ }
247
+
248
+ /// The ranges of a property expression. Every range comes from the tables of `ucd`, which hold one fixed version of
249
+ /// Unicode, so the answer does not depend on the toolchain.
250
+ fn property_pairs(expr: &str) -> Option<Vec<Rune>> {
251
+ if let Some((name, value)) = expr.split_once('=') {
252
+ return match name {
253
+ "General_Category" | "gc" => category_pairs(value),
254
+ "Script" | "sc" => ucd::script(value).map(|t| t.pairs()),
255
+ "Script_Extensions" | "scx" => ucd::script_extensions(value).map(|t| t.pairs()),
256
+ _ => None,
257
+ };
258
+ }
259
+ if let Some(pairs) = category_pairs(expr) {
260
+ return Some(pairs);
261
+ }
262
+ match binary_name(expr)? {
263
+ "ASCII" => Some(vec![0, 0x7F]),
264
+ "Any" => Some(vec![0, MAX_RUNE]),
265
+ canonical => ucd::binary(canonical).map(|t| t.pairs()),
266
+ }
267
+ }
268
+
269
+ fn category_pairs(value: &str) -> Option<Vec<Rune>> {
270
+ ucd::category(category_name(value)?).map(|t| t.pairs())
271
+ }