mirror of
https://github.com/Noratrieb/mono-fmt.git
synced 2026-01-17 00:35:05 +01:00
rewrite
This commit is contained in:
parent
2f7a13cb2f
commit
594047c0a1
8 changed files with 444 additions and 620 deletions
|
|
@ -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(),
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue