blob: a1d94bdba69ad622df684d32cafaea485f64c19b [file] [edit]
//! codegen for table objects
use crate::{fields::FieldConstructorInfo, parsing::logged_syn_error};
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> {
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 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(|| {
let clone_fields = item
.iter_shape_field_names()
.map(|name| quote!(#name: self.#name));
quote! {
impl<#generic> Clone for #marker_name<#generic> {
fn clone(&self) -> Self {
Self {
#( #clone_fields, )*
offset_type: std::marker::PhantomData,
}
}
}
impl<#generic> Copy for #marker_name<#generic> {}
}
});
// 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, #shape_name} = self;
TableRef {
shape: #marker_name {
#( #shape_fields, )*
offset_type: std::marker::PhantomData,
}, data
}
}
}
}
});
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
#( #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 )*
}
#top_level
#impl_clone_copy
#font_read
#impl_into_generic
#( #docs )*
pub type #raw_name<'a, #generic> = TableRef<'a, #marker_name<#generic>>;
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 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."
);)
});
// the cursor doesn't need to be mut if there are no fields,
// which happens at least once (in glyf)?
let maybe_mut_kw = (!item.fields.fields.is_empty()).then(|| quote!(mut));
if let Some(read_args) = &item.attrs.read_args {
let args_type = read_args.args_type();
let destructure_pattern = read_args.destructure_pattern();
Ok(quote! {
#error_if_phantom_and_read_args
impl ReadArgs for #name<'_> {
type Args = #args_type;
}
impl<'a> FontReadWithArgs<'a> for #name<'a> {
fn read_with_args(data: FontData<'a>, args: &#args_type) -> Result<Self, ReadError> {
let #destructure_pattern = *args;
let #maybe_mut_kw cursor = data.cursor();
#( #field_validation_stmts )*
cursor.finish( #marker_name {
#( #shape_field_names, )*
})
}
}
})
} else {
Ok(quote! {
impl<'a, #generic> FontRead<'a> for #name<'a, #generic> {
fn read(data: FontData<'a>) -> Result<Self, ReadError> {
let #maybe_mut_kw cursor = data.cursor();
#( #field_validation_stmts )*
cursor.finish( #marker_name {
#( #shape_field_names, )*
#phantom
})
}
}
})
}
}
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 as_some_table_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())) });
as_some_table_arms.push(quote! { #name :: #var_name(table) => table });
}
Ok(quote! {
#( #docs)*
pub enum #name <'a> {
#( #variant_decls, )*
}
impl<'a> FontRead<'a> for #name <'a> {
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())),
}
}
}
#[cfg(feature = "traversal")]
impl<'a> #name <'a> {
fn as_some_table(&self) -> &(dyn SomeTable<'a> + 'a) {
match self {
#( #as_some_table_arms, )*
}
}
}
#[cfg(feature = "traversal")]
impl<'a> SomeTable<'a> for #name <'a> {
fn get_field(&self, idx: usize) -> Option<Field<'a>> {
self.as_some_table().get_field(idx)
}
fn type_name(&self) -> &str {
self.as_some_table().type_name()
}
}
})
}
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 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 = "traversal")]
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
match idx {
#( #field_arms, )*
_ => None,
}
}
}
#[cfg(feature = "traversal")]
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().into_iter());
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, )*
}
}
}
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 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)) },
);
}
let first_var_name = &item.variants.first().unwrap().name;
Ok(quote! {
#( #docs)*
#[derive(Debug, Clone)]
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, )*
}
}
}
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 {}
})
}
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 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)]
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 )*
}
}
}
impl Validate for #name {
fn validate_impl(&self, ctx: &mut ValidationCtx) {
match self {
#( #validation_arms )*
}
}
}
#from_obj_impl
})
}
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());
constructors.push(quote! {
#[doc = #docstring]
pub fn #constructor_ident ( #( #constructor_args,)* ) -> Self {
Self::#var_name( #var_type::new( #( #constructor_arg_names, )* ))
}
});
}
Ok(quote! {
impl #name {
#( #constructors )*
}
})
}
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().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) -> syn::Result<TokenStream> {
let name = &item.name;
let docs = &item.attrs.docs;
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<'a>) )
});
let format = &item.format;
let match_arms = item.variants.iter().map(|variant| {
let name = &variant.name;
let lhs = if let Some(expr) = variant.attrs.match_stmt.as_deref() {
let expr = &expr.expr;
quote!(format if #expr)
} else {
let typ = variant.marker_name();
quote!(#typ::FORMAT)
};
quote! {
#lhs => {
Ok(Self::#name(FontRead::read(data)?))
}
}
});
let traversal_arms = item.variants.iter().map(|variant| {
let name = &variant.name;
quote!(Self::#name(table) => table)
});
Ok(quote! {
#( #docs )*
pub enum #name<'a> {
#( #variants ),*
}
impl<'a> FontRead<'a> for #name<'a> {
fn read(data: FontData<'a>) -> Result<Self, ReadError> {
let format: #format = data.read_at(0)?;
match format {
#( #match_arms ),*
other => Err(ReadError::InvalidFormat(other.into())),
}
}
}
#[cfg(feature = "traversal")]
impl<'a> #name<'a> {
fn dyn_inner<'b>(&'b self) -> &'b dyn SomeTable<'a> {
match self {
#( #traversal_arms, )*
}
}
}
#[cfg(feature = "traversal")]
impl<'a> std::fmt::Debug for #name<'a> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.dyn_inner().fmt(f)
}
}
#[cfg(feature = "traversal")]
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 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.available.is_some() {
prev_field_end_expr = quote!(compile_error!(
"non-version dependent field cannot follow version-dependent field"
));
let start_field_name = field.shape_byte_start_field_name();
return Some(quote! {
fn #fn_name(&self) -> Option<Range<usize>> {
let start = self.#start_field_name?;
Some(start..start + #len_expr)
}
});
}
let result = quote! {
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_map(|arg| {
self.fields
.referenced_fields
.needs_at_runtime(&arg.ident)
.then(|| (arg.ident.clone(), arg.typ.to_token_stream()))
}));
}
for next in self.fields.iter() {
let is_versioned = next.attrs.available.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;
}
})
}
}
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,)* ) )
}
}
}
fn iter_table_ref_getters<'a>(
&'a self,
referenced_fields: &'a ReferencedFields,
) -> impl Iterator<Item = TokenStream> + 'a {
self.args.iter().filter_map(|TableReadArg { ident, typ }| {
referenced_fields.needs_at_runtime(ident).then(|| {
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())
}