fish-shell/fish-rust/src/wchar_ext.rs

221 lines
6.0 KiB
Rust
Raw Normal View History

2023-01-15 06:56:24 +08:00
use crate::wchar::{wstr, WString};
use widestring::utfstr::CharsUtf32;
/// Helpers to convert things to widestring.
/// This is like std::string::ToString.
pub trait ToWString {
fn to_wstring(&self) -> WString;
}
#[inline]
fn to_wstring_impl(mut val: u64, neg: bool) -> WString {
// 20 digits max in u64: 18446744073709551616.
let mut digits = [0; 24];
let mut ndigits = 0;
while val > 0 {
digits[ndigits] = (val % 10) as u8;
val /= 10;
ndigits += 1;
}
if ndigits == 0 {
digits[0] = 0;
ndigits = 1;
}
let mut result = WString::with_capacity(ndigits + neg as usize);
if neg {
result.push('-');
}
for i in (0..ndigits).rev() {
result.push((digits[i] + b'0') as char);
}
result
}
/// Implement to_wstring() for signed types.
macro_rules! impl_to_wstring_signed {
($t:ty) => {
impl ToWString for $t {
fn to_wstring(&self) -> WString {
let val = *self as i64;
to_wstring_impl(val.unsigned_abs(), val < 0)
}
}
};
}
impl_to_wstring_signed!(i8);
impl_to_wstring_signed!(i16);
impl_to_wstring_signed!(i32);
impl_to_wstring_signed!(i64);
impl_to_wstring_signed!(isize);
/// Implement to_wstring() for unsigned types.
macro_rules! impl_to_wstring_unsigned {
($t:ty) => {
impl ToWString for $t {
fn to_wstring(&self) -> WString {
to_wstring_impl(*self as u64, false)
}
}
};
}
impl_to_wstring_unsigned!(u8);
impl_to_wstring_unsigned!(u16);
impl_to_wstring_unsigned!(u32);
impl_to_wstring_unsigned!(u64);
impl_to_wstring_unsigned!(usize);
#[test]
fn test_to_wstring() {
assert_eq!(0_u64.to_wstring(), "0");
assert_eq!(1_u64.to_wstring(), "1");
assert_eq!(0_i64.to_wstring(), "0");
assert_eq!(1_i64.to_wstring(), "1");
assert_eq!((-1_i64).to_wstring(), "-1");
assert_eq!((-5_i64).to_wstring(), "-5");
let mut val: i64 = 1;
loop {
assert_eq!(val.to_wstring(), val.to_string());
let Some(next) = val.checked_mul(-3) else { break; };
val = next;
}
assert_eq!(u64::MAX.to_wstring(), "18446744073709551615");
assert_eq!(i64::MIN.to_wstring(), "-9223372036854775808");
assert_eq!(i64::MAX.to_wstring(), "9223372036854775807");
}
2023-01-15 06:56:24 +08:00
/// A thing that a wide string can start with or end with.
/// It must have a chars() method which returns a double-ended char iterator.
pub trait CharPrefixSuffix {
type Iter: DoubleEndedIterator<Item = char>;
fn chars(self) -> Self::Iter;
}
impl CharPrefixSuffix for char {
type Iter = std::iter::Once<char>;
fn chars(self) -> Self::Iter {
std::iter::once(self)
}
}
impl<'a> CharPrefixSuffix for &'a str {
type Iter = std::str::Chars<'a>;
fn chars(self) -> Self::Iter {
str::chars(self)
}
}
impl<'a> CharPrefixSuffix for &'a wstr {
type Iter = CharsUtf32<'a>;
fn chars(self) -> Self::Iter {
wstr::chars(self)
}
}
impl<'a> CharPrefixSuffix for &'a WString {
type Iter = CharsUtf32<'a>;
fn chars(self) -> Self::Iter {
2023-02-05 07:45:25 +08:00
wstr::chars(self)
2023-01-15 06:56:24 +08:00
}
}
/// \return true if \p prefix is a prefix of \p contents.
2023-02-05 07:45:25 +08:00
fn iter_prefixes_iter<Prefix, Contents>(prefix: Prefix, mut contents: Contents) -> bool
2023-01-15 06:56:24 +08:00
where
Prefix: Iterator,
Contents: Iterator,
Prefix::Item: PartialEq<Contents::Item>,
{
2023-02-05 07:45:25 +08:00
for c1 in prefix {
2023-01-15 06:56:24 +08:00
match contents.next() {
Some(c2) if c1 == c2 => {}
_ => return false,
}
}
true
}
/// Convenience functions for WString.
pub trait WExt {
/// Access the chars of a WString or wstr.
fn as_char_slice(&self) -> &[char];
/// \return the char at an index.
/// If the index is equal to the length, return '\0'.
/// If the index exceeds the length, then panic.
fn char_at(&self, index: usize) -> char {
let chars = self.as_char_slice();
if index == chars.len() {
'\0'
} else {
chars[index]
}
}
/// \return the index of the first occurrence of the given char, or None.
fn find_char(&self, c: char) -> Option<usize> {
self.as_char_slice().iter().position(|&x| x == c)
}
/// \return whether we start with a given Prefix.
/// The Prefix can be a char, a &str, a &wstr, or a &WString.
fn starts_with<Prefix: CharPrefixSuffix>(&self, prefix: Prefix) -> bool {
iter_prefixes_iter(prefix.chars(), self.as_char_slice().iter().copied())
}
/// \return whether we end with a given Suffix.
/// The Suffix can be a char, a &str, a &wstr, or a &WString.
fn ends_with<Suffix: CharPrefixSuffix>(&self, suffix: Suffix) -> bool {
iter_prefixes_iter(
suffix.chars().rev(),
self.as_char_slice().iter().copied().rev(),
)
}
}
impl WExt for WString {
fn as_char_slice(&self) -> &[char] {
self.as_utfstr().as_char_slice()
}
}
impl WExt for wstr {
fn as_char_slice(&self) -> &[char] {
wstr::as_char_slice(self)
}
}
#[cfg(test)]
mod tests {
use super::WExt;
use crate::wchar::{WString, L};
/// Write some tests.
#[cfg(test)]
fn test_find_char() {
assert_eq!(Some(0), L!("abc").find_char('a'));
assert_eq!(Some(1), L!("abc").find_char('b'));
assert_eq!(None, L!("abc").find_char('X'));
assert_eq!(None, L!("").find_char('X'));
}
#[cfg(test)]
fn test_prefix() {
assert!(L!("").starts_with(L!("")));
assert!(L!("abc").starts_with(L!("")));
assert!(L!("abc").starts_with('a'));
assert!(L!("abc").starts_with("ab"));
assert!(L!("abc").starts_with(L!("ab")));
assert!(L!("abc").starts_with(&WString::from_str("abc")));
}
#[cfg(test)]
fn test_suffix() {
assert!(L!("").ends_with(L!("")));
assert!(L!("abc").ends_with(L!("")));
assert!(L!("abc").ends_with('c'));
assert!(L!("abc").ends_with("bc"));
assert!(L!("abc").ends_with(L!("bc")));
assert!(L!("abc").ends_with(&WString::from_str("abc")));
}
}