diff --git a/mono-fmt-macro/src/lib.rs b/mono-fmt-macro/src/lib.rs index 82ab180..3fa66bc 100644 --- a/mono-fmt-macro/src/lib.rs +++ b/mono-fmt-macro/src/lib.rs @@ -65,9 +65,9 @@ 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(), + Self::Debug(_) => f.debug_tuple("Debug").finish(), + Self::Display(_) => f.debug_tuple("Display").finish(), + Self::Advanced(arg0, _) => f.debug_tuple("Advanced").field(arg0).finish(), } } } @@ -234,7 +234,9 @@ impl ToTokens for FmtPart { pub fn format_args(tokens: TokenStream) -> TokenStream { let input = parse_macro_input!(tokens as Input); - parser::FmtSpecParser::new(&mut input.format_str.chars().peekable()).parse(); + if false { + parser::FmtSpecParser::new(&mut input.format_str.chars().peekable()).parse().unwrap(); + } let formatter = Formatter { string: input.format_str.chars().peekable(), diff --git a/mono-fmt-macro/src/parser.rs b/mono-fmt-macro/src/parser.rs index b7ad82d..d964fd0 100644 --- a/mono-fmt-macro/src/parser.rs +++ b/mono-fmt-macro/src/parser.rs @@ -1,9 +1,27 @@ use std::{iter::Peekable, str::Chars}; -#[derive(Debug, PartialEq, Default)] +#[derive(Debug, PartialEq)] +pub enum Alignment { + Left, + Center, + Right, +} + +impl Alignment { + fn from_char(char: char) -> Result { + match char { + '<' => Ok(Self::Left), + '^' => Ok(Self::Center), + '>' => Ok(Self::Right), + _ => Err(()), + } + } +} + +#[derive(Debug, PartialEq)] pub struct Align { - amount: usize, - char: Option, + kind: Alignment, + fill: Option, } #[derive(Debug, PartialEq, Default)] @@ -24,6 +42,12 @@ pub enum FmtType { Other(String), } +#[derive(Debug, PartialEq)] +enum Precision { + Num(usize), + Asterisk, +} + #[derive(Debug, PartialEq, Default)] pub struct FmtSpec { arg: Argument, @@ -32,12 +56,13 @@ pub struct FmtSpec { alternate: bool, zero: bool, width: Option, - precision: Option, + precision: Option, kind: FmtType, } pub struct FmtSpecParser<'a> { chars: &'a mut Peekable>, + /// The last state of the parser. state: State, argument: FmtSpec, } @@ -47,13 +72,12 @@ enum State { Initial, Argument, // : here - Fill, Align, Sign, + Alternate, Zero, Width, Precision, - Type, Done, } @@ -89,21 +113,33 @@ impl<'a> FmtSpecParser<'a> { false } - fn eat_until(&mut self, char: char) -> Option { + fn expect(&mut self, char: char) -> Result<(), ()> { + if !self.eat(char) { + return Err(()); + } + Ok(()) + } + + fn eat_until(&mut self, should_stop: impl Fn(char) -> bool) -> Option { let mut string = String::new(); let mut has_char = false; - while self.peek() != Some(char) { - self.next(); - string.push(char); + // let_chains would be neat here + while self.peek().is_some() && !should_stop(self.peek().unwrap()) { + let next = self.next().unwrap(); + string.push(next); has_char = true; } has_char.then_some(string) } + fn eat_until_match(&mut self, char: char) -> Option { + self.eat_until(|c| c == char) + } + fn step(&mut self) -> Result<(), ()> { match self.state { State::Initial => { - let argument = if let Some(arg) = self.eat_until(':') { + let argument = if let Some(arg) = self.eat_until_match(':') { if let Ok(num) = arg.parse() { Argument::PositionalExplicit(num) } else { @@ -119,18 +155,94 @@ impl<'a> FmtSpecParser<'a> { if !self.eat(':') { return Err(()); } - - Ok(()) } - State::Argument => todo!(), - State::Fill => todo!(), - State::Align => todo!(), - State::Sign => todo!(), - State::Zero => todo!(), - State::Width => todo!(), - State::Precision => todo!(), - State::Type => todo!(), + State::Argument => match self.next().ok_or(())? { + c @ ('>' | '^' | '<') => { + self.argument.align = Some(Align { + kind: Alignment::from_char(c)?, + fill: None, + }); + self.state = State::Align; + } + other => { + if let Some(c @ ('>' | '^' | '<')) = self.peek() { + self.argument.align = Some(Align { + kind: Alignment::from_char(c).unwrap(), + fill: Some(other), + }); + } + + self.state = State::Align; + } + }, + State::Align => { + if let Some(c @ ('+' | '-')) = self.peek() { + 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(|_| ())?; + 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 == "*" { + Precision::Asterisk + } else { + Precision::Num(precision.parse().map_err(|_| ())?) + }; + 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; + self.expect('}')?; + } + } + }, State::Done => unreachable!(), } + + Ok(()) } } diff --git a/src/args.rs b/src/args.rs index 494c26b..da67877 100644 --- a/src/args.rs +++ b/src/args.rs @@ -80,7 +80,7 @@ pub fn ConstWidthArg(value: T) -> ConstWidthArg } impl Arguments for ConstWidthArg { - fn fmt(&self, f: &mut Formatter) -> Result { + fn fmt(&self, _: &mut Formatter) -> Result { todo!() } } diff --git a/src/lib.rs b/src/lib.rs index 1fcd7ff..7a52cab 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,3 +1,5 @@ +#![allow(dead_code)] + // for the test macro expansion #[cfg(test)] extern crate self as mono_fmt; @@ -7,7 +9,6 @@ mod opts; mod write; pub use mono_fmt_macro::format_args; -use opts::{WithAlternate, WithCenterAlign, WithFill, WithLeftAlign, WithRightAlign, WithWidth}; pub use crate::args::Arguments; pub use crate::opts::FmtOpts;