use crate::{Delimiter, Group, Ident, PFacetAttr, Punct, Spacing, TokenStream, TokenTree};
use quote::{ToTokens, quote, quote_spanned};
fn const_prefix_tokens(needs_const_dispatch: bool) -> TokenStream {
if needs_const_dispatch {
quote! { @const }
} else {
TokenStream::new()
}
}
fn emit_extension_attr_with_prefix(
ns_ident: &Ident,
key_ident: &impl ToTokens,
args: &TokenStream,
facet_crate: &TokenStream,
const_prefix: &TokenStream,
) -> TokenStream {
if args.is_empty() {
quote! {
#facet_crate::__ext!(#const_prefix #ns_ident::#key_ident { })
}
} else {
quote! {
#facet_crate::__ext!(#const_prefix #ns_ident::#key_ident { | #args })
}
}
}
fn emit_extension_attr_for_field_with_prefix(
ns_ident: &Ident,
key_ident: &impl ToTokens,
args: &TokenStream,
field_name: &impl ToTokens,
field_type: &TokenStream,
facet_crate: &TokenStream,
const_prefix: &TokenStream,
) -> TokenStream {
if args.is_empty() {
quote! {
#facet_crate::__ext!(#const_prefix #ns_ident::#key_ident { #field_name : #field_type })
}
} else {
quote! {
#facet_crate::__ext!(#const_prefix #ns_ident::#key_ident { #field_name : #field_type | #args })
}
}
}
fn emit_builtin_attr_with_prefix(
key: &impl ToTokens,
args: &TokenStream,
facet_crate: &TokenStream,
const_prefix: &TokenStream,
) -> TokenStream {
if args.is_empty() {
quote! {
#facet_crate::__attr!(#const_prefix @ns { #facet_crate::builtin } #key { })
}
} else {
quote! {
#facet_crate::__attr!(#const_prefix @ns { #facet_crate::builtin } #key { | #args })
}
}
}
fn emit_builtin_attr_for_field_with_prefix(
key: &impl ToTokens,
args: &TokenStream,
field_name: &impl ToTokens,
field_type: &TokenStream,
facet_crate: &TokenStream,
const_prefix: &TokenStream,
) -> TokenStream {
if args.is_empty() {
quote! {
#facet_crate::__attr!(#const_prefix @ns { #facet_crate::builtin } #key { #field_name : #field_type })
}
} else {
quote! {
#facet_crate::__attr!(#const_prefix @ns { #facet_crate::builtin } #key { #field_name : #field_type | #args })
}
}
}
pub fn emit_extension_attr_for_field(
ns_ident: &Ident,
key_ident: &impl ToTokens,
args: &TokenStream,
field_name: &impl ToTokens,
field_type: &TokenStream,
facet_crate: &TokenStream,
needs_const_dispatch: bool,
) -> TokenStream {
let const_prefix = const_prefix_tokens(needs_const_dispatch);
emit_extension_attr_for_field_with_prefix(
ns_ident,
key_ident,
args,
field_name,
field_type,
facet_crate,
&const_prefix,
)
}
pub fn emit_extension_attr(
ns_ident: &Ident,
key_ident: &impl ToTokens,
args: &TokenStream,
facet_crate: &TokenStream,
needs_const_dispatch: bool,
) -> TokenStream {
let const_prefix = const_prefix_tokens(needs_const_dispatch);
emit_extension_attr_with_prefix(ns_ident, key_ident, args, facet_crate, &const_prefix)
}
pub fn emit_attr(
attr: &PFacetAttr,
facet_crate: &TokenStream,
needs_const_dispatch: bool,
) -> TokenStream {
let key = &attr.key;
let args = &attr.args;
match &attr.ns {
Some(ns) => {
emit_extension_attr(ns, key, args, facet_crate, needs_const_dispatch)
}
None => {
let const_prefix = const_prefix_tokens(needs_const_dispatch);
emit_builtin_attr_with_prefix(key, args, facet_crate, &const_prefix)
}
}
}
pub fn emit_attr_for_field(
attr: &PFacetAttr,
field_name: &impl ToTokens,
field_type: &TokenStream,
facet_crate: &TokenStream,
needs_const_dispatch: bool,
) -> TokenStream {
let key = &attr.key;
let args = &attr.args;
match &attr.ns {
Some(ns) => {
emit_extension_attr_for_field(
ns,
key,
args,
field_name,
field_type,
facet_crate,
needs_const_dispatch,
)
}
None => {
let const_prefix = const_prefix_tokens(needs_const_dispatch);
emit_builtin_attr_for_field_with_prefix(
key,
args,
field_name,
field_type,
facet_crate,
&const_prefix,
)
}
}
}
pub fn ext_attr(input: TokenStream) -> TokenStream {
let mut tokens = input.into_iter().peekable();
let mut use_const_dispatch = false;
if let Some(TokenTree::Punct(p)) = tokens.peek()
&& p.as_char() == '@'
{
tokens.next(); match tokens.next() {
Some(TokenTree::Ident(mode)) if mode == "const" => {
use_const_dispatch = true;
}
_ => {
return quote! {
::core::compile_error!("__ext!: expected `const` after `@`")
};
}
}
}
let ns_ident = match tokens.next() {
Some(TokenTree::Ident(ident)) => ident,
_ => {
return quote! {
::core::compile_error!("__ext!: expected namespace identifier")
};
}
};
match (tokens.next(), tokens.next()) {
(Some(TokenTree::Punct(p1)), Some(TokenTree::Punct(p2)))
if p1.as_char() == ':' && p2.as_char() == ':' => {}
_ => {
return quote! {
::core::compile_error!("__ext!: expected '::'")
};
}
}
let attr_ident = match tokens.next() {
Some(TokenTree::Ident(ident)) => ident,
_ => {
return quote! {
::core::compile_error!("__ext!: expected attribute name")
};
}
};
let body = match tokens.next() {
Some(TokenTree::Group(g)) if g.delimiter() == Delimiter::Brace => g,
_ => {
return quote! {
::core::compile_error!("__ext!: expected braced body")
};
}
};
let __attr = Ident::new("__attr", attr_ident.span());
let at = Punct::new('@', Spacing::Alone);
let ns_keyword = Ident::new("ns", attr_ident.span());
let colon1 = Punct::new(':', Spacing::Joint);
let colon2 = Punct::new(':', Spacing::Alone);
let bang = Punct::new('!', Spacing::Alone);
let mut output = TokenStream::new();
output.extend([TokenTree::Ident(ns_ident.clone())]);
output.extend([TokenTree::Punct(colon1.clone())]);
output.extend([TokenTree::Punct(colon2.clone())]);
output.extend([TokenTree::Ident(__attr)]);
output.extend([TokenTree::Punct(bang)]);
let mut macro_args = TokenStream::new();
if use_const_dispatch {
macro_args.extend([TokenTree::Punct(Punct::new('@', Spacing::Alone))]);
macro_args.extend([TokenTree::Ident(Ident::new("const", attr_ident.span()))]);
}
macro_args.extend([TokenTree::Punct(at)]);
macro_args.extend([TokenTree::Ident(ns_keyword)]);
let mut ns_group_content = TokenStream::new();
ns_group_content.extend([TokenTree::Ident(ns_ident)]);
macro_args.extend([TokenTree::Group(Group::new(
Delimiter::Brace,
ns_group_content,
))]);
macro_args.extend([TokenTree::Ident(attr_ident)]);
macro_args.extend([TokenTree::Group(body)]);
let args_group = Group::new(Delimiter::Parenthesis, macro_args);
output.extend([TokenTree::Group(args_group)]);
output
}
pub fn unknown_attr(input: TokenStream) -> TokenStream {
let mut tokens = input.into_iter();
let ident = match tokens.next() {
Some(TokenTree::Ident(ident)) => ident,
_ => {
return quote! {
::core::compile_error!("__unknown_attr!: expected identifier")
};
}
};
let span = ident.span();
let message = format!("unknown extension attribute `{ident}`");
quote_spanned! { span =>
::core::compile_error!(#message)
}
}
pub fn no_args(input: TokenStream) -> TokenStream {
let mut tokens = input.into_iter();
let msg = match tokens.next() {
Some(TokenTree::Literal(lit)) => {
let s = lit.to_string();
s.trim_matches('"').to_string()
}
_ => {
return quote! {
::core::compile_error!("__no_args!: expected string literal")
};
}
};
tokens.next();
let span = match tokens.next() {
Some(tt) => tt.span(),
None => {
return quote! {
::core::compile_error!("__no_args!: expected token for span")
};
}
};
let message = format!("{msg} does not accept arguments");
quote_spanned! { span =>
::core::compile_error!(#message)
}
}