1use std::collections::BTreeSet;
18use std::fs;
19use std::io::{BufRead, BufReader};
20use std::iter::FusedIterator;
21use std::ops::Range;
22use std::path::Path;
23
24use thiserror::Error;
25
26use crate::config::DEFAULT_CHAR_DEF_BYTES;
27use crate::dic::category_type::CategoryType;
28use crate::prelude::*;
29
30#[derive(Error, Debug, Eq, PartialEq)]
32#[non_exhaustive]
33pub enum Error {
34 #[error("Invalid format at line {0}")]
35 InvalidFormat(usize),
36
37 #[error("Invalid type {1} at line {0}")]
38 InvalidCategoryType(usize, String),
39
40 #[error("Multiple definition for type {1} at line {0}")]
41 MultipleTypeDefinition(usize, String),
42
43 #[error("Invalid character {0:X} at line {1}")]
44 InvalidChar(u32, usize),
45}
46
47#[derive(Debug, Clone, PartialEq, Eq)]
48struct CatRange {
49 begin: u32,
50 end: u32,
51 categories: CategoryType,
52}
53
54#[derive(Debug, Clone)]
56pub struct CharacterCategory {
57 boundaries: Vec<u32>,
65
66 categories: Vec<CategoryType>,
71}
72
73impl Default for CharacterCategory {
74 fn default() -> Self {
75 CharacterCategory {
76 boundaries: Vec::new(),
77 categories: vec![CategoryType::DEFAULT],
78 }
79 }
80}
81
82impl CharacterCategory {
83 pub fn from_file(path: &Path) -> SudachiResult<CharacterCategory> {
85 let reader = BufReader::new(fs::File::open(path)?);
86 Self::from_reader(reader)
87 }
88
89 pub fn from_bytes(bytes: &[u8]) -> SudachiResult<CharacterCategory> {
90 let reader = BufReader::new(bytes);
91 Self::from_reader(reader)
92 }
93
94 pub fn from_reader<T: BufRead>(data: T) -> SudachiResult<CharacterCategory> {
95 let ranges = Self::read_character_definition(data)?;
96 Ok(Self::compile(&ranges))
97 }
98
99 pub fn from_embedded() -> CharacterCategory {
100 Self::from_bytes(DEFAULT_CHAR_DEF_BYTES).unwrap()
101 }
102
103 fn read_character_definition<T: BufRead>(reader: T) -> SudachiResult<Vec<CatRange>> {
117 let mut ranges: Vec<CatRange> = Vec::new();
118 for (i, line) in reader.lines().enumerate() {
119 let line = line?;
120 let line = line.trim();
121 if line.is_empty() || line.starts_with('#') || !line.starts_with("0x") {
122 continue;
123 }
124
125 let cols: Vec<_> = line.split_whitespace().collect();
126 if cols.len() < 2 {
127 return Err(SudachiError::InvalidCharacterCategory(
128 Error::InvalidFormat(i),
129 ));
130 }
131
132 let r: Vec<_> = cols[0].split("..").collect();
133 let begin = u32::from_str_radix(String::from(r[0]).trim_start_matches("0x"), 16)?;
134 let end = if r.len() > 1 {
135 u32::from_str_radix(String::from(r[1]).trim_start_matches("0x"), 16)? + 1
136 } else {
137 begin + 1
138 };
139 if begin >= end {
140 return Err(SudachiError::InvalidCharacterCategory(
141 Error::InvalidFormat(i),
142 ));
143 }
144 if char::from_u32(begin).is_none() {
145 return Err(SudachiError::InvalidCharacterCategory(Error::InvalidChar(
146 begin, i,
147 )));
148 }
149
150 if char::from_u32(end).is_none() {
151 return Err(SudachiError::InvalidCharacterCategory(Error::InvalidChar(
152 end, i,
153 )));
154 }
155
156 let mut categories = CategoryType::empty();
157 for elem in cols[1..].iter().take_while(|elem| !elem.starts_with('#')) {
158 categories.insert(match elem.parse() {
159 Ok(t) => t,
160 Err(_) => {
161 return Err(SudachiError::InvalidCharacterCategory(
162 Error::InvalidCategoryType(i, elem.to_string()),
163 ))
164 }
165 });
166 }
167
168 ranges.push(CatRange {
169 begin,
170 end,
171 categories,
172 });
173 }
174
175 Ok(ranges)
176 }
177
178 fn compile(ranges: &Vec<CatRange>) -> CharacterCategory {
183 if ranges.is_empty() {
184 return CharacterCategory::default();
185 }
186
187 let boundaries = Self::collect_boundaries(ranges);
188 let mut categories = vec![CategoryType::empty(); boundaries.len()];
189
190 for range in ranges {
191 let start_idx = match boundaries.binary_search(&range.begin) {
192 Ok(i) => i + 1,
193 Err(_) => panic!("there can not be not found boundaries"),
194 };
195 for i in start_idx..boundaries.len() {
197 if boundaries[i] > range.end {
198 break;
199 }
200 categories[i] |= range.categories;
201 }
202 }
203
204 debug_assert_eq!(categories[0], CategoryType::empty());
206 categories[0] = CategoryType::DEFAULT;
207 let mut final_boundaries = Vec::with_capacity(boundaries.len());
209 let mut final_categories = Vec::with_capacity(categories.len());
210
211 let mut last_category = categories[0];
212 let mut last_boundary = boundaries[0];
213 for i in 1..categories.len() {
214 if categories[i] == last_category {
215 last_boundary = boundaries[i];
216 continue;
217 }
218 final_boundaries.push(last_boundary);
219 final_categories.push(last_category);
220 last_category = categories[i];
221 last_boundary = boundaries[i];
222 }
223
224 final_categories.push(last_category);
225 final_boundaries.push(last_boundary);
226
227 for cat in final_categories.iter_mut() {
229 if cat.is_empty() {
230 *cat = CategoryType::DEFAULT;
231 }
232 }
233
234 final_categories.push(CategoryType::DEFAULT);
236
237 final_boundaries.shrink_to_fit();
238 final_categories.shrink_to_fit();
239
240 CharacterCategory {
241 boundaries: final_boundaries,
242 categories: final_categories,
243 }
244 }
245
246 fn collect_boundaries(data: &Vec<CatRange>) -> Vec<u32> {
248 let mut boundaries = BTreeSet::new();
249 for i in data {
250 boundaries.insert(i.begin);
251 boundaries.insert(i.end);
252 }
253 boundaries.into_iter().collect()
254 }
255
256 pub fn get_category_types(&self, c: char) -> CategoryType {
258 if self.boundaries.is_empty() {
259 return CategoryType::DEFAULT;
260 }
261 let cint = c as u32;
262 match self.boundaries.binary_search(&cint) {
263 Ok(idx) => self.categories[idx + 1],
265 Err(idx) => self.categories[idx],
267 }
268 }
269
270 pub fn iter(&self) -> CharCategoryIter<'_> {
271 CharCategoryIter {
272 categories: self,
273 current: 0,
274 }
275 }
276}
277
278pub struct CharCategoryIter<'a> {
279 categories: &'a CharacterCategory,
280 current: usize,
281}
282
283impl Iterator for CharCategoryIter<'_> {
284 type Item = (Range<char>, CategoryType);
285
286 fn next(&mut self) -> Option<Self::Item> {
287 if self.current == self.categories.boundaries.len() + 1 {
288 return None;
289 }
290
291 let range = if self.current == self.categories.boundaries.len() {
293 let left = char::from_u32(*self.categories.boundaries.last().unwrap()).unwrap();
294 (left..char::MAX, *self.categories.categories.last().unwrap())
295 } else if self.current == 0 {
296 let right = char::from_u32(*self.categories.boundaries.first().unwrap()).unwrap();
297 let r = (0 as char)..right;
298 (r, self.categories.categories[0])
299 } else {
300 let left = char::from_u32(self.categories.boundaries[self.current - 1]).unwrap();
301 let right = char::from_u32(self.categories.boundaries[self.current]).unwrap();
302 let cat = self.categories.categories[self.current];
303 (left..right, cat)
304 };
305
306 self.current += 1;
307 Some(range)
308 }
309}
310
311impl FusedIterator for CharCategoryIter<'_> {}
312
313#[cfg(test)]
314mod tests {
315 use super::*;
316 use claim::assert_matches;
317 use std::path::PathBuf;
318
319 const TEST_RESOURCE_DIR: &str = "./tests/resources/";
320 const TEST_CHAR_DEF_FILE: &str = "char.def";
321 type CT = CategoryType;
322
323 #[test]
324 fn get_category_types() {
325 let path = PathBuf::from(TEST_RESOURCE_DIR).join(TEST_CHAR_DEF_FILE);
326 let cat = CharacterCategory::from_file(&path).expect("failed to load char.def for test");
327 let cats = cat.get_category_types('熙');
328 assert_eq!(1, cats.count());
329 assert!(cats.contains(CT::KANJI));
330 }
331
332 fn read_categories(data: &str) -> CharacterCategory {
333 let ranges = CharacterCategory::read_character_definition(data.as_bytes())
334 .expect("error when parsing character categories");
335 CharacterCategory::compile(&ranges)
336 }
337
338 #[test]
339 fn read_cdef_1() {
340 let cat = read_categories(
341 "
342 0x0030..0x0039 NUMERIC
343 0x0032 KANJI",
344 );
345 assert_eq!(cat.get_category_types('\u{0030}'), CT::NUMERIC);
346 assert_eq!(cat.get_category_types('\u{0031}'), CT::NUMERIC);
347 assert_eq!(cat.get_category_types('\u{0032}'), CT::NUMERIC | CT::KANJI);
348 assert_eq!(cat.get_category_types('\u{0033}'), CT::NUMERIC);
349 assert_eq!(cat.get_category_types('\u{0039}'), CT::NUMERIC);
350 }
351
352 #[test]
353 fn read_cdef_2() {
354 let cat = read_categories(
355 "
356 0x0030..0x0039 NUMERIC
357 0x0070..0x0079 ALPHA
358 0x3007 KANJI
359 0x0030 KANJI",
360 );
361 assert_eq!(cat.get_category_types('\u{0030}'), CT::NUMERIC | CT::KANJI);
362 assert_eq!(cat.get_category_types('\u{0039}'), CT::NUMERIC);
363 assert_eq!(cat.get_category_types('\u{3007}'), CT::KANJI);
364 assert_eq!(cat.get_category_types('\u{0069}'), CT::DEFAULT);
365 assert_eq!(cat.get_category_types('\u{0070}'), CT::ALPHA);
366 assert_eq!(cat.get_category_types('\u{0080}'), CT::DEFAULT);
367 }
368
369 #[test]
370 fn read_cdef_3() {
371 let cat = read_categories(
372 "
373 0x0030..0x0039 KATAKANA
374 0x3007 KANJI KANJINUMERIC
375 0x3008 KANJI KANJINUMERIC
376 0x3009 KANJI KANJINUMERIC
377 0x0039..0x0040 ALPHA
378 0x0030..0x0039 NUMERIC
379 0x0030 KANJI",
380 );
381 assert_eq!(cat.get_category_types('\u{0029}'), CT::DEFAULT);
382 assert_eq!(
383 cat.get_category_types('\u{0030}'),
384 CT::NUMERIC | CT::KATAKANA | CT::KANJI
385 );
386 assert_eq!(
387 cat.get_category_types('\u{0039}'),
388 CT::NUMERIC | CT::ALPHA | CT::KATAKANA
389 );
390 assert_eq!(cat.get_category_types('\u{0040}'), CT::ALPHA);
391 assert_eq!(cat.get_category_types('\u{0041}'), CT::DEFAULT);
392 assert_eq!(
393 cat.get_category_types('\u{3007}'),
394 CT::KANJI | CT::KANJINUMERIC
395 );
396 assert_eq!(cat.get_category_types('\u{4007}'), CT::DEFAULT);
397 }
398
399 #[test]
400 fn read_cdef_4() {
401 let cat = read_categories(
402 "
403 0x4E00..0x9FFF KANJI
404 0x4E8C KANJI KANJINUMERIC",
405 );
406 assert_eq!(cat.get_category_types('男'), CT::KANJI);
407 assert_eq!(cat.get_category_types('\u{4E8B}'), CT::KANJI);
408 assert_eq!(
409 cat.get_category_types('\u{4E8C}'),
410 CT::KANJI | CT::KANJINUMERIC
411 );
412 assert_eq!(cat.get_category_types('\u{4E8D}'), CT::KANJI);
413 }
414
415 #[test]
416 fn read_cdef_holes_1() {
417 let cat = read_categories(
418 "
419 0x0030 USER1
420 0x0032 USER2",
421 );
422 assert_eq!(cat.get_category_types('\u{0029}'), CT::DEFAULT);
423 assert_eq!(cat.get_category_types('\u{0030}'), CT::USER1);
424 assert_eq!(cat.get_category_types('\u{0031}'), CT::DEFAULT);
425 assert_eq!(cat.get_category_types('\u{0032}'), CT::USER2);
426 assert_eq!(cat.get_category_types('\u{0033}'), CT::DEFAULT);
427 }
428
429 #[test]
430 fn read_cdef_merge_1() {
431 let cat = read_categories(
432 "
433 0x0030 USER1
434 0x0031 USER1",
435 );
436 assert_eq!(cat.boundaries.len(), 2);
437 assert_eq!(cat.categories.len(), 3);
438 assert_eq!(cat.get_category_types('\u{0029}'), CT::DEFAULT);
439 assert_eq!(cat.get_category_types('\u{0030}'), CT::USER1);
440 assert_eq!(cat.get_category_types('\u{0031}'), CT::USER1);
441 assert_eq!(cat.get_category_types('\u{0032}'), CT::DEFAULT);
442 }
443
444 #[test]
445 fn read_cdef_merge_2() {
446 let cat = read_categories(
447 "
448 0x0030 USER1
449 0x0031..0x0032 USER1",
450 );
451 assert_eq!(cat.boundaries.len(), 2);
452 assert_eq!(cat.categories.len(), 3);
453 assert_eq!(cat.get_category_types('\u{0029}'), CT::DEFAULT);
454 assert_eq!(cat.get_category_types('\u{0030}'), CT::USER1);
455 assert_eq!(cat.get_category_types('\u{0031}'), CT::USER1);
456 assert_eq!(cat.get_category_types('\u{0032}'), CT::USER1);
457 assert_eq!(cat.get_category_types('\u{0033}'), CT::DEFAULT);
458 }
459
460 #[test]
461 fn read_cdef_merge_3() {
462 let cat = read_categories(
463 "
464 0x0030..0x0031 USER1
465 0x0032..0x0033 USER1",
466 );
467 assert_eq!(cat.boundaries.len(), 2);
468 assert_eq!(cat.categories.len(), 3);
469 assert_eq!(cat.get_category_types('\u{0029}'), CT::DEFAULT);
470 assert_eq!(cat.get_category_types('\u{0030}'), CT::USER1);
471 assert_eq!(cat.get_category_types('\u{0031}'), CT::USER1);
472 assert_eq!(cat.get_category_types('\u{0032}'), CT::USER1);
473 assert_eq!(cat.get_category_types('\u{0033}'), CT::USER1);
474 assert_eq!(cat.get_category_types('\u{0034}'), CT::DEFAULT);
475 }
476
477 #[test]
478 fn read_character_definition_with_invalid_format() {
479 let data = "0x0030..0x0039";
480 let result = CharacterCategory::read_character_definition(data.as_bytes());
481 assert_matches!(
482 result,
483 Err(SudachiError::InvalidCharacterCategory(
484 Error::InvalidFormat(0)
485 ))
486 );
487 }
488
489 #[test]
490 fn read_character_definition_with_invalid_range() {
491 let data = "0x0030..0x0029 NUMERIC";
492 let result = CharacterCategory::read_character_definition(data.as_bytes());
493 assert_matches!(
494 result,
495 Err(SudachiError::InvalidCharacterCategory(
496 Error::InvalidFormat(0)
497 ))
498 );
499 }
500
501 #[test]
502 fn read_character_definition_with_invalid_type() {
503 let data = "0x0030..0x0039 FOO";
504 let result = CharacterCategory::read_character_definition(data.as_bytes());
505 assert_matches!(result, Err(SudachiError::InvalidCharacterCategory(Error::InvalidCategoryType(0, s))) if s == "FOO");
506 }
507
508 #[test]
509 fn check_test_cdef() {
510 let data: &[u8] = include_bytes!("../../tests/resources/char.def");
511 let c = CharacterCategory::from_reader(data).expect("failed to read chars");
512 assert_eq!(c.get_category_types('â'), CT::ALPHA);
513 assert_eq!(c.get_category_types('b'), CT::ALPHA);
514 assert_eq!(c.get_category_types('C'), CT::ALPHA);
515 assert_eq!(c.get_category_types('漢'), CT::KANJI);
516 assert_eq!(c.get_category_types('々'), CT::KANJI | CT::SYMBOL);
517 assert_eq!(c.get_category_types('𡈽'), CT::DEFAULT);
518 assert_eq!(c.get_category_types('ア'), CT::KATAKANA);
519 assert_eq!(c.get_category_types('コ'), CT::KATAKANA);
520 assert_eq!(c.get_category_types('゙'), CT::KATAKANA);
521 }
522
523 #[test]
524 fn iter_cdef_holes_1() {
525 let cat = read_categories(
526 "
527 0x0030 USER1
528 0x0032 USER2",
529 );
530 let mut iter = cat.iter();
531 assert_matches!(
532 iter.next(),
533 Some((
534 Range {
535 start: '\x00',
536 end: '\x30'
537 },
538 CT::DEFAULT
539 ))
540 );
541 assert_matches!(
542 iter.next(),
543 Some((
544 Range {
545 start: '\x30',
546 end: '\x31'
547 },
548 CT::USER1
549 ))
550 );
551 assert_matches!(
552 iter.next(),
553 Some((
554 Range {
555 start: '\x31',
556 end: '\x32'
557 },
558 CT::DEFAULT
559 ))
560 );
561 assert_matches!(
562 iter.next(),
563 Some((
564 Range {
565 start: '\x32',
566 end: '\x33'
567 },
568 CT::USER2
569 ))
570 );
571 assert_matches!(
572 iter.next(),
573 Some((
574 Range {
575 start: '\x33',
576 end: char::MAX
577 },
578 CT::DEFAULT
579 ))
580 );
581 assert_eq!(iter.next(), None);
582 }
583}