fayalite/crates/fayalite-proc-macros-impl/src/lib.rs

732 lines
21 KiB
Rust
Raw Normal View History

2024-06-11 06:09:13 +00:00
// 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,
AttrStyle, Attribute, Error, Item, Token,
2024-06-11 06:09:13 +00:00
};
mod fold;
mod module;
mod value_derive_common;
mod value_derive_enum;
mod value_derive_struct;
mod kw {
pub(crate) use syn::token::{
Enum as enum_, Extern as extern_, Struct as struct_, Where as where_,
};
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<proc_macro2::Span> {
Some(self.span)
}
}
crate::fold::no_op_fold!($kw);
};
}
custom_keyword!(clock_domain);
custom_keyword!(connect_inexact);
custom_keyword!(fixed_type);
custom_keyword!(flip);
custom_keyword!(hdl);
custom_keyword!(input);
custom_keyword!(instance);
custom_keyword!(m);
custom_keyword!(memory);
custom_keyword!(memory_array);
custom_keyword!(memory_with_init);
custom_keyword!(no_reset);
custom_keyword!(outline_generated);
custom_keyword!(output);
custom_keyword!(reg_builder);
custom_keyword!(reset);
custom_keyword!(reset_default);
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<T> {
pub(crate) pound_token: Pound,
pub(crate) style: AttrStyle,
pub(crate) bracket_token: syn::token::Bracket,
pub(crate) hdl: kw::hdl,
pub(crate) paren_token: Option<syn::token::Paren>,
pub(crate) body: T,
}
crate::fold::impl_fold! {
struct HdlAttr<T,> {
pound_token: Pound,
style: AttrStyle,
bracket_token: syn::token::Bracket,
hdl: kw::hdl,
paren_token: Option<syn::token::Paren>,
body: T,
}
}
#[allow(dead_code)]
impl<T> HdlAttr<T> {
pub(crate) fn split_body(self) -> (HdlAttr<()>, T) {
let Self {
pound_token,
style,
bracket_token,
hdl,
paren_token,
body,
} = self;
(
HdlAttr {
pound_token,
style,
bracket_token,
hdl,
paren_token,
body: (),
},
body,
)
}
pub(crate) fn replace_body<T2>(self, body: T2) -> HdlAttr<T2> {
let Self {
pound_token,
style,
bracket_token,
hdl,
paren_token,
body: _,
} = self;
HdlAttr {
pound_token,
style,
bracket_token,
hdl,
paren_token,
body,
}
}
pub(crate) fn as_ref(&self) -> HdlAttr<&T> {
let Self {
pound_token,
style,
bracket_token,
hdl,
paren_token,
ref body,
} = *self;
HdlAttr {
pound_token,
style,
bracket_token,
hdl,
paren_token,
body,
}
}
pub(crate) fn try_map<R, E, F: FnOnce(T) -> Result<R, E>>(self, f: F) -> Result<HdlAttr<R>, E> {
let Self {
pound_token,
style,
bracket_token,
hdl,
paren_token,
body,
} = self;
Ok(HdlAttr {
pound_token,
style,
bracket_token,
hdl,
paren_token,
body: f(body)?,
})
}
pub(crate) fn map<R, F: FnOnce(T) -> R>(self, f: F) -> HdlAttr<R> {
let Self {
pound_token,
style,
bracket_token,
hdl,
paren_token,
body,
} = self;
HdlAttr {
pound_token,
style,
bracket_token,
hdl,
paren_token,
body: f(body),
}
}
fn to_attr(&self) -> Attribute
where
T: ToTokens,
{
parse_quote! { #self }
}
}
impl<T: Default> Default for HdlAttr<T> {
fn default() -> Self {
T::default().into()
}
}
impl<T> From<T> for HdlAttr<T> {
fn from(body: T) -> Self {
HdlAttr {
pound_token: Default::default(),
style: AttrStyle::Outer,
bracket_token: Default::default(),
hdl: Default::default(),
paren_token: Default::default(),
body,
}
}
}
impl<T: ToTokens> ToTokens for HdlAttr<T> {
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.hdl.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.hdl.span)
.surround(tokens, |tokens| tokens.extend([body]));
}
}
}
});
}
}
fn is_hdl_attr(attr: &Attribute) -> bool {
attr.path().is_ident("hdl")
}
impl<T: Parse> HdlAttr<T> {
fn parse_and_take_attr(attrs: &mut Vec<Attribute>) -> syn::Result<Option<Self>> {
let mut retval = None;
let mut errors = Errors::new();
attrs.retain(|attr| {
if is_hdl_attr(attr) {
if retval.is_some() {
errors.push(Error::new_spanned(attr, "more than one #[hdl] attribute"));
}
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<Option<Self>> {
let mut retval = None;
let mut errors = Errors::new();
for attr in attrs {
if is_hdl_attr(attr) {
if retval.is_some() {
errors.push(Error::new_spanned(attr, "more than one #[hdl] attribute"));
}
errors.unwrap_or_default(Self::parse_attr(attr).map(|v| retval = Some(v)));
}
}
errors.finish()?;
Ok(retval)
}
fn parse_attr(attr: &Attribute) -> syn::Result<Self> {
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<Self> {
let bracket_content;
let bracket_token = bracketed!(bracket_content in input);
let hdl = 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,
hdl,
paren_token,
body,
})
}
fn parse_inner(input: ParseStream) -> syn::Result<Self> {
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<Self> {
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<T1, T2, P1, P2, ValueFn: FnMut(T1) -> T2, PunctFn: FnMut(P1) -> P2>(
self,
mut value_fn: ValueFn,
mut punct_fn: PunctFn,
) -> impl Iterator<Item = Pair<T2, P2>>
where
Self: Iterator<Item = Pair<T1, P1>>,
{
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<T1, T2, P1, P2, ValueFn: FnMut(T1) -> Option<T2>, PunctFn: FnMut(P1) -> P2>(
self,
mut value_fn: ValueFn,
mut punct_fn: PunctFn,
) -> impl Iterator<Item = Pair<T2, P2>>
where
Self: Iterator<Item = Pair<T1, P1>>,
{
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<T1, T2, P, F: FnMut(T1) -> T2>(
self,
f: F,
) -> impl Iterator<Item = Pair<T2, P>>
where
Self: Iterator<Item = Pair<T1, P>>,
{
self.map_pair(f, |v| v)
}
fn filter_map_pair_value<T1, T2, P, F: FnMut(T1) -> Option<T2>>(
self,
f: F,
) -> impl Iterator<Item = Pair<T2, P>>
where
Self: Iterator<Item = Pair<T1, P>>,
{
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<Item = Pair<T2, P>> + 'a
where
Self: Iterator<Item = Pair<T1, &'a mut P>> + '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<T2> + 'a,
>(
self,
f: F,
) -> impl Iterator<Item = Pair<T2, P>> + 'a
where
Self: Iterator<Item = Pair<T1, &'a mut P>> + '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<Item = Pair<T2, P>> + 'a
where
Self: Iterator<Item = Pair<T1, &'a P>> + '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<T2> + 'a,
>(
self,
f: F,
) -> impl Iterator<Item = Pair<T2, P>> + 'a
where
Self: Iterator<Item = Pair<T1, &'a P>> + 'a,
{
self.filter_map_pair(f, |v| v.clone())
}
}
impl<T, P, Iter: Iterator<Item = Pair<T, P>>> PairsIterExt for Iter {}
2024-06-11 06:09:13 +00:00
pub(crate) struct Errors {
error: Option<Error>,
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<T>(&mut self, v: syn::Result<T>) -> Option<T> {
match v {
Ok(v) => Some(v),
Err(e) => {
self.push(e);
None
}
}
}
pub(crate) fn unwrap_or_else<T>(
&mut self,
v: syn::Result<T>,
fallback: impl FnOnce() -> T,
) -> T {
match v {
Ok(v) => v,
Err(e) => {
self.push(e);
fallback()
}
}
}
pub(crate) fn unwrap_or<T>(&mut self, v: syn::Result<T>, fallback: T) -> T {
self.unwrap_or_else(v, || fallback)
}
pub(crate) fn unwrap_or_default<T: Default>(&mut self, v: syn::Result<T>) -> 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 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<proc_macro2::Span> {
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]
$(#[$($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)]
$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)?)>,)*
}
}
impl syn::parse::Parse for $options_name {
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
#![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() {
return Err(old_input.error(concat!("duplicate ", stringify!($key), " option")));
}
})*
}
if input.is_empty() {
break;
}
input.parse::<syn::Token![,]>()?;
}
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<syn::Token![,]> = 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),
);)?
2024-06-11 06:09:13 +00:00
})*
}
}
};
(
$(#[$($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<Self> {
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));
)?
})*
}
}
}
};
}
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();
let contents = prettyplease::unparse(&parse_quote! { #contents });
let hash = <sha2::Sha256 as sha2::Digest>::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);
}
}
pub fn module(attr: TokenStream, item: TokenStream) -> syn::Result<TokenStream> {
let options = syn::parse2::<module::ConfigOptions>(attr)?;
let options = HdlAttr::from(options);
let func = syn::parse2::<module::ModuleFn>(quote! { #options #item })?;
let mut contents = func.generate();
if options.body.outline_generated.is_some() {
contents = outline_generated(contents, "module-");
}
Ok(contents)
}
pub fn value_derive(item: TokenStream) -> syn::Result<TokenStream> {
let item = syn::parse2::<Item>(item)?;
match item {
Item::Enum(item) => value_derive_enum::value_derive_enum(item),
Item::Struct(item) => value_derive_struct::value_derive_struct(item),
_ => Err(syn::Error::new(
Span::call_site(),
"derive(Value) can only be used on structs or enums",
)),
}
}