This commit is contained in:
nora 2022-09-14 22:24:10 +02:00
parent 2f7a13cb2f
commit 594047c0a1
8 changed files with 444 additions and 620 deletions

View file

@ -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"

View file

@ -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<Align>,
pub sign: Option<Sign>,
pub alternate: bool,
pub zero: bool,
pub width: Option<Count<'a>>,
pub precision: Option<Count<'a>>,
pub debug_hex: Option<DebugHex>,
}
#[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<FormatArgRef<'a>>,
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<Piece<'a>>,
}
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)
}
}

View file

@ -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<Expr, Token![,]>,
positional_args: Vec<Expr>,
named_args: Vec<(Ident, Expr)>,
}
impl Parse for Input {
fn parse(input: ParseStream) -> syn::Result<Self> {
fn parse(input: ParseStream<'_>) -> Result<Self> {
let crate_ident = input.parse()?;
let first = input.parse::<syn::LitStr>()?;
let mut exprs = Punctuated::new();
let format_str = input.parse::<LitStr>()?.value();
if !input.is_empty() {
let _ = input.parse::<Token![,]>();
}
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::<Token![,]>()?;
if input.is_empty() {
break;
}
let value = input.parse()?;
exprs.push(value);
let expr = input.parse::<Expr>()?;
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<Expr>),
}
fn format_args_impl(input: Input) -> syn::Result<TokenStream> {
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<Chars<'a>>,
crate_ident: Ident,
exprs: I,
fmt_parts: Vec<FmtPart>,
}
impl<'a, I> Formatter<'a, I>
where
I: Iterator<Item = Expr>,
{
fn expect_expr(&mut self) -> Expr {
self.exprs
.next()
.expect("missing argument for display formatting")
}
fn parse(mut self) -> Result<Vec<FmtPart>, 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<parser::FmtSpec, Error> {
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::<WithUwu<WithOwo<()>>>(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<Expr> {
Box::new(syn::parse_str("1").unwrap())
}
fn fake_exprs(count: usize) -> Vec<Expr> {
vec![fake_expr(); count]
}
fn crate_ident() -> Ident {
Ident::new("mono_fmt", Span::call_site())
}
fn run_test(string: &str, expr_count: usize) -> Vec<FmtPart> {
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:-<?}", 1);
assert_eq!(
parts,
vec![FmtPart::Spec(
crate_ident(),
FmtSpec {
arg: Argument::Keyword("uwu".to_string()),
align: Some(Align {
kind: Alignment::Left,
fill: Some('-'),
}),
kind: FmtType::Debug,
..FmtSpec::default()
},
fake_expr_box()
)]
);
match format_args_impl(input) {
Ok(tt) => tt,
Err(err) => err.to_compile_error().into(),
}
}

View file

@ -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<T> = std::result::Result<T, Error>;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Alignment {
Left,
Center,
Right,
}
impl Alignment {
fn from_char(char: char) -> Result<Self> {
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<char>,
}
#[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<Align>,
pub sign: Option<char>,
pub alternate: bool,
pub zero: bool,
pub width: Option<usize>,
pub precision: Option<Precision>,
pub kind: FmtType,
}
pub struct FmtSpecParser<'a, 'b> {
chars: &'a mut PeekMoreIterator<Chars<'b>>,
/// 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<Chars<'b>>) -> Self {
Self {
chars,
state: State::Initial,
argument: FmtSpec::default(),
}
}
pub fn parse(mut self) -> Result<FmtSpec> {
while self.state != State::Done {
self.step()?;
}
Ok(self.argument)
}
fn next(&mut self) -> Option<char> {
self.chars.next()
}
fn peek(&mut self) -> Option<char> {
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<String> {
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(())
}
}

View file

@ -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)
}
}

View file

