| //! codegen for table objects |
| |
| use std::collections::HashMap; |
| |
| use crate::{fields::FieldConstructorInfo, parsing::logged_syn_error}; |
| use indexmap::IndexMap; |
| use proc_macro2::{Span, TokenStream}; |
| use quote::{quote, ToTokens}; |
| |
| use crate::parsing::{Attr, GenericGroup, Item, Items, Phase}; |
| |
| use super::parsing::{Field, ReferencedFields, Table, TableFormat, TableReadArg, TableReadArgs}; |
| |
| pub(crate) fn generate(item: &Table) -> syn::Result<TokenStream> { |
| if item.attrs.write_only.is_some() { |
| return Ok(Default::default()); |
| } |
| let docs = &item.attrs.docs; |
| let generic = item.attrs.generic_offset.as_ref(); |
| let generic_with_default = generic.map(|t| quote!(#t = ())); |
| let phantom_decl = generic.map(|t| quote!(offset_type: std::marker::PhantomData<*const #t>)); |
| let marker_name = item.marker_name(); |
| let raw_name = item.raw_name(); |
| let shape_byte_range_fns = item.iter_shape_byte_fns(); |
| let optional_min_byte_range_trait_impl = item.impl_min_byte_range_trait(); |
| let shape_fields = item.iter_shape_fields(); |
| let derive_clone_copy = generic.is_none().then(|| quote!(Clone, Copy)); |
| let impl_clone_copy = generic.is_some().then(|| { |
| quote! { |
| impl<#generic> Clone for #marker_name<#generic> { |
| fn clone(&self) -> Self { |
| *self |
| } |
| } |
| |
| impl<#generic> Copy for #marker_name<#generic> {} |
| } |
| }); |
| |
| let of_unit_docs = " Replace the specific generic type on this implementation with `()`"; |
| |
| let fixed_fields_name = item.fixed_fields_name(); |
| let fixed_fields = item |
| .fields |
| .iter() |
| .take_while(|field| field.is_fixed) |
| .collect::<Vec<_>>(); |
| let fixed_field_names = fixed_fields.iter().map(|fld| &fld.name); |
| let fixed_field_types = fixed_fields.iter().map(|fld| fld.type_for_record()); |
| let fixed_field_inner_types = fixed_fields.iter().map(|fld| fld.typ.cooked_type_tokens()); |
| |
| let fixed_size_for_fixed_fields = if fixed_fields.is_empty() { |
| quote!( |
| impl FixedSize for #fixed_fields_name { |
| const RAW_BYTE_LEN: usize = 0; |
| } |
| ) |
| } else { |
| quote!( |
| impl FixedSize for #fixed_fields_name { |
| const RAW_BYTE_LEN: usize = #( #fixed_field_inner_types::RAW_BYTE_LEN )+*; |
| } |
| ) |
| }; |
| |
| // In the presence of a generic param we only impl FontRead for Name<()>, |
| // and then use into() to convert it to the concrete generic type. |
| let impl_into_generic = generic.as_ref().map(|t| { |
| let shape_fields = item |
| .iter_shape_field_names() |
| .map(|name| quote!(#name: shape.#name)) |
| .collect::<Vec<_>>(); |
| |
| let shape_name = if shape_fields.is_empty() { |
| quote!(..) |
| } else { |
| quote!(shape) |
| }; |
| |
| quote! { |
| impl<'a> #raw_name<'a, ()> { |
| #[allow(dead_code)] |
| pub(crate) fn into_concrete<T>(self) -> #raw_name<'a, #t> { |
| let TableRef { data, fixed_fields, #shape_name} = self; |
| TableRef { |
| shape: #marker_name { |
| #( #shape_fields, )* |
| offset_type: std::marker::PhantomData, |
| }, data, fixed_fields |
| } |
| } |
| } |
| |
| // we also generate a conversion from typed to untyped, which |
| // we use to write convenience methods on the wrapper |
| impl<'a, #t> #raw_name<'a, #t> { |
| #[allow(dead_code)] |
| #[doc = #of_unit_docs] |
| pub(crate) fn of_unit_type(&self) -> #raw_name<'a, ()> { |
| let TableRef { data, fixed_fields, #shape_name} = self; |
| TableRef { |
| shape: #marker_name { |
| #( #shape_fields, )* |
| offset_type: std::marker::PhantomData, |
| }, data: *data, fixed_fields: *fixed_fields |
| } |
| } |
| } |
| } |
| }); |
| |
| let table_ref_getters = item.iter_table_ref_getters(); |
| |
| let optional_format_trait_impl = item.impl_format_trait(); |
| let font_read = generate_font_read(item)?; |
| let debug = generate_debug(item)?; |
| let top_level = item.attrs.tag.as_ref().map(|tag| { |
| let tag_str = tag.value(); |
| let doc = format!(" `{tag_str}`"); |
| let byte_tag = syn::LitByteStr::new(tag_str.as_bytes(), tag.span()); |
| quote! { |
| impl TopLevelTable for #raw_name<'_> { |
| #[doc = #doc] |
| const TAG: Tag = Tag::new(#byte_tag); |
| } |
| } |
| }); |
| |
| Ok(quote! { |
| #optional_format_trait_impl |
| |
| #[derive(Copy, Clone, Debug, bytemuck::AnyBitPattern)] |
| #[repr(C)] |
| #[repr(packed)] |
| pub struct #fixed_fields_name { |
| #( pub #fixed_field_names: #fixed_field_types, )* |
| } |
| |
| #fixed_size_for_fixed_fields |
| |
| #( #docs )* |
| #[derive(Debug, #derive_clone_copy)] |
| #[doc(hidden)] |
| pub struct #marker_name <#generic_with_default> { |
| #( #shape_fields, )* |
| #phantom_decl |
| } |
| |
| impl <#generic> #marker_name <#generic> { |
| #( #shape_byte_range_fns )* |
| } |
| |
| #optional_min_byte_range_trait_impl |
| |
| #top_level |
| |
| #impl_clone_copy |
| |
| #font_read |
| |
| #impl_into_generic |
| |
| #( #docs )* |
| pub type #raw_name<'a, #generic> = TableRef<'a, #marker_name<#generic>, #fixed_fields_name>; |
| |
| #[allow(clippy::needless_lifetimes)] |
| impl<'a, #generic> #raw_name<'a, #generic> { |
| |
| #( #table_ref_getters )* |
| |
| } |
| |
| #debug |
| }) |
| } |
| |
| fn generate_font_read(item: &Table) -> syn::Result<TokenStream> { |
| let marker_name = item.marker_name(); |
| let name = item.raw_name(); |
| let fixed_fields_name = item.fixed_fields_name(); |
| let field_validation_stmts = item.iter_field_validation_stmts(); |
| let shape_field_names = item.iter_shape_field_names(); |
| let generic = item.attrs.generic_offset.as_ref(); |
| let phantom = generic.map(|_| quote!(offset_type: std::marker::PhantomData,)); |
| let error_if_phantom_and_read_args = generic.map(|_| { |
| quote!(compile_error!( |
| "ReadWithArgs not implemented for tables with phantom params." |
| );) |
| }); |
| |
| if let Some(read_args) = &item.attrs.read_args { |
| let args_type = read_args.args_type(); |
| let destructure_pattern = read_args.destructure_pattern(); |
| let constructor_args = read_args.constructor_args(); |
| let args_from_constructor_args = read_args.read_args_from_constructor_args(); |
| Ok(quote! { |
| #error_if_phantom_and_read_args |
| impl ReadArgs for #name<'_> { |
| type Args = #args_type; |
| } |
| |
| impl<'a> FontReadWithArgs<'a> for #name<'a> { |
| #[inline] |
| fn read_with_args(data: FontData<'a>, args: &#args_type) -> Result<Self, ReadError> { |
| let #destructure_pattern = *args; |
| let mut cursor = data.cursor(); |
| let fixed_fields: &'a #fixed_fields_name = cursor.read_ref()?; |
| #( #field_validation_stmts )* |
| cursor.finish( #marker_name { |
| #( #shape_field_names, )* |
| }, fixed_fields) |
| } |
| } |
| |
| impl<'a> #name<'a> { |
| /// A constructor that requires additional arguments. |
| /// |
| /// This type requires some external state in order to be |
| /// parsed. |
| #[inline] |
| pub fn read(data: FontData<'a>, #( #constructor_args, )* ) -> Result<Self, ReadError> { |
| let args = #args_from_constructor_args; |
| Self::read_with_args(data, &args) |
| } |
| } |
| }) |
| } else { |
| Ok(quote! { |
| impl<'a, #generic> FontRead<'a> for #name<'a, #generic> { |
| #[inline] |
| fn read(data: FontData<'a>) -> Result<Self, ReadError> { |
| let mut cursor = data.cursor(); |
| let fixed_fields: &'a #fixed_fields_name = cursor.read_ref()?; |
| #( #field_validation_stmts )* |
| cursor.finish( #marker_name { |
| #( #shape_field_names, )* |
| #phantom |
| }, fixed_fields) |
| } |
| } |
| }) |
| } |
| } |
| |
| pub(crate) fn generate_group(item: &GenericGroup) -> syn::Result<TokenStream> { |
| let docs = &item.attrs.docs; |
| let name = &item.name; |
| let inner = &item.inner_type; |
| let type_field = &item.inner_field; |
| |
| let mut variant_decls = Vec::new(); |
| let mut read_match_arms = Vec::new(); |
| let mut dyn_inner_arms = Vec::new(); |
| let mut of_unit_arms = Vec::new(); |
| for var in &item.variants { |
| let var_name = &var.name; |
| let type_id = &var.type_id; |
| let typ = &var.typ; |
| variant_decls.push(quote! { #var_name ( #inner <'a, #typ<'a>> ) }); |
| read_match_arms |
| .push(quote! { #type_id => Ok(#name :: #var_name (untyped.into_concrete())) }); |
| dyn_inner_arms.push(quote! { #name :: #var_name(table) => table }); |
| of_unit_arms.push(quote! { #name :: #var_name(inner) => inner.of_unit_type() }); |
| } |
| |
| let of_unit_docs = &[ |
| " Return the inner table, removing the specific generics.", |
| "", |
| " This lets us return a single concrete type we can call methods on.", |
| ]; |
| |
| Ok(quote! { |
| #( #docs)* |
| pub enum #name <'a> { |
| #( #variant_decls, )* |
| } |
| |
| impl<'a> FontRead<'a> for #name <'a> { |
| #[inline] |
| fn read(bytes: FontData<'a>) -> Result<Self, ReadError> { |
| let untyped = #inner::read(bytes)?; |
| match untyped.#type_field() { |
| #( #read_match_arms, )* |
| other => Err(ReadError::InvalidFormat(other.into())), |
| } |
| } |
| } |
| |
| impl<'a> #name <'a> { |
| #[allow(dead_code)] |
| #( #[doc = #of_unit_docs] )* |
| pub(crate) fn of_unit_type(&self) -> #inner<'a, ()> { |
| match self { |
| #( #of_unit_arms, )* |
| } |
| } |
| } |
| |
| #[cfg(feature = "experimental_traverse")] |
| impl<'a> #name <'a> { |
| fn dyn_inner(&self) -> &(dyn SomeTable<'a> + 'a) { |
| match self { |
| #( #dyn_inner_arms, )* |
| } |
| } |
| } |
| |
| #[cfg(feature = "experimental_traverse")] |
| impl<'a> SomeTable<'a> for #name <'a> { |
| |
| fn get_field(&self, idx: usize) -> Option<Field<'a>> { |
| self.dyn_inner().get_field(idx) |
| } |
| |
| fn type_name(&self) -> &str { |
| self.dyn_inner().type_name() |
| } |
| } |
| |
| #[cfg(feature = "experimental_traverse")] |
| impl std::fmt::Debug for #name<'_> { |
| fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { |
| self.dyn_inner().fmt(f) |
| } |
| } |
| }) |
| } |
| |
| fn generate_debug(item: &Table) -> syn::Result<TokenStream> { |
| let name = item.raw_name(); |
| let name_str = name.to_string(); |
| let generic = item.attrs.generic_offset.as_ref(); |
| let generic_bounds = generic |
| .is_some() |
| .then(|| quote!(: FontRead<'a> + SomeTable<'a> + 'a)); |
| let version = item.fields.version_field().map(|fld| { |
| let name = &fld.name; |
| quote!(let version = self.#name();) |
| }); |
| let condition_inputs = item |
| .fields |
| .conditional_input_idents() |
| .into_iter() |
| .map(|fld| quote!( let #fld = self.#fld(); )); |
| let field_arms = item.fields.iter_field_traversal_match_arms(false); |
| let attrs = item.fields.fields.is_empty().then(|| { |
| quote! { |
| #[allow(unused_variables)] |
| #[allow(clippy::match_single_binding)] |
| } |
| }); |
| |
| Ok(quote! { |
| #[cfg(feature = "experimental_traverse")] |
| impl<'a, #generic #generic_bounds> SomeTable<'a> for #name <'a, #generic> { |
| fn type_name(&self) -> &str { |
| #name_str |
| } |
| |
| #attrs |
| fn get_field(&self, idx: usize) -> Option<Field<'a>> { |
| #version |
| #( #condition_inputs )* |
| match idx { |
| #( #field_arms, )* |
| _ => None, |
| } |
| } |
| } |
| |
| #[cfg(feature = "experimental_traverse")] |
| #[allow(clippy::needless_lifetimes)] |
| impl<'a, #generic #generic_bounds> std::fmt::Debug for #name<'a, #generic> { |
| fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { |
| (self as &dyn SomeTable<'a>).fmt(f) |
| } |
| } |
| }) |
| } |
| |
| pub(crate) fn generate_compile(item: &Table, parse_module: &syn::Path) -> syn::Result<TokenStream> { |
| let decl = super::record::generate_compile_impl(item.raw_name(), &item.attrs, &item.fields)?; |
| if decl.is_empty() { |
| return Ok(decl); |
| } |
| |
| let to_owned_impl = item |
| .attrs |
| .skip_from_obj |
| .is_none() |
| .then(|| generate_to_owned_impl(item, parse_module)) |
| .transpose()?; |
| let top_level = item.attrs.tag.as_ref().map(|tag| { |
| let name = item.raw_name(); |
| let byte_tag = syn::LitByteStr::new(tag.value().as_bytes(), tag.span()); |
| quote! { |
| impl TopLevelTable for #name { |
| const TAG: Tag = Tag::new(#byte_tag); |
| } |
| } |
| }); |
| Ok(quote! { |
| #decl |
| #top_level |
| #to_owned_impl |
| }) |
| } |
| |
| fn generate_to_owned_impl(item: &Table, parse_module: &syn::Path) -> syn::Result<TokenStream> { |
| let name = item.raw_name(); |
| let field_to_owned_stmts = item.fields.iter_from_obj_ref_stmts(false); |
| let comp_generic = item.attrs.generic_offset.as_ref().map(|attr| &attr.attr); |
| let parse_generic = comp_generic |
| .is_some() |
| .then(|| syn::Ident::new("U", Span::call_site())); |
| let impl_generics = comp_generic.into_iter().chain(parse_generic.as_ref()); |
| let impl_generics2 = impl_generics.clone(); |
| let where_clause = comp_generic.map(|t| { |
| quote! { |
| where |
| U: FontRead<'a>, |
| #t: FromTableRef<U> + Default + 'static, |
| } |
| }); |
| |
| let impl_font_read = item.attrs.read_args.is_none() && item.attrs.generic_offset.is_none(); |
| let maybe_font_read = impl_font_read.then(|| { |
| quote! { |
| impl<'a> FontRead<'a> for #name { |
| fn read(data: FontData<'a>) -> Result<Self, ReadError> { |
| <#parse_module :: #name as FontRead>::read(data) |
| .map(|x| x.to_owned_table()) |
| } |
| } |
| } |
| }); |
| |
| let should_bind_offset_data = item.fields.from_obj_requires_offset_data(false); |
| let offset_data_src = item.fields.iter().find_map(|fld| { |
| fld.attrs |
| .offset_data |
| .as_ref() |
| .map(|Attr { attr, .. }| quote!(#attr)) |
| }); |
| let maybe_bind_offset_data = should_bind_offset_data.then(|| match offset_data_src { |
| Some(ident) => quote!(let offset_data = obj. #ident ();), |
| None => quote!( let offset_data = obj.offset_data(); ), |
| }); |
| |
| Ok(quote! { |
| impl<'a, #( #impl_generics, )* > FromObjRef<#parse_module :: #name<'a, #parse_generic>> for #name<#comp_generic> #where_clause { |
| fn from_obj_ref(obj: &#parse_module :: #name<'a, #parse_generic>, _: FontData) -> Self { |
| #maybe_bind_offset_data |
| #name { |
| #( #field_to_owned_stmts, )* |
| } |
| } |
| } |
| |
| #[allow(clippy::needless_lifetimes)] |
| impl<'a, #(#impl_generics2,)* > FromTableRef<#parse_module :: #name<'a, #parse_generic >> for #name<#comp_generic> #where_clause {} |
| |
| #maybe_font_read |
| }) |
| } |
| |
| pub(crate) fn generate_group_compile( |
| item: &GenericGroup, |
| parse_module: &syn::Path, |
| ) -> syn::Result<TokenStream> { |
| let docs = &item.attrs.docs; |
| let name = &item.name; |
| let inner = &item.inner_type; |
| |
| let mut variant_decls = Vec::new(); |
| let mut write_match_arms = Vec::new(); |
| let mut validate_match_arms = Vec::new(); |
| let mut from_obj_match_arms = Vec::new(); |
| let mut type_arms = Vec::new(); |
| let mut from_impls = Vec::new(); |
| let from_type = quote!(#parse_module :: #name); |
| for var in &item.variants { |
| let var_name = &var.name; |
| let typ = &var.typ; |
| |
| variant_decls.push(quote! { #var_name ( #inner <#typ> ) }); |
| write_match_arms.push(quote! { Self :: #var_name (table) => table.write_into(writer) }); |
| validate_match_arms.push(quote! { Self :: #var_name(table) => table.validate_impl(ctx) }); |
| from_obj_match_arms.push( |
| quote! { #from_type :: #var_name(table) => Self :: #var_name(table.to_owned_obj(data)) }, |
| ); |
| type_arms.push(quote! { Self:: #var_name(table) => table.table_type() }); |
| from_impls.push(quote! { |
| impl From<#inner <#typ>> for #name { |
| fn from(src: #inner <#typ>) -> #name { |
| #name :: #var_name ( src ) |
| } |
| } |
| }); |
| } |
| let first_var_name = &item.variants.first().unwrap().name; |
| |
| Ok(quote! { |
| #( #docs)* |
| #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] |
| #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] |
| pub enum #name { |
| #( #variant_decls, )* |
| } |
| |
| impl Default for #name { |
| fn default() -> Self { |
| Self::#first_var_name(Default::default()) |
| } |
| } |
| |
| impl FontWrite for #name { |
| fn write_into(&self, writer: &mut TableWriter) { |
| match self { |
| #( #write_match_arms, )* |
| } |
| } |
| |
| fn table_type(&self) -> TableType { |
| match self { |
| #( #type_arms, )* |
| } |
| } |
| } |
| |
| impl Validate for #name { |
| fn validate_impl(&self, ctx: &mut ValidationCtx) { |
| match self { |
| #( #validate_match_arms, )* |
| } |
| } |
| } |
| |
| impl FromObjRef< #from_type :: <'_>> for #name { |
| fn from_obj_ref(from: & #from_type :: <'_>, data: FontData) -> Self { |
| match from { |
| #( #from_obj_match_arms, )* |
| } |
| } |
| } |
| |
| impl FromTableRef< #from_type <'_>> for #name {} |
| |
| #( #from_impls )* |
| |
| }) |
| } |
| |
| pub(crate) fn generate_format_compile( |
| item: &TableFormat, |
| items: &Items, |
| ) -> syn::Result<TokenStream> { |
| let name = &item.name; |
| let docs = &item.attrs.docs; |
| let parse_module = &items.parse_module_path; |
| let variants = item.variants.iter().map(|variant| { |
| let name = &variant.name; |
| let typ = variant.type_name(); |
| let docs = &variant.attrs.docs; |
| quote! ( #( #docs )* #name(#typ) ) |
| }); |
| |
| let default_variant = &item.variants.first().unwrap().name; |
| |
| let write_arms = item.variants.iter().map(|variant| { |
| let var_name = &variant.name; |
| quote!( Self::#var_name(item) => item.write_into(writer), ) |
| }); |
| |
| let validation_arms = item.variants.iter().map(|variant| { |
| let var_name = &variant.name; |
| quote!( Self::#var_name(item) => item.validate_impl(ctx), ) |
| }); |
| |
| let table_type_arms = item.variants.iter().map(|variant| { |
| let var_name = &variant.name; |
| quote!( Self::#var_name(item) => item.table_type(), ) |
| }); |
| |
| let from_impls = item.variants.iter().map(|variant| { |
| let var_name = &variant.name; |
| let typ = variant.type_name(); |
| quote!( impl From<#typ> for #name { |
| fn from(src: #typ) -> #name { |
| #name::#var_name(src) |
| } |
| } ) |
| }); |
| |
| let from_obj_impl = item |
| .attrs |
| .skip_from_obj |
| .is_none() |
| .then(|| generate_format_from_obj(item, parse_module)) |
| .transpose()?; |
| |
| let constructors = generate_format_constructors(item, items)?; |
| Ok(quote! { |
| #( #docs )* |
| #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] |
| #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] |
| pub enum #name { |
| #( #variants ),* |
| } |
| |
| #constructors |
| |
| impl Default for #name { |
| fn default() -> Self { |
| Self::#default_variant(Default::default()) |
| } |
| } |
| |
| impl FontWrite for #name { |
| fn write_into(&self, writer: &mut TableWriter) { |
| match self { |
| #( #write_arms )* |
| } |
| } |
| |
| fn table_type(&self) -> TableType { |
| match self { |
| #( #table_type_arms )* |
| } |
| } |
| } |
| |
| impl Validate for #name { |
| fn validate_impl(&self, ctx: &mut ValidationCtx) { |
| match self { |
| #( #validation_arms )* |
| } |
| } |
| } |
| |
| #from_obj_impl |
| |
| #( #from_impls )* |
| |
| }) |
| } |
| |
| fn generate_format_constructors(item: &TableFormat, items: &Items) -> syn::Result<TokenStream> { |
| let mut constructors = Vec::new(); |
| let name = &item.name; |
| |
| for variant in &item.variants { |
| let var_name = &variant.name; |
| let var_type = variant.type_name(); |
| |
| let Some(Item::Table(table)) = items.get(var_type) else { |
| return Err(logged_syn_error(var_type.span(), "Unknown type; codegen currently expects types in format groups to be local to the file.")); |
| }; |
| if table.attrs.skip_constructor.is_some() { |
| continue; |
| } |
| |
| let constructor_args_raw = table.fields.iter_constructor_info().collect::<Vec<_>>(); |
| let constructor_args = constructor_args_raw.iter().map( |
| |FieldConstructorInfo { |
| name, arg_tokens, .. |
| }| quote!(#name: #arg_tokens), |
| ); |
| let constructor_arg_names = constructor_args_raw.iter().map(|info| &info.name); |
| |
| let constructor_ident = make_snake_case_ident(var_name); |
| |
| let docstring = format!(" Construct a new `{}` subtable", variant.type_name()); |
| // judiciously allow this lint |
| let too_many_args = |
| (constructor_args.len() > 7).then(|| quote!(#[allow(clippy::too_many_arguments)])); |
| constructors.push(quote! { |
| #[doc = #docstring] |
| #too_many_args |
| pub fn #constructor_ident ( #( #constructor_args,)* ) -> Self { |
| Self::#var_name( #var_type::new( #( #constructor_arg_names, )* )) |
| } |
| }); |
| } |
| |
| Ok(quote! { |
| impl #name { |
| |
| #( #constructors )* |
| } |
| }) |
| } |
| |
| fn generate_format_shared_getters(item: &TableFormat, items: &Items) -> syn::Result<TokenStream> { |
| // okay so we want to identify the getters that exist on all variants. |
| let all_variants = item |
| .variants |
| .iter() |
| .map(|var| { |
| let type_name = var.type_name(); |
| match items.get(type_name) { |
| Some(Item::Table(item)) => Ok(item), |
| _ => Err(logged_syn_error( |
| type_name.span(), |
| "must be a table defined in this file", |
| )), |
| } |
| }) |
| .collect::<Result<Vec<_>, _>>()?; |
| // okay so now we have all of the actual inner types, and we need to find which |
| // getters are shared between all of them |
| let mut field_counts = IndexMap::new(); |
| let mut all_fields = HashMap::new(); |
| for table in &all_variants { |
| for field in table.fields.iter().filter(|fld| fld.has_getter()) { |
| let key = (&field.name, &field.typ); |
| // we have to convert the tokens to a string to get hash/ord/etc |
| *field_counts.entry(key).or_insert(0usize) += 1; |
| all_fields.entry(&field.name).or_insert(field); |
| } |
| } |
| |
| let shared_fields = field_counts |
| .into_iter() |
| .filter(|(_, count)| *count == all_variants.len()) |
| .map(|((name, _), _)| all_fields.get(name).unwrap()) |
| .collect::<Vec<_>>(); |
| |
| let getters = shared_fields |
| .iter() |
| .map(|fld| generate_format_getter_for_shared_field(item, fld)); |
| |
| // and we also want to have a wrapper for offset_data(): |
| let data_arms = item |
| .variants |
| .iter() |
| .filter(|v| v.attrs.write_only.is_none()) |
| .map(|variant| { |
| let var_name = &variant.name; |
| quote!(Self::#var_name(item) => item.offset_data(), ) |
| }); |
| |
| // now we have a collection of fields present on all variants, and |
| // we need to actually generate the wrapping getter |
| |
| Ok(quote! { |
| #[doc = "Return the `FontData` used to resolve offsets for this table."] |
| #[inline] |
| pub fn offset_data(&self) -> FontData<'a> { |
| match self { |
| #( #data_arms )* |
| } |
| } |
| #( #getters )* |
| }) |
| } |
| |
| fn generate_format_getter_for_shared_field(item: &TableFormat, field: &Field) -> TokenStream { |
| let docs = &field.attrs.docs; |
| let method_name = &field.name; |
| let return_type = field.table_getter_return_type(); |
| let arms = item.variants.iter().map(|variant| { |
| let var_name: &syn::Ident = &variant.name; |
| quote!(Self::#var_name(item) => item.#method_name(), ) |
| }); |
| |
| // but we also need to handle offset getters, and that's a pain |
| |
| quote! { |
| #( #docs )* |
| #[inline] |
| pub fn #method_name(&self) -> #return_type { |
| match self { |
| #( #arms )* |
| } |
| } |
| } |
| } |
| |
| fn generate_format_from_obj( |
| item: &TableFormat, |
| parse_module: &syn::Path, |
| ) -> syn::Result<TokenStream> { |
| let name = &item.name; |
| let to_owned_arms = item |
| .variants |
| .iter() |
| .filter(|variant| variant.attrs.write_only.is_none()) |
| .map(|variant| { |
| let var_name = &variant.name; |
| quote!( ObjRefType::#var_name(item) => #name::#var_name(item.to_owned_table()), ) |
| }); |
| |
| Ok(quote! { |
| impl FromObjRef<#parse_module:: #name<'_>> for #name { |
| fn from_obj_ref(obj: &#parse_module:: #name, _: FontData) -> Self { |
| use #parse_module::#name as ObjRefType; |
| match obj { |
| #( #to_owned_arms )* |
| } |
| } |
| } |
| |
| impl FromTableRef<#parse_module::#name<'_>> for #name {} |
| |
| impl<'a> FontRead<'a> for #name { |
| fn read(data: FontData<'a>) -> Result<Self, ReadError> { |
| <#parse_module :: #name as FontRead>::read(data) |
| .map(|x| x.to_owned_table()) |
| } |
| } |
| }) |
| } |
| |
| pub(crate) fn generate_format_group(item: &TableFormat, items: &Items) -> syn::Result<TokenStream> { |
| let name = &item.name; |
| let docs = &item.attrs.docs; |
| let variants = item |
| .variants |
| .iter() |
| .filter(|variant| variant.attrs.write_only.is_none()) |
| .map(|variant| { |
| let name = &variant.name; |
| let typ = variant.type_name(); |
| let docs = &variant.attrs.docs; |
| quote! ( #( #docs )* #name(#typ<'a>) ) |
| }); |
| |
| let format = &item.format; |
| // if we have any fancy match statement we disable a clippy lint |
| let mut has_any_match_stmt = false; |
| let match_arms = item |
| .variants |
| .iter() |
| .filter(|variant| variant.attrs.write_only.is_none()) |
| .map(|variant| { |
| let name = &variant.name; |
| let lhs = if let Some(expr) = variant.attrs.match_stmt.as_deref() { |
| has_any_match_stmt = true; |
| let expr = &expr.expr; |
| quote!(format if #expr) |
| } else { |
| let typ = variant.marker_name(); |
| quote!(#typ::FORMAT) |
| }; |
| Some(quote! { |
| #lhs => { |
| Ok(Self::#name(FontRead::read(data)?)) |
| } |
| }) |
| }) |
| .collect::<Vec<_>>(); |
| |
| let maybe_allow_lint = has_any_match_stmt.then(|| quote!(#[allow(clippy::redundant_guards)])); |
| |
| let traversal_arms = item |
| .variants |
| .iter() |
| .filter(|variant| variant.attrs.write_only.is_none()) |
| .map(|variant| { |
| let name = &variant.name; |
| quote!(Self::#name(table) => table) |
| }); |
| |
| let format_offset = item |
| .format_offset |
| .as_ref() |
| .map(|lit| lit.base10_parse::<usize>().unwrap()) |
| .unwrap_or(0); |
| |
| let getters = generate_format_shared_getters(item, items)?; |
| let getters = (!getters.is_empty()).then(|| { |
| quote! { |
| impl<'a> #name<'a> { |
| #getters |
| } |
| } |
| }); |
| |
| let min_byte_arms = item |
| .variants |
| .iter() |
| .filter(|variant| variant.attrs.write_only.is_none()) |
| .map(|variant| { |
| let var_name: &syn::Ident = &variant.name; |
| quote!(Self::#var_name(item) => item.min_byte_range(), ) |
| }); |
| |
| Ok(quote! { |
| #( #docs )* |
| #[derive(Clone)] |
| pub enum #name<'a> { |
| #( #variants ),* |
| } |
| |
| #getters |
| |
| impl<'a> FontRead<'a> for #name<'a> { |
| fn read(data: FontData<'a>) -> Result<Self, ReadError> { |
| let format: #format = data.read_at(#format_offset)?; |
| #maybe_allow_lint |
| match format { |
| #( #match_arms ),* |
| other => Err(ReadError::InvalidFormat(other.into())), |
| } |
| } |
| } |
| |
| impl MinByteRange for #name<'_> { |
| fn min_byte_range(&self) -> Range<usize> { |
| match self { |
| #( #min_byte_arms )* |
| } |
| } |
| } |
| |
| #[cfg(feature = "experimental_traverse")] |
| impl<'a> #name<'a> { |
| fn dyn_inner<'b>(&'b self) -> &'b dyn SomeTable<'a> { |
| match self { |
| #( #traversal_arms, )* |
| } |
| } |
| } |
| |
| #[cfg(feature = "experimental_traverse")] |
| impl std::fmt::Debug for #name<'_> { |
| fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { |
| self.dyn_inner().fmt(f) |
| } |
| } |
| |
| #[cfg(feature = "experimental_traverse")] |
| impl<'a> SomeTable<'a> for #name<'a> { |
| fn type_name(&self) -> &str { |
| self.dyn_inner().type_name() |
| } |
| |
| fn get_field(&self, idx: usize) -> Option<Field<'a>> { |
| self.dyn_inner().get_field(idx) |
| } |
| } |
| }) |
| } |
| |
| impl Table { |
| pub(crate) fn sanity_check(&self, phase: Phase) -> syn::Result<()> { |
| self.fields.sanity_check(phase) |
| } |
| |
| fn marker_name(&self) -> syn::Ident { |
| quote::format_ident!("{}Marker", self.raw_name()) |
| } |
| |
| fn fixed_fields_name(&self) -> syn::Ident { |
| quote::format_ident!("{}FixedFields", self.raw_name()) |
| } |
| |
| fn iter_shape_byte_fns(&self) -> impl Iterator<Item = TokenStream> + '_ { |
| let mut prev_field_end_expr = quote!(0); |
| let mut iter = self.fields.iter(); |
| |
| std::iter::from_fn(move || { |
| let field = iter.next()?; |
| let fn_name = field.shape_byte_range_fn_name(); |
| let len_expr = field.shape_len_expr(); |
| |
| // versioned fields have a different signature |
| if field.attrs.conditional.is_some() { |
| prev_field_end_expr = quote! { |
| self.#fn_name().map(|range| range.end) |
| .unwrap_or_else(|| #prev_field_end_expr) |
| }; |
| let start_field_name = field.shape_byte_start_field_name(); |
| return Some(quote! { |
| pub fn #fn_name(&self) -> Option<Range<usize>> { |
| let start = self.#start_field_name?; |
| Some(start..start + #len_expr) |
| } |
| }); |
| } |
| |
| let result = quote! { |
| pub fn #fn_name(&self) -> Range<usize> { |
| let start = #prev_field_end_expr; |
| start..start + #len_expr |
| } |
| }; |
| prev_field_end_expr = quote!( self.#fn_name().end ); |
| |
| Some(result) |
| }) |
| } |
| |
| fn iter_shape_fields(&self) -> impl Iterator<Item = TokenStream> + '_ { |
| self.iter_shape_field_names_and_types() |
| .into_iter() |
| .map(|(ident, typ)| quote!( #ident: #typ )) |
| } |
| |
| fn iter_shape_field_names(&self) -> impl Iterator<Item = syn::Ident> + '_ { |
| self.iter_shape_field_names_and_types() |
| .into_iter() |
| .map(|(name, _)| name) |
| } |
| |
| fn iter_shape_field_names_and_types(&self) -> Vec<(syn::Ident, TokenStream)> { |
| let mut result = Vec::new(); |
| // if an input arg is needed later, save it in the shape. |
| if let Some(args) = &self.attrs.read_args { |
| result.extend( |
| args.args |
| .iter() |
| .filter(|arg| self.fields.referenced_fields.needs_at_runtime(&arg.ident)) |
| .map(|arg| (arg.ident.clone(), arg.typ.to_token_stream())), |
| ); |
| } |
| |
| for next in self.fields.iter() { |
| let is_versioned = next.attrs.conditional.is_some(); |
| let has_computed_len = next.has_computed_len(); |
| if !(is_versioned || has_computed_len) { |
| continue; |
| } |
| if is_versioned { |
| let field_name = next.shape_byte_start_field_name(); |
| result.push((field_name, quote!(Option<usize>))); |
| } |
| |
| if has_computed_len { |
| let field_name = next.shape_byte_len_field_name(); |
| if is_versioned { |
| result.push((field_name, quote!(Option<usize>))); |
| } else { |
| result.push((field_name, quote!(usize))); |
| } |
| }; |
| } |
| result |
| } |
| |
| fn iter_field_validation_stmts(&self) -> impl Iterator<Item = TokenStream> + '_ { |
| self.fields.iter().map(Field::field_parse_validation_stmts) |
| } |
| |
| fn iter_table_ref_getters(&self) -> impl Iterator<Item = TokenStream> + '_ { |
| let generic = self.attrs.generic_offset.as_ref().map(|attr| &attr.attr); |
| self.fields |
| .iter() |
| .filter_map(move |fld| fld.table_getter(generic)) |
| .chain( |
| self.attrs |
| .read_args |
| .as_ref() |
| .into_iter() |
| .flat_map(|args| args.iter_table_ref_getters(&self.fields.referenced_fields)), |
| ) |
| } |
| |
| pub(crate) fn impl_format_trait(&self) -> Option<TokenStream> { |
| let field = self.fields.iter().find(|fld| fld.attrs.format.is_some())?; |
| let name = self.marker_name(); |
| let value = &field.attrs.format.as_ref().unwrap(); |
| let typ = field.typ.cooked_type_tokens(); |
| |
| Some(quote! { |
| impl Format<#typ> for #name { |
| const FORMAT: #typ = #value; |
| } |
| }) |
| } |
| |
| pub(crate) fn impl_min_byte_range_trait(&self) -> Option<TokenStream> { |
| let field = self |
| .fields |
| .iter() |
| .filter(|fld| fld.attrs.conditional.is_none()) |
| .last()?; |
| let name = self.marker_name(); |
| |
| let fn_name = field.shape_byte_range_fn_name(); |
| Some(quote! { |
| impl MinByteRange for #name { |
| fn min_byte_range(&self) -> Range<usize> { |
| 0..self.#fn_name().end |
| } |
| } |
| }) |
| } |
| } |
| |
| impl TableReadArgs { |
| pub(crate) fn args_type(&self) -> TokenStream { |
| match self.args.as_slice() { |
| [TableReadArg { typ, .. }] => typ.to_token_stream(), |
| other => { |
| let typs = other.iter().map(|arg| &arg.typ); |
| quote!( ( #(#typs,)* ) ) |
| } |
| } |
| } |
| |
| pub(crate) fn destructure_pattern(&self) -> TokenStream { |
| match self.args.as_slice() { |
| [TableReadArg { ident, .. }] => ident.to_token_stream(), |
| other => { |
| let idents = other.iter().map(|arg| &arg.ident); |
| quote!( ( #(#idents,)* ) ) |
| } |
| } |
| } |
| |
| pub(crate) fn constructor_args(&self) -> impl Iterator<Item = TokenStream> + '_ { |
| self.args |
| .iter() |
| .map(|TableReadArg { ident, typ }| quote!(#ident: #typ)) |
| } |
| |
| // if only one arg then just that, else a tuple of args |
| pub(crate) fn read_args_from_constructor_args(&self) -> TokenStream { |
| match self.args.as_slice() { |
| [TableReadArg { ident, .. }] => ident.to_token_stream(), |
| other => { |
| let idents = other.iter().map(|arg| &arg.ident); |
| quote!( ( #(#idents,)* ) ) |
| } |
| } |
| } |
| |
| fn iter_table_ref_getters<'a>( |
| &'a self, |
| referenced_fields: &'a ReferencedFields, |
| ) -> impl Iterator<Item = TokenStream> + 'a { |
| self.args |
| .iter() |
| .filter(|arg| referenced_fields.needs_at_runtime(&arg.ident)) |
| .map(|TableReadArg { ident, typ }| { |
| quote! { |
| pub(crate) fn #ident(&self) -> #typ { |
| self.shape.#ident |
| } |
| } |
| }) |
| } |
| } |
| |
| // An overwrought and likely incorrect way of converting 'Format1' to 'format_1' -_- |
| fn make_snake_case_ident(ident: &syn::Ident) -> syn::Ident { |
| let input = ident.to_string(); |
| let mut output = String::with_capacity(input.len() + 2); |
| let mut prev_char = input.chars().next().unwrap(); |
| output.extend(prev_char.to_lowercase()); |
| for c in input.chars().skip(1) { |
| if (c.is_uppercase() && !prev_char.is_uppercase()) |
| || (c.is_numeric() && !prev_char.is_numeric()) |
| { |
| output.push('_'); |
| } |
| output.extend(c.to_lowercase()); |
| prev_char = c; |
| } |
| |
| syn::Ident::new(&output, ident.span()) |
| } |