diff --git a/mono-fmt-macro/src/lib.rs b/mono-fmt-macro/src/lib.rs index d817229..6f6f90e 100644 --- a/mono-fmt-macro/src/lib.rs +++ b/mono-fmt-macro/src/lib.rs @@ -11,6 +11,8 @@ use syn::{ Expr, LitStr, Token, }; +// TODO: Rewrite using state machine please + struct Input { format_str: String, exprs: Punctuated, @@ -43,10 +45,48 @@ impl Parse for Input { } } +#[derive(Debug, PartialEq)] +struct Advanced { + width: Option, + fill: Option, + align: Option, +} + enum FmtPart { Literal(String), Debug(Expr), Display(Expr), + Advanced(Advanced, Expr), +} + +impl std::fmt::Debug for FmtPart { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Literal(arg0) => f.debug_tuple("Literal").field(arg0).finish(), + Self::Debug(arg0) => f.debug_tuple("Debug").finish(), + Self::Display(arg0) => f.debug_tuple("Display").finish(), + Self::Advanced(arg0, arg1) => f.debug_tuple("Advanced").field(arg0).finish(), + } + } +} + +impl PartialEq for FmtPart { + fn eq(&self, other: &Self) -> bool { + match (self, other) { + (Self::Literal(a), Self::Literal(b)) => a == b, + (Self::Debug(_), Self::Debug(_)) => true, + (Self::Display(_), Self::Display(_)) => true, + (Self::Advanced(a, _), Self::Advanced(b, _)) => a == b, + _ => false, + } + } +} + +#[derive(Debug, PartialEq)] +enum Alignment { + Left, + Center, + Right, } struct Formatter<'a, I> { @@ -59,7 +99,6 @@ impl<'a, I> Formatter<'a, I> where I: Iterator, { - fn expect_expr(&mut self) -> Expr { self.exprs .next() @@ -77,30 +116,19 @@ where } } + fn eat(&mut self, char: char) -> bool { + if self.string.peek() == Some(&char) { + self.string.next(); + return true; + } + false + } + fn parse(mut self) -> Vec { let mut next_string = String::new(); while let Some(char) = self.string.next() { match char { - '{' => match self.string.next() { - Some('}') => { - self.save_string(std::mem::take(&mut next_string)); - let expr = self.expect_expr(); - self.fmt_parts.push(FmtPart::Display(expr)); - } - Some(':') => { - self.save_string(std::mem::take(&mut next_string)); - self.expect_char('?'); - self.expect_char('}'); - let expr = self.expect_expr(); - self.fmt_parts.push(FmtPart::Debug(expr)) - } - Some(other) => { - panic!("expected }}, found '{}'", other) - } - None => { - panic!("expected '}}'") - } - }, + '{' => self.fmt_part(&mut next_string), other => { next_string.push(other); } @@ -111,6 +139,65 @@ where self.fmt_parts } + fn fmt_part(&mut self, next_string: &mut String) { + match self.string.next() { + Some('}') => { + self.save_string(std::mem::take(next_string)); + let expr = self.expect_expr(); + self.fmt_parts.push(FmtPart::Display(expr)); + } + Some(':') => { + self.save_string(std::mem::take(next_string)); + + if self.eat('?') { + let expr = self.expect_expr(); + self.fmt_parts.push(FmtPart::Debug(expr)); + } else { + let mut advanced = Advanced { + width: None, + fill: None, + align: None, + }; + self.advanced_fmt(&mut advanced, true); + let expr = self.expect_expr(); + self.fmt_parts.push(FmtPart::Advanced(advanced, expr)); + } + + self.expect_char('}'); + } + Some(other) => { + panic!("expected }}, found '{}'", other) + } + None => { + panic!("expected '}}'") + } + } + } + + fn advanced_fmt(&mut self, advanced: &mut Advanced, allow_fill: bool) { + match self.string.next().expect("expected something after {:") { + '?' => unreachable!(), + '<' => { + advanced.align = Some(Alignment::Left); + } + '>' => { + advanced.align = Some(Alignment::Right); + } + '^' => { + advanced.align = Some(Alignment::Center); + } + fill if allow_fill => { + advanced.fill = Some(fill); + self.advanced_fmt(advanced, false) + } + char => panic!("invalid char {char}"), + } + + if let Some(width) = self.string.next() { + advanced.width = Some(width.to_string().parse().unwrap()); + } + } + fn save_string(&mut self, string: String) { if string.is_empty() { return; @@ -132,6 +219,9 @@ impl ToTokens for FmtPart { FmtPart::Debug(expr) => { quote! { ::mono_fmt::_private::DebugArg(#expr) } } + FmtPart::Advanced(_, _) => { + todo!() + } }; tokens.extend(own_tokens); @@ -155,3 +245,44 @@ pub fn format_args(tokens: TokenStream) -> TokenStream { } .into() } + +#[cfg(test)] +mod tests { + use syn::Expr; + + use crate::{Advanced, Alignment, FmtPart}; + + fn fake_expr() -> Expr { + syn::parse_str("1").unwrap() + } + + fn fake_exprs(count: usize) -> Vec { + vec![fake_expr(); count] + } + + fn run_test(string: &str, expr_count: usize) -> Vec { + let fmt = super::Formatter { + string: string.chars().peekable(), + exprs: fake_exprs(expr_count).into_iter(), + fmt_parts: Vec::new(), + }; + fmt.parse() + } + + #[test] + fn parse_fmt() { + let string = "{:<5}"; + let parts = run_test(string, 1); + assert_eq!( + parts, + vec![FmtPart::Advanced( + Advanced { + width: Some(5), + fill: None, + align: Some(Alignment::Left), + }, + fake_expr() + )] + ); + } +} diff --git a/src/lib.rs b/src/lib.rs index 319bf73..1fcd7ff 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -7,7 +7,7 @@ mod opts; mod write; pub use mono_fmt_macro::format_args; -use opts::{WithAlternate, WithFill, WithWidth, WithLeftAlign, WithRightAlign, WithCenterAlign}; +use opts::{WithAlternate, WithCenterAlign, WithFill, WithLeftAlign, WithRightAlign, WithWidth}; pub use crate::args::Arguments; pub use crate::opts::FmtOpts; @@ -94,7 +94,11 @@ pub fn format(args: A) -> String { } mod _private { - pub use super::args::{ConstWidthArg, DebugArg, DisplayArg, Str}; + pub use crate::args::{ConstWidthArg, DebugArg, DisplayArg, Str}; + + pub use crate::opts::{ + WithAlternate, WithCenterAlign, WithFill, WithLeftAlign, WithRightAlign, WithWidth, + }; } #[macro_export]