use proc_macro2::TokenStream;
use quote::quote;
use syn::parse::{Parse, ParseStream};
use syn::{Attribute, Error, LitInt, LitStr, Result, Token, parenthesized};
pub(crate) enum Written {
Truth,
Text,
Hex(Option<usize>),
Decimal(u16),
Int(u16),
Named(Vec<String>, Box<Written>),
Seq(Box<Written>),
Opt(Box<Written>),
}
impl Parse for Written {
fn parse(input: ParseStream<'_>) -> Result<Self> {
let name: syn::Ident = input.parse()?;
let argument = |input: ParseStream<'_>| -> Result<Option<TokenStream>> {
if input.peek(syn::token::Paren) {
let inner;
parenthesized!(inner in input);
Ok(Some(inner.parse()?))
} else {
Ok(None)
}
};
match name.to_string().as_str() {
"truth" => Ok(Self::Truth),
"text" => Ok(Self::Text),
"hex" => match argument(input)? {
Some(tokens) => Ok(Self::Hex(Some(syn::parse2::<LitInt>(tokens)?.base10_parse()?))),
None => Ok(Self::Hex(None)),
},
"decimal" => Ok(Self::Decimal(width(&name, argument(input)?)?)),
"int" => Ok(Self::Int(width(&name, argument(input)?)?)),
"name" => {
let tokens = argument(input)?.ok_or_else(|| {
Error::new(name.span(), "`name` states a name and a form, as `name(hash32, hex(32))`")
})?;
let named = syn::parse::Parser::parse2(
|input: ParseStream<'_>| {
let named: syn::Ident = input.parse()?;
let _: Token![,] = input.parse()?;
let form: Written = input.parse()?;
Ok((named, form))
},
tokens,
)?;
Ok(Self::Named(crate::words::of_snake(&named.0.to_string()), Box::new(named.1)))
}
"seq" => Ok(Self::Seq(Box::new(nested(&name, argument(input)?)?))),
"opt" => Ok(Self::Opt(Box::new(nested(&name, argument(input)?)?))),
other => Err(Error::new(
name.span(),
format!(
"`{other}` is not a written form; state one of truth, text, hex, decimal, int, \
name, seq or opt"
),
)),
}
}
}
fn width(name: &syn::Ident, argument: Option<TokenStream>) -> Result<u16> {
let tokens =
argument.ok_or_else(|| Error::new(name.span(), format!("`{name}` states a width, as `{name}(64)`")))?;
syn::parse2::<LitInt>(tokens)?.base10_parse()
}
fn nested(name: &syn::Ident, argument: Option<TokenStream>) -> Result<Written> {
let tokens = argument
.ok_or_else(|| Error::new(name.span(), format!("`{name}` states what it contains, as `{name}(text)`")))?;
syn::parse2(tokens)
}
impl Written {
pub(crate) fn shape(&self) -> TokenStream {
match self {
Self::Truth => quote!(alg.truth()),
Self::Text => quote!(alg.text()),
Self::Hex(len) => match len {
Some(len) => quote!(alg.bytes_hex(Some(#len))),
None => quote!(alg.bytes_hex(None)),
},
Self::Decimal(bits) => quote!(alg.int_decimal(false, #bits)),
Self::Int(bits) => quote!(alg.int(false, #bits)),
Self::Named(words, item) => {
let item = item.shape();
let words = words.iter().map(|word| LitStr::new(word, proc_macro2::Span::call_site()));
quote!(alg.named(&[#(#words),*], #item))
}
Self::Seq(item) => {
let item = item.shape();
quote!(alg.seq(#item))
}
Self::Opt(item) => {
let item = item.shape();
quote!(alg.opt(#item))
}
}
}
}
pub(crate) fn written_of(attrs: &[Attribute]) -> Result<Option<Written>> {
let mut written = None;
for attr in attrs.iter().filter(|attr| attr.path().is_ident("written")) {
written = Some(attr.parse_args::<Written>()?);
}
Ok(written)
}
const _: Option<Token![,]> = None;