diff --git a/mono-fmt-macro/Cargo.toml b/mono-fmt-macro/Cargo.toml index a817180..944da5d 100644 --- a/mono-fmt-macro/Cargo.toml +++ b/mono-fmt-macro/Cargo.toml @@ -9,7 +9,8 @@ proc-macro = true # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -peekmore = "1.0.0" +nom = "7.1.1" proc-macro2 = "1.0.43" quote = "1.0.21" syn = { version = "1.0.99", features = ["full"] } +unicode-ident = "1.0.4" diff --git a/mono-fmt-macro/src/format.rs b/mono-fmt-macro/src/format.rs new file mode 100644 index 0000000..6b4a6ae --- /dev/null +++ b/mono-fmt-macro/src/format.rs @@ -0,0 +1,233 @@ +use std::str::FromStr; + +use nom::{ + branch::alt, + bytes::complete::tag, + character::complete::{anychar, char, digit1, none_of, satisfy}, + combinator::{all_consuming, cut, map, map_res, opt, recognize, value}, + multi::{many0, many1}, + sequence::{delimited, pair, preceded, terminated}, + IResult, +}; + +fn identifier(input: &str) -> IResult<&str, &str> { + recognize(pair( + alt((satisfy(unicode_ident::is_xid_start), char('_'))), + many0(satisfy(unicode_ident::is_xid_continue)), + ))(input) +} + +pub trait Parse<'a>: Sized { + fn parse(input: &'a str) -> IResult<&str, Self>; +} + +#[derive(Debug, Clone, Copy)] +pub enum Align { + Left, + Center, + Right, +} + +impl<'a> Parse<'a> for Align { + fn parse(input: &'a str) -> IResult<&str, Self> { + alt(( + value(Self::Left, tag("<")), + value(Self::Center, tag("^")), + value(Self::Right, tag(">")), + ))(input) + } +} + +#[derive(Debug, Clone, Copy)] +pub enum Sign { + Plus, + Minus, +} + +impl<'a> Parse<'a> for Sign { + fn parse(input: &'a str) -> IResult<&str, Self> { + alt((value(Self::Plus, tag("+")), value(Self::Minus, tag("-"))))(input) + } +} + +#[derive(Debug, Clone, Copy)] +pub enum DebugHex { + Lower, + Upper, +} + +#[derive(Debug, Default, Clone, Copy)] +pub struct FormatterArgs<'a> { + pub align: Option, + pub sign: Option, + pub alternate: bool, + pub zero: bool, + pub width: Option>, + pub precision: Option>, + pub debug_hex: Option, +} + +#[derive(Debug, Clone, Copy)] +pub enum FormatTrait { + Display, + Debug, + Octal, + LowerHex, + UpperHex, + Pointer, + Binary, + LowerExp, + UpperExp, +} + +impl Default for FormatTrait { + fn default() -> Self { + Self::Display + } +} + +#[derive(Debug, Clone, Copy)] +pub enum Count<'a> { + Parameter(FormatArgRef<'a>), + Integer(usize), +} + +impl<'a> Parse<'a> for Count<'a> { + fn parse(input: &'a str) -> IResult<&str, Self> { + alt(( + map(terminated(FormatArgRef::parse, tag("$")), Self::Parameter), + map(map_res(digit1, usize::from_str), Self::Integer), + ))(input) + } +} + +#[derive(Debug, Default, Clone)] +pub struct FormatSpec<'a> { + pub formatter_args: FormatterArgs<'a>, + pub format_trait: FormatTrait, +} + +impl<'a> Parse<'a> for FormatSpec<'a> { + fn parse(input: &'a str) -> IResult<&str, Self> { + let (input, align) = opt(alt(( + pair(anychar, Align::parse), + map(Align::parse, |align| (' ', align)), + )))(input)?; + let align = align.map(|(fill, align)| { + if fill != ' ' { + todo!() + } + align + }); + let (input, sign) = opt(Sign::parse)(input)?; + let (input, alternate) = opt(value(true, tag("#")))(input)?; + let (input, zero) = opt(value(true, tag("0")))(input)?; + let (input, width) = opt(Count::parse)(input)?; + let (input, precision) = opt(preceded(tag("."), Count::parse))(input)?; + let (input, debug_hex_and_format_trait) = opt(alt(( + value((None, FormatTrait::Debug), tag("?")), + value((Some(DebugHex::Lower), FormatTrait::Debug), tag("x?")), + value((Some(DebugHex::Upper), FormatTrait::Debug), tag("X?")), + value((None, FormatTrait::Octal), tag("o")), + value((None, FormatTrait::LowerHex), tag("x")), + value((None, FormatTrait::UpperHex), tag("X")), + value((None, FormatTrait::Pointer), tag("p")), + value((None, FormatTrait::Binary), tag("b")), + value((None, FormatTrait::LowerExp), tag("e")), + value((None, FormatTrait::UpperExp), tag("E")), + )))(input)?; + let debug_hex = debug_hex_and_format_trait.and_then(|(debug_hex, _)| debug_hex); + let format_trait = debug_hex_and_format_trait.map(|(_, format_trait)| format_trait); + Ok(( + input, + FormatSpec { + formatter_args: FormatterArgs { + align, + sign, + alternate: alternate.unwrap_or_default(), + zero: zero.unwrap_or_default(), + width, + precision, + debug_hex, + }, + format_trait: format_trait.unwrap_or_default(), + }, + )) + } +} + +#[derive(Debug, Clone, Copy)] +pub enum FormatArgRef<'a> { + Positional(usize), + Named(&'a str), +} + +impl<'a> Parse<'a> for FormatArgRef<'a> { + fn parse(input: &'a str) -> IResult<&str, Self> { + alt(( + map(map_res(digit1, usize::from_str), FormatArgRef::Positional), + map(identifier, FormatArgRef::Named), + ))(input) + } +} + +#[derive(Debug, Clone)] +pub struct FormatArg<'a> { + pub arg: Option>, + pub format_spec: FormatSpec<'a>, +} + +impl<'a> Parse<'a> for FormatArg<'a> { + fn parse(input: &'a str) -> IResult<&str, Self> { + let (input, arg) = opt(FormatArgRef::parse)(input)?; + let (input, format_spec) = opt(preceded(tag(":"), FormatSpec::parse))(input)?; + Ok(( + input, + Self { + arg, + format_spec: format_spec.unwrap_or_default(), + }, + )) + } +} + +#[derive(Debug, Clone)] +#[allow(variant_size_differences)] +pub enum Piece<'a> { + Lit(&'a str), + Arg(FormatArg<'a>), +} + +impl<'a> Piece<'a> { + pub fn parse_lit(input: &'a str) -> IResult<&str, Self> { + alt(( + map(recognize(many1(none_of("{}"))), Self::Lit), + value(Self::Lit("{"), tag("{{")), + value(Self::Lit("}"), tag("}}")), + ))(input) + } + + pub fn parse_arg(input: &'a str) -> IResult<&str, Self> { + map( + delimited(tag("{"), cut(FormatArg::parse), tag("}")), + Self::Arg, + )(input) + } +} + +impl<'a> Parse<'a> for Piece<'a> { + fn parse(input: &'a str) -> IResult<&str, Self> { + alt((Self::parse_lit, Self::parse_arg))(input) + } +} + +#[derive(Debug, Clone)] +pub struct Format<'a> { + pub pieces: Vec>, +} + +impl<'a> Parse<'a> for Format<'a> { + fn parse(input: &'a str) -> IResult<&str, Self> { + all_consuming(map(many0(Piece::parse), |pieces| Self { pieces }))(input) + } +} diff --git a/mono-fmt-macro/src/lib.rs b/mono-fmt-macro/src/lib.rs index 710192d..23a2a20 100644 --- a/mono-fmt-macro/src/lib.rs +++ b/mono-fmt-macro/src/lib.rs @@ -1,307 +1,88 @@ -use std::str::Chars; +//! a bunch of this code is adapted from [stylish](https://github.com/Nullus157/stylish-rs) +#![allow(dead_code, unreachable_code, unused_variables)] -use parser::{Alignment, Error, FmtSpec, FmtType}; -use peekmore::{PeekMore, PeekMoreIterator}; +use format::Parse as _; use proc_macro::TokenStream; -use proc_macro2::Span; -use quote::{quote, ToTokens}; +use quote::quote; use syn::{ parse::{Parse, ParseStream}, - parse_macro_input, - punctuated::Punctuated, - Expr, Ident, LitStr, Token, + parse_macro_input, Expr, ExprAssign, ExprPath, Ident, LitStr, PathArguments, Result, Token, }; -mod parser; +mod format; +mod to_tokens; struct Input { crate_ident: Ident, format_str: String, - str_span: Span, - exprs: Punctuated, + positional_args: Vec, + named_args: Vec<(Ident, Expr)>, } impl Parse for Input { - fn parse(input: ParseStream) -> syn::Result { + fn parse(input: ParseStream<'_>) -> Result { let crate_ident = input.parse()?; - let first = input.parse::()?; - let mut exprs = Punctuated::new(); + let format_str = input.parse::()?.value(); - if !input.is_empty() { - let _ = input.parse::(); - } - - while !input.is_empty() { - let punct = input.parse()?; - exprs.push(punct); + let mut positional_args = Vec::new(); + let mut named_args = Vec::new(); + let mut onto_named = false; + while input.peek(Token![,]) { + input.parse::()?; if input.is_empty() { break; } - let value = input.parse()?; - exprs.push(value); + let expr = input.parse::()?; + match expr { + Expr::Assign(ExprAssign { left, right, .. }) + if matches!( + &*left, + Expr::Path(ExprPath { path, .. }) + if path.segments.len() == 1 && matches!(path.segments[0].arguments, PathArguments::None) + ) => + { + let ident = if let Expr::Path(ExprPath { mut path, .. }) = *left { + path.segments.pop().unwrap().into_value().ident + } else { + panic!() + }; + named_args.push((ident, *right)); + onto_named = true; + } + expr => { + if onto_named { + panic!("positional arg after named") + } + positional_args.push(expr); + } + } } - Ok(Self { crate_ident, - format_str: first.value(), - str_span: first.span(), - exprs, + format_str, + positional_args, + named_args, }) } } -enum FmtPart { - Literal(Ident, String), - Spec(Ident, FmtSpec, Box), -} +fn format_args_impl(input: Input) -> syn::Result { + todo!(); + let (_, fmt_parts) = format::Format::parse(&input.format_str).unwrap(); -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::Spec(_, spec, _) => f.debug_tuple("Spec").field(spec).finish(), - } - } -} - -impl PartialEq for FmtPart { - fn eq(&self, other: &Self) -> bool { - match (self, other) { - (Self::Literal(_, a), Self::Literal(_, b)) => a == b, - (Self::Spec(_, a, _), Self::Spec(_, b, _)) => a == b, - _ => false, - } - } -} - -struct Formatter<'a, I> { - string: PeekMoreIterator>, - crate_ident: Ident, - exprs: I, - fmt_parts: Vec, -} - -impl<'a, I> Formatter<'a, I> -where - I: Iterator, -{ - fn expect_expr(&mut self) -> Expr { - self.exprs - .next() - .expect("missing argument for display formatting") - } - - fn parse(mut self) -> Result, Error> { - let mut next_string = String::new(); - while let Some(char) = self.string.next() { - match char { - '{' => { - self.save_string(std::mem::take(&mut next_string)); - let argument = self.fmt_spec()?; - let expr = self.expect_expr(); - self.fmt_parts - .push(FmtPart::Spec(self.crate_ident.clone(), argument, Box::new(expr))); - } - other => { - next_string.push(other); - } - } - } - self.save_string(next_string); - - Ok(self.fmt_parts) - } - - fn fmt_spec(&mut self) -> Result { - let parser = parser::FmtSpecParser::new(&mut self.string); - parser.parse() - } - - fn save_string(&mut self, string: String) { - if string.is_empty() { - return; - } - self.fmt_parts - .push(FmtPart::Literal(self.crate_ident.clone(), string)); - } -} - -impl ToTokens for Alignment { - fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { - tokens.extend(match self { - Self::Left => quote! { 1 }, - Self::Center => quote! { 2 }, - Self::Right => quote! { 3 }, - }) - } -} - -impl ToTokens for FmtPart { - fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { - let own_tokens = match self { - FmtPart::Literal(crate_ident, lit) => { - let literal = LitStr::new(lit, Span::call_site()); - quote! { #crate_ident::_private::Str(#literal) } - } - FmtPart::Spec(crate_ident, spec, expr) => { - let mut opt_toks = quote! { () }; - - // FIXME: Wait no we want `DebugArg::>>(expr)` - - if let Some(align) = &spec.align { - if let Some(fill) = align.fill { - opt_toks = quote! { #crate_ident::_private::WithFill<#opt_toks, #fill> }; - } - let alignment = align.kind; - opt_toks = quote! { #crate_ident::_private::WithAlign<#opt_toks, #alignment> }; - } - - if spec.alternate { - opt_toks = quote! { #crate_ident::_private::WithAlternate<_, #opt_toks }; - } - - if spec.zero { - todo!() - } - - if let Some(_) = spec.width { - todo!() - } - - if let Some(_) = spec.precision { - todo!() - } - - match spec.kind { - FmtType::Default => quote! { - #crate_ident::_private::DisplayArg::<_, #opt_toks>(#expr, ::std::marker::PhantomData) - }, - FmtType::Debug => quote! { - #crate_ident::_private::DebugArg::<_, #opt_toks>(#expr, ::std::marker::PhantomData) - }, - _ => todo!(), - } - } - }; - - tokens.extend(own_tokens); + Ok(quote! { + fmt_parts } + .into()) } #[proc_macro] pub fn __format_args(tokens: TokenStream) -> TokenStream { let input = parse_macro_input!(tokens as Input); - let formatter = Formatter { - string: input.format_str.chars().peekmore(), - crate_ident: input.crate_ident, - exprs: input.exprs.into_iter(), - fmt_parts: Vec::new(), - }; - - let fmt_parts = match formatter.parse() { - Ok(parts) => parts, - Err(error) => { - return syn::Error::new(input.str_span, error.message) - .to_compile_error() - .into() - } - }; - - quote! { - (#(#fmt_parts),*,) - } - .into() -} - -#[cfg(test)] -mod tests { - use peekmore::PeekMore; - use proc_macro2::{Ident, Span}; - use syn::Expr; - - use crate::{ - parser::{Align, Alignment, Argument, FmtSpec, FmtType}, - FmtPart, - }; - - fn fake_expr() -> Expr { - syn::parse_str("1").unwrap() - } - - - fn fake_expr_box() -> Box { - Box::new(syn::parse_str("1").unwrap()) - } - - fn fake_exprs(count: usize) -> Vec { - vec![fake_expr(); count] - } - - fn crate_ident() -> Ident { - Ident::new("mono_fmt", Span::call_site()) - } - - fn run_test(string: &str, expr_count: usize) -> Vec { - let fmt = super::Formatter { - string: string.chars().peekmore(), - crate_ident: crate_ident(), - 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( - crate_ident(), - FmtSpec { - ..FmtSpec::default() - }, - fake_expr_box() - )] - ); - } - - #[test] - fn debug() { - let parts = run_test("{:?}", 1); - assert_eq!( - parts, - vec![FmtPart::Spec( - crate_ident(), - FmtSpec { - kind: FmtType::Debug, - ..FmtSpec::default() - }, - fake_expr_box() - )] - ); - } - - #[test] - fn many() { - let parts = run_test("{uwu:- tt, + Err(err) => err.to_compile_error().into(), } } diff --git a/mono-fmt-macro/src/parser.rs b/mono-fmt-macro/src/parser.rs deleted file mode 100644 index f507d45..0000000 --- a/mono-fmt-macro/src/parser.rs +++ /dev/null @@ -1,264 +0,0 @@ -use std::str::Chars; - -use peekmore::PeekMoreIterator; - -#[derive(Debug)] -pub struct Error { - pub message: String, -} - -impl Error { - fn new(message: String) -> Self { - Self { message } - } -} - -pub type Result = std::result::Result; - -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -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(Error::new(format!("Invalid alignment specifier {char}"))), - } - } -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub struct Align { - pub kind: Alignment, - pub fill: Option, -} - -#[derive(Debug, PartialEq, Eq, Default)] -pub enum Argument { - #[default] - Positional, - PositionalExplicit(usize), - Keyword(String), -} - -#[derive(Debug, PartialEq, Eq, Default)] -pub enum FmtType { - #[default] - Default, - Debug, - LowerHex, - UpperHex, - Other(String), -} - -#[derive(Debug, PartialEq, Eq)] -pub enum Precision { - Num(usize), - Asterisk, -} - -#[derive(Debug, PartialEq, Eq, Default)] -pub struct FmtSpec { - 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> { - chars: &'a mut PeekMoreIterator>, - /// The last state of the parser. - state: State, - argument: FmtSpec, -} - -#[derive(Debug, Clone, Copy, PartialEq)] -enum State { - Initial, - Argument, - // : here - Align, - Done, -} - -impl<'a, 'b> FmtSpecParser<'a, 'b> { - pub fn new(chars: &'a mut PeekMoreIterator>) -> Self { - Self { - chars, - state: State::Initial, - argument: FmtSpec::default(), - } - } - - pub fn parse(mut self) -> Result { - while self.state != State::Done { - self.step()?; - } - Ok(self.argument) - } - - fn next(&mut self) -> Option { - self.chars.next() - } - - fn peek(&mut self) -> Option { - self.chars.peek().copied() - } - - fn eat(&mut self, char: char) -> bool { - if self.peek() == Some(char) { - self.next(); - return true; - } - false - } - - fn expect(&mut self, char: char) -> Result<()> { - if !self.eat(char) { - return Err(Error::new(format!( - "Expected {char}, found {}", - self.peek() - .map(|c| c.to_string()) - .unwrap_or_else(|| "end of input".to_string()) - ))); - } - Ok(()) - } - - fn eat_until(&mut self, should_stop: impl Fn(char) -> bool) -> Option { - let mut string = String::new(); - let mut has_char = false; - // 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 step(&mut self) -> Result<()> { - match self.state { - State::Initial => { - let argument = if let Some(arg) = self.eat_until(|c| matches!(c, ':' | '}')) { - if let Ok(num) = arg.parse() { - Argument::PositionalExplicit(num) - } else { - Argument::Keyword(arg) - } - } else { - Argument::Positional - }; - - self.argument.arg = argument; - self.state = State::Argument; - - if self.argument.arg != Argument::Positional { - self.expect(':')?; - } - self.eat(':'); - } - State::Argument => match self - .peek() - .ok_or_else(|| Error::new("unexpected end of input".to_string()))? - { - c @ ('>' | '^' | '<') => { - self.next(); - self.argument.align = Some(Align { - kind: Alignment::from_char(c)?, - fill: None, - }); - self.state = State::Align; - } - other => { - 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), - }); - } - - self.state = State::Align; - } - }, - State::Align => { - if let Some(c @ ('+' | '-')) = self.peek() { - self.next(); - self.argument.sign = Some(c); - } - - if self.eat('#') { - self.argument.alternate = true; - } - - if self.eat('0') { - self.argument.zero = true; - } - - 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); - } - - 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(|_| { - Error::new("precision specified too long".to_string()) - })?) - }; - self.argument.precision = Some(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('}')?; - } - } - } - - self.state = State::Done; - } - State::Done => unreachable!(), - } - - Ok(()) - } -} diff --git a/mono-fmt-macro/src/to_tokens.rs b/mono-fmt-macro/src/to_tokens.rs new file mode 100644 index 0000000..8ef9c61 --- /dev/null +++ b/mono-fmt-macro/src/to_tokens.rs @@ -0,0 +1,56 @@ +use proc_macro2::TokenStream; +use quote::{quote, ToTokens}; + +use crate::format::{Align, FormatTrait}; + +pub struct Scoped<'a, T> { + export: &'a syn::Path, + inner: &'a T, +} + +impl<'a, T> Scoped<'a, T> { + pub fn new(export: &'a syn::Path, inner: &'a T) -> Self { + Self { export, inner } + } + + fn scope<'b, U>(&self, inner: &'b U) -> Scoped<'b, U> + where + 'a: 'b, + { + Scoped { + inner, + export: self.export, + } + } + + fn as_ref(&self) -> &'a T { + self.inner + } +} + +impl ToTokens for Align { + fn to_tokens(&self, tokens: &mut TokenStream) { + tokens.extend(match self { + Self::Left => quote! { 1 }, + Self::Center => quote! { 2 }, + Self::Right => quote! { 3 }, + }) + } +} + +impl ToTokens for FormatTrait { + fn to_tokens(&self, tokens: &mut TokenStream) { + match self { + FormatTrait::Display => quote!(Display), + FormatTrait::Debug => quote!(Debug), + FormatTrait::Octal => quote!(Octal), + FormatTrait::LowerHex => quote!(LowerHex), + FormatTrait::UpperHex => quote!(UpperHex), + FormatTrait::Pointer => quote!(Pointer), + FormatTrait::Binary => quote!(Binary), + FormatTrait::LowerExp => quote!(LowerExp), + FormatTrait::UpperExp => quote!(UpperExp), + } + .to_tokens(tokens) + } +} diff --git a/src/args.rs b/src/args.rs index cc28409..3f4cfcc 100644 --- a/src/args.rs +++ b/src/args.rs @@ -1,11 +1,9 @@ -use std::marker::PhantomData; - -use crate::{Debug, Display, FmtOpts, Formatter, Result, Write}; +use crate::{FmtOpts, Formatter, Result, Write}; pub trait Arguments { fn fmt(&self, f: &mut Formatter) -> Result; } -macro_rules! impl_arguments { +macro_rules! tuple_args { () => {}; ($first:ident $($rest:ident)*) => { impl<$first, $($rest),*> Arguments for ($first, $($rest),*) @@ -24,12 +22,12 @@ macro_rules! impl_arguments { } } - impl_arguments!($($rest)*); + tuple_args!($($rest)*); }; } #[rustfmt::skip] - impl_arguments!( + tuple_args!( A1 A2 A3 A4 A5 A6 A7 A8 A9 A10 // A11 A12 A13 A14 A15 A16 A17 A18 A19 A20 // A21 A22 A23 A24 A25 A26 A27 A28 A29 A30 @@ -50,37 +48,33 @@ impl Arguments for Str { } } -pub struct DebugArg(pub T, pub PhantomData); +macro_rules! traits { + ($(struct $name:ident: trait $trait:ident);* $(;)?) => { + $( + pub struct $name(pub T, pub O); -impl Arguments for DebugArg { - fn fmt(&self, f: &mut Formatter) -> Result { - Debug::fmt(&self.0, f) - } + pub trait $trait { + fn fmt(&self, f: &mut Formatter) -> Result; + } + + impl Arguments for $name { + fn fmt(&self, f: &mut Formatter) -> Result { + let mut f = f.with_opts(&self.1); + + ::fmt(&self.0, &mut f) + } + } + )* + }; } -pub struct DisplayArg(pub T, pub PhantomData); - -impl Arguments for DisplayArg { - fn fmt(&self, f: &mut Formatter) -> Result { - Display::fmt(&self.0, f) - } -} - -pub struct ConstWidthArg { - value: T, - _boo: PhantomData<[(); WIDTH]>, -} - -#[allow(non_snake_case)] -pub fn ConstWidthArg(value: T) -> ConstWidthArg { - ConstWidthArg { - value, - _boo: PhantomData, - } -} - -impl Arguments for ConstWidthArg { - fn fmt(&self, _: &mut Formatter) -> Result { - todo!() - } -} +traits!( + struct DebugArg: trait Debug; + struct DisplayArg: trait Display; + struct BinaryArg: trait Binary; + struct OctalArg: trait Octal; + struct LowerHexArg: trait LowerHex; + struct UpperHexArg: trait UpperHex; + struct UpperExpArg: trait UpperExp; + struct LowerExpArg: trait LowerExp; +); diff --git a/src/lib.rs b/src/lib.rs index 3749d4c..4336de3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -12,7 +12,10 @@ macro_rules! format_args { }; } -pub use crate::{args::Arguments, opts::FmtOpts}; +pub use crate::{ + args::{Arguments, Binary, Debug, Display, LowerExp, LowerHex, Octal, UpperExp, UpperHex}, + opts::FmtOpts, +}; pub type Result = std::result::Result<(), Error>; @@ -46,33 +49,6 @@ impl Write for &mut W { } } -pub trait Debug { - fn fmt(&self, f: &mut Formatter) -> Result; -} - -pub trait Display { - fn fmt(&self, f: &mut Formatter) -> Result; -} - -pub trait Binary { - fn fmt(&self, f: &mut Formatter) -> Result; -} -pub trait Octal { - fn fmt(&self, f: &mut Formatter) -> Result; -} -pub trait LowerHex { - fn fmt(&self, f: &mut Formatter) -> Result; -} -pub trait UpperHex { - fn fmt(&self, f: &mut Formatter) -> Result; -} -pub trait UpperExp { - fn fmt(&self, f: &mut Formatter) -> Result; -} -pub trait LowerExp { - fn fmt(&self, f: &mut Formatter) -> Result; -} - pub struct Formatter { buf: W, opts: O, @@ -103,6 +79,15 @@ impl Formatter { } } +impl Formatter { + fn with_opts<'opt, ONew>(&mut self, opts: &'opt ONew) -> Formatter<&mut W, &'opt ONew> { + Formatter { + buf: &mut self.buf, + opts, + } + } +} + pub fn write(buffer: W, args: A) -> Result { let mut fmt = Formatter::new(buffer); args.fmt(&mut fmt) @@ -120,10 +105,11 @@ mod _private { pub use mono_fmt_macro::__format_args; pub use crate::{ - args::{ConstWidthArg, DebugArg, DisplayArg, Str}, - opts::{ - WithAlternate, WithFill, WithAlign, WithWidth, + args::{ + BinaryArg, DebugArg, DisplayArg, LowerExpArg, LowerHexArg, OctalArg, Str, UpperExpArg, + UpperHexArg, }, + opts::{WithAlign, WithAlternate, WithFill, WithWidth}, }; } @@ -168,3 +154,14 @@ mod tests { assert_eq!(result, "a: 32523532"); } } + +fn fmt() { + let a = ( + _private::Str("amount: "), + _private::DebugArg::<_, _private::WithAlternate<()>>(5, _private::WithAlternate(())), + ); + + let mut str = String::new(); + let mut f = Formatter::new(&mut str); + Arguments::fmt(&a, &mut f).unwrap(); +} diff --git a/src/opts.rs b/src/opts.rs index 7b8f3d0..bd3c74b 100644 --- a/src/opts.rs +++ b/src/opts.rs @@ -11,7 +11,7 @@ pub enum Alignment { macro_rules! options { ( $( - fn $name:ident() -> $ret:ty { + fn $name:ident(&self) -> $ret:ty { $($default:tt)* } @@ -24,10 +24,12 @@ macro_rules! options { #[doc(hidden)] type Inner: FmtOpts; + fn inner(&self) -> &Self::Inner; + $( #[inline] - fn $name() -> $ret { - Self::Inner::$name() + fn $name(&self) -> $ret { + Self::Inner::$name(Self::inner(self)) } )* } @@ -35,19 +37,39 @@ macro_rules! options { impl FmtOpts for () { type Inner = Self; + fn inner(&self) -> &Self::Inner { + self + } + $( #[inline] - fn $name() -> $ret { + fn $name(&self) -> $ret { $($default)* } )* } + impl FmtOpts for &'_ O { + type Inner = O::Inner; + + fn inner(&self) -> &Self::Inner { + O::inner(self) + } + + $( + #[inline] + fn $name(&self) -> $ret { + O::$name(self) + } + )* + } + + impl Formatter { $( #[inline] pub fn $name(&self) -> $ret { - O::$name() + O::$name(&self.opts) } )* } @@ -58,7 +80,11 @@ macro_rules! options { impl FmtOpts for $with_name { type Inner = I; - fn $name() -> $ret { + fn inner(&self) -> &Self::Inner { + &self.0 + } + + fn $name(&self) -> $ret { $($struct_body)* } } @@ -67,21 +93,21 @@ macro_rules! options { } options!( - fn alternate() -> bool { + fn alternate(&self) -> bool { false } struct WithAlternate { true } - fn width() -> Option { + fn width(&self) -> Option { None } struct WithWidth { Some(A) } - fn align() -> Alignment { + fn align(&self) -> Alignment { Alignment::Unknown } struct WithAlign { @@ -95,49 +121,49 @@ options!( } - fn fill() -> char { + fn fill(&self) -> char { ' ' } struct WithFill { A } - fn sign_plus() -> bool { + fn sign_plus(&self) -> bool { false } struct WithSignPlus { true } - fn sign_aware_zero_pad() -> bool { + fn sign_aware_zero_pad(&self) -> bool { false } struct WithSignAwareZeroPad { true } - fn sign_minus() -> bool { + fn sign_minus(&self) -> bool { false } struct WithMinus { true } - fn precision() -> Option { + fn precision(&self) -> Option { None } struct WithPrecision { Some(A) } - fn debug_lower_hex() -> bool { + fn debug_lower_hex(&self) -> bool { false } struct WithDebugLowerHex { true } - fn debug_upper_hex() -> bool { + fn debug_upper_hex(&self) -> bool { false } struct WithDebugUpperHex {