From 50234637bf0585092a528b7abd5db3b81c5f124f Mon Sep 17 00:00:00 2001 From: TheVeryDarkness <3266343194@qq.com> Date: Sun, 6 Oct 2024 23:29:39 +0800 Subject: [PATCH] Re-design separator and rank system and remove `WriteOneInto`. --- examples/doc_macro_show.rs | 20 ++--- examples/doc_macro_show.txt | 14 ++-- examples/doc_show.txt | 3 +- examples/macros-output.txt | 8 +- examples/macros.rs | 8 +- src/array/mod.rs | 2 +- src/formatted.rs | 35 +++++++- src/lib.rs | 8 +- src/write/impls.rs | 113 +++++++++++++++++-------- src/write/macros.rs | 23 +++--- src/write/mod.rs | 159 +++++++++++++++++++----------------- src/write/ranked.rs | 32 ++++++++ src/{ => write}/sep_by.rs | 40 +++++---- src/write/separator.rs | 97 ++++++++++++++++++++++ src/write/writer.rs | 62 +++++--------- tests/array.rs | 10 +-- tests/char.rs | 17 +++- tests/ill_buffer.rs | 10 +-- tests/ill_data.rs | 20 +++-- tests/integers.rs | 17 ++-- tests/mat.rs | 8 +- tests/slice.rs | 0 tests/stdout.rs | 10 +-- tests/string.rs | 16 +++- tests/tuple.rs | 6 +- tests/vec.rs | 10 +-- 26 files changed, 488 insertions(+), 260 deletions(-) create mode 100644 src/write/ranked.rs rename src/{ => write}/sep_by.rs (56%) create mode 100644 src/write/separator.rs create mode 100644 tests/slice.rs diff --git a/examples/doc_macro_show.rs b/examples/doc_macro_show.rs index 8ffb648..36635f1 100644 --- a/examples/doc_macro_show.rs +++ b/examples/doc_macro_show.rs @@ -3,14 +3,14 @@ fn main() { // This will write "42\n" to the standard output. show!(42); - // This will write "42 Hello, World!\n" to the standard output. - show!(42, "Hello, World!"); - // This will write "42 Hello, World! 1 2 3 4\n" to the standard output. - show!(42, "Hello, World!", [1, 2, 3, 4]); - // This will write "42 Hello, World! 1 2 3 4 1 2 3 4\n" to the standard output. - show!(42, "Hello, World!", [1, 2, 3, 4], [[1, 2], [3, 4]]); - // This will write "42, Hello, World!, 1 2 3 4, 1 2 3 4\n" to the standard output. - show!(42, "Hello, World!", [1, 2, 3, 4], [[1, 2], [3, 4]]; sep=", "); - // This will write "42, Hello, World!, 1 2 3 4, 1 2 3 4!" to the standard output. - show!(42, "Hello, World!", [1, 2, 3, 4], [[1, 2], [3, 4]]; sep=", ", end="!"); + // This will write "Hello, World!\n" to the standard output. + show!("Hello, World!"); + // This will write "1 2 3 4\n" to the standard output. + show!([1, 2, 3, 4]); + // This will write "1 2\n3 4\n" to the standard output. + show!([[1, 2], [3, 4]]); + // This will write "1, 2\n3, 4\n" to the standard output. + show!([[1, 2], [3, 4]], sep=["\n", ", "]); + // This will write "1, 2\n3, 4!" to the standard output. + show!([[1, 2], [3, 4]], sep=["\n", ", "], end="!"); } diff --git a/examples/doc_macro_show.txt b/examples/doc_macro_show.txt index eb539ad..ee54fff 100644 --- a/examples/doc_macro_show.txt +++ b/examples/doc_macro_show.txt @@ -1,9 +1,9 @@ 42 -42 Hello, World! -42 Hello, World! 1 2 3 4 -42 Hello, World! 1 2 3 4 1 2 +Hello, World! +1 2 3 4 +1 2 3 4 -42, Hello, World!, 1 2 3 4, 1 2 -3 4 -42, Hello, World!, 1 2 3 4, 1 2 -3 4! \ No newline at end of file +1, 2 +3, 4 +1, 2 +3, 4! \ No newline at end of file diff --git a/examples/doc_show.txt b/examples/doc_show.txt index 91936eb..3ed2196 100644 --- a/examples/doc_show.txt +++ b/examples/doc_show.txt @@ -6,5 +6,6 @@ Hello, World! .@/ #$$ 1 2 3 -1 2 3 4 +1 2 +3 4 diff --git a/examples/macros-output.txt b/examples/macros-output.txt index 5fc1188..562e445 100644 --- a/examples/macros-output.txt +++ b/examples/macros-output.txt @@ -2,9 +2,7 @@ 3 3 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 +, 2 2 -3 3 -1 2 3 4 5 6 7 8 9 -10 11 12 13 14 15 16 17 18 -2, 2 2 :: 3 3 :: 1 2 3 4 5 6 7 8 9 -10 11 12 13 14 15 16 17 18 +3 :: 3 +1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 :: 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 diff --git a/examples/macros.rs b/examples/macros.rs index 55a42bc..95e610d 100644 --- a/examples/macros.rs +++ b/examples/macros.rs @@ -27,8 +27,8 @@ fn main() { show!(b); show!(c); - show!(a, b, c; sep = "\n"); - show!(a; end = ""); - show!(", ", a, " "; sep = "", end = ""); - show!(a, b, c; sep = " :: "); + show!((", ", a, " "), sep = [""], end = "\n"); + show!(a, end = "\n"); + show!(b, sep = [" :: "]); + show!(c, sep = [" :: ", " | "]); } diff --git a/src/array/mod.rs b/src/array/mod.rs index dd17026..bc61698 100644 --- a/src/array/mod.rs +++ b/src/array/mod.rs @@ -15,7 +15,7 @@ mod tests; /// /// # Examples /// -/// ```rust,ignore +/// ```rust,ignore "This function is no longer exported." /// use iof::array_try_from_fn; /// let array: Result<[String; 3], ()> = array_try_from_fn(|| Ok("hello".to_string())); /// assert_eq!(array, Ok(["hello", "hello", "hello"])); diff --git a/src/formatted.rs b/src/formatted.rs index 7d6f910..c597e39 100644 --- a/src/formatted.rs +++ b/src/formatted.rs @@ -1,4 +1,7 @@ -use crate::sep_by; +use crate::write::{ + sep_by::{self}, + separator::Separator, +}; /// [std::fmt::Display] with given separator. /// @@ -10,16 +13,40 @@ use crate::sep_by; /// let s = format!("{}", v.sep_by(", ")); /// assert_eq!(s, "1, 2, 3"); /// ``` -pub trait SepBy: IntoIterator { +pub trait SepBy: IntoIterator +where + ::IntoIter: Clone, +{ /// Create an iterator that implement [core::fmt::Display] using given separator. - fn sep_by(self, sep: &'_ str) -> sep_by::SepBy<'_, Self::IntoIter>; + fn sep_by(self, sep: &'_ S) -> sep_by::SepBy<'_, Self::IntoIter, S>; + + /// Create an iterator that implement [WriteInto](crate::WriteInto) using given separator. + fn sep_by_write_into( + self, + sep: &'_ S, + ) -> sep_by::SepBy<'_, Self::IntoIter, S> + where + Self::Item: crate::WriteInto; } impl SepBy for I where I::IntoIter: Clone, { - fn sep_by(self, sep: &'_ str) -> sep_by::SepBy<'_, Self::IntoIter> { + fn sep_by(self, sep: &'_ S) -> sep_by::SepBy<'_, Self::IntoIter, S> { sep_by::SepBy::new(self.into_iter(), sep) } + + fn sep_by_write_into( + self, + sep: &'_ S, + ) -> sep_by::SepBy<'_, Self::IntoIter, S> + where + Self::Item: crate::WriteInto, + { + let iter = sep_by::SepBy::new(self.into_iter(), sep); + fn check_impl_write_into(_: &T) {} + check_impl_write_into(&iter); + iter + } } diff --git a/src/lib.rs b/src/lib.rs index 0d775b8..26d488c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -313,14 +313,18 @@ pub use { }, stdio::{read_into::*, stdin, stdout, stream::*}, stream::{input_stream::InputStream, traits::BufReadExt}, - write::{writer::Writer, WriteInto, WriteOneInto}, + write::{ + ranked, + separator::{GetDefaultSeparator, Separator}, + writer::Writer, + WriteInto, + }, }; mod array; mod formatted; mod mat; mod read; -mod sep_by; mod stdio; mod stream; mod write; diff --git a/src/write/impls.rs b/src/write/impls.rs index 9299bba..fe05e6a 100644 --- a/src/write/impls.rs +++ b/src/write/impls.rs @@ -1,8 +1,8 @@ -use super::WriteOneInto; -use crate::impl_write_into; +use super::{ranked::*, Separator, WriteInto}; +use crate::impl_for_single; use std::{io, num::*}; -impl_write_into!( +impl_for_single!( f32 f64 bool str String @@ -18,51 +18,100 @@ impl_write_into!( NonZeroIsize NonZeroUsize ); -impl WriteOneInto for char { - const SEP_ITEM: &'static str = ""; - - fn try_write_one_into(&self, s: &mut S) -> io::Result<()> { +impl WriteInto for char { + fn try_write_into_with_sep( + &self, + s: &mut S, + sep: &[impl Separator], + ) -> super::Result { + debug_assert_eq!(sep.len(), Self::RANK); s.write_all(&[*self as u8]) } } -macro_rules! impl_write_one_into_for_tuple { - ($n0:ident $t0:ident $(, $n:ident $t:ident)* $(,)?) => { - impl<$t0: WriteOneInto, $($t: WriteOneInto),*> WriteOneInto for ($t0, $($t,)*) { - const SEP_ITEM: &'static str = " "; +impl Rank for char { + const RANK: usize = 0; + const SPACE: bool = false; +} + +macro_rules! check_separators_count { + ($sep:expr, $t0:ty $(, $t:ty)*) => { + debug_assert_eq!($sep.len(), Self::RANK, "Separator count mismatch."); + $( + debug_assert_eq!( + <$t0 as Rank>::RANK, + <$t as Rank>::RANK, + "Rank mismatch: {} != {}", + <$t0 as Rank>::RANK, + <$t as Rank>::RANK, + ); + )* + }; + ($sep:expr $(,)?) => { + debug_assert_eq!($sep.len(), Self::RANK, "Separator count mismatch."); + }; +} + +macro_rules! impl_for_tuple { + ($n0:ident $t0:ident $(, $n:ident $t:ident)+ $(,)?) => { + impl<$t0: WriteInto, $($t: WriteInto),*> WriteInto for ($t0, $($t,)*) { + fn try_write_into_with_sep(&self, s: &mut S, sep: &[impl Separator]) -> io::Result<()> { + check_separators_count!(sep, $t0 $(, $t)*); - fn try_write_one_into(&self, s: &mut S) -> io::Result<()> { let ($n0, $($n, )*) = self; - $n0.try_write_one_into(s)?; + let (sep, residual) = sep.split_first().expect("Separator count mismatch."); + $n0.try_write_into_with_sep(s, residual)?; $( - s.write_all(Self::SEP_ITEM.as_bytes())?; - $n.try_write_one_into(s)?; + sep.write(s)?; + $n.try_write_into_with_sep(s, residual)?; )* Ok(()) } } + impl<$t0: Rank, $($t: Rank,)*> Rank for ($t0, $($t,)*) { + const RANK: usize = 1 + $t0::RANK; + const SPACE: bool = true; + } + }; + ($n0:ident $t0:ident $(,)?) => { + impl<$t0: WriteInto> WriteInto for ($t0, ) { + fn try_write_into_with_sep(&self, s: &mut S, sep: &[impl Separator]) -> io::Result<()> { + check_separators_count!(sep, $t0); + let ($n0, ) = self; + let (_sep, residual) = sep.split_first().expect("Separator count mismatch."); + $n0.try_write_into_with_sep(s, residual)?; + Ok(()) + } + } + impl<$t0: Rank> Rank for ($t0, ) { + const RANK: usize = 1 + $t0::RANK; + const SPACE: bool = true; + } }; () => { - impl WriteOneInto for () { - const SEP_ITEM: &'static str = " "; - - fn try_write_one_into(&self, _s: &mut S) -> io::Result<()> { + impl WriteInto for () { + fn try_write_into_with_sep(&self, _s: &mut S, _sep: &[impl Separator]) -> io::Result<()> { + check_separators_count!(_sep, ); Ok(()) } } + impl Rank for () { + const RANK: usize = 0; + const SPACE: bool = true; + } }; } -impl_write_one_into_for_tuple!(); -impl_write_one_into_for_tuple!(t1 T1); -impl_write_one_into_for_tuple!(t1 T1, t2 T2); -impl_write_one_into_for_tuple!(t1 T1, t2 T2, t3 T3); -impl_write_one_into_for_tuple!(t1 T1, t2 T2, t3 T3, t4 T4); -impl_write_one_into_for_tuple!(t1 T1, t2 T2, t3 T3, t4 T4, t5 T5); -impl_write_one_into_for_tuple!(t1 T1, t2 T2, t3 T3, t4 T4, t5 T5, t6 T6); -impl_write_one_into_for_tuple!(t1 T1, t2 T2, t3 T3, t4 T4, t5 T5, t6 T6, t7 T7); -impl_write_one_into_for_tuple!(t1 T1, t2 T2, t3 T3, t4 T4, t5 T5, t6 T6, t7 T7, t8 T8); -impl_write_one_into_for_tuple!(t1 T1, t2 T2, t3 T3, t4 T4, t5 T5, t6 T6, t7 T7, t8 T8, t9 T9); -impl_write_one_into_for_tuple!(t1 T1, t2 T2, t3 T3, t4 T4, t5 T5, t6 T6, t7 T7, t8 T8, t9 T9, t10 T10); -impl_write_one_into_for_tuple!(t1 T1, t2 T2, t3 T3, t4 T4, t5 T5, t6 T6, t7 T7, t8 T8, t9 T9, t10 T10, t11 T11); -impl_write_one_into_for_tuple!(t1 T1, t2 T2, t3 T3, t4 T4, t5 T5, t6 T6, t7 T7, t8 T8, t9 T9, t10 T10, t11 T11, t12 T12); +impl_for_tuple!(); +impl_for_tuple!(t1 T1); +impl_for_tuple!(t1 T1, t2 T2); +impl_for_tuple!(t1 T1, t2 T2, t3 T3); +impl_for_tuple!(t1 T1, t2 T2, t3 T3, t4 T4); +impl_for_tuple!(t1 T1, t2 T2, t3 T3, t4 T4, t5 T5); +impl_for_tuple!(t1 T1, t2 T2, t3 T3, t4 T4, t5 T5, t6 T6); +impl_for_tuple!(t1 T1, t2 T2, t3 T3, t4 T4, t5 T5, t6 T6, t7 T7); +impl_for_tuple!(t1 T1, t2 T2, t3 T3, t4 T4, t5 T5, t6 T6, t7 T7, t8 T8); +impl_for_tuple!(t1 T1, t2 T2, t3 T3, t4 T4, t5 T5, t6 T6, t7 T7, t8 T8, t9 T9); +impl_for_tuple!(t1 T1, t2 T2, t3 T3, t4 T4, t5 T5, t6 T6, t7 T7, t8 T8, t9 T9, t10 T10); +impl_for_tuple!(t1 T1, t2 T2, t3 T3, t4 T4, t5 T5, t6 T6, t7 T7, t8 T8, t9 T9, t10 T10, t11 T11); +impl_for_tuple!(t1 T1, t2 T2, t3 T3, t4 T4, t5 T5, t6 T6, t7 T7, t8 T8, t9 T9, t10 T10, t11 T11, t12 T12); diff --git a/src/write/macros.rs b/src/write/macros.rs index 5e6ec6b..089bc9e 100644 --- a/src/write/macros.rs +++ b/src/write/macros.rs @@ -13,20 +13,19 @@ /// [WriteInto]: crate::WriteInto #[macro_export(local_inner_macros)] macro_rules! show { - ($($expr:expr),* $(,)? ; $($opt:ident=$val:expr),* $(,)?) => { + ($expr:expr $(, $opt:ident=$val:expr)* $(,)? => $buf:expr) => { unwrap!(|| -> ::std::io::Result<()> { $crate::Writer::new() - $(.$opt($val))* - $(.write(&$expr)?)* - .finish()?; + $(.$opt(&$val))* + .write(&$expr, &mut $buf)?; Ok(()) }()) }; - ($($expr:expr),* $(,)?) => { + ($expr:expr $(, $opt:ident=$val:expr)* $(,)?) => { unwrap!(|| -> ::std::io::Result<()> { $crate::Writer::new() - $(.write(&$expr)?)* - .finish()?; + $(.$opt(&$val))* + .write(&$expr, &mut $crate::stdout())?; Ok(()) }()) }; @@ -36,14 +35,18 @@ macro_rules! show { /// /// [WriteInto]: crate::WriteInto #[macro_export(local_inner_macros)] -macro_rules! impl_write_into { +macro_rules! impl_for_single { ($($ty:ty)*) => { $( - impl $crate::WriteOneInto for $ty { - fn try_write_one_into(&self, s: &mut S) -> ::std::io::Result<()> { + impl $crate::WriteInto for $ty { + fn try_write_into_with_sep(&self, s: &mut S, _sep: &[impl Separator]) -> ::std::io::Result<()> { ::std::write!(s, "{}", self) } } + impl $crate::ranked::Rank for $ty { + const RANK: usize = 0; + const SPACE: bool = true; + } )* }; } diff --git a/src/write/mod.rs b/src/write/mod.rs index 94af63c..f39fd17 100644 --- a/src/write/mod.rs +++ b/src/write/mod.rs @@ -1,38 +1,17 @@ -use crate::{stdout, unwrap, Mat, SepBy}; +use crate::{stdout, SepBy}; +use ranked::Rank; +use separator::{GetDefaultSeparator, Separator}; use std::io::{self, Write}; mod impls; mod macros; +pub mod ranked; +pub(super) mod sep_by; +pub(super) mod separator; pub(super) mod writer; type Result = io::Result; -/// Write an element into a stream. -/// -/// Most types that implement [Display] also implement this. -/// -/// [Display]: std::fmt::Display -pub trait WriteOneInto { - /// Separator between items. - const SEP_ITEM: &'static str = " "; - /// Separator between lines. - const SEP_LINE: &'static str = "\n"; - /// Write into a stream. - fn try_write_one_into(&self, s: &mut S) -> Result; - /// Unwrapping version of [WriteOneInto::try_write_one_into]. - #[track_caller] - #[inline] - fn write_one_into(&self, s: &mut S) { - unwrap!(self.try_write_one_into(s)) - } -} - -impl WriteOneInto for &T { - fn try_write_one_into(&self, s: &mut S) -> Result { - (*self).try_write_one_into(s) - } -} - /// Write into a stream. /// /// - Most types that implement [std::fmt::Display] also implement this. @@ -42,77 +21,105 @@ impl WriteOneInto for &T { /// They write each row separated by a newline, and each item in a row separated by a space. /// /// [Mat]: crate::Mat -pub trait WriteInto { +pub trait WriteInto: Rank { + /// Write into a stream with given separator. + fn try_write_into_with_sep( + &self, + s: &mut S, + sep: &[impl Separator], + ) -> Result; /// Write into a stream. - fn try_write_into(&self, s: &mut S) -> Result; - /// Unwrapping version of [WriteInto::try_write_into]. - #[track_caller] #[inline] - fn write_into(&self, s: &mut S) { - unwrap!(self.try_write_into(s)) + fn try_write_into(&self, s: &mut S) -> Result + where + Self: GetDefaultSeparator, + { + self.try_write_into_with_sep(s, &Self::DEFAULT_SEPARATOR) } - /// Write into a string. - fn try_write_into_string(&self) -> Result { + /// Write into a string with given separator. + #[inline] + fn try_write_into_string_with_sep(&self, sep: &[impl Separator]) -> Result { let mut s = Vec::new(); - self.try_write_into(&mut s)?; + self.try_write_into_with_sep(&mut s, sep)?; // What if the string is not valid UTF-8? String::from_utf8(s).map_err(|err| io::Error::new(io::ErrorKind::InvalidData, err)) } - /// Unwrapping version of [WriteInto::try_write_into_string]. - #[track_caller] + /// Write into a string. #[inline] - fn write_into_string(&self) -> String { - unwrap!(self.try_write_into_string()) + fn try_write_into_string(&self) -> Result + where + Self: GetDefaultSeparator, + { + self.try_write_into_string_with_sep(&Self::DEFAULT_SEPARATOR) } - /// Write into [std::io::Stdout]. - fn try_write(&self) -> Result { - self.try_write_into(&mut stdout()) + /// Write into [std::io::Stdout] with given separator. + #[inline] + fn try_write_with_sep(&self, sep: &[impl Separator]) -> Result { + self.try_write_into_with_sep(&mut stdout(), sep) } - /// Unwrapping version of [WriteInto::try_write]. - #[track_caller] + /// Write into [std::io::Stdout]. #[inline] - fn write(&self) { - unwrap!(self.try_write()) + fn try_write(&self) -> Result + where + Self: GetDefaultSeparator, + { + self.try_write_with_sep(&Self::DEFAULT_SEPARATOR) } } -impl WriteInto for T { - fn try_write_into(&self, s: &mut S) -> Result<()> { - self.try_write_one_into(s) - } -} +// impl WriteInto for T { +// #[inline] +// fn try_write_into_with_sep( +// &self, +// s: &mut S, +// sep: &[impl Separator], +// ) -> Result<()> { +// debug_assert!(sep.is_empty()); +// self.try_write_one_into(s) +// } +// } -impl WriteInto for Vec { - fn try_write_into(&self, s: &mut S) -> Result<()> { - self.as_slice().try_write_into(s) +impl WriteInto for &T { + #[inline] + fn try_write_into_with_sep( + &self, + s: &mut S, + sep: &[impl Separator], + ) -> Result<()> { + (*self).try_write_into_with_sep(s, sep) } } -impl WriteInto for [T; N] { - fn try_write_into(&self, s: &mut S) -> Result<()> { - self.as_slice().try_write_into(s) - } -} -impl WriteInto for [T] { - fn try_write_into(&self, s: &mut S) -> Result<()> { - WriteInto::try_write_into(&self.sep_by(T::SEP_ITEM), s) +impl WriteInto for Vec { + #[inline] + fn try_write_into_with_sep( + &self, + s: &mut S, + sep: &[impl Separator], + ) -> Result<()> { + debug_assert_eq!(sep.len(), Self::RANK); + self.as_slice().try_write_into_with_sep(s, sep) } } -impl WriteInto for Mat { - fn try_write_into(&self, s: &mut S) -> Result<()> { - self.iter() - .map(|row| row.iter().sep_by(T::SEP_ITEM)) - .sep_by(T::SEP_LINE) - .try_write_into(s) +impl WriteInto for [T; N] { + #[inline] + fn try_write_into_with_sep( + &self, + s: &mut S, + sep: &[impl Separator], + ) -> Result<()> { + self.as_slice().try_write_into_with_sep(s, sep) } } - -impl WriteInto for [[T; N]; M] { - fn try_write_into(&self, s: &mut S) -> Result<()> { - self.iter() - .map(|row| row.iter().sep_by(T::SEP_ITEM)) - .sep_by(T::SEP_LINE) - .try_write_into(s) +impl WriteInto for [T] { + #[inline] + fn try_write_into_with_sep( + &self, + s: &mut S, + sep: &[impl Separator], + ) -> Result<()> { + let (sep, residual) = sep.split_first().expect("Separator count mismatch."); + WriteInto::try_write_into_with_sep(&self.sep_by_write_into(sep), s, residual) } } diff --git a/src/write/ranked.rs b/src/write/ranked.rs new file mode 100644 index 0000000..1cf22a2 --- /dev/null +++ b/src/write/ranked.rs @@ -0,0 +1,32 @@ +//! Rank markers. + +/// A trait for types with a dimension. +pub trait Rank { + /// Dimension. + /// + /// Currently this is only a marker, + /// but it can be used after feature `generic_const_exprs` is stable. + /// See + const RANK: usize; + /// Need space between every two items? + const SPACE: bool; +} + +impl Rank for &T { + const RANK: usize = T::RANK; + const SPACE: bool = T::SPACE; +} + +// Implementation for higher-rank types. +impl Rank for Vec { + const RANK: usize = T::RANK + 1; + const SPACE: bool = T::SPACE; +} +impl Rank for [T] { + const RANK: usize = T::RANK + 1; + const SPACE: bool = T::SPACE; +} +impl Rank for [T; N] { + const RANK: usize = T::RANK + 1; + const SPACE: bool = T::SPACE; +} diff --git a/src/sep_by.rs b/src/write/sep_by.rs similarity index 56% rename from src/sep_by.rs rename to src/write/sep_by.rs index fb72657..474c3c9 100644 --- a/src/sep_by.rs +++ b/src/write/sep_by.rs @@ -1,10 +1,9 @@ +use super::{ranked::Rank, separator::Separator, WriteInto}; use std::{ fmt::{self, Binary, Display, LowerExp, LowerHex, Octal, Pointer, UpperExp, UpperHex}, - io::{self, Write}, + io::Write, }; -use crate::WriteInto; - /// Separate items with given separator in [fmt::Display]. /// /// `I` is the iterator type, and it should be [Clone] and [Iterator] for this to work. @@ -13,23 +12,24 @@ use crate::WriteInto; /// /// All configuration on [fmt::Formatter] is delegated to the item type. #[derive(Debug, Clone)] -pub struct SepBy<'a, I> { - sep: &'a str, +pub struct SepBy<'a, I, S: ?Sized> { + sep: &'a S, iter: I, } -impl<'a, I: Iterator + Clone> SepBy<'a, I> { +impl<'a, I: Iterator + Clone, S: Separator + ?Sized> SepBy<'a, I, S> { /// Create a [SepBy]. - pub fn new(iter: I, sep: &'a str) -> Self { + pub fn new(iter: I, sep: &'a S) -> Self { Self { sep, iter } } } macro_rules! impl_for_sep_by { ($trait:ident) => { - impl<'a, I: Iterator + Clone> $trait for SepBy<'a, I> + impl<'a, I: Iterator + Clone, S: Separator + ?Sized> $trait for SepBy<'a, I, S> where I::Item: $trait, + S: Display, { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let mut iter = self.iter.clone(); @@ -37,7 +37,7 @@ macro_rules! impl_for_sep_by { $trait::fmt(&first, f)?; } for item in iter { - f.write_str(self.sep)?; + Display::fmt(&self.sep, f)?; $trait::fmt(&item, f)?; } Ok(()) @@ -56,15 +56,27 @@ impl_for_sep_by!(Pointer); impl_for_sep_by!(LowerExp); impl_for_sep_by!(UpperExp); -impl + Clone, T: WriteInto> WriteInto for SepBy<'_, I> { - fn try_write_into(&self, s: &mut S) -> Result<(), io::Error> { +impl + Clone, T: WriteInto, S: Separator + ?Sized> Rank for SepBy<'_, I, S> { + const RANK: usize = 0; + const SPACE: bool = T::SPACE; +} + +impl + Clone, T: WriteInto, S: Separator + ?Sized> WriteInto + for SepBy<'_, I, S> +{ + fn try_write_into_with_sep( + &self, + s: &mut Stream, + residual: &[impl Separator], + ) -> super::Result { let mut iter = self.iter.clone(); + // eprintln!("sep: {:?}; residual: {:?}", self.sep, residual); if let Some(first) = iter.next() { - first.try_write_into(s)?; + first.try_write_into_with_sep(s, residual)?; } for item in iter { - let _ = s.write(self.sep.as_bytes())?; - item.try_write_into(s)? + self.sep.write(s)?; + item.try_write_into_with_sep(s, residual)? } Ok(()) } diff --git a/src/write/separator.rs b/src/write/separator.rs new file mode 100644 index 0000000..fc3d09e --- /dev/null +++ b/src/write/separator.rs @@ -0,0 +1,97 @@ +use super::ranked::Rank; +use std::io; + +/// Separator. +pub trait Separator: std::fmt::Debug { + /// Write the separator. + fn write(&self, s: &mut S) -> io::Result<()>; +} + +impl Separator for str { + fn write(&self, s: &mut S) -> io::Result<()> { + s.write_all(self.as_bytes()) + } +} + +impl Separator for &T { + fn write(&self, s: &mut S) -> io::Result<()> { + ::write(*self, s) + } +} + +/// Get default separator. +pub trait GetDefaultSeparator { + /// Separator type. + type Separator: Separator + 'static; + /// Default separator. + /// + /// ```rust + /// use iof::*; + /// assert_eq!( as GetDefaultSeparator>::DEFAULT_SEPARATOR, &[" "]); + /// assert_eq!( as GetDefaultSeparator>::DEFAULT_SEPARATOR, &[" "]); + /// assert_eq!( as GetDefaultSeparator>::DEFAULT_SEPARATOR, &[""]); + /// assert_eq!( as GetDefaultSeparator>::DEFAULT_SEPARATOR, &["\n", " "]); + /// assert_eq!( as GetDefaultSeparator>::DEFAULT_SEPARATOR, &["\n", " "]); + /// assert_eq!( as GetDefaultSeparator>::DEFAULT_SEPARATOR, &["\n", ""]); + /// ``` + const DEFAULT_SEPARATOR: &'static [Self::Separator]; +} + +// impl GetDefaultSeparator for T0 { +// type Separator = &'static str; +// const DEFAULT_SEPARATOR: &'static [&'static str] = &[]; +// } + +// macro_rules! impl_rank1 { +// ($ty:ty, $($tt:tt)*) => { +// impl GetDefaultSeparator for $ty { +// type Separator = &'static str; +// const DEFAULT_SEPARATOR: &'static [&'static str] = &[" "]; +// } +// }; +// } + +// impl_rank1!(Vec,); +// impl_rank1!([T0; N], const N: usize); +// impl_rank1!([T0],); + +// macro_rules! impl_rank2 { +// ($ty:ty, $($tt:tt)*) => { +// impl GetDefaultSeparator for $ty { +// type Separator = &'static str; +// const DEFAULT_SEPARATOR: &'static [&'static str] = &[" ", "\n"]; +// } +// }; +// } + +// impl_rank2!(Vec>, ); +// impl_rank2!([Vec], ); +// impl_rank2!([Vec; N], const N: usize); + +// impl_rank2!(Vec<[T0; M]>, const M: usize); +// impl_rank2!([[T0; M]], const M: usize); +// impl_rank2!([[T0; M]; N], const M: usize, const N: usize); + +// impl_rank2!(Vec<&[T0]>, ); +// impl_rank2!([&[T0]], ); +// impl_rank2!([&[T0]; N], const N: usize); + +const fn get_rank(rank: usize, space: bool) -> &'static [&'static str] { + match (rank, space) { + (0, _) => &[], + (1, true) => &[" "], + (1, false) => &[""], + (2, true) => &["\n", " "], + (2, false) => &["\n", ""], + // Rank > 2 is not supported. + // `unimplemented!()` would cause a compile-time error, + // so we use an empty slice instead. + (_, true) => &[], + (_, false) => &[], + } +} + +impl GetDefaultSeparator for T { + type Separator = &'static str; + const DEFAULT_SEPARATOR: &'static [&'static str] = get_rank(T::RANK, T::SPACE); +} diff --git a/src/write/writer.rs b/src/write/writer.rs index 9dfc604..2f6aa44 100644 --- a/src/write/writer.rs +++ b/src/write/writer.rs @@ -1,34 +1,31 @@ +use crate::Separator; + use super::WriteInto; -use crate::stdout; use std::io::{self, Write}; /// Configuration for the writer. -pub struct Writer<'sep, 'end, 'buf> { - index: usize, - sep: &'sep str, +pub struct Writer<'sep, 'end> { + sep: Option<&'sep [&'sep str]>, end: &'end str, - buf: Option<&'buf mut dyn Write>, } -impl<'sep, 'end, 'buf> Default for Writer<'sep, 'end, 'buf> { +impl<'sep, 'end> Default for Writer<'sep, 'end> { fn default() -> Self { Self { - index: 0, - sep: " ", + sep: None, end: "\n", - buf: None, } } } -impl<'sep, 'end, 'buf> Writer<'sep, 'end, 'buf> { +impl<'sep, 'end> Writer<'sep, 'end> { /// Create a new writer. pub fn new() -> Self { Self::default() } - /// Set the separator. - pub fn sep(&mut self, sep: &'sep str) -> &mut Self { - self.sep = sep; + /// Set the separator. + pub fn sep(&mut self, sep: &'sep [&'sep str]) -> &mut Self { + self.sep = Some(sep); self } /// Set the end. @@ -36,38 +33,17 @@ impl<'sep, 'end, 'buf> Writer<'sep, 'end, 'buf> { self.end = end; self } - /// Set the buffer. - pub fn buf(&mut self, buf: &'buf mut impl Write) -> &mut Self { - self.buf = Some(buf); - self - } /// Write a value. - pub fn write(&mut self, value: &V) -> io::Result<&mut Self> { - match self.buf.as_mut() { - Some(buf) => { - if self.index > 0 { - self.sep.try_write_into(buf)?; - } - value.try_write_into(buf)?; - } - None => { - let buf = &mut stdout(); - if self.index > 0 { - self.sep.try_write_into(buf)?; - } - value.try_write_into(buf)?; - } - } - self.index += 1; - Ok(self) - } - /// Finish writing. - pub fn finish(&mut self) -> io::Result<()> { - if let Some(buf) = self.buf.as_mut() { - self.end.try_write_into(buf) + pub fn write( + &mut self, + value: &V, + buf: &mut impl Write, + ) -> io::Result<()> { + if let Some(sep) = self.sep { + value.try_write_into_with_sep(buf, sep)?; } else { - let buf = &mut stdout(); - self.end.try_write_into(buf) + value.try_write_into(buf)?; } + self.end.write(buf) } } diff --git a/tests/array.rs b/tests/array.rs index 40ac960..4ce4471 100644 --- a/tests/array.rs +++ b/tests/array.rs @@ -118,17 +118,17 @@ fn read_boxed_array_insufficient() { #[test] fn display() { let s = [1, 2, 3]; - assert_eq!(s.try_write_into_string().unwrap(), "1 2 3"); + assert_eq!(s.try_write_into_string_with_sep(&[" "]).unwrap(), "1 2 3"); let s = [[1, 2, 3], [4, 5, 6]]; - assert_eq!(s.try_write_into_string().unwrap(), "1 2 3\n4 5 6"); + assert_eq!(s.try_write_into_string_with_sep(&["\n", " "]).unwrap(), "1 2 3\n4 5 6"); let s = [[1, 2], [3, 4]]; - assert_eq!(s.try_write_into_string().unwrap(), "1 2\n3 4"); + assert_eq!(s.try_write_into_string_with_sep(&["\n", " "]).unwrap(), "1 2\n3 4"); let s = [[1, 2]]; - assert_eq!(s.try_write_into_string().unwrap(), "1 2"); + assert_eq!(s.try_write_into_string_with_sep(&["\n", " "]).unwrap(), "1 2"); let s: [[usize; 0]; 0] = []; - assert_eq!(s.try_write_into_string().unwrap(), ""); + assert_eq!(s.try_write_into_string_with_sep(&[" "]).unwrap(), ""); } diff --git a/tests/char.rs b/tests/char.rs index 26793d6..519343d 100644 --- a/tests/char.rs +++ b/tests/char.rs @@ -1,6 +1,21 @@ -use iof::{InputStream, ReadInto, ReadOneInto}; +use iof::{InputStream, Mat, ReadInto, ReadOneInto}; use std::io::Cursor; +#[test] +fn check_separator() { + use iof::GetDefaultSeparator; + assert_eq!(::DEFAULT_SEPARATOR, &[""; 0]); + assert_eq!(>::DEFAULT_SEPARATOR, &[""]); + assert_eq!(>::DEFAULT_SEPARATOR, &["\n", ""]); +} + +#[allow(dead_code)] +mod check_impl { + use iof::{ranked::Rank, GetDefaultSeparator}; + const fn check_impl() {} + const CHAR: () = check_impl::(); +} + #[test] #[should_panic = "expect more characters before EOF"] fn from_empty() { diff --git a/tests/ill_buffer.rs b/tests/ill_buffer.rs index 42728e3..20b494b 100644 --- a/tests/ill_buffer.rs +++ b/tests/ill_buffer.rs @@ -1,4 +1,4 @@ -use iof::{BufReadExt, InputStream, ReadInto, ReadOneInto, WriteInto, WriteOneInto}; +use iof::{unwrap, BufReadExt, InputStream, ReadInto, ReadOneInto, WriteInto}; use std::io; struct IllBuffer; @@ -114,13 +114,7 @@ fn try_write() { #[test] #[should_panic = "ill buffer"] fn write_into() { - [1, 2, 3].write_into(&mut IllBuffer); -} - -#[test] -#[should_panic = "ill buffer"] -fn write_one_into() { - 42_usize.write_one_into(&mut IllBuffer); + unwrap!([1, 2, 3].try_write_into(&mut IllBuffer)); } #[test] diff --git a/tests/ill_data.rs b/tests/ill_data.rs index 9198b19..f558106 100644 --- a/tests/ill_data.rs +++ b/tests/ill_data.rs @@ -1,10 +1,18 @@ -use iof::WriteInto; +use iof::{ranked::Rank, unwrap, Separator, WriteInto}; use std::io::{Result, Write}; struct IllData(&'static [u8]); +impl Rank for IllData { + const RANK: usize = 0; + const SPACE: bool = true; +} impl WriteInto for IllData { - fn try_write_into(&self, s: &mut S) -> Result<()> { + fn try_write_into_with_sep( + &self, + s: &mut S, + _sep: &[impl Separator], + ) -> Result<()> { s.write_all(self.0)?; Ok(()) } @@ -12,23 +20,23 @@ impl WriteInto for IllData { #[test] fn write_unicode() { - IllData("🦀🦀🦀".as_bytes()).write(); + unwrap!(IllData("🦀🦀🦀".as_bytes()).try_write()); } #[test] #[should_panic = "incomplete utf-8 byte sequence from index 0"] fn try_write_to_string_ill_0xcc() { - let _ = IllData(b"\xcc").write_into_string(); + let _ = unwrap!(IllData(b"\xcc").try_write_into_string()); } #[test] #[should_panic = "invalid utf-8 sequence of 1 bytes from index 0"] fn try_write_to_string_ill_0xff() { - let _ = IllData(b"\xff").write_into_string(); + let _ = unwrap!(IllData(b"\xff").try_write_into_string()); } #[test] #[should_panic = "incomplete utf-8 byte sequence from index 0"] fn try_write_to_string_ill_0xd0() { - let _ = IllData(b"\xd0").write_into_string(); + let _ = unwrap!(IllData(b"\xd0").try_write_into_string()); } diff --git a/tests/integers.rs b/tests/integers.rs index 84cb51c..462201f 100644 --- a/tests/integers.rs +++ b/tests/integers.rs @@ -1,6 +1,13 @@ use iof::*; use std::io::Cursor; +#[test] +fn check_separator() { + use iof::GetDefaultSeparator; + assert_eq!(>::DEFAULT_SEPARATOR, &[" "]); + assert_eq!(>::DEFAULT_SEPARATOR, &[" "]); +} + #[test] fn try_read_single_3() { let reader = Cursor::new("1 2 3".as_bytes()); @@ -199,15 +206,7 @@ fn try_read_char_only_sign() { #[test] fn try_write_one_into() { let mut s = Vec::new(); - 42.try_write_one_into(&mut s).unwrap(); - let s = String::from_utf8(s).unwrap(); - assert_eq!(s, "42"); -} - -#[test] -fn write_one_into() { - let mut s = Vec::new(); - 42.write_one_into(&mut s); + 42.try_write_into(&mut s).unwrap(); let s = String::from_utf8(s).unwrap(); assert_eq!(s, "42"); } diff --git a/tests/mat.rs b/tests/mat.rs index f659c56..fd79a4e 100644 --- a/tests/mat.rs +++ b/tests/mat.rs @@ -38,7 +38,7 @@ fn read_m_n() { ]" ); - assert_eq!(mat.write_into_string(), "1 2 3\n4 5 6"); + assert_eq!(unwrap!(mat.try_write_into_string()), "1 2 3\n4 5 6"); assert!(::try_read_n_from(&mut reader, 1).is_err()); } @@ -61,7 +61,7 @@ fn read_same_rows() { assert_eq!(row, [2, 3, 2].as_slice()); } - assert_eq!(mat.write_into_string(), "2 3 2\n2 3 2\n2 3 2"); + assert_eq!(unwrap!(mat.try_write_into_string()), "2 3 2\n2 3 2\n2 3 2"); assert!(::try_read_m_n_from(&mut reader, 1, 1).is_err()); } @@ -78,7 +78,7 @@ fn read_all_same() { assert_eq!(mat.iter().size_hint(), (2, Some(2))); assert_eq!(format!("{:?}", mat), "[[2, 2, 2], [2, 2, 2]]"); - assert_eq!(mat.write_into_string(), "2 2 2\n2 2 2"); + assert_eq!(unwrap!(mat.try_write_into_string()), "2 2 2\n2 2 2"); assert!(::try_read_m_n_from(&mut reader, 1, 1).is_err()); } @@ -95,7 +95,7 @@ fn read_char_mat() { assert_eq!(mat.iter().size_hint(), (2, Some(2))); assert_eq!(format!("{:?}", mat), "[['1', '2', '3'], ['4', '5', '6']]"); - assert_eq!(mat.write_into_string(), "123\n456"); + assert_eq!(unwrap!(mat.try_write_into_string()), "123\n456"); assert!(::try_read_m_n_from(&mut reader, 1, 1).is_err()); } diff --git a/tests/slice.rs b/tests/slice.rs new file mode 100644 index 0000000..e69de29 diff --git a/tests/stdout.rs b/tests/stdout.rs index 7b0d645..70d5d3a 100644 --- a/tests/stdout.rs +++ b/tests/stdout.rs @@ -1,13 +1,9 @@ -use iof::WriteInto; +use iof::{unwrap, WriteInto}; #[test] fn try_write_vec() { let vec = vec![1, 2, 3]; - vec.try_write().unwrap(); -} - -#[test] -fn write_vec() { + unwrap!(vec.try_write()); let vec = vec![1]; - vec.write(); + unwrap!(vec.try_write()); } diff --git a/tests/string.rs b/tests/string.rs index 87c5e1b..cdcbdae 100644 --- a/tests/string.rs +++ b/tests/string.rs @@ -1,6 +1,15 @@ -use iof::{show, InputStream, ReadFrom, ReadInto, ReadOneFrom, ReadOneInto}; +use iof::{show, unwrap, InputStream, ReadFrom, ReadInto, ReadOneFrom, ReadOneInto}; use std::io::Cursor; +#[allow(dead_code)] +mod check_impl { + use iof::{ranked::Rank, GetDefaultSeparator}; + const fn check_impl() {} + const STRING: () = check_impl::(); + const STR: () = check_impl::(); + const STATIC_STR: () = check_impl::<&'static str>(); +} + #[test] fn read_strings() { let reader = Cursor::new("Hello, World!".as_bytes()); @@ -148,6 +157,7 @@ fn read_in_line_some_unicode() { #[test] fn string() { let mut buf = Cursor::new(Vec::new()); - show!("Hello, World!"; end = "", buf = &mut buf); - show!("🦀🦀🦀"; buf = &mut buf); + show!("Hello, World!", end = "" => &mut buf); + show!("🦀🦀🦀", => &mut buf); + assert_eq!(unwrap!(String::from_utf8(buf.into_inner())), "Hello, World!🦀🦀🦀\n"); } diff --git a/tests/tuple.rs b/tests/tuple.rs index 8438800..fcad648 100644 --- a/tests/tuple.rs +++ b/tests/tuple.rs @@ -69,8 +69,8 @@ fn show() { show!((1, 2, 3)); show!(((1, 2), (3, 4))); show!(()); - show!((1, 2, 3), (4, 5, 6), (7, 8, 9)); + show!(((1, 2, 3), (4, 5, 6), (7, 8, 9))); let mut buf = Vec::new(); - show!((1, 2, 3), (4, 5, 6), (7, 8, 9); buf = &mut buf); - assert_eq!(String::from_utf8(buf).unwrap(), "1 2 3 4 5 6 7 8 9\n"); + show!(((1, 2, 3), (4, 5, 6), (7, 8, 9)) => buf); + assert_eq!(String::from_utf8(buf).unwrap(), "1 2 3\n4 5 6\n7 8 9\n"); } diff --git a/tests/vec.rs b/tests/vec.rs index dce49dd..921faf5 100644 --- a/tests/vec.rs +++ b/tests/vec.rs @@ -8,7 +8,7 @@ fn read_n() { let vec: Vec = reader.read_n(3); assert_eq!(vec, &[1, 2, 3]); - assert_eq!(vec.sep_by(" ").to_string(), "1 2 3"); + assert_eq!(vec.sep_by(&" ").to_string(), "1 2 3"); assert!(::try_read_n_from(&mut reader, 1).is_err()); } @@ -113,7 +113,7 @@ fn read_all() -> anyhow::Result<()> { let set: BTreeSet = reader.read_all().into_iter().collect(); assert_eq!(set, BTreeSet::from([1, 2, 3])); - assert_eq!(set.iter().sep_by(" ").to_string(), "1 2 3"); + assert_eq!(set.iter().sep_by(&" ").to_string(), "1 2 3"); Ok(()) } @@ -140,15 +140,15 @@ fn read_all_digit_error() { fn display() { let s = Vec::from([1, 2, 3]); assert_eq!(s.try_write_into_string().unwrap(), "1 2 3"); - assert_eq!(s.write_into_string(), "1 2 3"); + assert_eq!(unwrap!(s.try_write_into_string()), "1 2 3"); let s = Vec::from([1]); assert_eq!(s.try_write_into_string().unwrap(), "1"); - assert_eq!(s.write_into_string(), "1"); + assert_eq!(unwrap!(s.try_write_into_string()), "1"); let s: Vec = Vec::from([]); assert_eq!(s.try_write_into_string().unwrap(), ""); - assert_eq!(s.write_into_string(), ""); + assert_eq!(unwrap!(s.try_write_into_string()), ""); } #[test]