Skip to content

Commit

Permalink
current status
Browse files Browse the repository at this point in the history
  • Loading branch information
ArthurZucker committed Jun 16, 2024
1 parent 9559dea commit e53f4ca
Show file tree
Hide file tree
Showing 2 changed files with 69 additions and 50 deletions.
27 changes: 22 additions & 5 deletions tokenizers/display_derive/src/fmt_parsing.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use quote::ToTokens;
use syn::{
parse::{Parse, ParseStream},
punctuated::Punctuated,
token, Expr,
token, Attribute, Expr,
};

/// Representation of a [`fmt`]-like attribute.
Expand All @@ -18,15 +18,15 @@ pub struct FmtAttribute {
/// Interpolation [`syn::LitStr`].
///
/// [`syn::LitStr`]: struct@syn::LitStr
lit: syn::LitStr,
pub lit: syn::LitStr,

/// Optional [`token::Comma`].
///
/// [`token::Comma`]: struct@token::Comma
comma: Option<token::Comma>,

/// Interpolation arguments.
args: Punctuated<FmtArgument, token::Comma>,
pub args: Punctuated<FmtArgument, token::Comma>,
}

impl Parse for FmtAttribute {
Expand Down Expand Up @@ -67,11 +67,11 @@ impl ToTokens for FmtAttribute {
/// in a [`FmtAttribute`].
/// This should be used in `[display(fmt="", alias=alias, expr)]`.
/// [1]: https://doc.rust-lang.org/stable/std/fmt/index.html#named-parameters
struct FmtArgument {
pub struct FmtArgument {
/// `identifier =` [`Ident`].
///
/// [`Ident`]: struct@syn::Ident
alias: Option<(syn::Ident, token::Eq)>,
pub alias: Option<(syn::Ident, token::Eq)>,

/// `expression` [`Expr`].
expr: Expr,
Expand Down Expand Up @@ -106,3 +106,20 @@ impl ToTokens for FmtArgument {
self.expr.to_tokens(tokens)
}
}

pub fn find_display_attribute(attrs: &[Attribute]) -> Option<FmtAttribute> {
let display_attr = attrs.iter().find(|attr| attr.path.is_ident("display"));

let attr: Option<FmtAttribute> = if let Some(attr) = display_attr {
match attr.parse_args::<FmtAttribute>() {
Ok(display_macro) => Some(display_macro),
Err(e) => {
e.to_compile_error();
None
}
}
} else {
None
};
attr
}
92 changes: 47 additions & 45 deletions tokenizers/display_derive/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,45 +1,31 @@
extern crate proc_macro;
use proc_macro::TokenStream;
use quote::quote;
use syn::{parse_macro_input, DeriveInput};
use syn::{parse_macro_input, stringify_punct, DeriveInput};

mod fmt_parsing;
use fmt_parsing::FmtAttribute;
use fmt_parsing::{find_display_attribute, FmtAttribute};

#[proc_macro_derive(Display, attributes(display))]
pub fn display_derive(input: TokenStream) -> TokenStream {
// Parse the parsed_input tokens into a syntax tree
let parsed_input = parse_macro_input!(input as DeriveInput);
// Find the `display` attribute
let display_attr = parsed_input
.attrs
.iter()
.find(|attr| attr.path.is_ident("display"));

let fmt = if let Some(attr) = display_attr {
match attr.parse_args::<FmtAttribute>() {
Ok(display_macro) => quote! { write!(f, #display_macro) },
Err(e) => return e.to_compile_error().into(),
}
} else {
quote! {}
};
let attr = find_display_attribute(&parsed_input.attrs);
// 1. If the attrs are not None, then we defer to this.
// Meaning we juste return quote!{ format!(#fmt, #attr)}
let ident = &parsed_input.ident;

let body = if fmt.is_empty() {
let body = {
// 2. We automatically parse
match &parsed_input.data {
syn::Data::Struct(s) => generate_fmt_impl_for_struct(s, ident),
syn::Data::Struct(s) => generate_fmt_impl_for_struct(s, ident, &attr),
syn::Data::Enum(e) => generate_fmt_impl_for_enum(e, ident),
syn::Data::Union(u) => {
let error = syn::Error::new_spanned(u.union_token, "Unions are not supported");
return proc_macro::TokenStream::from(error.into_compile_error());
}
}
} else {
fmt
};

let expanded = quote! {
Expand All @@ -57,41 +43,57 @@ pub fn display_derive(input: TokenStream) -> TokenStream {
fn generate_fmt_impl_for_struct(
data_struct: &syn::DataStruct,
ident: &syn::Ident,
attrs: &Option<FmtAttribute>,
) -> proc_macro2::TokenStream {
let fields = &data_struct.fields;

// Extract field names and types
let field_names: Vec<_> = fields.iter().map(|f| f.ident.as_ref().unwrap()).collect();
let field_types: Vec<_> = fields.iter().map(|f| &f.ty).collect();

quote! {
write!(f, "{}(", stringify!(#ident))?;
let mut first = true;
#(
if !first {
write!(f, ", ")?;
}
first = false;
// Generate field formatting expressions
let field_formats: Vec<_> = fields
.iter()
.map(|f| {
let field_name = &f.ident;
let fmts = find_display_attribute(&f.attrs);

let field_value = &self.#field_names;
write!(f, "{}=", stringify!(#field_names))?;
if std::any::TypeId::of::<#field_types>() == std::any::TypeId::of::<String>() {
println!("We have a string!");
write!(f, "\"{}\"", field_value)?;
if let Some(attr) = attrs {
if attr.args.is_empty() {
// If there is a prefix and no args, use fmts if it exists
if let Some(fmt) = fmts {
// Combine prefix and fmts
quote! {
write!(f, "{}{}", #fmt.lit.value(), #fmt.args.to_string())?;
}
} else {
// If no fmts, write just the field value
quote! {
write!(f, "{}", self.#field_name)?;
}
}
} else {
// If there are args to the attribute, use attr.lit and attr.args exclusively
quote! {
write!(f, "{}{}", #attr.lit.value(), #attr.args.to_string())?;
}
}
} else {
let s = format!("{}", field_value);
let mut chars = s.chars();
let mut prefix = (&mut chars).take(100 - 1).collect::<String>();
if chars.next().is_some() {
prefix.push('…');
// If there is no attribute, print everything directly
quote! {
write!(f, "{}", self.#field_name)?;
}
write!(f, "{}", prefix)?;
}
)*
write!(f, ")")
})
.collect();

// Generate the final implementation of Display trait for the struct
quote! {
impl std::fmt::Display for #ident {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "{}(", stringify!(#ident))?;
#(#field_formats)*
write!(f, ")")
}
}
}
}

fn generate_fmt_impl_for_enum(
data_enum: &syn::DataEnum,
ident: &syn::Ident,
Expand Down

0 comments on commit e53f4ca

Please sign in to comment.