use proc_macro2::{Span, TokenStream};
use quote::quote;
use syn::parse::*;
-use syn::punctuated::Punctuated;
use syn::*;
-mod kw {
- syn::custom_keyword!(derive);
- syn::custom_keyword!(DEBUG_FORMAT);
- syn::custom_keyword!(MAX);
- syn::custom_keyword!(ENCODABLE);
- syn::custom_keyword!(custom);
- syn::custom_keyword!(ORD_IMPL);
-}
-
-#[derive(Debug)]
-enum DebugFormat {
- // The user will provide a custom `Debug` impl, so we shouldn't generate
- // one
- Custom,
- // Use the specified format string in the generated `Debug` impl
- // By default, this is "{}"
- Format(String),
-}
-
// We parse the input and emit the output in a single step.
// This field stores the final macro output
struct Newtype(TokenStream);
impl Parse for Newtype {
fn parse(input: ParseStream<'_>) -> Result<Self> {
- let attrs = input.call(Attribute::parse_outer)?;
+ let mut attrs = input.call(Attribute::parse_outer)?;
let vis: Visibility = input.parse()?;
input.parse::<Token![struct]>()?;
let name: Ident = input.parse()?;
// Any additional `#[derive]` macro paths to apply
let mut derive_paths: Vec<Path> = Vec::new();
- let mut debug_format: Option<DebugFormat> = None;
+ let mut debug_format: Option<Lit> = None;
let mut max = None;
let mut consts = Vec::new();
let mut encodable = true;
let mut ord = true;
- // Parse an optional trailing comma
- let try_comma = || -> Result<()> {
- if body.lookahead1().peek(Token![,]) {
- body.parse::<Token![,]>()?;
- }
- Ok(())
- };
-
- if body.lookahead1().peek(Token![..]) {
- body.parse::<Token![..]>()?;
- } else {
- loop {
- if body.lookahead1().peek(kw::derive) {
- body.parse::<kw::derive>()?;
- let derives;
- bracketed!(derives in body);
- let derives: Punctuated<Path, Token![,]> =
- derives.parse_terminated(Path::parse)?;
- try_comma()?;
- derive_paths.extend(derives);
- continue;
- }
- if body.lookahead1().peek(kw::DEBUG_FORMAT) {
- body.parse::<kw::DEBUG_FORMAT>()?;
- body.parse::<Token![=]>()?;
- let new_debug_format = if body.lookahead1().peek(kw::custom) {
- body.parse::<kw::custom>()?;
- DebugFormat::Custom
- } else {
- let format_str: LitStr = body.parse()?;
- DebugFormat::Format(format_str.value())
- };
- try_comma()?;
- if let Some(old) = debug_format.replace(new_debug_format) {
- panic!("Specified multiple debug format options: {:?}", old);
- }
- continue;
- }
- if body.lookahead1().peek(kw::MAX) {
- body.parse::<kw::MAX>()?;
- body.parse::<Token![=]>()?;
- let val: Lit = body.parse()?;
- try_comma()?;
- if let Some(old) = max.replace(val) {
- panic!("Specified multiple MAX: {:?}", old);
- }
- continue;
- }
- if body.lookahead1().peek(kw::ENCODABLE) {
- body.parse::<kw::ENCODABLE>()?;
- body.parse::<Token![=]>()?;
- body.parse::<kw::custom>()?;
- try_comma()?;
+ attrs.retain(|attr| match attr.path.get_ident() {
+ Some(ident) => match &*ident.to_string() {
+ "custom_encodable" => {
encodable = false;
- continue;
+ false
}
- if body.lookahead1().peek(kw::ORD_IMPL) {
- body.parse::<kw::ORD_IMPL>()?;
- body.parse::<Token![=]>()?;
- body.parse::<kw::custom>()?;
+ "no_ord_impl" => {
ord = false;
- continue;
+ false
}
+ "max" => {
+ let Ok(Meta::NameValue(literal) )= attr.parse_meta() else {
+ panic!("#[max = NUMBER] attribute requires max value");
+ };
+
+ if let Some(old) = max.replace(literal.lit) {
+ panic!("Specified multiple max: {old:?}");
+ }
- // We've parsed everything that the user provided, so we're done
- if body.is_empty() {
- break;
+ false
}
+ "debug_format" => {
+ let Ok(Meta::NameValue(literal) )= attr.parse_meta() else {
+ panic!("#[debug_format = FMT] attribute requires a format");
+ };
- // Otherwise, we are parsing a user-defined constant
- let const_attrs = body.call(Attribute::parse_outer)?;
- body.parse::<Token![const]>()?;
- let const_name: Ident = body.parse()?;
- body.parse::<Token![=]>()?;
- let const_val: Expr = body.parse()?;
- try_comma()?;
- consts.push(quote! { #(#const_attrs)* #vis const #const_name: #name = #name::from_u32(#const_val); });
+ if let Some(old) = debug_format.replace(literal.lit) {
+ panic!("Specified multiple debug format options: {old:?}");
+ }
+
+ false
+ }
+ _ => true,
+ },
+ _ => true,
+ });
+
+ loop {
+ // We've parsed everything that the user provided, so we're done
+ if body.is_empty() {
+ break;
}
+
+ // Otherwise, we are parsing a user-defined constant
+ let const_attrs = body.call(Attribute::parse_outer)?;
+ body.parse::<Token![const]>()?;
+ let const_name: Ident = body.parse()?;
+ body.parse::<Token![=]>()?;
+ let const_val: Expr = body.parse()?;
+ body.parse::<Token![;]>()?;
+ consts.push(quote! { #(#const_attrs)* #vis const #const_name: #name = #name::from_u32(#const_val); });
}
- let debug_format = debug_format.unwrap_or(DebugFormat::Format("{}".to_string()));
+ let debug_format =
+ debug_format.unwrap_or_else(|| Lit::Str(LitStr::new("{}", Span::call_site())));
+
// shave off 256 indices at the end to allow space for packing these indices into enums
let max = max.unwrap_or_else(|| Lit::Int(LitInt::new("0xFFFF_FF00", Span::call_site())));
quote! {}
};
- let debug_impl = match debug_format {
- DebugFormat::Custom => quote! {},
- DebugFormat::Format(format) => {
- quote! {
- impl ::std::fmt::Debug for #name {
- fn fmt(&self, fmt: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
- write!(fmt, #format, self.as_u32())
- }
- }
+ let debug_impl = quote! {
+ impl ::std::fmt::Debug for #name {
+ fn fmt(&self, fmt: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
+ write!(fmt, #debug_format, self.as_u32())
}
}
};
+
let spec_partial_eq_impl = if let Lit::Int(max) = &max {
if let Ok(max_val) = max.base10_parse::<u32>() {
quote! {