use proc_macro::TokenStream;
use proc_macro2::{Span, TokenStream as TokenStream2, TokenTree};
use syn::parse::{Parse, ParseStream};
use syn::{Expr, Ident, LitInt, LitStr, Token};
use crate::manifest::Source;
pub(crate) struct Arg {
pub(crate) name: String,
pub(crate) span: Span,
pub(crate) value: Expr,
}
pub(crate) struct Call {
pub(crate) id: LitStr,
pub(crate) args: Vec<Arg>,
}
impl Parse for Call {
fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
if !input.peek(LitStr) {
return Err(input
.error("tr! expects a string-literal message id: tr!(\"id\", name = value, …)"));
}
let id: LitStr = input.parse()?;
let mut args = Vec::new();
while !input.is_empty() {
input.parse::<Token![,]>()?;
if input.is_empty() {
break;
}
let (name, span) = if input.peek(LitStr) {
let l: LitStr = input.parse()?;
(l.value(), l.span())
} else {
let i: Ident = input.call(syn::ext::IdentExt::parse_any)?;
(i.to_string().trim_start_matches("r#").to_owned(), i.span())
};
input.parse::<Token![=]>()?;
let value: Expr = input.parse()?;
args.push(Arg { name, span, value });
}
Ok(Call { id, args })
}
}
pub(crate) struct Input {
pub(crate) source: Source,
pub(crate) hash: u64,
pub(crate) krate: TokenStream2,
pub(crate) call: Call,
}
struct Tail {
krate: TokenStream2,
call: Call,
}
impl Parse for Tail {
fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
let mut krate = TokenStream2::new();
loop {
if input.is_empty() {
return Err(input.error("expected `;` after the crate path"));
}
if input.peek(Token![;]) {
input.parse::<Token![;]>()?;
break;
}
let tt: TokenTree = input.parse()?;
krate.extend([tt]);
}
let call: Call = input.parse()?;
Ok(Tail { krate, call })
}
}
pub(crate) fn input(tokens: TokenStream) -> syn::Result<Input> {
use proc_macro::TokenTree as T;
let err = |msg: &str| syn::Error::new(Span::call_site(), msg);
let mut it = tokens.into_iter();
let source = match it.next() {
Some(T::Literal(l)) => {
Source::Path(syn::parse::<LitStr>(TokenStream::from(T::Literal(l)))?.value())
}
Some(T::Ident(i)) if i.to_string() == "bytes" => match it.next() {
Some(T::Literal(l)) => Source::Bytes(l),
_ => return Err(err("expected the manifest bytes after `bytes`")),
},
_ => return Err(err("expected the manifest path or `bytes`")),
};
let hash = match it.next() {
Some(T::Literal(l)) => {
syn::parse::<LitInt>(TokenStream::from(T::Literal(l)))?.base10_parse::<u64>()?
}
_ => return Err(err("expected the manifest hash")),
};
match it.next() {
Some(T::Punct(p)) if p.as_char() == ';' => {}
_ => return Err(err("expected `;` after the manifest hash")),
}
let Tail { krate, call } = syn::parse::<Tail>(it.collect())?;
Ok(Input {
source,
hash,
krate,
call,
})
}