blob: a7110ab55861a320e57a02b30e1c9efd9b2e870b [file] [edit]
//! Field and field-type parsing.
use std::hash::Hash;
use log::debug;
use proc_macro2::{Span, TokenStream};
use quote::{quote, ToTokens};
use syn::{
braced,
parse::{Parse, ParseStream},
punctuated::Punctuated,
spanned::Spanned,
Token,
};
use super::{logged_syn_error, FieldAttrs, TableReadArgs};
#[derive(Debug, Clone)]
pub(crate) struct Fields {
// not parsed, but set when the table/record is parsed
pub(crate) read_args: Option<TableReadArgs>,
pub(crate) fields: Vec<Field>,
}
#[derive(Debug, Clone)]
pub(crate) struct Field {
pub(crate) attrs: FieldAttrs,
pub(crate) name: syn::Ident,
pub(crate) typ: FieldType,
/// `true` if the presence of this field is guaranteed if the containing
/// table parses successfully.
///
/// This is true for fields at the start of a table, up to the first conditional.
///
/// These fields must be present, which means reads can unwrap (and could even
/// be unsafe.)
pub(crate) validated_at_parse: bool,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub(crate) enum FieldType {
Offset {
typ: syn::Ident,
target: OffsetTarget,
},
Scalar {
typ: syn::Ident,
},
Struct {
typ: syn::Ident,
},
/// A type that may be a struct or a scalar.
///
/// This only exists at parse time; when parsing is finished this will be
/// resolved (or be an error).
PendingResolution {
typ: syn::Ident,
},
Array {
inner_typ: Box<FieldType>,
},
ComputedArray(CustomArray),
VarLenArray(CustomArray),
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub(crate) enum OffsetTarget {
Table(syn::Ident),
Array(Box<FieldType>),
}
/// A representation shared between computed & varlen arrays
#[derive(Debug, Clone)]
pub(crate) struct CustomArray {
span: Span,
inner: syn::Ident,
lifetime: Option<syn::Lifetime>,
}
impl PartialEq for CustomArray {
fn eq(&self, other: &Self) -> bool {
self.inner == other.inner && self.lifetime == other.lifetime
}
}
impl Eq for CustomArray {}
impl Hash for CustomArray {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.inner.hash(state);
self.lifetime.hash(state);
}
}
impl CustomArray {
pub(crate) fn compile_type(&self) -> TokenStream {
let inner = &self.inner;
quote!(Vec<#inner>)
}
pub(crate) fn raw_inner_type(&self) -> &syn::Ident {
&self.inner
}
pub(crate) fn type_with_lifetime(&self) -> TokenStream {
let inner = &self.inner;
if self.lifetime.is_some() {
quote!(#inner<'a>)
} else {
inner.to_token_stream()
}
}
pub(crate) fn span(&self) -> Span {
self.span
}
}
impl OffsetTarget {
pub(crate) fn getter_return_type(&self, is_generic: bool) -> TokenStream {
match self {
OffsetTarget::Table(ident) if !is_generic => quote!(Result<#ident <'a>, ReadError>),
OffsetTarget::Table(ident) => quote!(Result<#ident, ReadError>),
OffsetTarget::Array(inner) => {
let elem_type = match std::ops::Deref::deref(inner) {
FieldType::Scalar { typ } => quote!(BigEndian<#typ>),
FieldType::Struct { typ } => typ.to_token_stream(),
_ => panic!("we should have returned a humane error before now"),
};
quote!(Result<&'a [#elem_type], ReadError>)
}
}
}
pub(crate) fn compile_type(&self) -> TokenStream {
match self {
Self::Table(ident) => ident.to_token_stream(),
Self::Array(thing) => {
let cooked = thing.cooked_type_tokens();
quote!(Vec<#cooked>)
}
}
}
}
// ── Parse impls ─────────────────────────────────────────────────────
impl Parse for Fields {
fn parse(input: ParseStream) -> syn::Result<Self> {
let content;
let _ = braced!(content in input);
let fields = Punctuated::<Field, Token![,]>::parse_terminated(&content)?
.into_iter()
.collect();
Self::new(fields)
}
}
impl Parse for Field {
fn parse(input: ParseStream) -> syn::Result<Self> {
let attrs = input.parse()?;
let name = input.parse::<syn::Ident>().unwrap();
let _ = input.parse::<Token![:]>()?;
let typ = input.parse()?;
Ok(Field {
attrs,
name,
typ,
// computed later
validated_at_parse: false,
})
}
}
impl Parse for FieldType {
fn parse(input: ParseStream) -> syn::Result<Self> {
let type_: syn::Type = input.parse()?;
Self::from_syn_type(&type_)
}
}
// ── FieldType resolution ────────────────────────────────────────────
impl FieldType {
fn from_syn_type(type_: &syn::Type) -> syn::Result<Self> {
// Figure out any "obvious" types, leave anything non-obvious for later
if let syn::Type::Slice(slice) = type_ {
let inner_type = FieldType::from_syn_type(&slice.elem)?;
if matches!(inner_type, FieldType::Array { .. }) {
return Err(logged_syn_error(
slice.elem.span(),
"nested arrays are invalid",
));
}
return Ok(FieldType::Array {
inner_typ: Box::new(inner_type),
});
}
let path = match type_ {
syn::Type::Path(path) => &path.path,
_ => return Err(logged_syn_error(type_.span(), "expected slice or path")),
};
let last = get_single_path_segment(path)?;
if last.ident == "ComputedArray" || last.ident == "VarLenArray" {
let inner_typ = get_single_generic_type_arg(&last.arguments)?;
let inner = get_single_path_segment(&inner_typ)?;
let lifetime = get_single_lifetime(&inner.arguments)?;
let array = CustomArray {
span: inner.span(),
inner: inner.ident.clone(),
lifetime,
};
if last.ident == "ComputedArray" {
return Ok(FieldType::ComputedArray(array));
} else {
return Ok(FieldType::VarLenArray(array));
}
}
if WellKnownScalar::from_path(last).is_ok() {
return Ok(FieldType::Scalar {
typ: last.ident.clone(),
});
}
if ["Offset16", "Offset24", "Offset32"].contains(&last.ident.to_string().as_str()) {
let target = get_offset_target(last)?;
return Ok(FieldType::Offset {
typ: last.ident.clone(),
target,
});
}
// We'll figure it out later, what could go wrong?
if !last.arguments.is_empty() {
return Err(logged_syn_error(path.span(), "Unexpected path arguments"));
}
debug!("Pending {}", quote! { #last });
Ok(FieldType::PendingResolution {
typ: last.ident.clone(),
})
}
}
pub(super) fn resolve_ident<'a>(
known: &'a std::collections::HashMap<syn::Ident, FieldType>,
field_name: &syn::Ident,
field_type: &syn::Ident,
) -> Result<&'a FieldType, syn::Error> {
if let Some(item) = known.get(field_type) {
debug!("Resolve {}: {} to {:?}", field_name, field_type, item);
Ok(item)
} else {
Err(logged_syn_error(
field_type.span(),
"Error: undeclared type. Missing a record, table, extern table, or extern record?",
))
}
}
pub(super) fn resolve_field(
known: &std::collections::HashMap<syn::Ident, FieldType>,
field: &mut Field,
) -> Result<(), syn::Error> {
if let FieldType::PendingResolution { typ } = &field.typ {
let resolved_typ = resolve_ident(known, &field.name, typ)?;
*field = Field {
typ: resolved_typ.clone(),
..field.clone()
}
}
// Array and offsets can nest FieldType, pursue the rabbit
if let FieldType::Array { inner_typ } = &field.typ {
if let FieldType::PendingResolution { typ } = inner_typ.as_ref() {
let resolved_typ = resolve_ident(known, &field.name, typ)?;
*field = Field {
typ: FieldType::Array {
inner_typ: Box::new(resolved_typ.clone()),
},
..field.clone()
}
}
}
if let FieldType::Offset { typ, target } = &field.typ {
let offset_typ = typ;
if let OffsetTarget::Array(array_of) = target {
if let FieldType::PendingResolution { typ } = array_of.as_ref() {
let resolved_typ = resolve_ident(known, &field.name, typ)?;
*field = Field {
typ: FieldType::Offset {
typ: offset_typ.clone(),
target: OffsetTarget::Array(Box::new(resolved_typ.clone())),
},
..field.clone()
}
}
}
}
Ok(())
}
// ── Helper types and functions ──────────────────────────────────────
// https://learn.microsoft.com/en-us/typography/opentype/spec/otff#data-types
// Offset(16,24,32) get special handling, not listed here
// GlyphId, NameId, and MajorMinor are *not* spec names for scalar but are captured here
#[derive(Debug, PartialEq)]
enum WellKnownScalar {
UInt8,
Int8,
UInt16,
Int16,
UInt24,
Int24,
UInt32,
Int32,
Fixed,
FWord,
UFWord,
F2Dot14,
LongDateTime,
Tag,
Version16Dot16,
GlyphId16,
GlyphId24,
NameId,
MajorMinor,
}
impl std::str::FromStr for WellKnownScalar {
type Err = ();
// TODO(https://github.com/googlefonts/fontations/issues/84) use spec names
fn from_str(str: &str) -> Result<WellKnownScalar, ()> {
match str {
"u8" => Ok(WellKnownScalar::UInt8),
"i8" => Ok(WellKnownScalar::Int8),
"u16" => Ok(WellKnownScalar::UInt16),
"i16" => Ok(WellKnownScalar::Int16),
"u24" => Ok(WellKnownScalar::UInt24),
"Uint24" => Ok(WellKnownScalar::UInt24),
"Int24" => Ok(WellKnownScalar::Int24),
"i24" => Ok(WellKnownScalar::Int24),
"u32" => Ok(WellKnownScalar::UInt32),
"i32" => Ok(WellKnownScalar::Int32),
"Fixed" => Ok(WellKnownScalar::Fixed),
"FWord" => Ok(WellKnownScalar::FWord),
"UfWord" => Ok(WellKnownScalar::UFWord),
"F2Dot14" => Ok(WellKnownScalar::F2Dot14),
"LongDateTime" => Ok(WellKnownScalar::LongDateTime),
"Tag" => Ok(WellKnownScalar::Tag),
"Version16Dot16" => Ok(WellKnownScalar::Version16Dot16),
"GlyphId16" => Ok(WellKnownScalar::GlyphId16),
"GlyphId24" => Ok(WellKnownScalar::GlyphId24),
"NameId" => Ok(WellKnownScalar::NameId),
"MajorMinor" => Ok(WellKnownScalar::MajorMinor),
_ => Err(()),
}
}
}
impl WellKnownScalar {
fn from_path(path: &syn::PathSegment) -> Result<WellKnownScalar, ()> {
if !path.arguments.is_empty() {
return Err(());
}
std::str::FromStr::from_str(path.ident.to_string().as_str())
}
}
fn get_single_path_segment(path: &syn::Path) -> syn::Result<&syn::PathSegment> {
if path.segments.len() != 1 {
return Err(logged_syn_error(path.span(), "expect a single-item path"));
}
Ok(path.segments.last().unwrap())
}
// either a single ident or an array
pub(super) fn get_offset_target(input: &syn::PathSegment) -> syn::Result<OffsetTarget> {
match get_single_generic_arg(&input.arguments)? {
Some(syn::GenericArgument::Type(syn::Type::Slice(t))) => {
let inner = FieldType::from_syn_type(&t.elem)?;
if matches!(
inner,
FieldType::Scalar { .. }
| FieldType::Struct { .. }
| FieldType::PendingResolution { .. }
) {
Ok(OffsetTarget::Array(Box::new(inner)))
} else {
Err(logged_syn_error(
t.elem.span(),
"offsets can only point to arrays of records or scalars",
))
}
}
Some(syn::GenericArgument::Type(syn::Type::Path(t)))
if t.path.segments.len() == 1 && t.path.get_ident().is_some() =>
{
Ok(OffsetTarget::Table(t.path.get_ident().unwrap().clone()))
}
Some(_) => Err(logged_syn_error(input.span(), "expected path or slice")),
None => Err(logged_syn_error(input.span(), "expected offset target")),
}
}
fn get_single_generic_type_arg(input: &syn::PathArguments) -> syn::Result<syn::Path> {
match get_single_generic_arg(input)? {
Some(syn::GenericArgument::Type(syn::Type::Path(path)))
if path.qself.is_none() && path.path.segments.len() == 1 =>
{
Ok(path.path.clone())
}
_ => Err(logged_syn_error(input.span(), "expected type")),
}
}
fn get_single_generic_arg(
input: &syn::PathArguments,
) -> syn::Result<Option<&syn::GenericArgument>> {
match input {
syn::PathArguments::None => Ok(None),
syn::PathArguments::AngleBracketed(args) if args.args.len() == 1 => {
Ok(Some(args.args.last().unwrap()))
}
_ => Err(logged_syn_error(
input.span(),
"expected single generic argument",
)),
}
}
fn get_single_lifetime(input: &syn::PathArguments) -> syn::Result<Option<syn::Lifetime>> {
match get_single_generic_arg(input)? {
None => Ok(None),
Some(syn::GenericArgument::Lifetime(arg)) => Ok(Some(arg.clone())),
_ => Err(logged_syn_error(input.span(), "expected single lifetime")),
}
}
#[cfg(test)]
mod tests {
use super::*;
fn make_path_seg(s: &str) -> syn::PathSegment {
let path = syn::parse_str::<syn::Path>(s).unwrap();
path.segments.last().unwrap().clone()
}
#[test]
fn offset_target() {
let array_target = make_path_seg("Offset16<[u16]>");
assert!(get_offset_target(&array_target).is_ok());
let path_target = make_path_seg("Offset16<SomeType>");
assert!(get_offset_target(&path_target).is_ok());
let non_target = make_path_seg("Offset16");
assert!(get_offset_target(&non_target).is_err());
let tuple_target = make_path_seg("Offset16<(u16, u16)>");
assert!(get_offset_target(&tuple_target).is_err());
}
}