1#![cfg_attr(feature = "no_std", no_std)]
51#![cfg_attr(test, feature(test))]
52#![deny(missing_docs, unsafe_code)]
53
54#[cfg(all(test, feature = "no_std"))]
55#[macro_use]
56extern crate std;
57
58use core::{char, fmt};
59use generated::{
60 MAX_NAME_LENGTH, PHRASEBOOK_OFFSETS1, PHRASEBOOK_OFFSETS2, PHRASEBOOK_OFFSET_SHIFT,
61};
62
63#[allow(dead_code)]
64#[rustfmt::skip]
65#[allow(clippy::all)]
66mod generated {
67 include!(concat!(env!("OUT_DIR"), "/generated.rs"));
68}
69#[allow(dead_code)]
70#[rustfmt::skip]
71#[allow(clippy::all)]
72mod generated_phf {
73 include!(concat!(env!("OUT_DIR"), "/generated_phf.rs"));
74}
75#[allow(dead_code)]
76mod jamo;
77
78#[allow(dead_code)]
81static ALIASES: phf::Map<&'static [u8], char> =
82 include!(concat!(env!("OUT_DIR"), "/generated_alias.rs"));
83
84mod iter_str;
85
86static HANGUL_SYLLABLE_PREFIX: &str = "HANGUL SYLLABLE ";
87static CJK_UNIFIED_IDEOGRAPH_PREFIX: &str = "CJK UNIFIED IDEOGRAPH-";
88
89fn is_cjk_unified_ideograph(ch: char) -> bool {
90 generated::CJK_IDEOGRAPH_RANGES
91 .iter()
92 .any(|&(lo, hi)| lo <= ch && ch <= hi)
93}
94
95#[derive(Clone)]
101pub struct Name {
102 data: Name_,
103}
104#[allow(clippy::upper_case_acronyms)]
105#[derive(Clone)]
106enum Name_ {
107 Plain(iter_str::IterStr),
108 CJK(CJK),
109 Hangul(Hangul),
110}
111
112#[allow(clippy::upper_case_acronyms)]
113#[derive(Copy)]
114struct CJK {
115 emit_prefix: bool,
116 idx: u8,
117 data: [u8; 6],
119}
120#[derive(Copy)]
121struct Hangul {
122 emit_prefix: bool,
123 idx: u8,
124 data: [u8; 3],
127}
128impl Clone for CJK {
129 fn clone(&self) -> CJK {
130 *self
131 }
132}
133impl Clone for Hangul {
134 fn clone(&self) -> Hangul {
135 *self
136 }
137}
138
139#[allow(clippy::len_without_is_empty)]
140impl Name {
141 pub fn len(&self) -> usize {
146 let counted = self.clone();
147 counted.fold(0, |a, s| a + s.len())
148 }
149}
150
151impl Iterator for Name {
152 type Item = &'static str;
153 fn next(&mut self) -> Option<&'static str> {
154 match self.data {
155 Name_::Plain(ref mut s) => s.next(),
156 Name_::CJK(ref mut state) => {
157 if state.emit_prefix {
159 state.emit_prefix = false;
160 return Some(CJK_UNIFIED_IDEOGRAPH_PREFIX);
161 }
162 state
166 .data
167 .get(state.idx as usize)
168 .map(|digit| *digit as usize)
170 .map(|d| {
171 state.idx += 1;
172 static DIGITS: &str = "0123456789ABCDEF";
173 &DIGITS[d..d + 1]
174 })
175 }
176 Name_::Hangul(ref mut state) => {
177 if state.emit_prefix {
178 state.emit_prefix = false;
179 return Some(HANGUL_SYLLABLE_PREFIX);
180 }
181
182 let idx = state.idx as usize;
183 state.data.get(idx).map(|x| *x as usize).map(|x| {
184 state.idx += 1;
186 [jamo::CHOSEONG, jamo::JUNGSEONG, jamo::JONGSEONG][idx][x]
187 })
188 }
189 }
190 }
191
192 fn size_hint(&self) -> (usize, Option<usize>) {
193 let counted = self.clone();
195 let n = counted.count();
196 (n, Some(n))
197 }
198}
199
200impl fmt::Debug for Name {
201 fn fmt(&self, fmtr: &mut fmt::Formatter) -> fmt::Result {
202 fmt::Display::fmt(self, fmtr)
203 }
204}
205impl fmt::Display for Name {
206 fn fmt(&self, fmtr: &mut fmt::Formatter) -> fmt::Result {
207 self.clone().try_for_each(|s| fmtr.write_str(s))
208 }
209}
210
211pub fn name(c: char) -> Option<Name> {
234 let cc = c as usize;
235 let offset =
236 (PHRASEBOOK_OFFSETS1[cc >> PHRASEBOOK_OFFSET_SHIFT] as usize) << PHRASEBOOK_OFFSET_SHIFT;
237
238 let mask = (1 << PHRASEBOOK_OFFSET_SHIFT) - 1;
239 let offset = PHRASEBOOK_OFFSETS2[offset + (cc & mask)];
240 if offset == 0 {
241 if is_cjk_unified_ideograph(c) {
242 let mut data = [b'0'; 6];
244 let mut number = c as u32;
245 let mut data_start = 6;
246 for place in data.iter_mut().rev() {
247 if number == 0 {
250 break;
251 }
252 *place = (number % 16) as u8;
253 number /= 16;
254 data_start -= 1;
255 }
256 Some(Name {
257 data: Name_::CJK(CJK {
258 emit_prefix: true,
259 idx: data_start,
260 data,
261 }),
262 })
263 } else {
264 jamo::syllable_decomposition(c).map(|(ch, ju, jo)| Name {
266 data: Name_::Hangul(Hangul {
267 emit_prefix: true,
268 idx: 0,
269 data: [ch, ju, jo],
270 }),
271 })
272 }
273 } else {
274 Some(Name {
275 data: Name_::Plain(iter_str::IterStr::new(offset)),
276 })
277 }
278}
279
280fn fnv_hash<I: Iterator<Item = u8>>(x: I) -> u64 {
281 let mut g = 0xcbf29ce484222325 ^ generated_phf::NAME2CODE_N;
282 for b in x {
283 g ^= b as u64;
284 g = g.wrapping_mul(0x100000001b3);
285 }
286 g
287}
288fn displace(f1: u32, f2: u32, d1: u32, d2: u32) -> u32 {
289 d2.wrapping_add(f1.wrapping_mul(d1)).wrapping_add(f2)
290}
291fn split(hash: u64) -> (u32, u32, u32) {
292 let bits = 21;
293 let mask = (1 << bits) - 1;
294 (
295 (hash & mask) as u32,
296 ((hash >> bits) & mask) as u32,
297 ((hash >> (2 * bits)) & mask) as u32,
298 )
299}
300
301fn character_by_alias(name: &[u8]) -> Option<char> {
303 ALIASES.get(name).copied()
304}
305
306pub fn character(search_name: &str) -> Option<char> {
324 let mut buf = [0; MAX_NAME_LENGTH + 1];
327 for (place, byte) in buf.iter_mut().zip(search_name.bytes()) {
328 *place = byte.to_ascii_uppercase();
329 }
330 let search_name = buf.get(..search_name.len())?;
331
332 if search_name.starts_with(HANGUL_SYLLABLE_PREFIX.as_bytes()) {
334 let remaining = &search_name[HANGUL_SYLLABLE_PREFIX.len()..];
335 let (choseong, remaining) = jamo::slice_shift_choseong(remaining);
336 let (jungseong, remaining) = jamo::slice_shift_jungseong(remaining);
337 let (jongseong, remaining) = jamo::slice_shift_jongseong(remaining);
338 match (choseong, jungseong, jongseong, remaining) {
339 (Some(choseong), Some(jungseong), Some(jongseong), b"") => {
340 let c = 0xac00 + (choseong * 21 + jungseong) * 28 + jongseong;
341 return char::from_u32(c);
342 }
343 (_, _, _, _) => {
344 return None;
347 }
348 }
349 }
350
351 if search_name.starts_with(CJK_UNIFIED_IDEOGRAPH_PREFIX.as_bytes()) {
353 let remaining = &search_name[CJK_UNIFIED_IDEOGRAPH_PREFIX.len()..];
354 if remaining.len() > 5 {
355 return None;
356 } let mut v = 0u32;
359 for &c in remaining {
360 v = match c {
361 b'0'..=b'9' => (v << 4) | (c - b'0') as u32,
362 b'A'..=b'F' => (v << 4) | (c - b'A' + 10) as u32,
363 _ => return None,
364 }
365 }
366 let ch = match char::from_u32(v) {
367 Some(ch) => ch,
368 None => return None,
369 };
370
371 if is_cjk_unified_ideograph(ch) {
373 return Some(ch);
374 } else {
375 return None;
378 }
379 }
380
381 let (g, f1, f2) = split(fnv_hash(search_name.iter().copied()));
383 let (d1, d2) = generated_phf::NAME2CODE_DISP[g as usize % generated_phf::NAME2CODE_DISP.len()];
385
386 let idx = displace(f1, f2, d1 as u32, d2 as u32) as usize;
388 let codepoint = generated_phf::NAME2CODE_CODE[idx % generated_phf::NAME2CODE_CODE.len()];
390
391 let maybe_name = match name(codepoint) {
397 None => {
398 if true {
399 debug_assert!(false)
400 }
401 return character_by_alias(search_name);
402 }
403 Some(name) => name,
404 };
405
406 let mut passed_name = search_name;
409 for part in maybe_name {
410 let part = part.as_bytes();
411 let part_l = part.len();
412 if passed_name.len() < part_l || &passed_name[..part_l] != part {
413 return character_by_alias(search_name);
414 }
415 passed_name = &passed_name[part_l..]
416 }
417
418 Some(codepoint)
419}
420
421#[cfg(test)]
422mod tests {
423 use super::*;
424 use rand::{
425 distributions::{Distribution, Standard},
426 prelude::{SeedableRng, StdRng},
427 };
428 use std::char;
429 use std::prelude::v1::*;
430
431 extern crate test;
432
433 use test::bench::Bencher;
434
435 static DATA: &str = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/data/UnicodeData.txt"));
436
437 #[test]
438 fn exhaustive() {
439 fn negative_range(from: u32, to: u32) {
442 for c in (from..to).filter_map(char::from_u32) {
443 if !is_cjk_unified_ideograph(c) && !jamo::is_hangul_syllable(c) {
444 let n = name(c);
445 assert!(
446 n.is_none(),
447 "{} ({}) shouldn't have a name but is called {}",
448 c,
449 c as u32,
450 n.unwrap()
451 );
452 }
453 }
454 }
455
456 let mut last = 0;
457 for line in DATA.lines() {
458 let mut it = line.split(';');
459
460 let raw_c = it.next();
461 let c = match char::from_u32(
462 raw_c.and_then(|s| u32::from_str_radix(s, 16).ok()).unwrap(),
463 ) {
464 Some(c) => c,
465 None => continue,
466 };
467
468 let n = it.next().unwrap();
469 if n.starts_with("<") {
470 continue;
471 }
472
473 let computed_n = name(c).unwrap();
474 let n_str = computed_n.to_string();
475 assert_eq!(n_str, n.to_string());
476 assert_eq!(computed_n.len(), n_str.len());
477
478 let (hint_low, hint_high) = computed_n.size_hint();
479 let number_of_parts = computed_n.count();
480 assert_eq!(hint_low, number_of_parts);
481 assert_eq!(hint_high, Some(number_of_parts));
482
483 assert_eq!(character(n), Some(c));
484 assert_eq!(character(&n.to_ascii_lowercase()), Some(c));
485
486 negative_range(last, c as u32);
487 last = c as u32 + 1;
488 }
489 negative_range(last, 0x10FFFF + 1)
490 }
491
492 #[test]
493 fn name_to_string() {
494 let n = name('a').unwrap();
495 assert_eq!(n.to_string(), "LATIN SMALL LETTER A".to_string());
496 let n = name('🁣').unwrap();
497 assert_eq!(n.to_string(), "DOMINO TILE VERTICAL-00-00".to_string());
498 }
499
500 #[test]
501 fn character_negative() {
502 let long_name = "x".repeat(100);
503 assert!(long_name.len() > MAX_NAME_LENGTH); let names = ["", "x", "öäå", "SPAACE", &long_name];
505 for &n in names.iter() {
506 assert_eq!(character(n), None);
507 }
508 }
509
510 #[test]
511 fn name_hangul_syllable() {
512 assert_eq!(
513 name('\u{ac00}').map(|s| s.to_string()),
514 Some("HANGUL SYLLABLE GA".to_string())
515 ); assert_eq!(
517 name('\u{bdc1}').map(|s| s.to_string()),
518 Some("HANGUL SYLLABLE BWELG".to_string())
519 );
520 assert_eq!(
521 name('\u{d7a3}').map(|s| s.to_string()),
522 Some("HANGUL SYLLABLE HIH".to_string())
523 ); }
525
526 #[test]
527 fn character_hangul_syllable() {
528 assert_eq!(character("HANGUL SYLLABLE GA"), Some('\u{ac00}'));
529 assert_eq!(character("HANGUL SYLLABLE BWELG"), Some('\u{bdc1}'));
530 assert_eq!(character("HANGUL SYLLABLE HIH"), Some('\u{d7a3}'));
531 assert_eq!(character("HANGUL SYLLABLE BLAH"), None);
532 }
533
534 #[test]
535 fn cjk_unified_ideograph_exhaustive() {
536 for &(lo, hi) in generated::CJK_IDEOGRAPH_RANGES.iter() {
537 for x in lo as u32..=hi as u32 {
538 let c = char::from_u32(x).unwrap();
539
540 let real_name = format!("CJK UNIFIED IDEOGRAPH-{:X}", x);
541 let lower_real_name = format!("CJK UNIFIED IDEOGRAPH-{:x}", x);
542 assert_eq!(character(&real_name), Some(c));
543 assert_eq!(character(&lower_real_name), Some(c));
544
545 assert_eq!(name(c).map(|s| s.to_string()), Some(real_name));
546 }
547 }
548 }
549 #[test]
550 fn name_cjk_unified_ideograph() {
551 assert_eq!(
552 name('\u{4e00}').map(|s| s.to_string()),
553 Some("CJK UNIFIED IDEOGRAPH-4E00".to_string())
554 ); assert_eq!(
556 name('\u{9fcc}').map(|s| s.to_string()),
557 Some("CJK UNIFIED IDEOGRAPH-9FCC".to_string())
558 ); assert_eq!(
560 name('\u{20000}').map(|s| s.to_string()),
561 Some("CJK UNIFIED IDEOGRAPH-20000".to_string())
562 ); assert_eq!(
564 name('\u{2a6d6}').map(|s| s.to_string()),
565 Some("CJK UNIFIED IDEOGRAPH-2A6D6".to_string())
566 );
567 assert_eq!(
568 name('\u{2a700}').map(|s| s.to_string()),
569 Some("CJK UNIFIED IDEOGRAPH-2A700".to_string())
570 );
571 assert_eq!(
572 name('\u{2b81d}').map(|s| s.to_string()),
573 Some("CJK UNIFIED IDEOGRAPH-2B81D".to_string())
574 ); }
576
577 #[test]
578 fn character_cjk_unified_ideograph() {
579 assert_eq!(character("CJK UNIFIED IDEOGRAPH-4E00"), Some('\u{4e00}'));
580 assert_eq!(character("CJK UNIFIED IDEOGRAPH-9FCC"), Some('\u{9fcc}'));
581 assert_eq!(character("CJK UNIFIED IDEOGRAPH-20000"), Some('\u{20000}'));
582 assert_eq!(character("CJK UNIFIED IDEOGRAPH-2A6D6"), Some('\u{2a6d6}'));
583 assert_eq!(character("CJK UNIFIED IDEOGRAPH-2A700"), Some('\u{2a700}'));
584 assert_eq!(character("CJK UNIFIED IDEOGRAPH-2B81D"), Some('\u{2b81d}'));
585 assert_eq!(character("CJK UNIFIED IDEOGRAPH-"), None);
586 assert_eq!(character("CJK UNIFIED IDEOGRAPH-!@#$"), None);
587 assert_eq!(character("CJK UNIFIED IDEOGRAPH-1234"), None);
588 assert_eq!(character("CJK UNIFIED IDEOGRAPH-EFGH"), None);
589 assert_eq!(character("CJK UNIFIED IDEOGRAPH-12345"), None);
590 assert_eq!(character("CJK UNIFIED IDEOGRAPH-2A6FF"), None); assert_eq!(character("CJK UNIFIED IDEOGRAPH-2A6FF"), None);
592 }
593
594 #[test]
595 fn character_by_alias() {
596 assert_eq!(super::character_by_alias(b"NEW LINE"), Some('\n'));
597 assert_eq!(super::character_by_alias(b"BACKSPACE"), Some('\u{8}'));
598 assert_eq!(super::character_by_alias(b"NOT AN ALIAS"), None);
599 }
600
601 #[bench]
602 fn name_basic(b: &mut Bencher) {
603 b.iter(|| {
604 for s in name('ö').unwrap() {
605 test::black_box(s);
606 }
607 })
608 }
609
610 #[bench]
611 fn character_basic(b: &mut Bencher) {
612 b.iter(|| character("LATIN SMALL LETTER O WITH DIAERESIS"));
613 }
614
615 #[bench]
616 fn name_10000_invalid(b: &mut Bencher) {
617 let mut rng = StdRng::seed_from_u64(0x12345678);
619 let chars: Vec<char> = Standard
620 .sample_iter(&mut rng)
621 .take(10000)
622 .filter_map(|c| match c {
623 c if name(c).is_none() => Some(c),
624 _ => None,
625 })
626 .collect();
627
628 b.iter(|| {
629 for &c in chars.iter() {
630 assert!(name(c).is_none());
631 }
632 })
633 }
634
635 #[bench]
636 fn name_all_valid(b: &mut Bencher) {
637 let chars = (0u32..0x10FFFF)
638 .filter_map(|x| match char::from_u32(x) {
639 Some(c) if name(c).is_some() => Some(c),
640 _ => None,
641 })
642 .collect::<Vec<char>>();
643
644 b.iter(|| {
645 for c in chars.iter() {
646 for s in name(*c).unwrap() {
647 test::black_box(s);
648 }
649 }
650 });
651 }
652
653 #[bench]
654 fn character_10000(b: &mut Bencher) {
655 let mut rng = StdRng::seed_from_u64(0x12345678);
657
658 let names: Vec<_> = Standard
659 .sample_iter(&mut rng)
660 .take(10000)
661 .filter_map(name)
662 .map(|name| name.to_string())
663 .collect();
664
665 b.iter(|| {
666 for n in names.iter() {
667 test::black_box(character(n));
668 }
669 })
670 }
671}
672
673#[cfg(all(feature = "no_std", not(test)))]
674mod std {
675 pub use core::{clone, fmt, marker};
676}