// SPDX-License-Identifier: LGPL-3.0-or-later // See Notices.txt for copyright information #![cfg_attr(test, recursion_limit = "512")] use proc_macro2::{Span, TokenStream}; use quote::{quote, ToTokens}; use std::io::{ErrorKind, Write}; use syn::{ bracketed, parenthesized, parse::{Parse, ParseStream, Parser}, parse_quote, punctuated::Pair, spanned::Spanned, AttrStyle, Attribute, Error, Item, ItemFn, Token, }; mod fold; mod hdl_bundle; mod hdl_enum; mod hdl_type_alias; mod hdl_type_common; mod module; pub(crate) trait CustomToken: Copy + Spanned + ToTokens + std::fmt::Debug + Eq + std::hash::Hash + Default + quote::IdentFragment + Parse { const IDENT_STR: &'static str; } mod kw { pub(crate) use syn::token::Extern as extern_; macro_rules! custom_keyword { ($kw:ident) => { syn::custom_keyword!($kw); impl quote::IdentFragment for $kw { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { f.write_str(stringify!($kw)) } fn span(&self) -> Option { Some(self.span) } } crate::fold::no_op_fold!($kw); impl crate::CustomToken for $kw { const IDENT_STR: &'static str = stringify!($kw); } }; } custom_keyword!(clock_domain); custom_keyword!(connect_inexact); custom_keyword!(custom_bounds); custom_keyword!(flip); custom_keyword!(hdl); custom_keyword!(hdl_module); custom_keyword!(input); custom_keyword!(incomplete_wire); custom_keyword!(instance); custom_keyword!(m); custom_keyword!(memory); custom_keyword!(memory_array); custom_keyword!(memory_with_init); custom_keyword!(no_reset); custom_keyword!(no_runtime_generics); custom_keyword!(no_static); custom_keyword!(outline_generated); custom_keyword!(output); custom_keyword!(reg_builder); custom_keyword!(reset); custom_keyword!(skip); custom_keyword!(target); custom_keyword!(wire); } type Pound = Token![#]; // work around https://github.com/rust-lang/rust/issues/50676 #[derive(Clone, Debug)] pub(crate) struct HdlAttr { pub(crate) pound_token: Pound, pub(crate) style: AttrStyle, pub(crate) bracket_token: syn::token::Bracket, pub(crate) kw: KW, pub(crate) paren_token: Option, pub(crate) body: T, } crate::fold::impl_fold! { struct HdlAttr { pound_token: Pound, style: AttrStyle, bracket_token: syn::token::Bracket, kw: KW, paren_token: Option, body: T, } } #[allow(dead_code)] impl HdlAttr { pub(crate) fn split_body(self) -> (HdlAttr<(), KW>, T) { let Self { pound_token, style, bracket_token, kw, paren_token, body, } = self; ( HdlAttr { pound_token, style, bracket_token, kw, paren_token, body: (), }, body, ) } pub(crate) fn replace_body(self, body: T2) -> HdlAttr { let Self { pound_token, style, bracket_token, kw, paren_token, body: _, } = self; HdlAttr { pound_token, style, bracket_token, kw, paren_token, body, } } pub(crate) fn as_ref(&self) -> HdlAttr<&T, KW> where KW: Clone, { let Self { pound_token, style, bracket_token, ref kw, paren_token, ref body, } = *self; HdlAttr { pound_token, style, bracket_token, kw: kw.clone(), paren_token, body, } } pub(crate) fn try_map Result>( self, f: F, ) -> Result, E> { let Self { pound_token, style, bracket_token, kw, paren_token, body, } = self; Ok(HdlAttr { pound_token, style, bracket_token, kw, paren_token, body: f(body)?, }) } pub(crate) fn map R>(self, f: F) -> HdlAttr { let Self { pound_token, style, bracket_token, kw, paren_token, body, } = self; HdlAttr { pound_token, style, bracket_token, kw, paren_token, body: f(body), } } fn to_attr(&self) -> Attribute where T: ToTokens, KW: ToTokens, { parse_quote! { #self } } } impl Default for HdlAttr { fn default() -> Self { T::default().into() } } impl From for HdlAttr { fn from(body: T) -> Self { HdlAttr { pound_token: Default::default(), style: AttrStyle::Outer, bracket_token: Default::default(), kw: Default::default(), paren_token: Default::default(), body, } } } impl ToTokens for HdlAttr { fn to_tokens(&self, tokens: &mut TokenStream) { self.pound_token.to_tokens(tokens); match self.style { AttrStyle::Inner(style) => style.to_tokens(tokens), AttrStyle::Outer => {} }; self.bracket_token.surround(tokens, |tokens| { self.kw.to_tokens(tokens); match self.paren_token { Some(paren_token) => { paren_token.surround(tokens, |tokens| self.body.to_tokens(tokens)) } None => { let body = self.body.to_token_stream(); if !body.is_empty() { syn::token::Paren(self.kw.span()) .surround(tokens, |tokens| tokens.extend([body])); } } } }); } } fn is_hdl_attr(attr: &Attribute) -> bool { attr.path().is_ident(KW::IDENT_STR) } impl HdlAttr { fn parse_and_take_attr(attrs: &mut Vec) -> syn::Result> where KW: ToTokens, { let mut retval = None; let mut errors = Errors::new(); attrs.retain(|attr| { if let Ok(kw) = syn::parse2::(attr.path().to_token_stream()) { if retval.is_some() { errors.push(Error::new_spanned( attr, format_args!("more than one #[{}] attribute", kw.to_token_stream()), )); } errors.unwrap_or_default(Self::parse_attr(attr).map(|v| retval = Some(v))); false } else { true } }); errors.finish()?; Ok(retval) } fn parse_and_leave_attr(attrs: &[Attribute]) -> syn::Result> where KW: ToTokens, { let mut retval = None; let mut errors = Errors::new(); for attr in attrs { if let Ok(kw) = syn::parse2::(attr.path().to_token_stream()) { if retval.is_some() { errors.push(Error::new_spanned( attr, format_args!("more than one #[{}] attribute", kw.to_token_stream()), )); } errors.unwrap_or_default(Self::parse_attr(attr).map(|v| retval = Some(v))); } } errors.finish()?; Ok(retval) } fn parse_attr(attr: &Attribute) -> syn::Result { match attr.style { AttrStyle::Outer => Parser::parse2(Self::parse_outer, attr.to_token_stream()), AttrStyle::Inner(_) => Parser::parse2(Self::parse_inner, attr.to_token_stream()), } } fn parse_starting_with_brackets( pound_token: Token![#], style: AttrStyle, input: ParseStream, ) -> syn::Result { let bracket_content; let bracket_token = bracketed!(bracket_content in input); let kw = bracket_content.parse()?; let paren_content; let body; let paren_token; if bracket_content.is_empty() { body = match syn::parse2(TokenStream::default()) { Ok(body) => body, Err(_) => { parenthesized!(paren_content in bracket_content); unreachable!(); } }; paren_token = None; } else { paren_token = Some(parenthesized!(paren_content in bracket_content)); body = paren_content.parse()?; } Ok(Self { pound_token, style, bracket_token, kw, paren_token, body, }) } fn parse_inner(input: ParseStream) -> syn::Result { let pound_token = input.parse()?; let style = AttrStyle::Inner(input.parse()?); Self::parse_starting_with_brackets(pound_token, style, input) } fn parse_outer(input: ParseStream) -> syn::Result { let pound_token = input.parse()?; let style = AttrStyle::Outer; Self::parse_starting_with_brackets(pound_token, style, input) } } #[allow(dead_code)] pub(crate) trait PairsIterExt: Sized + Iterator { fn map_pair T2, PunctFn: FnMut(P1) -> P2>( self, mut value_fn: ValueFn, mut punct_fn: PunctFn, ) -> impl Iterator> where Self: Iterator>, { self.map(move |p| { let (t, p) = p.into_tuple(); let t = value_fn(t); let p = p.map(&mut punct_fn); Pair::new(t, p) }) } fn filter_map_pair Option, PunctFn: FnMut(P1) -> P2>( self, mut value_fn: ValueFn, mut punct_fn: PunctFn, ) -> impl Iterator> where Self: Iterator>, { self.filter_map(move |p| { let (t, p) = p.into_tuple(); let t = value_fn(t)?; let p = p.map(&mut punct_fn); Some(Pair::new(t, p)) }) } fn map_pair_value T2>( self, f: F, ) -> impl Iterator> where Self: Iterator>, { self.map_pair(f, |v| v) } fn filter_map_pair_value Option>( self, f: F, ) -> impl Iterator> where Self: Iterator>, { self.filter_map_pair(f, |v| v) } fn map_pair_value_mut<'a, T1: 'a, T2: 'a, P: Clone + 'a, F: FnMut(T1) -> T2 + 'a>( self, f: F, ) -> impl Iterator> + 'a where Self: Iterator> + 'a, { self.map_pair(f, |v| v.clone()) } fn filter_map_pair_value_mut< 'a, T1: 'a, T2: 'a, P: Clone + 'a, F: FnMut(T1) -> Option + 'a, >( self, f: F, ) -> impl Iterator> + 'a where Self: Iterator> + 'a, { self.filter_map_pair(f, |v| v.clone()) } fn map_pair_value_ref<'a, T1: 'a, T2: 'a, P: Clone + 'a, F: FnMut(T1) -> T2 + 'a>( self, f: F, ) -> impl Iterator> + 'a where Self: Iterator> + 'a, { self.map_pair(f, |v| v.clone()) } fn filter_map_pair_value_ref< 'a, T1: 'a, T2: 'a, P: Clone + 'a, F: FnMut(T1) -> Option + 'a, >( self, f: F, ) -> impl Iterator> + 'a where Self: Iterator> + 'a, { self.filter_map_pair(f, |v| v.clone()) } } impl>> PairsIterExt for Iter {} pub(crate) struct Errors { error: Option, finished: bool, } impl Drop for Errors { fn drop(&mut self) { if !std::thread::panicking() { assert!(self.finished, "didn't run finish"); } } } impl Errors { pub(crate) fn new() -> Self { Self { error: None, finished: false, } } pub(crate) fn push(&mut self, e: Error) -> &mut Self { match self.error { Some(ref mut old) => old.combine(e), None => self.error = Some(e), } self } pub(crate) fn push_result(&mut self, e: syn::Result<()>) -> &mut Self { self.ok(e); self } pub(crate) fn error( &mut self, tokens: impl ToTokens, message: impl std::fmt::Display, ) -> &mut Self { self.push(Error::new_spanned(tokens, message)); self } pub(crate) fn ok(&mut self, v: syn::Result) -> Option { match v { Ok(v) => Some(v), Err(e) => { self.push(e); None } } } pub(crate) fn unwrap_or_else( &mut self, v: syn::Result, fallback: impl FnOnce() -> T, ) -> T { match v { Ok(v) => v, Err(e) => { self.push(e); fallback() } } } pub(crate) fn unwrap_or(&mut self, v: syn::Result, fallback: T) -> T { self.unwrap_or_else(v, || fallback) } pub(crate) fn unwrap_or_default(&mut self, v: syn::Result) -> T { self.unwrap_or_else(v, T::default) } pub(crate) fn finish(&mut self) -> syn::Result<()> { self.finished = true; match self.error.take() { Some(e) => Err(e), None => Ok(()), } } } impl Default for Errors { fn default() -> Self { Self::new() } } macro_rules! impl_extra_traits_for_options { ( #[no_ident_fragment] $enum_vis:vis enum $option_enum_name:ident { $($Variant:ident($key:ident),)* } ) => { impl Copy for $option_enum_name {} }; ( $enum_vis:vis enum $option_enum_name:ident { $($Variant:ident($key:ident),)* } ) => { impl Copy for $option_enum_name {} impl PartialEq for $option_enum_name { fn eq(&self, other: &Self) -> bool { self.cmp(other).is_eq() } } impl Eq for $option_enum_name {} impl PartialOrd for $option_enum_name { fn partial_cmp(&self, other: &Self) -> Option { Some(self.cmp(other)) } } impl Ord for $option_enum_name { fn cmp(&self, other: &Self) -> std::cmp::Ordering { self.variant().cmp(&other.variant()) } } impl quote::IdentFragment for $option_enum_name { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { let _ = f; match *self { $(Self::$Variant(ref v) => quote::IdentFragment::fmt(&v.0, f),)* } } fn span(&self) -> Option { match *self { $(Self::$Variant(ref v) => quote::IdentFragment::span(&v.0),)* } } } impl $option_enum_name { #[allow(dead_code)] $enum_vis fn span(&self) -> proc_macro2::Span { quote::IdentFragment::span(self).unwrap() } } }; ( $(#[no_ident_fragment])? $enum_vis:vis enum $option_enum_name:ident { $($Variant:ident($key:ident $(, $value:ty)?),)* } ) => {}; } pub(crate) use impl_extra_traits_for_options; macro_rules! options { ( #[options = $options_name:ident] $($tt:tt)* ) => { crate::options! { #[options = $options_name, punct = syn::Token![,], allow_duplicates = false] $($tt)* } }; ( #[options = $options_name:ident, punct = $Punct:ty, allow_duplicates = true] $(#[$($enum_meta:tt)*])* $enum_vis:vis enum $option_enum_name:ident { $($Variant:ident($key:ident $(, $value:ty)?),)* } ) => { crate::options! { #[options = $options_name, punct = $Punct, allow_duplicates = (true)] $(#[$($enum_meta)*])* $enum_vis enum $option_enum_name { $($Variant($key $(, $value)?),)* } } impl Extend<$option_enum_name> for $options_name { fn extend>(&mut self, iter: T) { iter.into_iter().for_each(|v| match v { $($option_enum_name::$Variant(v) => { self.$key = Some(v); })* }); } } impl FromIterator<$option_enum_name> for $options_name { fn from_iter>(iter: T) -> Self { let mut retval = Self::default(); retval.extend(iter); retval } } impl Extend<$options_name> for $options_name { fn extend>(&mut self, iter: T) { iter.into_iter().for_each(|v| { $(if let Some(v) = v.$key { self.$key = Some(v); })* }); } } impl FromIterator<$options_name> for $options_name { fn from_iter>(iter: T) -> Self { let mut retval = Self::default(); retval.extend(iter); retval } } }; ( #[options = $options_name:ident, punct = $Punct:ty, allow_duplicates = $allow_duplicates:expr] $(#[$($enum_meta:tt)*])* $enum_vis:vis enum $option_enum_name:ident { $($Variant:ident($key:ident $(, $value:ty)?),)* } ) => { crate::options! { $(#[$($enum_meta)*])* $enum_vis enum $option_enum_name { $($Variant($key $(, $value)?),)* } } #[derive(Clone, Debug, Default)] #[allow(non_snake_case)] $enum_vis struct $options_name { $( $enum_vis $key: Option<(crate::kw::$key, $(syn::token::Paren, $value)?)>, )* } crate::fold::impl_fold! { struct $options_name<> { $($key: Option<(crate::kw::$key, $(syn::token::Paren, $value)?)>,)* } } const _: () = { #[derive(Clone, Debug)] $enum_vis struct Iter($enum_vis $options_name); impl IntoIterator for $options_name { type Item = $option_enum_name; type IntoIter = Iter; fn into_iter(self) -> Self::IntoIter { Iter(self) } } impl Iterator for Iter { type Item = $option_enum_name; fn next(&mut self) -> Option { $( if let Some(value) = self.0.$key.take() { return Some($option_enum_name::$Variant(value)); } )* None } #[allow(unused_mut, unused_variables)] fn fold B>(mut self, mut init: B, mut f: F) -> B { $( if let Some(value) = self.0.$key.take() { init = f(init, $option_enum_name::$Variant(value)); } )* init } } }; impl syn::parse::Parse for $options_name { fn parse(input: syn::parse::ParseStream) -> syn::Result { #![allow(unused_mut, unused_variables, unreachable_code)] let mut retval = Self::default(); while !input.is_empty() { let old_input = input.fork(); match input.parse::<$option_enum_name>()? { $($option_enum_name::$Variant(v) => { if retval.$key.replace(v).is_some() && !$allow_duplicates { return Err(old_input.error(concat!("duplicate ", stringify!($key), " option"))); } })* } if input.is_empty() { break; } input.parse::<$Punct>()?; } Ok(retval) } } impl quote::ToTokens for $options_name { #[allow(unused_mut, unused_variables, unused_assignments)] fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { let mut separator: Option<$Punct> = None; $(if let Some(v) = &self.$key { separator.to_tokens(tokens); separator = Some(Default::default()); v.0.to_tokens(tokens); $(v.1.surround( tokens, |tokens| <$value as quote::ToTokens>::to_tokens(&v.2, tokens), );)? })* } } }; ( $(#[$($enum_meta:tt)*])* $enum_vis:vis enum $option_enum_name:ident { $($Variant:ident($key:ident $(, $value:ty)?),)* } ) => { #[derive(Clone, Debug)] $enum_vis enum $option_enum_name { $($Variant((crate::kw::$key, $(syn::token::Paren, $value)?)),)* } crate::impl_extra_traits_for_options! { $(#[$($enum_meta)*])* $enum_vis enum $option_enum_name { $($Variant($key $(, $value)?),)* } } crate::fold::impl_fold! { enum $option_enum_name<> { $($Variant((crate::kw::$key, $(syn::token::Paren, $value)?)),)* } } impl syn::parse::Parse for $option_enum_name { fn parse(input: syn::parse::ParseStream) -> syn::Result { let lookahead = input.lookahead1(); $( if lookahead.peek(crate::kw::$key) { #[allow(unused_variables)] let paren_content: syn::parse::ParseBuffer; return Ok($option_enum_name::$Variant(( input.parse()?, $( syn::parenthesized!(paren_content in input), paren_content.parse::<$value>()?, )? ))); } )* Err(lookahead.error()) } } impl quote::ToTokens for $option_enum_name { fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) { let _ = tokens; match *self { $($option_enum_name::$Variant(ref v) => { v.0.to_tokens(tokens); $( let value: &$value = &v.2; v.1.surround(tokens, |tokens| value.to_tokens(tokens)); )? })* } } } impl $option_enum_name { #[allow(dead_code)] fn variant(&self) -> usize { #[repr(usize)] enum Variant { $($Variant,)* __Last, // so it doesn't complain about zero-variant enums } match *self { $(Self::$Variant(..) => Variant::$Variant as usize,)* } } } }; } use crate::hdl_type_alias::hdl_type_alias_impl; pub(crate) use options; pub(crate) fn outline_generated(contents: TokenStream, prefix: &str) -> TokenStream { let out_dir = env!("OUT_DIR"); let mut file = tempfile::Builder::new() .prefix(prefix) .rand_bytes(6) .suffix(".tmp.rs") .tempfile_in(out_dir) .unwrap(); struct PrintOnPanic<'a>(&'a TokenStream); impl Drop for PrintOnPanic<'_> { fn drop(&mut self) { if std::thread::panicking() { println!("{}", self.0); } } } let _print_on_panic = PrintOnPanic(&contents); let contents = prettyplease::unparse(&parse_quote! { #contents }); let hash = ::digest(&contents); let hash = base16ct::HexDisplay(&hash[..5]); file.write_all(contents.as_bytes()).unwrap(); let dest_file = std::path::Path::new(out_dir).join(format!("{prefix}{hash:x}.rs")); // don't write if it already exists so cargo doesn't try to recompile constantly. match file.persist_noclobber(&dest_file) { Err(e) if e.error.kind() == ErrorKind::AlreadyExists => {} e => { e.unwrap(); } } eprintln!("generated {}", dest_file.display()); let dest_file = dest_file.to_str().unwrap(); quote! { include!(#dest_file); } } fn hdl_module_impl(item: ItemFn) -> syn::Result { let func = module::ModuleFn::parse_from_fn(item)?; let options = func.config_options(); let mut contents = func.generate(); if options.outline_generated.is_some() { contents = outline_generated(contents, "module-"); } Ok(contents) } pub fn hdl_module(attr: TokenStream, item: TokenStream) -> syn::Result { let kw = kw::hdl_module::default(); hdl_module_impl(syn::parse2(quote! { #[#kw(#attr)] #item })?) } pub fn hdl_attr(attr: TokenStream, item: TokenStream) -> syn::Result { let kw = kw::hdl::default(); let item = quote! { #[#kw(#attr)] #item }; let item = syn::parse2::(item)?; match item { Item::Enum(item) => hdl_enum::hdl_enum(item), Item::Struct(item) => hdl_bundle::hdl_bundle(item), Item::Fn(item) => hdl_module_impl(item), Item::Type(item) => hdl_type_alias_impl(item), _ => Err(syn::Error::new( Span::call_site(), "top-level #[hdl] can only be used on structs, enums, type aliases, or functions", )), } }