blob: 88a2b9659bddedd6ab9dff557462d636fe6fe9fd [file] [log] [blame]
use proc_macro::{Delimiter, Ident, Literal, Span, TokenStream, TokenTree};
use crate::{iter::TokenIter, to_tokens::ToTokens, utils::tt_span, Result};
pub(crate) struct Func {
attrs: Vec<Attribute>,
// [const] [async] [unsafe] [extern [<abi>]] fn
sig: Vec<TokenTree>,
body: TokenStream,
pub(crate) print_const: bool,
}
pub(crate) fn parse_input(input: TokenStream) -> Result<Func> {
let mut input = TokenIter::new(input);
let attrs = parse_attrs(&mut input)?;
let sig = parse_signature(&mut input);
let body: TokenStream = input.collect();
if body.is_empty()
|| !sig
.iter()
.any(|tt| if let TokenTree::Ident(i) = tt { i.to_string() == "fn" } else { false })
{
return Err(error!(
Span::call_site(),
"#[const_fn] attribute may only be used on functions"
));
}
Ok(Func { attrs, sig, body, print_const: true })
}
impl ToTokens for Func {
fn to_tokens(&self, tokens: &mut TokenStream) {
self.attrs.iter().for_each(|attr| attr.to_tokens(tokens));
if self.print_const {
self.sig.iter().for_each(|attr| attr.to_tokens(tokens));
} else {
self.sig
.iter()
.filter(
|tt| if let TokenTree::Ident(i) = tt { i.to_string() != "const" } else { true },
)
.for_each(|tt| tt.to_tokens(tokens));
}
self.body.to_tokens(tokens);
}
}
fn parse_signature(input: &mut TokenIter) -> Vec<TokenTree> {
let mut sig = Vec::new();
let mut has_const = false;
loop {
match input.peek() {
None => break,
Some(TokenTree::Ident(i)) if !has_const => {
match &*i.to_string() {
"fn" => {
sig.push(TokenTree::Ident(Ident::new("const", i.span())));
sig.push(input.next().unwrap());
break;
}
"const" => {
has_const = true;
}
"async" | "unsafe" | "extern" => {
has_const = true;
sig.push(TokenTree::Ident(Ident::new("const", i.span())));
}
_ => {}
}
sig.push(input.next().unwrap());
}
Some(TokenTree::Ident(i)) if i.to_string() == "fn" => {
sig.push(input.next().unwrap());
break;
}
Some(_) => sig.push(input.next().unwrap()),
}
}
sig
}
fn parse_attrs(input: &mut TokenIter) -> Result<Vec<Attribute>> {
let mut attrs = Vec::new();
loop {
let pound_token = match input.peek() {
Some(TokenTree::Punct(p)) if p.as_char() == '#' => input.next().unwrap(),
_ => break,
};
let group = match input.peek() {
Some(TokenTree::Group(g)) if g.delimiter() == Delimiter::Bracket => {
input.next().unwrap()
}
tt => return Err(error!(tt_span(tt), "expected `[`")),
};
attrs.push(Attribute { pound_token, group });
}
Ok(attrs)
}
pub(crate) struct Attribute {
// `#`
pub(crate) pound_token: TokenTree,
// `[...]`
pub(crate) group: TokenTree,
}
impl ToTokens for Attribute {
fn to_tokens(&self, tokens: &mut TokenStream) {
self.pound_token.to_tokens(tokens);
self.group.to_tokens(tokens);
}
}
pub(crate) struct LitStr {
pub(crate) token: Literal,
value: String,
}
impl LitStr {
pub(crate) fn new(token: Literal) -> Result<Self> {
let value = token.to_string();
// unlike `syn::LitStr`, only accepts `"..."`
if value.starts_with('"') && value.ends_with('"') {
Ok(Self { token, value })
} else {
Err(error!(token.span(), "expected string literal"))
}
}
pub(crate) fn value(&self) -> &str {
&self.value[1..self.value.len() - 1]
}
pub(crate) fn span(&self) -> Span {
self.token.span()
}
}
impl ToTokens for LitStr {
fn to_tokens(&self, tokens: &mut TokenStream) {
self.token.to_tokens(tokens);
}
}