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}