spinoso_symbol/casecmp/unicode.rs
1//! Unicode case folding comparisons for byte content resolved from `Symbol`s.
2
3use core::str;
4
5use artichoke_core::intern::Intern;
6use focaccia::CaseFold;
7
8/// Compare the byte contents of two symbols using Unicode case-folding
9/// comparison for equality.
10///
11/// The byte slice associated with each symbol is resolved via the given
12/// interner. Unresolved symbols are compared as if they resolve to `&[]`.
13///
14/// This comparison function attempts to convert each symbol's byte content to a
15/// UTF-8 [`str`](prim@str). If both symbols resolve to UTF-8 contents, [Unicode
16/// case folding] is used when comparing the contents and this function returns
17/// `Ok(Some(bool))`. If neither symbol resolves to UTF-8 contents, this
18/// function falls back to [`ascii_casecmp`] and returns `Ok(Some(bool))`.
19/// Otherwise, the two symbols have byte contents with different encodings and
20/// `Ok(None)` is returned.
21///
22/// This function can be used to implement [`Symbol#casecmp?`] for the
23/// [`Symbol`] type defined in Ruby Core.
24///
25/// # Errors
26///
27/// If the interner returns an error while retrieving a symbol, that error is
28/// returned. See [`Intern::lookup_symbol`].
29///
30/// [Unicode case folding]: https://www.w3.org/International/wiki/Case_folding
31/// [`ascii_casecmp`]: crate::casecmp::ascii_casecmp
32/// [`Symbol#casecmp?`]: https://ruby-doc.org/core-3.1.2/Symbol.html#method-i-casecmp-3F
33/// [`Symbol`]: https://ruby-doc.org/core-3.1.2/Symbol.html
34#[inline]
35#[cfg_attr(docsrs, doc(cfg(feature = "artichoke")))]
36pub fn case_eq<T, U>(interner: &T, left: U, right: U, fold: CaseFold) -> Result<Option<bool>, T::Error>
37where
38 T: Intern<Symbol = U>,
39 U: Copy,
40{
41 let left = interner.lookup_symbol(left)?.unwrap_or_default();
42 let right = interner.lookup_symbol(right)?.unwrap_or_default();
43 let cmp = match (str::from_utf8(left), str::from_utf8(right)) {
44 // Both slices are UTF-8, compare with the given Unicode case folding
45 // scheme.
46 (Ok(left), Ok(right)) => fold.case_eq(left, right),
47 // Both slices are not UTF-8, fallback to ASCII comparator.
48 (Err(_), Err(_)) => focaccia::ascii_case_eq(left, right),
49 // Encoding mismatch, the bytes are not comparable using Unicode case
50 // folding.
51 //
52 // > `nil` is returned if the two symbols have incompatible encodings,
53 // > or if `other_symbol` is not a symbol.
54 // > <https://ruby-doc.org/core-3.1.2/Symbol.html#method-i-casecmp-3F>
55 (Ok(_), Err(_)) | (Err(_), Ok(_)) => return Ok(None),
56 };
57 Ok(Some(cmp))
58}