diff --git a/libs/lg2/Cargo.toml b/libs/lg2/Cargo.toml new file mode 100644 index 00000000..197afa89 --- /dev/null +++ b/libs/lg2/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "lg2" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] diff --git a/libs/lg2/src/lib.rs b/libs/lg2/src/lib.rs new file mode 100644 index 00000000..c4f41367 --- /dev/null +++ b/libs/lg2/src/lib.rs @@ -0,0 +1,238 @@ +mod map; +mod table; +mod vec2; +mod vecs; + +pub use map::hmap; +pub use map::vmap; +use std::borrow::Borrow; +use std::fmt; +use table::Align; +pub use vec2::vec2; +pub use vecs::hvec; +pub use vecs::vvec; + +pub fn bools(iter: I) -> String +where + B: Borrow, + I: IntoIterator, +{ + format!( + "[{}]", + iter.into_iter() + .map(|b| ['.', '#'][usize::from(*(b.borrow()))]) + .collect::(), + ) +} + +pub fn align_of(s: &str) -> Align { + // To improve this: https://doc.rust-lang.org/reference/tokens.html#floating-point-literals + match s.parse::() { + Ok(_) => Align::Right, + Err(_) => Align::Left, + } +} + +#[macro_export] +macro_rules! lg { + (@contents $head:expr $(, $tail:expr)*) => {{ + $crate::__lg_internal!($head); + $( + eprint!(","); + $crate::__lg_internal!($tail); + )* + eprintln!(); + }}; + ($($expr:expr),* $(,)?) => {{ + eprint!("{} \u{276f}", line!()); + $crate::lg!(@contents $($expr),*) + }}; +} + +#[doc(hidden)] +#[macro_export] +macro_rules! __lg_internal { + ($value:expr) => {{ + match $value { + head => { + eprint!(" {} = {}", stringify!($value), $crate::format(&head)); + } + } + }}; +} + +#[macro_export] +macro_rules! table { + ($vec2:expr) => { + eprintln!( + "{}", + $crate::vec2($crate::remove_ampersand(stringify!($vec2)), $vec2) + ); + }; +} + +#[macro_export] +macro_rules! vmap { + ($map:expr) => { + eprintln!( + "{}", + $crate::vmap($crate::remove_ampersand(stringify!($map)), $map) + ); + }; +} + +#[macro_export] +macro_rules! hmap { + ($map:expr) => { + eprintln!( + "{}", + $crate::hmap($crate::remove_ampersand(stringify!($map)), $map) + ); + }; +} + +#[macro_export] +macro_rules! vvec { + ($($(@field $field:ident)* $vecs:expr),+ $(,)?) => { + let mut vecs = Vec::new(); + $( + let name = $crate::remove_ampersand(stringify!($vecs)); + #[allow(unused_mut, unused_assignments)] + let mut has_field = false; + $( + #[allow(unused_mut, unused_assignments)] + { + let mut name = name.to_owned(); + has_field = true; + name.push_str("."); + name.push_str(stringify!($field)); + let values = (&$vecs).into_iter().map(|v| $crate::format(&v.$field)).collect::>(); + vecs.push((name, values)) + } + )* + if !has_field { + let values = (&$vecs).into_iter().map(|v| $crate::format(&v)).collect::>(); + vecs.push((name.to_owned(), values)) + } + )+ + eprintln!("{}", $crate::vvec(&vecs)); + }; +} + +#[macro_export] +macro_rules! hvec { + ($($(@field $field:ident)* $vecs:expr),+ $(,)?) => { + let mut vecs = Vec::new(); + $( + let name = $crate::remove_ampersand(stringify!($vecs)); + #[allow(unused_mut, unused_assignments)] + let mut has_field = false; + $( + #[allow(unused_mut, unused_assignments)] + { + let mut name = name.to_owned(); + has_field = true; + name.push_str("."); + name.push_str(stringify!($field)); + let values = (&$vecs).into_iter().map(|v| $crate::format(&v.$field)).collect::>(); + vecs.push((name, values)) + } + )* + if !has_field { + let values = (&$vecs).into_iter().map(|v| $crate::format(&v)).collect::>(); + vecs.push((name.to_owned(), values)) + } + )+ + eprintln!("{}", $crate::hvec(&vecs)); + }; +} + +pub fn remove_ampersand(mut s: &str) -> &str { + while let Some(t) = s.strip_prefix('&') { + s = t; + } + s +} + +pub fn format(t: &T) -> String { + let s = format!("{t:?}") + .replace("340282366920938463463374607431768211455", "*") // u128 + .replace("170141183460469231731687303715884105727", "*") // i128 + .replace("18446744073709551615", "*") // u64 + .replace("9223372036854775807", "*") // i64 + .replace("-9223372036854775808", "*") // i64 + .replace("4294967295", "*") // u32 + .replace("2147483647", "*") // i32 + .replace("-2147483648", "*") // i32 + .replace("None", "*") + .replace("true", "#") + .replace("false", "."); + let mut s = s.as_str(); + while s.starts_with("Some(") { + s = s.strip_prefix("Some(").unwrap(); + s = s.strip_suffix(')').unwrap(); + } + while s.len() > 2 && s.starts_with('"') && s.ends_with('"') { + s = s.strip_prefix('"').unwrap(); + s = s.strip_suffix('"').unwrap(); + } + s.to_owned() +} + +#[cfg(test)] +mod test { + use super::*; + use std::collections::BTreeSet; + use std::iter::empty; + + #[test] + fn test_macro_invocation() { + vmap!(&[(0, 0)]); + hmap!(&[(0, 0)]); + hvec!(&[0]); + hvec!(&[0], &["a", "b"]); + vvec!(&[0]); + vvec!(&[0], &["a", "b"]); + lg!(4); + + let a = [0..3, 4..6]; + hvec!(&a); + hvec!( + &a, + @field start @field end &a, + @field start &a, + @field end &a, + ); + vvec!(&a); + vvec!( + @field start &a, + @field end &a, + ); + } + + #[test] + fn test_bools_format() { + assert_eq!(bools([false]).as_str(), "[.]"); + assert_eq!(bools([true]).as_str(), "[#]"); + assert_eq!(bools([false, true]).as_str(), "[.#]"); + assert_eq!(bools([true, false]).as_str(), "[#.]"); + } + + #[test] + fn test_bools_generics() { + assert_eq!(bools(<[bool; 0]>::default()).as_str(), "[]"); + assert_eq!(bools(<[bool; 0]>::default()).as_str(), "[]"); + assert_eq!(bools(<[&bool; 0]>::default()).as_str(), "[]"); + assert_eq!(bools(<[bool; 0]>::default().as_slice()).as_str(), "[]"); + assert_eq!(bools(Vec::::new()).as_str(), "[]"); + assert_eq!(bools(Vec::<&bool>::new()).as_str(), "[]"); + assert_eq!(bools(Vec::<&mut bool>::new()).as_str(), "[]"); + assert_eq!(bools(Vec::::new()).as_str(), "[]"); + assert_eq!(bools(Vec::::new()).as_str(), "[]"); + assert_eq!(bools(BTreeSet::::new()).as_str(), "[]"); + assert_eq!(bools(empty::()).as_str(), "[]"); + assert_eq!(bools(empty::()).as_str(), "[]"); + assert_eq!(bools(empty::<&bool>()).as_str(), "[]"); + assert_eq!(bools(empty::<&bool>()).as_str(), "[]"); + } +} diff --git a/libs/lg2/src/map.rs b/libs/lg2/src/map.rs new file mode 100644 index 00000000..6bd27576 --- /dev/null +++ b/libs/lg2/src/map.rs @@ -0,0 +1,140 @@ +use crate::align_of; +use crate::format; +use crate::table::Align; +use crate::table::Cell; +use crate::table::Table; +use std::collections; +use std::collections::BTreeMap; +use std::collections::HashMap; +use std::fmt; +use std::iter; +use std::slice; +use std::vec; + +pub fn vmap<'a, K, V, M>(title: &str, map: M) -> Table +where + M: Copy + Map<'a, K = K, V = V>, + K: fmt::Debug, + V: fmt::Debug, +{ + Table { + table: iter::once(vec![ + Cell { + text: String::new(), + align: Align::Left, + }, + Cell { + text: title.to_string(), + align: Align::Center, + }, + ]) + .chain(map.map_iter().map(|(k, v)| { + let v = format(&v); + vec![ + Cell { + text: format(&k), + align: Align::Center, + }, + Cell { + align: align_of(&v), + text: v, + }, + ] + })) + .collect(), + } +} + +pub fn hmap<'a, K, V, M>(title: &str, map: M) -> Table +where + M: Copy + Map<'a, K = K, V = V>, + K: fmt::Debug, + V: fmt::Debug, +{ + Table { + table: vec![ + iter::once(Cell { + text: String::new(), + align: Align::Left, + }) + .chain(map.map_iter().map(|(k, _)| Cell { + text: format(&k), + align: Align::Center, + })) + .collect(), + iter::once(Cell { + text: title.to_string(), + align: Align::Left, + }) + .chain(map.map_iter().map(|(_, v)| { + let v = format(&v); + Cell { + align: align_of(&v), + text: v, + } + })) + .collect(), + ], + } +} + +pub fn deconstruct_ref_tuple((k, v): &(K, V)) -> (&K, &V) { + (k, v) +} + +pub trait Map<'a>: 'a { + type K; + type V; + type I: Iterator; + fn map_iter(self) -> Self::I; +} + +impl<'a, K, V> Map<'a> for &'a HashMap { + type I = collections::hash_map::Iter<'a, K, V>; + type K = K; + type V = V; + + fn map_iter(self) -> Self::I { + self.iter() + } +} + +impl<'a, K, V> Map<'a> for &'a BTreeMap { + type I = collections::btree_map::Iter<'a, K, V>; + type K = K; + type V = V; + + fn map_iter(self) -> Self::I { + self.iter() + } +} + +impl<'a, K, V> Map<'a> for &'a [(K, V)] { + type I = iter::Map, fn(&(K, V)) -> (&K, &V)>; + type K = K; + type V = V; + + fn map_iter(self) -> Self::I { + self.iter().map(deconstruct_ref_tuple) + } +} + +impl<'a, K, V> Map<'a> for &'a Vec<(K, V)> { + type I = iter::Map, fn(&(K, V)) -> (&K, &V)>; + type K = K; + type V = V; + + fn map_iter(self) -> Self::I { + self.iter().map(deconstruct_ref_tuple) + } +} + +impl<'a, const N: usize, K, V> Map<'a> for &'a [(K, V); N] { + type I = iter::Map, fn(&(K, V)) -> (&K, &V)>; + type K = K; + type V = V; + + fn map_iter(self) -> Self::I { + self.iter().map(deconstruct_ref_tuple) + } +} diff --git a/libs/lg2/src/table.rs b/libs/lg2/src/table.rs new file mode 100644 index 00000000..a9abc008 --- /dev/null +++ b/libs/lg2/src/table.rs @@ -0,0 +1,64 @@ +use core::fmt; + +const GRAY: &str = "\x1b[48;2;127;127;127;37m"; +const RESET: &str = "\x1b[0m"; + +pub struct Table { + pub table: Vec>, +} + +pub struct Cell { + pub text: String, + pub align: Align, +} + +pub enum Align { + Left, + Center, + Right, +} + +impl fmt::Display for Table { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + struct ColumnFormat<'a> { + pre: &'a str, + width: usize, + post: &'a str, + } + let Self { table } = self; + let w = table[0].len(); + assert!(table.iter().all(|row| row.len() == w)); + let column_format = (0..w) + .map(|j| ColumnFormat { + pre: " ", + width: table + .iter() + .map(|row| row[j].text.len().max(1)) + .max() + .unwrap(), + post: if j == 0 { " │" } else { " " }, + }) + .collect::>(); + for (i, row) in table.iter().enumerate() { + if i == 0 { + write!(f, "{GRAY}")?; + } + for (&ColumnFormat { pre, width, post }, Cell { text, align }) in + column_format.iter().zip(row) + { + write!(f, "{pre}")?; + match align { + Align::Left => write!(f, "{: write!(f, "{:^width$}", text)?, + Align::Right => write!(f, "{:>width$}", text)?, + } + write!(f, "{post}")?; + } + if i == 0 { + write!(f, "{RESET}")?; + } + writeln!(f)?; + } + Ok(()) + } +} diff --git a/libs/lg2/src/vec2.rs b/libs/lg2/src/vec2.rs new file mode 100644 index 00000000..28cdd4c0 --- /dev/null +++ b/libs/lg2/src/vec2.rs @@ -0,0 +1,53 @@ +use crate::align_of; +use crate::format; +use crate::table::Align; +use crate::table::Cell; +use crate::table::Table; +use std::fmt; +use std::iter; + +pub fn vec2<'a, T, R, S>(title: &str, vec2: &'a S) -> Table +where + T: fmt::Debug + 'a, + &'a R: Copy + IntoIterator + 'a, + &'a S: Copy + IntoIterator, +{ + let w = vec2 + .into_iter() + .map(|row| row.into_iter().count()) + .max() + .unwrap(); + Table { + table: iter::once( + iter::once(Cell { + text: title.to_string(), + align: Align::Left, + }) + .chain((0..w).map(|i| Cell { + text: i.to_string(), + align: Align::Center, + })) + .collect(), + ) + .chain(vec2.into_iter().enumerate().map(|(j, row)| { + iter::once(Cell { + text: j.to_string(), + align: Align::Center, + }) + .chain(row.into_iter().map(|v| { + let v = format(&v); + Cell { + align: align_of(&v), + text: v, + } + })) + .chain(iter::repeat_with(|| Cell { + text: String::new(), + align: Align::Left, + })) + .take(1 + w) + .collect() + })) + .collect(), + } +} diff --git a/libs/lg2/src/vecs.rs b/libs/lg2/src/vecs.rs new file mode 100644 index 00000000..ae40beb1 --- /dev/null +++ b/libs/lg2/src/vecs.rs @@ -0,0 +1,71 @@ +use super::table::Cell; +use super::table::Table; +use crate::align_of; +use crate::table::Align; +use std::iter; + +pub fn hvec(vecs: &[(String, Vec)]) -> Table { + let w = vecs.iter().map(|(_, row)| row.len()).max().unwrap(); + Table { + table: iter::once( + iter::once(Cell { + text: String::new(), + align: Align::Left, + }) + .chain((0..w).map(|i| Cell { + text: i.to_string(), + align: Align::Center, + })) + .collect(), + ) + .chain(vecs.iter().map(|(title, row)| { + iter::once(Cell { + text: title.to_string(), + align: Align::Center, + }) + .chain(row.iter().map(|v| Cell { + align: align_of(v), + text: v.clone(), + })) + .chain(iter::repeat_with(|| Cell { + text: String::new(), + align: Align::Left, + })) + .take(1 + w) + .collect() + })) + .collect(), + } +} + +pub fn vvec(vecs: &[(String, Vec)]) -> Table { + let h = vecs.iter().map(|(_, col)| col.len()).max().unwrap(); + Table { + table: iter::once( + iter::once(Cell { + text: String::new(), + align: Align::Center, + }) + .chain(vecs.iter().map(|(title, _)| Cell { + text: title.to_string(), + align: Align::Center, + })) + .collect(), + ) + .chain((0..h).map(|i| { + iter::once(Cell { + text: i.to_string(), + align: Align::Center, + }) + .chain(vecs.iter().map(|(_, vec)| { + let v = vec.get(i).map_or("", String::as_str); + Cell { + align: align_of(v), + text: v.to_string(), + } + })) + .collect() + })) + .collect(), + } +}