diff --git a/mono-fmt-macro/src/lib.rs b/mono-fmt-macro/src/lib.rs index 05af70d..4fb3672 100644 --- a/mono-fmt-macro/src/lib.rs +++ b/mono-fmt-macro/src/lib.rs @@ -1,7 +1,7 @@ use std::str::Chars; use parser::{Error, FmtSpec}; -use peekmore::{PeekMoreIterator, PeekMore}; +use peekmore::{PeekMore, PeekMoreIterator}; use proc_macro::TokenStream; use proc_macro2::Span; use quote::{quote, ToTokens}; @@ -14,8 +14,6 @@ use syn::{ mod parser; -// TODO: Rewrite using state machine please - struct Input { format_str: String, str_span: Span, @@ -163,9 +161,13 @@ pub fn format_args(tokens: TokenStream) -> TokenStream { #[cfg(test)] mod tests { + use peekmore::PeekMore; use syn::Expr; - use crate::FmtPart; + use crate::{ + parser::{Align, Alignment, Argument, FmtSpec, FmtType}, + FmtPart, + }; fn fake_expr() -> Expr { syn::parse_str("1").unwrap() @@ -177,10 +179,59 @@ mod tests { fn run_test(string: &str, expr_count: usize) -> Vec { let fmt = super::Formatter { - string: string.chars().peekable(), + string: string.chars().peekmore(), exprs: fake_exprs(expr_count).into_iter(), fmt_parts: Vec::new(), }; fmt.parse().unwrap() } + + #[test] + fn empty() { + let parts = run_test("{}", 1); + assert_eq!( + parts, + vec![FmtPart::Spec( + FmtSpec { + ..FmtSpec::default() + }, + fake_expr() + )] + ); + } + + #[test] + fn debug() { + let parts = run_test("{:?}", 1); + assert_eq!( + parts, + vec![FmtPart::Spec( + FmtSpec { + kind: FmtType::Debug, + ..FmtSpec::default() + }, + fake_expr() + )] + ); + } + + #[test] + fn many() { + let parts = run_test("{uwu:-, + pub kind: Alignment, + pub fill: Option, } #[derive(Debug, PartialEq, Default)] @@ -58,21 +58,21 @@ pub enum FmtType { } #[derive(Debug, PartialEq)] -enum Precision { +pub enum Precision { Num(usize), Asterisk, } #[derive(Debug, PartialEq, Default)] pub struct FmtSpec { - arg: Argument, - align: Option, - sign: Option, - alternate: bool, - zero: bool, - width: Option, - precision: Option, - kind: FmtType, + pub arg: Argument, + pub align: Option, + pub sign: Option, + pub alternate: bool, + pub zero: bool, + pub width: Option, + pub precision: Option, + pub kind: FmtType, } pub struct FmtSpecParser<'a, 'b> { @@ -88,11 +88,6 @@ enum State { Argument, // : here Align, - Sign, - Alternate, - Zero, - Width, - Precision, Done, } @@ -171,6 +166,7 @@ impl<'a, 'b> FmtSpecParser<'a, 'b> { if self.argument.arg != Argument::Positional { self.expect(':')?; } + self.eat(':'); } State::Argument => match self .peek() @@ -185,8 +181,9 @@ impl<'a, 'b> FmtSpecParser<'a, 'b> { self.state = State::Align; } other => { - // peek2 - if let Some(c @ ('>' | '^' | '<')) = self.peek() { + if let Some(c @ ('>' | '^' | '<')) = self.chars.peek_nth(1).copied() { + self.next(); // fill + self.next(); // align self.argument.align = Some(Align { kind: Alignment::from_char(c).unwrap(), fill: Some(other), @@ -201,30 +198,22 @@ impl<'a, 'b> FmtSpecParser<'a, 'b> { self.next(); self.argument.sign = Some(c); } - self.state = State::Sign; - } - State::Sign => { + if self.eat('#') { self.argument.alternate = true; } - self.state = State::Alternate; - } - State::Alternate => { + if self.eat('0') { self.argument.zero = true; } - self.state = State::Zero; - } - State::Zero => { + if let Some(width) = self.eat_until(|c| !c.is_ascii_digit()) { let width = width .parse() .map_err(|_| Error::new("width specified too long".to_string()))?; self.argument.width = Some(width); } - self.state = State::Width; - } - State::Width => { + if self.eat('.') { if let Some(precision) = self.eat_until(|c| c != '*' && !c.is_ascii_digit()) { let precision = if precision == "*" { @@ -237,34 +226,36 @@ impl<'a, 'b> FmtSpecParser<'a, 'b> { self.argument.precision = Some(precision); } } - self.state = State::Precision; - } - State::Precision => match self.next() { - Some('?') => { - self.argument.kind = FmtType::Debug; - self.expect('}')?; - } - Some('x') => { - self.expect('?')?; - self.argument.kind = FmtType::LowerHex; - self.expect('}')?; - } - Some('X') => { - self.expect('?')?; - self.argument.kind = FmtType::UpperHex; - self.expect('}')?; - } - Some('}') | None => {} - Some(other) => { - if let Some(kind) = self.eat_until(|c| c == '}') { - self.argument.kind = FmtType::Other(format!("{other}{kind}")); - self.expect('}')?; - } else { - self.argument.kind = FmtType::Default; + + match self.next() { + Some('?') => { + self.argument.kind = FmtType::Debug; self.expect('}')?; } + Some('x') => { + self.expect('?')?; + self.argument.kind = FmtType::LowerHex; + self.expect('}')?; + } + Some('X') => { + self.expect('?')?; + self.argument.kind = FmtType::UpperHex; + self.expect('}')?; + } + Some('}') | None => {} + Some(other) => { + if let Some(kind) = self.eat_until(|c| c == '}') { + self.argument.kind = FmtType::Other(format!("{other}{kind}")); + self.expect('}')?; + } else { + self.argument.kind = FmtType::Default; + self.expect('}')?; + } + } } - }, + + self.state = State::Done; + } State::Done => unreachable!(), } diff --git a/src/lib.rs b/src/lib.rs index 16782ee..7a52cab 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -121,19 +121,19 @@ mod tests { #[test] fn display() { - //let result = format!("{}", "uwu"); - //assert_eq!(result, "uwu"); + let result = format!("{}", "uwu"); + assert_eq!(result, "uwu"); } #[test] fn display_with_strings() { - //let result = format!("oow{} omg", "uwu"); - //assert_eq!(result, "oowuwu omg"); + let result = format!("oow{} omg", "uwu"); + assert_eq!(result, "oowuwu omg"); } #[test] fn debug() { - //let result = format!("test {:?} hello", "uwu"); - //assert_eq!(result, r#"test "uwu" hello"#); + let result = format!("test {:?} hello", "uwu"); + assert_eq!(result, r#"test "uwu" hello"#); } }