use proc_macro2::{Delimiter, Group, Ident, Literal, Punct, Spacing, Span, TokenStream, TokenTree};
use quote::spanned::Spanned;
use quote::{ToTokens, TokenStreamExt, format_ident, quote};
use crate::ParseResult;
use crate::class::FuncDefinition;
mod kv_parser;
mod list_parser;
pub(crate) use kv_parser::KvParser;
pub(crate) use list_parser::ListParser;
pub fn ident(s: &str) -> Ident {
format_ident!("{}", s)
}
pub fn ident_respan(existing_ident: &Ident, span: Span) -> Ident {
let mut ident = existing_ident.clone();
ident.set_span(span);
ident
}
pub fn c_str(string: &str) -> Literal {
let c_string = std::ffi::CString::new(string).expect("CString::new() failed");
Literal::c_string(&c_string)
}
pub fn class_name_obj(class: &impl ToTokens) -> TokenStream {
let class = class.to_token_stream();
quote! { <#class as ::godot::obj::GodotClass>::class_id() }
}
pub fn bail_fn<R, T>(msg: impl AsRef<str>, tokens: T) -> ParseResult<R>
where
T: Spanned,
{
Err(error_fn(msg, tokens))
}
macro_rules! bail {
($tokens:expr_2021, $format_string:literal $($rest:tt)*) => {
$crate::util::bail_fn(format!($format_string $($rest)*), $tokens)
}
}
macro_rules! require_api_version {
($min_version:literal, $span:expr_2021, $attribute:literal) => {
if !cfg!(since_api = $min_version) {
bail!(
$span,
"{} requires at least Godot API version {}",
$attribute,
$min_version
)
} else {
Ok(())
}
};
}
pub fn span_of<T: Spanned>(tokens: &T) -> Span {
tokens.__span()
}
pub fn error_fn<T: Spanned>(msg: impl AsRef<str>, tokens: T) -> venial::Error {
let span = span_of(&tokens);
venial::Error::new_at_span(span, msg.as_ref())
}
macro_rules! error {
($tokens:expr_2021, $format_string:literal $($rest:tt)*) => {
$crate::util::error_fn(format!($format_string $($rest)*), $tokens)
}
}
pub(crate) use bail;
pub(crate) use error;
pub(crate) use require_api_version;
pub fn retain_attributes_except<'a>(
attributes: &'a [venial::Attribute],
macro_name: &'a str,
) -> impl Iterator<Item = &'a venial::Attribute> {
attributes.iter().filter(move |attr| {
attr.get_single_path_segment()
.is_none_or(|segment| segment != macro_name)
})
}
pub fn reduce_to_signature(function: &venial::Function) -> venial::Function {
let mut reduced = function.clone();
reduced.vis_marker = None; reduced.attributes.clear();
reduced.tk_semicolon = None;
reduced.body = None;
reduced
}
pub fn parse_signature(mut signature: TokenStream) -> venial::Function {
signature.append(TokenTree::Group(Group::new(
Delimiter::Brace,
TokenStream::new(),
)));
let function_item = venial::parse_item(signature)
.unwrap()
.as_function()
.unwrap()
.clone();
reduce_to_signature(&function_item)
}
pub fn make_signature_param_type(param_types: &[venial::TypeExpr]) -> TokenStream {
quote::quote! {
(#(#param_types,)*)
}
}
fn is_punct(tt: &TokenTree, c: char) -> bool {
match tt {
TokenTree::Punct(punct) => punct.as_char() == c,
_ => false,
}
}
fn delimiter_opening_char(delimiter: Delimiter) -> char {
match delimiter {
Delimiter::Parenthesis => '(',
Delimiter::Brace => '{',
Delimiter::Bracket => '[',
Delimiter::None => 'Ø',
}
}
pub(crate) fn is_impl_named(original_impl: &venial::Impl, name: &str) -> bool {
let trait_name = original_impl.trait_ty.as_ref().unwrap(); let implementor =
extract_typename(trait_name).expect("`impl ExtensionLibrary` must have a typename");
implementor.ident == name
}
pub(crate) fn validate_impl(
original_impl: &venial::Impl,
expected_trait: Option<&str>,
attr: &str,
) -> ParseResult<Ident> {
if let Some(expected_trait) = expected_trait {
if !is_impl_named(original_impl, expected_trait) {
return bail!(
original_impl,
"#[{attr}] for trait impls requires trait to be `{expected_trait}`",
);
}
}
validate_self(original_impl, attr)
}
pub(crate) fn validate_trait_impl_virtual(
original_impl: &venial::Impl,
attr: &str,
) -> ParseResult<(Ident, venial::TypeExpr, Ident)> {
let trait_name = original_impl.trait_ty.as_ref().unwrap(); let typename = extract_typename(trait_name);
let Some(base_class) = typename
.as_ref()
.and_then(|seg| seg.ident.to_string().strip_prefix('I').map(ident))
else {
return bail!(
original_impl,
"#[{attr}] for trait impls requires a virtual method trait (trait name should start with 'I')",
);
};
validate_self(original_impl, attr).map(|class_name| {
(class_name, trait_name.clone(), base_class)
})
}
fn validate_self(original_impl: &venial::Impl, attr: &str) -> ParseResult<Ident> {
match extract_typename(&original_impl.self_ty) {
Some(segment) => {
if segment.generic_args.is_none() {
Ok(segment.ident)
} else {
bail!(
original_impl,
"#[{attr}] for does currently not support generic arguments",
)
}
}
_ => {
bail!(
original_impl,
"#[{attr}] requires Self type to be a simple path",
)
}
}
}
pub(crate) fn extract_typename(ty: &venial::TypeExpr) -> Option<venial::PathSegment> {
match ty.as_path() {
Some(mut path) => path.segments.pop(),
_ => None,
}
}
pub(crate) fn path_is_single(path: &[TokenTree], expected: &str) -> bool {
path.len() == 1 && path[0].to_string() == expected
}
pub(crate) fn path_ends_with(path: &[TokenTree], expected: &str) -> bool {
path.last().is_some_and(|last| last.to_string() == expected)
}
pub(crate) fn path_ends_with_complex(path: &venial::TypeExpr, expected: &str) -> bool {
path.as_path().is_some_and(|path| {
path.segments
.last()
.is_some_and(|seg| seg.ident == expected)
})
}
pub fn is_cfg_or_cfg_attr(attr: &venial::Attribute) -> bool {
let Some(attr_name) = attr.get_single_path_segment() else {
return false;
};
if attr_name == "cfg" {
return true;
}
if attr_name == "cfg_attr" && attr.value.to_token_stream().to_string().contains("cfg(") {
return true;
}
false
}
pub fn to_spanned_tuple(items: &[impl ToTokens], span: Span) -> Group {
let mut group = Group::new(Delimiter::Parenthesis, quote! { #(#items,)* });
group.set_span(span);
group
}
pub(crate) fn extract_cfg_attrs(
attrs: &[venial::Attribute],
) -> impl IntoIterator<Item = &venial::Attribute> {
attrs.iter().filter(|attr| is_cfg_or_cfg_attr(attr))
}
pub(crate) fn extract_doc_attrs(
attrs: &[venial::Attribute],
) -> impl IntoIterator<Item = &venial::Attribute> {
attrs.iter().filter(|attr| {
attr.get_single_path_segment()
.is_some_and(|attr_name| attr_name == "doc")
})
}
#[cfg(before_api = "4.3")] #[cfg_attr(published_docs, doc(cfg(before_api = "4.3")))]
pub fn make_virtual_tool_check() -> TokenStream {
quote! {
if ::godot::private::is_class_inactive(Self::__config().is_tool) {
return None;
}
}
}
#[cfg(since_api = "4.3")] #[cfg_attr(published_docs, doc(cfg(since_api = "4.3")))]
pub fn make_virtual_tool_check() -> TokenStream {
TokenStream::new()
}
#[rustfmt::skip]
pub fn safe_ident(s: &str) -> Ident {
match s {
| "as" | "break" | "const" | "continue" | "crate" | "else" | "enum" | "extern" | "false" | "fn" | "for" | "if"
| "impl" | "in" | "let" | "loop" | "match" | "mod" | "move" | "mut" | "pub" | "ref" | "return" | "self" | "Self"
| "static" | "struct" | "super" | "trait" | "true" | "type" | "unsafe" | "use" | "where" | "while"
| "async" | "await" | "dyn"
| "abstract" | "become" | "box" | "do" | "final" | "macro" | "override" | "priv" | "typeof" | "unsized" | "virtual" | "yield"
| "try"
| "gen"
=> format_ident!("{}_", s),
_ => ident(s)
}
}
pub fn venial_parse_meta(
meta: &TokenStream,
self_name: Ident,
content: &TokenStream,
) -> Result<venial::Item, venial::Error> {
let input = quote! {
#[#self_name(#meta)]
#content
};
venial::parse_item(input)
}
pub fn make_funcs_collection_constants(
funcs: &[FuncDefinition],
class_name: &Ident,
) -> Vec<TokenStream> {
funcs
.iter()
.map(|func| {
let cfg_attributes = extract_cfg_attrs(&func.external_attributes)
.into_iter()
.collect::<Vec<_>>();
make_funcs_collection_constant(
class_name,
&func.signature_info.method_name,
func.registered_name.as_ref(),
&cfg_attributes,
)
})
.collect()
}
pub fn make_funcs_collection_constant(
class_name: &Ident,
rust_function_name: &Ident,
registered_name: Option<&String>,
attributes: &[&venial::Attribute],
) -> TokenStream {
let const_name = format_funcs_collection_constant(class_name, rust_function_name);
let const_value = match registered_name {
Some(renamed) => renamed.to_string(),
None => rust_function_name.to_string(),
};
quote! {
#(#attributes)*
#[doc(hidden)]
#[allow(non_upper_case_globals)]
pub const #const_name: &str = #const_value;
}
}
pub fn replace_class_in_path(path: venial::Path, new_class: Ident) -> venial::Path {
match path.segments.as_slice() {
[] => unreachable!("empty path"),
[_single] => venial::Path {
segments: vec![venial::PathSegment {
ident: new_class,
generic_args: None,
tk_separator_colons: None,
}],
},
[path @ .., _last] => {
let mut segments = vec![];
segments.extend(path.iter().cloned());
segments.push(venial::PathSegment {
ident: new_class,
generic_args: None,
tk_separator_colons: Some([
Punct::new(':', Spacing::Joint),
Punct::new(':', Spacing::Alone),
]),
});
venial::Path { segments }
}
}
}
pub fn format_funcs_collection_constant(_class_name: &Ident, func_name: &Ident) -> Ident {
format_ident!("{func_name}", span = func_name.span())
}
pub fn format_class_deny_manual_init_macro(class_name: &Ident) -> Ident {
format_ident!("__deny_manual_init_{class_name}", span = class_name.span())
}
pub fn format_funcs_collection_struct(class_name: &Ident) -> Ident {
format_ident!("__godot_{class_name}_Funcs")
}
pub fn format_class_visibility_macro(class_name: &Ident) -> Ident {
format_ident!("__godot_{class_name}_vis_macro")
}
pub fn format_class_base_field_macro(class_name: &Ident) -> Ident {
format_ident!("__godot_{class_name}_has_base_field_macro")
}