forked from libre-chip/fayalite
initial public commit
This commit is contained in:
commit
0b958e7852
56 changed files with 30235 additions and 0 deletions
624
crates/fayalite-proc-macros-impl/src/lib.rs
Normal file
624
crates/fayalite-proc-macros-impl/src/lib.rs
Normal file
|
@ -0,0 +1,624 @@
|
|||
// 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, AttrStyle, Attribute, Error, Item, Token,
|
||||
};
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
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));)?
|
||||
})*
|
||||
}
|
||||
}
|
||||
};
|
||||
(
|
||||
$(#[$($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",
|
||||
)),
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue