use std::{
collections::{hash_map::DefaultHasher, HashSet},
fmt::Display,
hash::{Hash, Hasher},
};
use proc_macro2::{Span, TokenStream};
use quote::quote;
use syn::{
braced, bracketed,
parse::{Parse, ParseBuffer, ParseStream},
token::{Brace, Bracket},
Expr, Ident, Token, Type, TypePath,
};
use crate::client::SharedClient;
#[derive(Debug, Clone)]
pub struct Output {
pub ty: Type,
pub name: String,
pub options: Option<Vec<Ident>>,
pub rate_transition: Option<SharedClient>,
pub scope: bool,
pub logging: Option<Option<Expr>>,
}
impl Display for Output {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let ty = &self.ty;
write!(f, "[{}]", quote!(#ty))
}
}
impl Output {
pub fn new(ty: Type) -> syn::Result<Self> {
if let Type::Path(TypePath { path, .. }) = &ty {
if let Some(ident) = path.get_ident() {
Ok(ident.to_string().to_lowercase())
} else {
let mut hasher = DefaultHasher::new();
path.hash(&mut hasher);
Ok(format!("{}", hasher.finish()))
}
} else {
Err(syn::Error::new(
Span::call_site(),
&format!("expected Output Type variant Path found {:}", quote!(#ty)),
))
}
.map(|name| Self {
ty,
name,
options: None,
rate_transition: None,
scope: false,
logging: None,
})
}
pub fn expand_name(&self) -> TokenStream {
let ty = &self.ty;
quote!(#ty)
}
pub fn collect(&self, clients: &mut HashSet<SharedClient>) {
self.rate_transition
.as_ref()
.map(|client| clients.insert(client.clone()));
}
pub fn add_rate_transition(&mut self, output_rate: usize, input_rate: usize) {
self.rate_transition = Some(SharedClient::sampler(
self.name.as_str(),
output_rate,
input_rate,
));
}
pub fn add_option(&mut self, option: &str) {
self.options
.get_or_insert(vec![])
.push(Ident::new(option, Span::call_site()));
}
pub fn add_logging(&mut self, size: Option<Expr>) {
self.logging = Some(size);
}
pub fn add_scope(&mut self) {
self.scope = true;
}
}
impl<'a> TryFrom<ParseBuffer<'a>> for Output {
type Error = syn::parse::Error;
fn try_from(content: ParseBuffer<'a>) -> Result<Self, Self::Error> {
content.parse::<Type>().and_then(|ty| Output::new(ty))
}
}
pub struct MaybeOutput(Option<Output>);
impl From<Output> for MaybeOutput {
fn from(value: Output) -> Self {
Self(Some(value))
}
}
impl MaybeOutput {
pub fn into_inner(self) -> Option<Output> {
self.0
}
}
impl Parse for MaybeOutput {
fn parse(input: ParseStream) -> syn::Result<Self> {
if input.peek(Bracket) {
let content;
let _ = bracketed!(content in input);
let mut output = Output::try_from(content)?;
loop {
match (
input.peek(Token![!]),
input.peek(Token![$]),
input.peek(Token![..]),
input.peek(Token![~]),
) {
(true, false, false, false) => {
input
.parse::<Token![!]>()
.map(|_| output.add_option("bootstrap"))?;
}
(false, true, false, false) => {
input.parse::<Token![$]>().map(|_| {
let size = if input.peek(Brace) {
let content;
let _ = braced!(content in input);
Some(content.parse::<Expr>()?)
} else {
None
};
output.add_logging(size);
Ok(())
})??;
}
(false, false, true, false) => {
input
.parse::<Token![..]>()
.map(|_| output.add_option("unbounded"))?;
}
(false, false, false, true) => {
input.parse::<Token![~]>().map(|_| output.add_scope())?;
}
(false, false, false, false) => break,
_ => panic!(
"only the following combination of tokens is allowed: !, $, .. and ~"
),
}
}
Ok(output.into())
} else {
Ok(MaybeOutput(None))
}
}
}