1mod tables {
9 include!("generated/unicode/unicode_property_tables.rs");
10}
11
12fn find(names: &str, name: &str) -> Option<usize> {
14 names
15 .split(';')
16 .position(|group| group.split(',').any(|alias| alias == name))
17}
18
19fn read_varint(data: &[u8], position: &mut usize) -> u32 {
20 let mut value = 0;
21 let mut shift = 0;
22 loop {
23 let byte = data[*position];
24 *position += 1;
25 value |= u32::from(byte & 0x7f) << shift;
26 if byte < 0x80 {
27 return value;
28 }
29 shift += 7;
30 }
31}
32
33#[derive(Clone, Debug)]
37pub struct PropertyRanges {
38 sets: &'static [u32],
39 mask: u64,
40 position: usize,
41 remaining: u32,
42 end: u32,
43}
44
45impl PropertyRanges {
46 fn new(sets: &'static [u32], mask: u64) -> Self {
47 Self {
48 sets,
49 mask,
50 position: 0,
51 remaining: 0,
52 end: 0,
53 }
54 }
55}
56
57impl Iterator for PropertyRanges {
58 type Item = (u32, u32);
59
60 fn next(&mut self) -> Option<(u32, u32)> {
61 while self.remaining == 0 {
62 if self.mask == 0 {
63 return None;
64 }
65 let set = self.mask.trailing_zeros() as usize;
66 self.mask &= self.mask - 1;
67 self.position = self.sets[set] as usize;
68 self.remaining = read_varint(&tables::RANGE_DATA, &mut self.position);
69 self.end = 0;
70 }
71 self.remaining -= 1;
72 let start = self.end + read_varint(&tables::RANGE_DATA, &mut self.position);
73 self.end = start + read_varint(&tables::RANGE_DATA, &mut self.position);
74 Some((start, self.end))
75 }
76}
77
78pub fn general_category(name: &str) -> Option<PropertyRanges> {
79 let mask = tables::GENERAL_CATEGORY_MASKS[find(tables::GENERAL_CATEGORY_NAMES, name)?];
80 Some(PropertyRanges::new(
81 &tables::GENERAL_CATEGORY_SETS,
82 u64::from(mask),
83 ))
84}
85
86pub fn script(name: &str, extensions: bool) -> Option<PropertyRanges> {
87 let sets = if extensions {
88 &tables::SCRIPT_EXTENSIONS_SETS
89 } else {
90 &tables::SCRIPT_SETS
91 };
92 let index = find(tables::SCRIPT_NAMES, name)?;
93 Some(PropertyRanges::new(std::slice::from_ref(&sets[index]), 1))
94}
95
96pub fn binary_property(name: &str) -> Option<PropertyRanges> {
97 let index = find(tables::BINARY_PROPERTY_NAMES, name)?;
98 Some(PropertyRanges::new(
99 std::slice::from_ref(&tables::BINARY_PROPERTY_SETS[index]),
100 1,
101 ))
102}
103
104pub fn sequence_property(name: &str, mut visit: impl FnMut(&[u32])) -> Option<()> {
107 let index = find(tables::SEQUENCE_PROPERTY_NAMES, name)?;
108 let sets = tables::SEQUENCE_PROPERTY_SETS
110 .get(index)
111 .map_or(&tables::SEQUENCE_PROPERTY_SETS[..], std::slice::from_ref);
112 let data = &tables::SEQUENCE_DATA;
113 for &offset in sets {
114 let mut position = offset as usize;
115 let mut sequence = [0_u32; 16];
116 let mut previous_len = 0;
117 for _ in 0..read_varint(data, &mut position) {
118 let len = usize::from(data[position]);
119 position += 1;
120 for index in 0..len {
121 let base = match index {
122 _ if index < previous_len => sequence[index],
123 0 => 0,
124 _ => sequence[index - 1],
125 };
126 let delta = read_varint(data, &mut position);
127 let delta = (delta >> 1) as i32 ^ -((delta & 1) as i32);
128 sequence[index] = base.wrapping_add_signed(delta);
129 }
130 previous_len = len;
131 visit(&sequence[..len]);
132 }
133 }
134 Some(())
135}
136
137#[cfg(test)]
138mod tests {
139 use std::collections::HashSet;
140
141 use super::{
142 PropertyRanges, binary_property, general_category, script, sequence_property, tables,
143 };
144
145 fn sequences(name: &str) -> Vec<Vec<u32>> {
146 let mut sequences = Vec::new();
147 sequence_property(name, |sequence| sequences.push(sequence.to_vec())).unwrap();
148 sequences
149 }
150
151 fn assert_valid_set(ranges: PropertyRanges) {
152 let mut previous_end = 0;
153 for (index, (start, end)) in ranges.enumerate() {
154 assert!(start < end && end <= 0x11_0000);
155 assert!(index == 0 || start >= previous_end);
156 previous_end = end;
157 }
158 }
159
160 fn names(table: &str) -> Vec<&str> {
161 table
162 .split(';')
163 .flat_map(|group| group.split(','))
164 .collect()
165 }
166
167 fn sorted(ranges: PropertyRanges) -> Vec<(u32, u32)> {
168 merged(ranges.collect())
169 }
170
171 fn merged(mut ranges: Vec<(u32, u32)>) -> Vec<(u32, u32)> {
172 let mut merged: Vec<(u32, u32)> = Vec::new();
173 ranges.sort_unstable();
174 for (start, end) in ranges {
175 match merged.last_mut() {
176 Some(last) if start <= last.1 => last.1 = last.1.max(end),
177 _ => merged.push((start, end)),
178 }
179 }
180 merged
181 }
182
183 #[test]
184 fn generated_property_catalog_matches_pinned_quickjs_shape() {
185 assert_eq!(tables::GENERAL_CATEGORY_NAMES.split(';').count(), 38);
186 assert_eq!(tables::SCRIPT_NAMES.split(';').count(), 176);
187 assert_eq!(tables::BINARY_PROPERTY_NAMES.split(';').count(), 55);
188 assert_eq!(names(tables::GENERAL_CATEGORY_NAMES).len(), 80);
189 assert_eq!(names(tables::SCRIPT_NAMES).len(), 354);
190 assert_eq!(names(tables::BINARY_PROPERTY_NAMES).len(), 102);
191
192 for name in names(tables::GENERAL_CATEGORY_NAMES) {
193 let ranges = general_category(name).unwrap();
194 if ranges.mask.count_ones() == 1 {
195 assert_valid_set(ranges);
196 }
197 }
198 for name in names(tables::SCRIPT_NAMES) {
199 assert_valid_set(script(name, false).unwrap());
200 assert_valid_set(script(name, true).unwrap());
201 }
202 for name in names(tables::BINARY_PROPERTY_NAMES) {
203 assert_valid_set(binary_property(name).unwrap());
204 }
205 assert_eq!(sorted(binary_property("Any").unwrap()), [(0, 0x11_0000)]);
207 let all = PropertyRanges::new(&tables::GENERAL_CATEGORY_SETS, (1 << 30) - 1);
208 assert_eq!(sorted(all.clone()), [(0, 0x11_0000)]);
209 assert_eq!(all.map(|(start, end)| end - start).sum::<u32>(), 0x11_0000);
210 }
211
212 #[test]
213 fn generated_sequence_property_catalog_matches_pinned_quickjs_shape() {
214 let names = names(tables::SEQUENCE_PROPERTY_NAMES);
215 assert_eq!(
216 names,
217 [
218 "Basic_Emoji",
219 "Emoji_Keycap_Sequence",
220 "RGI_Emoji_Modifier_Sequence",
221 "RGI_Emoji_Flag_Sequence",
222 "RGI_Emoji_Tag_Sequence",
223 "RGI_Emoji_ZWJ_Sequence",
224 "RGI_Emoji",
225 ]
226 );
227 let counts = names
228 .iter()
229 .map(|name| sequences(name).len())
230 .collect::<Vec<_>>();
231 assert_eq!(counts, [1400, 12, 670, 259, 3, 1614, 3958]);
232
233 let all = sequences("RGI_Emoji");
234 let mut unique = HashSet::with_capacity(all.len());
235 for sequence in &all {
236 assert!(!sequence.is_empty());
237 assert!(sequence.iter().all(|code_point| *code_point < 0x11_0000));
238 assert!(unique.insert(sequence.clone()), "duplicate {sequence:x?}");
239 }
240 assert_eq!(
241 sequences("Emoji_Keycap_Sequence")[0],
242 [0x23, 0xfe0f, 0x20e3]
243 );
244 assert!(sequence_property("basic_emoji", |_| {}).is_none());
245 }
246
247 #[test]
248 fn aliases_are_exact_and_share_the_expected_sets() {
249 let gc = |name| general_category(name).map(sorted);
250 assert_eq!(gc("Letter"), gc("L"));
251 assert_eq!(gc("Uppercase_Letter"), gc("Lu"));
252 assert!(gc("letter").is_none());
253 assert_eq!(gc("Nd").unwrap()[0], (0x30, 0x3a));
254
255 let sc = |name, extensions| script(name, extensions).map(sorted);
256 assert_eq!(sc("Latin", false), sc("Latn", false));
257 assert_eq!(sc("Hiragana", true), sc("Hira", true));
258 assert_ne!(sc("Hiragana", false), sc("Hiragana", true));
259
260 let binary = |name| binary_property(name).map(sorted);
261 assert_eq!(binary("ASCII_Hex_Digit"), binary("AHex"));
262 assert_eq!(binary("Emoji_Presentation"), binary("EPres"));
263 assert_eq!(binary("ASCII").unwrap(), [(0, 0x80)]);
264 assert!(binary("RGI_Emoji").is_none());
265 assert!(binary("ID_Compat_Math_Start").is_none());
266 }
267}