Skip to content

Commit

Permalink
Update documentation and tests.
Browse files Browse the repository at this point in the history
  • Loading branch information
TheVeryDarkness committed Oct 11, 2024
1 parent ceaa0ff commit dec3103
Show file tree
Hide file tree
Showing 8 changed files with 194 additions and 34 deletions.
43 changes: 43 additions & 0 deletions src/read/locale.rs
Original file line number Diff line number Diff line change
Expand Up @@ -92,3 +92,46 @@ impl Locale<char> for WS<char> {
&self.0
}
}

#[cfg(test)]
mod tests {
use super::ASCII;
use crate::{
locale::{Locale, CSV, WHITE_SPACES, WS},
utf8char::FixedUtf8Char,
};

#[test]
fn equivalence() {
assert_eq!(
<ASCII as Locale<char>>::whitespace_chars(&ASCII),
<ASCII as Locale<FixedUtf8Char>>::whitespace_chars(&ASCII),
);
assert_eq!(
<&ASCII as Locale<char>>::whitespace_chars(&&ASCII),
<&ASCII as Locale<FixedUtf8Char>>::whitespace_chars(&&ASCII),
);
assert_eq!(
<CSV as Locale<char>>::whitespace_chars(&CSV),
<CSV as Locale<FixedUtf8Char>>::whitespace_chars(&CSV),
);
assert_eq!(
<&CSV as Locale<char>>::whitespace_chars(&&CSV),
<&CSV as Locale<FixedUtf8Char>>::whitespace_chars(&&CSV),
);

let seps = [' ', '\t', '\n', '\r'];
assert_eq!(
<WS<char> as Locale<char>>::whitespace_chars(&FromIterator::from_iter(seps)),
<WS<FixedUtf8Char> as Locale<FixedUtf8Char>>::whitespace_chars(
&FromIterator::from_iter(seps)
),
);
assert_eq!(
<WS<char> as Locale<char>>::whitespace_chars(&FromIterator::from_iter(WHITE_SPACES)),
<WS<FixedUtf8Char> as Locale<FixedUtf8Char>>::whitespace_chars(
&FromIterator::from_iter(WHITE_SPACES)
),
);
}
}
104 changes: 95 additions & 9 deletions src/stream/ext.rs
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,8 @@ pub trait Pattern: Sized {
fn find_first_matching(self, s: &str) -> Option<usize>;

/// Find the first matching character or the whole length.
///
/// Returns the whole length if the string is fully not matching.
#[inline]
fn find_first_matching_or_whole_length(self, s: &str) -> usize {
self.find_first_matching(s).unwrap_or(s.len())
Expand All @@ -98,6 +100,8 @@ pub trait Pattern: Sized {
fn find_first_not_matching(self, s: &str) -> Option<usize>;

/// Find the first not matching character or the whole length.
///
/// Returns the whole length if the string is fully matching.
#[inline]
fn find_first_not_matching_or_whole_length(self, s: &str) -> usize {
self.find_first_not_matching(s).unwrap_or(s.len())
Expand Down Expand Up @@ -205,19 +209,32 @@ impl Pattern for &[char] {
Some(s.len() - l)
}
}

#[inline]
fn find_first_not_matching_or_whole_length(self, s: &str) -> usize {
s.len() - s.trim_start_matches(self).len()
}
}

#[cfg(test)]
mod tests {
use super::Pattern;

#[test]
fn chars() {
let ws = <&[char] as Pattern>::EOL.as_slice();
assert!(ws.matches('\n'));
assert!(ws.matches('\r'));
assert!(!ws.matches(' '));
assert!(!ws.matches('a'));
use super::{CharExt, Pattern};
use crate::{stream::ext::StrExt, utf8char::FixedUtf8Char};
use std::fmt::{Debug, Display};

fn chars<Char>()
where
for<'a> &'a [Char]: Pattern<Item = Char>,
Char: From<char> + Copy + CharExt + PartialEq<char> + Debug + Display,
for<'a> &'a str: StrExt<'a, Char>,
char: PartialEq<Char> + From<Char>,
{
let ws = <&[Char] as Pattern>::EOL;
let ws = ws.as_slice();
assert!(ws.matches('\n'.into()));
assert!(ws.matches('\r'.into()));
assert!(!ws.matches(' '.into()));
assert!(!ws.matches('a'.into()));

let s = " \n\r\n";
assert_eq!(ws.trim_start(s), " \n\r\n");
Expand All @@ -226,5 +243,74 @@ mod tests {

assert_eq!(ws.find_first_matching(s), Some(1));
assert_eq!(ws.find_first_not_matching(s), Some(0));
assert_eq!(ws.find_first_matching_or_whole_length(s), 1);
assert_eq!(ws.find_first_not_matching_or_whole_length(s), 0);

let s = "\r\nabc\n";
assert_eq!(ws.trim_start(s), "abc\n");
assert_eq!(ws.trim_end(s), "\r\nabc");
assert_eq!(ws.trim(s), "abc");

assert_eq!(ws.find_first_matching(s), Some(0));
assert_eq!(ws.find_first_not_matching(s), Some(2));
assert_eq!(ws.find_first_matching_or_whole_length(s), 0);
assert_eq!(ws.find_first_not_matching_or_whole_length(s), 2);

let s = "\n\r\n";
assert_eq!(ws.trim_start(s), "");
assert_eq!(ws.trim_end(s), "");
assert_eq!(ws.trim(s), "");

assert_eq!(ws.find_first_matching(s), Some(0));
assert_eq!(ws.find_first_not_matching(s), None);
assert_eq!(ws.find_first_matching_or_whole_length(s), 0);
assert_eq!(ws.find_first_not_matching_or_whole_length(s), 3);

let s = "+-*/";
assert_eq!(ws.trim_start(s), "+-*/");
assert_eq!(ws.trim_end(s), "+-*/");
assert_eq!(ws.trim(s), "+-*/");

assert_eq!(ws.find_first_matching(s), None);
assert_eq!(ws.find_first_not_matching(s), Some(0));
assert_eq!(ws.find_first_matching_or_whole_length(s), 4);
assert_eq!(ws.find_first_not_matching_or_whole_length(s), 0);

let s = "";
assert_eq!(ws.trim_start(s), "");
assert_eq!(ws.trim_end(s), "");
assert_eq!(ws.trim(s), "");

assert_eq!(ws.find_first_matching(s), None);
assert_eq!(ws.find_first_not_matching(s), None);
assert_eq!(ws.find_first_matching_or_whole_length(s), 0);
assert_eq!(ws.find_first_not_matching_or_whole_length(s), 0);

let s = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789)(*&^%$#@!~";

let characters = s.chars().collect::<Vec<_>>();
for (i, c) in <&str as StrExt<Char>>::chars_ext(s).enumerate() {
assert_eq!(c, characters[i]);
assert_eq!(c.to_string(), characters[i].to_string());
assert_eq!(c.len_utf8(), characters[i].len_utf8());
}

let characters: Vec<Char> = s.chars_ext().collect();
for (i, (index, c)) in s.char_indices().enumerate() {
let sub = &s[index..];
assert_eq!(c, characters[i]);
assert_eq!(sub.first_char().map(Into::into), Some(c));
assert_eq!(sub.first_char().unwrap(), c);
}
}

#[test]
fn chars_char() {
chars::<char>();
}

#[test]
fn chars_fixed_utf8_char() {
chars::<FixedUtf8Char>();
}
}
23 changes: 19 additions & 4 deletions src/stream/line_buf.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ impl<'a> LineBuf<'a> {
}
}

impl<'a> BufReadExt<char> for LineBuf<'a> {
impl BufReadExt<char> for LineBuf<'_> {
#[inline]
fn get_cur_line(&self) -> &str {
let line = as_slice_from(self.buf, self.cursor);
Expand Down Expand Up @@ -112,9 +112,24 @@ mod tests {
fn try_get_until_in_line() {
let s = "Hello, world!";
let mut stream = LineBuf::new(s);
assert_eq!(stream.try_get_until_in_line(&[',']).unwrap(), "Hello",);
assert_eq!(stream.try_get_until_in_line(&['!']).unwrap(), ", world",);
assert_eq!(stream.try_get_until_in_line(&['!']).unwrap(), "");
assert_eq!(
stream
.try_get_until_in_line(&[','].map(Into::into))
.unwrap(),
"Hello",
);
assert_eq!(
stream
.try_get_until_in_line(&['!'].map(Into::into))
.unwrap(),
", world",
);
assert_eq!(
stream
.try_get_until_in_line(&['!'].map(Into::into))
.unwrap(),
"",
);
assert_eq!(stream.try_get_until_in_line(&[]).unwrap(), "!");
assert!(stream.try_get_until_in_line(&[]).is_err());
}
Expand Down
13 changes: 11 additions & 2 deletions src/stream/tests.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
use super::line_buf::LineBuf;
use super::{
ext::{self, CharExt},
line_buf::LineBuf,
};
use crate::{
locale::{Locale, ASCII},
unwrap, BufReadExt, InputStream,
Expand All @@ -8,7 +11,13 @@ use std::io::Cursor;
/// Test all methods.
///
/// Pass "Hello, world!\r\n" to the stream and test all methods.
fn all_1(stream: &mut impl BufReadExt<char>) {
fn all_1<Char: CharExt + Copy + From<char>>(stream: &mut impl BufReadExt<Char>)
where
for<'a> &'a [Char]: ext::Pattern<Item = Char>,
for<'a> &'a str: ext::StrExt<'a, Char>,
char: From<Char>,
ASCII: Locale<Char>,
{
let c = unwrap!(stream.try_get());
assert_eq!(c, 'H');

Expand Down
19 changes: 1 addition & 18 deletions src/stream/traits.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,18 +59,7 @@ where
/// Get the next character in current line, if any.
#[inline]
fn get_in_cur_line(&mut self) -> Result<Option<char>, StreamError> {
if let Some(c) = self.get_line()?.chars().next() {
unsafe { self.skip(c.len_utf8()) };
Ok(Some(c))
} else {
Ok(None)
}
}

/// Get the next character in current line, if any.
#[inline]
fn get_in_cur_line_utf8(&mut self) -> Result<Option<Char>, StreamError> {
if let Some(c) = self.get_line()?.first_char() {
if let Some(c) = self.get_cur_line().chars().next() {
unsafe { self.skip(c.len_utf8()) };
Ok(Some(c))
} else {
Expand All @@ -85,12 +74,6 @@ where
Ok(line.chars().next())
}

/// Get the next character in current line, if any.
#[inline]
fn peek_in_cur_line_utf8(&self) -> Result<Option<Char>, StreamError> {
Ok(self.get_cur_line().first_char())
}

/// Fill the buffer with a new line, ignoring the current line.
///
/// - Returns `Ok(())` if a new line is read.
Expand Down
14 changes: 13 additions & 1 deletion src/utf8char/extensible.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use super::utf8_len_from_first_byte;
use std::mem::transmute;
use std::{fmt::Display, mem::transmute};

/// A UTF-8 character that is fixed in size.
///
Expand Down Expand Up @@ -94,3 +94,15 @@ impl PartialEq<&Utf8Char> for char {
<Utf8Char as PartialEq<char>>::eq(other, self)
}
}

impl From<&Utf8Char> for char {
fn from(f: &Utf8Char) -> Self {
unsafe { f.as_str().chars().next().unwrap_unchecked() }
}
}

impl Display for Utf8Char {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
Display::fmt(self.as_str(), f)
}
}
8 changes: 8 additions & 0 deletions src/utf8char/fixed.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
use std::fmt::Display;

use crate::utf8char::utf8_len_from_first_byte;

/// A UTF-8 character that is fixed in size.
Expand Down Expand Up @@ -104,3 +106,9 @@ impl From<&FixedUtf8Char> for char {
unsafe { f.as_str().chars().next().unwrap_unchecked() }
}
}

impl Display for FixedUtf8Char {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
Display::fmt(self.as_str(), f)
}
}
4 changes: 4 additions & 0 deletions src/utf8char/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,17 +27,21 @@ fn iter_fixed_utf8_char() {
for ((c, f), u) in c.into_iter().zip(f.into_iter()).zip(u.into_iter()) {
let string = c.to_string();
let bytes = string.as_bytes();
assert_eq!(Into::<char>::into(f), c);
assert_eq!(f, c);
assert_eq!(c, f);
assert_eq!(c.to_string(), f.to_string());
assert_eq!(f.as_bytes(), bytes);
assert_eq!(AsRef::<[u8]>::as_ref(&f), bytes);
assert_eq!(f.as_str(), c.to_string());
assert_eq!(AsRef::<str>::as_ref(&f), c.to_string());

assert_eq!(Into::<char>::into(u), c);
assert_eq!(u, c);
assert_eq!(c, u);
assert_eq!(u, &c);
assert_eq!(&c, u);
assert_eq!(c.to_string(), u.to_string());
assert_eq!(u.as_bytes(), bytes);
assert_eq!(AsRef::<[u8]>::as_ref(&u), bytes);
assert_eq!(u.as_str(), c.to_string());
Expand Down

0 comments on commit dec3103

Please sign in to comment.