924 lines
27 KiB
Rust
924 lines
27 KiB
Rust
// 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<proc_macro2::Span> {
|
|
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<T, KW> {
|
|
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<syn::token::Paren>,
|
|
pub(crate) body: T,
|
|
}
|
|
|
|
crate::fold::impl_fold! {
|
|
struct HdlAttr<T, KW,> {
|
|
pound_token: Pound,
|
|
style: AttrStyle,
|
|
bracket_token: syn::token::Bracket,
|
|
kw: KW,
|
|
paren_token: Option<syn::token::Paren>,
|
|
body: T,
|
|
}
|
|
}
|
|
|
|
#[allow(dead_code)]
|
|
impl<T, KW> HdlAttr<T, KW> {
|
|
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<T2>(self, body: T2) -> HdlAttr<T2, KW> {
|
|
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<R, E, F: FnOnce(T) -> Result<R, E>>(
|
|
self,
|
|
f: F,
|
|
) -> Result<HdlAttr<R, KW>, 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, F: FnOnce(T) -> R>(self, f: F) -> HdlAttr<R, KW> {
|
|
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<T: Default, KW: Default> Default for HdlAttr<T, KW> {
|
|
fn default() -> Self {
|
|
T::default().into()
|
|
}
|
|
}
|
|
|
|
impl<T, KW: Default> From<T> for HdlAttr<T, KW> {
|
|
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<T: ToTokens, KW: ToTokens + Spanned> ToTokens for HdlAttr<T, KW> {
|
|
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<KW: CustomToken>(attr: &Attribute) -> bool {
|
|
attr.path().is_ident(KW::IDENT_STR)
|
|
}
|
|
|
|
impl<T: Parse, KW: Parse> HdlAttr<T, KW> {
|
|
fn parse_and_take_attr(attrs: &mut Vec<Attribute>) -> syn::Result<Option<Self>>
|
|
where
|
|
KW: ToTokens,
|
|
{
|
|
let mut retval = None;
|
|
let mut errors = Errors::new();
|
|
attrs.retain(|attr| {
|
|
if let Ok(kw) = syn::parse2::<KW>(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<Option<Self>>
|
|
where
|
|
KW: ToTokens,
|
|
{
|
|
let mut retval = None;
|
|
let mut errors = Errors::new();
|
|
for attr in attrs {
|
|
if let Ok(kw) = syn::parse2::<KW>(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<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 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<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 {}
|
|
|
|
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 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<std::cmp::Ordering> {
|
|
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<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]
|
|
$($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<T: IntoIterator<Item = $option_enum_name>>(&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<T: IntoIterator<Item = $option_enum_name>>(iter: T) -> Self {
|
|
let mut retval = Self::default();
|
|
retval.extend(iter);
|
|
retval
|
|
}
|
|
}
|
|
|
|
impl Extend<$options_name> for $options_name {
|
|
fn extend<T: IntoIterator<Item = $options_name>>(&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<T: IntoIterator<Item = $options_name>>(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<Self::Item> {
|
|
$(
|
|
if let Some(value) = self.0.$key.take() {
|
|
return Some($option_enum_name::$Variant(value));
|
|
}
|
|
)*
|
|
None
|
|
}
|
|
|
|
#[allow(unused_mut, unused_variables)]
|
|
fn fold<B, F: FnMut(B, Self::Item) -> 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<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() && !$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<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));
|
|
)?
|
|
})*
|
|
}
|
|
}
|
|
}
|
|
|
|
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 = <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);
|
|
}
|
|
}
|
|
|
|
fn hdl_module_impl(item: ItemFn) -> syn::Result<TokenStream> {
|
|
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<TokenStream> {
|
|
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<TokenStream> {
|
|
let kw = kw::hdl::default();
|
|
let item = quote! { #[#kw(#attr)] #item };
|
|
let item = syn::parse2::<Item>(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",
|
|
)),
|
|
}
|
|
}
|