@ -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<W: Write, O: FmtOpts>(&self, f: &mut Formatter<W, O>) -> 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<T, O>(pub T, pub PhantomData<O>);
macro_rules! traits {
($(struct $name:ident: trait $trait:ident);* $(;)?) => {
$(
pub struct $name<T, O>(pub T, pub O);
impl<T: Debug, OutOpt> Arguments for DebugArg<T, OutOpt> {
fn fmt<W: Write, O: FmtOpts>(&self, f: &mut Formatter<W, O>) -> Result {
Debug::fmt(&self.0, f)
}
pub trait $trait {
fn fmt<W: Write, O: FmtOpts>(&self, f: &mut Formatter<W, O>) -> Result;
}
impl<T: $trait, O: FmtOpts> Arguments for $name<T, O> {
fn fmt<W: Write, OldOpts: FmtOpts>(&self, f: &mut Formatter<W, OldOpts>) -> Result {
let mut f = f.with_opts(&self.1);
<T as $trait>::fmt(&self.0, &mut f)
}
}
)*
};
}
pub struct DisplayArg<T, O>(pub T, pub PhantomData<O>);
impl<T: Display, OutOpt> Arguments for DisplayArg<T, OutOpt> {
fn fmt<W: Write, O: FmtOpts>(&self, f: &mut Formatter<W, O>) -> Result {
Display::fmt(&self.0, f)
}
}
pub struct ConstWidthArg<T, const WIDTH: usize> {
value: T,
_boo: PhantomData<[(); WIDTH]>,
}
#[allow(non_snake_case)]
pub fn ConstWidthArg<T, const WIDTH: usize>(value: T) -> ConstWidthArg<T, WIDTH> {
ConstWidthArg {
value,
_boo: PhantomData,
}
}
impl<T: Display, const WIDTH: usize> Arguments for ConstWidthArg<T, WIDTH> {
fn fmt<W: Write, O: FmtOpts>(&self, _: &mut Formatter<W, O>) -> 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;
);

View file

@ -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<W: Write> Write for &mut W {
}
}
pub trait Debug {
fn fmt<W: Write, O: FmtOpts>(&self, f: &mut Formatter<W, O>) -> Result;
}
pub trait Display {
fn fmt<W: Write, O: FmtOpts>(&self, f: &mut Formatter<W, O>) -> Result;
}
pub trait Binary {
fn fmt<W: Write, O: FmtOpts>(&self, f: &mut Formatter<W, O>) -> Result;
}
pub trait Octal {
fn fmt<W: Write, O: FmtOpts>(&self, f: &mut Formatter<W, O>) -> Result;
}
pub trait LowerHex {
fn fmt<W: Write, O: FmtOpts>(&self, f: &mut Formatter<W, O>) -> Result;
}
pub trait UpperHex {
fn fmt<W: Write, O: FmtOpts>(&self, f: &mut Formatter<W, O>) -> Result;
}
pub trait UpperExp {
fn fmt<W: Write, O: FmtOpts>(&self, f: &mut Formatter<W, O>) -> Result;
}
pub trait LowerExp {
fn fmt<W: Write, O: FmtOpts>(&self, f: &mut Formatter<W, O>) -> Result;
}
pub struct Formatter<W, O> {
buf: W,
opts: O,
@ -103,6 +79,15 @@ impl<W: Write, O: FmtOpts> Formatter<W, O> {
}
}
impl<W, O> Formatter<W, O> {
fn with_opts<'opt, ONew>(&mut self, opts: &'opt ONew) -> Formatter<&mut W, &'opt ONew> {
Formatter {
buf: &mut self.buf,
opts,
}
}
}
pub fn write<W: Write, A: Arguments>(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();
}

View file

@ -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<O: FmtOpts> FmtOpts for &'_ O {
type Inner = O::Inner;
fn inner(&self) -> &Self::Inner {
O::inner(self)
}
$(
#[inline]
fn $name(&self) -> $ret {
O::$name(self)
}
)*
}
impl<W, O: FmtOpts> Formatter<W, O> {
$(
#[inline]
pub fn $name(&self) -> $ret {
O::$name()
O::$name(&self.opts)
}
)*
}
@ -58,7 +80,11 @@ macro_rules! options {
impl<I: FmtOpts, $($(const $const_gen_name: $with_ty),*)?> FmtOpts for $with_name<I, $($($const_gen_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<usize> {
fn width(&self) -> Option<usize> {
None
}
struct WithWidth<const A: usize> {
Some(A)
}
fn align() -> Alignment {
fn align(&self) -> Alignment {
Alignment::Unknown
}
struct WithAlign<const A: usize> {
@ -95,49 +121,49 @@ options!(
}
fn fill() -> char {
fn fill(&self) -> char {
' '
}
struct WithFill<const A: char> {
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<usize> {
fn precision(&self) -> Option<usize> {
None
}
struct WithPrecision<const A: usize> {
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 {