mirror of
https://github.com/Noratrieb/mono-fmt.git
synced 2026-01-14 23:35:05 +01:00
rewrite
This commit is contained in:
parent
2f7a13cb2f
commit
594047c0a1
8 changed files with 444 additions and 620 deletions
|
|
@ -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"
|
||||
|
|
|
|||
233
mono-fmt-macro/src/format.rs
Normal file
233
mono-fmt-macro/src/format.rs
Normal 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)
|
||||
}
|
||||
}
|
||||
|
|
@ -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(),
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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(())
|
||||
}
|
||||
}
|
||||
56
mono-fmt-macro/src/to_tokens.rs
Normal file
56
mono-fmt-macro/src/to_tokens.rs
Normal 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)
|
||||
}
|
||||
}
|
||||
68
src/args.rs
68
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<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;
|
||||
);
|
||||
|
|
|
|||
59
src/lib.rs
59
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<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();
|
||||
}
|
||||
|
|
|
|||
58
src/opts.rs
58
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<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 {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue