sdk-builder: using tokens instead of strings to transport type idents/paths

This commit is contained in:
Janis 2023-06-29 20:44:29 +02:00
parent 5b11e2998a
commit 15f2b70449

View file

@ -135,15 +135,127 @@ fn empty_or_some(s: &str) -> Option<&str> {
} }
} }
pub struct CanonicalNames { pub mod path_helper {
/// canonicalized type names for lookup when handling return types and parameters. use quote::{format_ident, quote, ToTokens, TokenStreamExt};
types: BTreeMap<ObjectRef, String>, use unreal_sdk::sdk::repr::Type;
use crate::rust::Builder;
pub struct Type2<'a> {
cache: &'a Builder,
inner: Type,
}
impl<'a> Type2<'a> {
pub fn new(cache: &'a Builder, inner: Type) -> Self {
Self { cache, inner }
}
fn child(&self, inner: Type) -> Self {
Self {
inner,
cache: self.cache,
}
}
}
impl<'a> ToTokens for Type2<'a> {
fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
match &self.inner {
Type::Ptr(inner) | Type::Ref(inner) => {
let inner = self.child(*inner.clone());
tokens.extend(quote! {
::core::option::Option<NonNull<#inner>>
});
}
Type::WeakPtr(inner) => {
let inner = self
.cache
.get_full_type_tokens(&inner)
.unwrap_or(quote!(crate::engine::UObject));
tokens.extend(quote!(
crate::engine::TWeakObjectPtr<#inner>
));
}
Type::SoftPtr(inner) => {
let inner = self
.cache
.get_full_type_tokens(&inner)
.unwrap_or(quote!(crate::engine::UObject));
tokens.extend(quote!(
crate::engine::TSoftObjectPtr<#inner>
));
}
Type::LazyPtr(inner) => {
let inner = self
.cache
.get_full_type_tokens(&inner)
.unwrap_or(quote!(crate::engine::UObject));
tokens.extend(quote!(
crate::engine::TLazyObjectPtr<#inner>
));
}
Type::AssetPtr(inner) => {
let inner = self
.cache
.get_full_type_tokens(&inner)
.unwrap_or(quote!(crate::engine::UObject));
tokens.extend(quote!(
crate::engine::TAssetPtr<#inner>,
));
}
Type::Array(inner) => {
let inner = self.child(*inner.clone());
tokens.extend(quote!(
crate::engine::TArray<#inner>,
));
}
Type::Primitive(prim) => {
tokens.append(format_ident!("{prim}"));
}
Type::RawArray { ty, len } => {
let ty = self.child(*ty.clone());
quote!([#ty; #len]);
}
Type::Name => tokens.extend(quote!(crate::engine::FName)),
Type::String => tokens.extend(quote!(crate::engine::FString)),
Type::Text => tokens.extend(quote!(crate::engine::FText)),
Type::Enum { enum_type, .. } => {
let inner = self
.cache
.get_full_type_tokens(&enum_type)
.unwrap_or(quote!(u8));
tokens.extend(inner);
}
Type::Class(class) => {
let inner = self
.cache
.get_full_type_tokens(&class)
.unwrap_or(quote!(crate::engine::UObject));
tokens.extend(quote!(::core::option::Option<#inner>));
}
Type::Struct(class) => {
let inner = self
.cache
.get_full_type_tokens(&class)
.unwrap_or(quote!(()));
tokens.extend(inner);
}
};
}
}
} }
pub mod rust { pub mod rust {
use std::{ use std::{
borrow::Cow, borrow::Cow,
collections::{BTreeMap, BTreeSet, HashMap, HashSet}, collections::{BTreeMap, BTreeSet},
path::Path, path::Path,
}; };
@ -151,13 +263,12 @@ pub mod rust {
use itertools::Itertools; use itertools::Itertools;
use proc_macro2::{Ident, TokenStream}; use proc_macro2::{Ident, TokenStream};
use quote::{format_ident, quote}; use quote::{format_ident, quote};
use rayon::prelude::{IntoParallelIterator, IntoParallelRefIterator, ParallelIterator};
use unreal_sdk::sdk::repr::{ use unreal_sdk::sdk::repr::{
Class, ClassField, ClassMethod, Enum, ObjectRef, PackageRef, PrimitiveType, Class, ClassField, ClassMethod, Enum, ObjectRef, PackageRef, PrimitiveType,
ProcessedPackage, Sdk, StructKind, Type, UnrealType, ProcessedPackage, Sdk, StructKind, Type, UnrealType,
}; };
use crate::split_at_illegal_char; use crate::{path_helper::Type2, split_at_illegal_char};
// const KEYWORDS: [&'static str; 51] = [ // const KEYWORDS: [&'static str; 51] = [
// "as", "break", "const", "continue", "crate", "else", "enum", "extern", "false", "fn", // "as", "break", "const", "continue", "crate", "else", "enum", "extern", "false", "fn",
@ -192,6 +303,10 @@ pub mod rust {
sdk: Sdk, sdk: Sdk,
} }
fn canonicalize_ident(name: &str) -> Ident {
format_ident!("{}", canonicalize_name(name))
}
fn canonicalize_name<'a>(name: &'a str) -> Cow<'a, str> { fn canonicalize_name<'a>(name: &'a str) -> Cow<'a, str> {
let valid = split_at_illegal_char(name, &CHARS).into_valid(&CHARS); let valid = split_at_illegal_char(name, &CHARS).into_valid(&CHARS);
if WORDS.contains(&valid.as_ref()) || valid.starts_with(|c: char| !c.is_alphabetic()) { if WORDS.contains(&valid.as_ref()) || valid.starts_with(|c: char| !c.is_alphabetic()) {
@ -221,9 +336,17 @@ pub mod rust {
/// returns the absolute path of a type with the assumption that all /// returns the absolute path of a type with the assumption that all
/// packages are children of the path `crate::sdk` /// packages are children of the path `crate::sdk`
fn get_type_package_path(&self, key: &ObjectRef) -> Option<String> { pub fn get_type_package_path(&self, key: &ObjectRef) -> Option<TokenStream> {
let pkg = &self.sdk.packages.get(&key.package)?.name; let pkg = format_ident!("{}", &self.sdk.packages.get(&key.package)?.name);
Some(format!("crate::sdk::{pkg}")) Some(quote!(crate::sdk::#pkg))
}
pub fn get_full_type_tokens(&self, key: &ObjectRef) -> Option<TokenStream> {
let pkg = self.get_type_package_path(key)?;
let name = format_ident!("{}", self.get_type_name(key)?);
Some(quote! {
#pkg::#name
})
} }
/// returns the absolute path of a type with the assumption that all /// returns the absolute path of a type with the assumption that all
@ -234,6 +357,12 @@ pub mod rust {
.map(|name| format!("crate::sdk::{pkg}::{name}")) .map(|name| format!("crate::sdk::{pkg}::{name}"))
} }
/// returns the precached, prefixed and cannonicalized (for this
/// language, Rust) `Ident` for this object-ref
fn get_type_ident(&self, key: &ObjectRef) -> Option<Ident> {
Some(format_ident!("{}", self.type_name_cache.get(key)?))
}
/// returns the precached, prefixed and cannonicalized (for this /// returns the precached, prefixed and cannonicalized (for this
/// language, Rust) name for this object-ref /// language, Rust) name for this object-ref
fn get_type_name(&self, key: &ObjectRef) -> Option<String> { fn get_type_name(&self, key: &ObjectRef) -> Option<String> {
@ -421,11 +550,11 @@ pub mod rust {
fn generate_enum(&self, enum0: &Enum) -> anyhow::Result<TokenStream> { fn generate_enum(&self, enum0: &Enum) -> anyhow::Result<TokenStream> {
let name = self let name = self
.get_type_name(&enum0.obj_ref) .get_type_ident(&enum0.obj_ref)
.context("enum name was not previously canonicalized and cached.")?; .context("enum name was not previously canonicalized and cached.")?;
let variants = enum0.values.iter().map(|(&value, name)| { let variants = enum0.values.iter().map(|(&value, name)| {
let name = canonicalize_name(&name); let name = canonicalize_ident(&name);
quote! { quote! {
#name = #value, #name = #value,
} }
@ -448,19 +577,17 @@ pub mod rust {
fn generate_object( fn generate_object(
&self, &self,
_class: &Class, _class: &Class,
name: &str, name: &Ident,
) -> anyhow::Result<(TokenStream, TokenStream)> { ) -> anyhow::Result<(TokenStream, TokenStream)> {
let ident = format_ident!("{name}");
let typedef = quote! { let typedef = quote! {
#[derive(Eq, PartialEq, Copy, Clone)] #[derive(Eq, PartialEq, Copy, Clone)]
pub struct #ident(pub ::core::ptr::NonNull<u8>); pub struct #name(pub ::core::ptr::NonNull<u8>);
}; };
let static_class_impl: TokenStream = Self::generate_find_object(name); let static_class_impl: TokenStream = Self::generate_find_object(name);
let impls = quote! { let impls = quote! {
impl crate::engine::AsUObject for #ident { impl crate::engine::AsUObject for #name {
fn as_uobject(&self) -> crate::engine::UObject { fn as_uobject(&self) -> crate::engine::UObject {
crate::engine::UObject(self.0) crate::engine::UObject(self.0)
} }
@ -470,7 +597,7 @@ pub mod rust {
} }
} }
impl crate::engine::AsPtr for #ident { impl crate::engine::AsPtr for #name {
fn as_ptr(&self) -> *const u8 { fn as_ptr(&self) -> *const u8 {
unsafe { self.0.as_ref().get() as _ } unsafe { self.0.as_ref().get() as _ }
} }
@ -480,7 +607,7 @@ pub mod rust {
} }
} }
impl crate::engine::StaticClass for #ident { impl crate::engine::StaticClass for #name {
fn get_static_class() -> ::core::option::Option<crate::engine::UClass> { fn get_static_class() -> ::core::option::Option<crate::engine::UClass> {
let class: ::core::option::Option<crate::engine::UClass> = let class: ::core::option::Option<crate::engine::UClass> =
#static_class_impl; #static_class_impl;
@ -498,30 +625,30 @@ pub mod rust {
fn generate_struct( fn generate_struct(
&self, &self,
class: &Class, class: &Class,
name: &str, name: &Ident,
ctor: Option<TokenStream>, ctor: Option<TokenStream>,
) -> anyhow::Result<(TokenStream, TokenStream)> { ) -> anyhow::Result<(TokenStream, TokenStream)> {
let size = class.size; let size = class.size;
let ident = format_ident!("{name}");
let typedef = quote! { let typedef = quote! {
pub struct #ident(pub ::core::cell::UnsafeCell<u8; #size>); pub struct #name(pub ::core::cell::UnsafeCell<u8; #size>);
}; };
let impls = quote! { let impls = quote! {
impl Eq for #ident {} impl Eq for #name {}
impl PartialEq for #ident { impl PartialEq for #name {
fn eq(&self, other: &Self) -> bool { fn eq(&self, other: &Self) -> bool {
unsafe {(&*self.0.get()).eq(&*other.0.get())} unsafe {(&*self.0.get()).eq(&*other.0.get())}
} }
} }
impl Clone for #ident { impl Clone for #name {
fn clone(&self) -> Self { fn clone(&self) -> Self {
Self(::core::cell::UnsafeCell::new(unsafe {&*self.0.get()}.clone())) Self(::core::cell::UnsafeCell::new(unsafe {&*self.0.get()}.clone()))
} }
} }
impl crate::engine::AsPtr for #ident { impl crate::engine::AsPtr for #name {
fn as_ptr(&self) -> *const u8 { fn as_ptr(&self) -> *const u8 {
self.0.get().cast() self.0.get().cast()
} }
@ -531,7 +658,7 @@ pub mod rust {
} }
} }
impl #ident { impl #name {
pub fn zeroed() -> Self { pub fn zeroed() -> Self {
unsafe { unsafe {
::core::mem::MaybeUninit::<Self>::zeroed().assume_init() ::core::mem::MaybeUninit::<Self>::zeroed().assume_init()
@ -552,7 +679,7 @@ pub mod rust {
fn generate_struct_methods( fn generate_struct_methods(
&self, &self,
class: &Class, class: &Class,
name: &str, name: &Ident,
) -> anyhow::Result<(Vec<TokenStream>, Vec<TokenStream>)> { ) -> anyhow::Result<(Vec<TokenStream>, Vec<TokenStream>)> {
let methods = class let methods = class
.methods .methods
@ -570,18 +697,18 @@ pub mod rust {
/// - the method wrapper. /// - the method wrapper.
fn generate_method( fn generate_method(
&self, &self,
struct_name: &str, struct_name: &Ident,
method: &ClassMethod, method: &ClassMethod,
) -> anyhow::Result<(TokenStream, TokenStream)> { ) -> anyhow::Result<(TokenStream, TokenStream)> {
let method_name = canonicalize_name(&method.unique_name()); let method_name = canonicalize_ident(&method.unique_name());
// all parameters collected as (parameter, canonicalized_name, type_ident) // all parameters collected as (parameter, canonicalized_name, type_ident)
let parameters = method let parameters = method
.parameters .parameters
.iter() .iter()
.map(|parameter| { .map(|parameter| {
let name = canonicalize_name(&parameter.unique_name()); let name = canonicalize_ident(&parameter.unique_name());
let type_name = self.type_name(&parameter.ty)?; let type_name = Type2::new(self, parameter.ty.clone());
anyhow::Ok((parameter, name, type_name)) anyhow::Ok((parameter, name, type_name))
}) })
@ -590,11 +717,7 @@ pub mod rust {
// all parameters converted into "arg: Type" format of tokens. // all parameters converted into "arg: Type" format of tokens.
let all_params = parameters let all_params = parameters
.iter() .iter()
.map(|(param, name, ty)| { .map(|(param, name, ty)| (param, quote! {#name: #ty}))
let name = format_ident!("{name}");
let ty = format_ident!("{ty}");
(param, quote! {#name: #ty})
})
.collect::<Vec<_>>(); .collect::<Vec<_>>();
// params that the function will accept as arguments. // params that the function will accept as arguments.
@ -611,7 +734,6 @@ pub mod rust {
// param token streams for setting the fields of the params struct // param token streams for setting the fields of the params struct
// with the arguments of the function. // with the arguments of the function.
let init_params = parameters.iter().map(|(_, name, _)| { let init_params = parameters.iter().map(|(_, name, _)| {
let name = format_ident!("{name}");
quote! {params.#name = #name;} quote! {params.#name = #name;}
}); });
@ -622,9 +744,6 @@ pub mod rust {
param.is_return_param() || (param.is_out_param() && !param.is_const_param()) param.is_return_param() || (param.is_out_param() && !param.is_const_param())
}) })
.map(|(_, name, ty)| { .map(|(_, name, ty)| {
let name = format_ident!("{name}");
let ty = format_ident!("{ty}");
( (
quote! { quote! {
#name #name
@ -680,8 +799,8 @@ pub mod rust {
fn generate_field_accessors( fn generate_field_accessors(
&self, &self,
field: &ClassField, field: &ClassField,
field_name: &Cow<str>, field_name: &Ident,
type_name: &String, type_name: &Type2,
) -> TokenStream { ) -> TokenStream {
let setter = format_ident!("set_{}", field_name); let setter = format_ident!("set_{}", field_name);
let getter = format_ident!("get_{}", field_name); let getter = format_ident!("get_{}", field_name);
@ -753,14 +872,14 @@ pub mod rust {
fn generate_struct_ctor( fn generate_struct_ctor(
&self, &self,
_class: &Class, _class: &Class,
type_name: &str, type_ident: &Ident,
fields: &Vec<(&ClassField, Cow<str>, String)>, fields: &Vec<(&ClassField, Ident, Type2)>,
) -> TokenStream { ) -> TokenStream {
let fields_defs = fields.iter().map(|(_, name, ty)| quote! {#name: #ty}); let fields_defs = fields.iter().map(|(_, name, ty)| quote! {#name: #ty});
let this_field_asignments = fields.iter().map(|(_, name, _ty)| { let this_field_asignments = fields.iter().map(|(_, name, _ty)| {
let setter = format_ident!("set_{}", name); let setter = format_ident!("set_{}", name);
let field_trait = format_ident!("{type_name}Fields"); let field_trait = format_ident!("{type_ident}Fields");
quote! {<Self as #field_trait>::#setter(this, #name);} quote! {<Self as #field_trait>::#setter(this, #name);}
}); });
@ -785,14 +904,14 @@ pub mod rust {
fn generate_struct_fields( fn generate_struct_fields(
&self, &self,
class: &Class, class: &Class,
name: &str, name: &Ident,
) -> anyhow::Result<(TokenStream, Option<TokenStream>)> { ) -> anyhow::Result<(TokenStream, Option<TokenStream>)> {
let fields = class let fields = class
.fields .fields
.iter() .iter()
.map(|field| { .map(|field| {
let name = canonicalize_name(&field.unique_name()); let name = canonicalize_ident(&field.unique_name());
let ty = self.type_name(&field.ty)?; let ty = Type2::new(self, field.ty.clone());
anyhow::Ok((field, name, ty)) anyhow::Ok((field, name, ty))
}) })
@ -821,7 +940,8 @@ pub mod rust {
Ok((fields_trait, ctor)) Ok((fields_trait, ctor))
} }
fn generate_find_object(name: &str) -> TokenStream { fn generate_find_object<S: ToString>(name: S) -> TokenStream {
let name = name.to_string();
let not_found = format!("static object \"{name}\" not found!"); let not_found = format!("static object \"{name}\" not found!");
quote! { quote! {
static OBJECT: ::once_cell::sync::OnceCell<::core::option::Option<UObject>> = ::once_cell::sync::OnceCell::new(); static OBJECT: ::once_cell::sync::OnceCell<::core::option::Option<UObject>> = ::once_cell::sync::OnceCell::new();
@ -856,7 +976,7 @@ pub mod rust {
fn generate_class(&self, class: &Class) -> anyhow::Result<TokenStream> { fn generate_class(&self, class: &Class) -> anyhow::Result<TokenStream> {
let name = &self let name = &self
.get_type_name(&class.obj_ref) .get_type_ident(&class.obj_ref)
.context("enum name was not previously canonicalized and cached.")?; .context("enum name was not previously canonicalized and cached.")?;
let (field_trait, ctor) = self.generate_struct_fields(class, name)?; let (field_trait, ctor) = self.generate_struct_fields(class, name)?;
@ -924,7 +1044,7 @@ pub mod rust {
pkg: &ProcessedPackage, pkg: &ProcessedPackage,
feature_gate: bool, feature_gate: bool,
) -> anyhow::Result<TokenStream> { ) -> anyhow::Result<TokenStream> {
let pkg_name = canonicalize_name(&pkg.name); let pkg_name = canonicalize_ident(&pkg.name);
log::info!( log::info!(
"generating package \"{pkg_name}\" with {} types..", "generating package \"{pkg_name}\" with {} types..",
pkg.types.len() pkg.types.len()