use std::{collections::HashSet, fmt::Display};
use proc_macro2::{Span, TokenStream};
use quote::quote;
use syn::{
bracketed,
parse::{Parse, ParseBuffer, ParseStream},
token::Bracket,
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: bool,
}
impl Display for Output {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "[{}]", self.name)
}
}
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 syn::Path { segments, .. } = path;
segments
.last()
.map(|segment| segment.ident.to_string().to_lowercase())
.ok_or(syn::Error::new(
Span::call_site(),
&format!("failed to get Output ident from Type {:}", quote!(#ty)),
))
}
} 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: false,
})
}
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) {
self.logging = true;
}
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(|_| output.add_logging())?;
}
(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,
_ => unimplemented!(),
}
}
Ok(output.into())
} else {
Ok(MaybeOutput(None))
}
}
}