blob: 986b35f8056a135e33f0459a2b09c17b5e6e282c [file] [edit]
//! raw parsing code
use std::{
backtrace::Backtrace, collections::HashMap, fmt::Display, hash::Hash, ops::Deref, str::FromStr,
};
use font_types::Tag;
use indexmap::IndexMap;
use log::{debug, trace};
use proc_macro2::{Span, TokenStream};
use quote::{quote, ToTokens};
use regex::Captures;
use syn::{
braced, parenthesized,
parse::{Parse, ParseStream},
punctuated::Punctuated,
spanned::Spanned,
Attribute, Token,
};
#[derive(Debug)]
pub(crate) struct Items {
pub(crate) parse_module_path: syn::Path,
// we use an IndexMap so that we generate code in the same order as items
// are declared in the input file.
items: IndexMap<syn::Ident, Item>,
}
#[derive(Debug, Clone)]
pub(crate) enum Item {
Table(Table),
Record(Record),
Format(TableFormat),
GenericGroup(GenericGroup),
RawEnum(RawEnum),
Flags(BitFlags),
Extern(Extern),
}
#[derive(Debug, Copy, Clone)]
pub(crate) enum Phase {
Parse,
Analysis,
}
#[derive(Debug, Clone)]
pub(crate) struct Table {
pub(crate) attrs: TableAttrs,
name: syn::Ident,
pub(crate) fields: Fields,
}
impl Table {
// here for visibility reasons
pub(crate) fn raw_name(&self) -> &syn::Ident {
&self.name
}
}
#[derive(Debug, Default, Clone)]
pub(crate) struct TableAttrs {
pub(crate) docs: Vec<syn::Attribute>,
pub(crate) skip_font_write: Option<syn::Path>,
pub(crate) skip_from_obj: Option<syn::Path>,
pub(crate) skip_constructor: Option<syn::Path>,
pub(crate) read_args: Option<Attr<TableReadArgs>>,
pub(crate) generic_offset: Option<Attr<syn::Ident>>,
pub(crate) tag: Option<Attr<syn::LitStr>>,
pub(crate) write_only: Option<syn::Path>,
/// Custom validation behaviour, must be a fn(&self, &mut ValidationCtx) for the type
pub(crate) validate: Option<Attr<syn::Ident>>,
}
#[derive(Debug, Clone)]
pub(crate) struct TableReadArgs {
pub(crate) args: Vec<TableReadArg>,
}
#[derive(Debug, Clone)]
pub(crate) struct TableReadArg {
pub(crate) ident: syn::Ident,
pub(crate) typ: syn::Ident,
}
#[derive(Debug, Clone)]
pub(crate) struct Record {
pub(crate) attrs: TableAttrs,
pub(crate) lifetime: Option<TokenStream>,
pub(crate) name: syn::Ident,
pub(crate) fields: Fields,
}
/// A table with a format; we generate an enum
#[derive(Debug, Clone)]
pub(crate) struct TableFormat {
pub(crate) attrs: TableAttrs,
pub(crate) name: syn::Ident,
pub(crate) format: syn::Ident,
pub(crate) format_offset: Option<syn::LitInt>,
pub(crate) variants: Vec<FormatVariant>,
}
#[derive(Debug, Clone)]
pub(crate) struct FormatVariant {
pub(crate) attrs: VariantAttrs,
pub(crate) name: syn::Ident,
typ: syn::Ident,
}
/// Generates an enum where each variant has a different generic param to a single type.
///
/// This is used in GPOS/GSUB, allowing us to provide more type information
/// to lookups.
#[derive(Debug, Clone)]
pub(crate) struct GenericGroup {
pub(crate) attrs: TableAttrs,
pub(crate) name: syn::Ident,
/// the inner type, which must accept a generic parameter
pub(crate) inner_type: syn::Ident,
/// The field on the inner type that determines the type of the generic param
pub(crate) inner_field: syn::Ident,
pub(crate) variants: Vec<GroupVariant>,
}
#[derive(Debug, Clone)]
pub(crate) struct GroupVariant {
pub(crate) type_id: syn::LitInt,
pub(crate) name: syn::Ident,
pub(crate) typ: syn::Ident,
}
#[derive(Clone, Debug, Default)]
pub(crate) struct VariantAttrs {
pub(crate) docs: Vec<syn::Attribute>,
pub(crate) match_stmt: Option<Attr<InlineExpr>>,
pub(crate) write_only: Option<syn::Path>,
}
impl FormatVariant {
pub(crate) fn marker_name(&self) -> syn::Ident {
quote::format_ident!("{}Marker", &self.typ)
}
pub(crate) fn type_name(&self) -> &syn::Ident {
&self.typ
}
}
#[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>,
pub(crate) referenced_fields: ReferencedFields,
}
#[derive(Debug, Clone)]
pub(crate) struct ReferencedFields(HashMap<syn::Ident, NeededWhen>);
#[derive(Debug, Clone, Copy)]
pub(crate) enum NeededWhen {
Parse,
Runtime,
Both,
}
#[derive(Debug, Clone)]
pub(crate) struct Field {
pub(crate) attrs: FieldAttrs,
pub(crate) name: syn::Ident,
pub(crate) typ: FieldType,
/// `true` if this field is required to be read in order to parse subsequent
/// fields.
///
/// For instance: in a versioned table, the version must be read to determine
/// whether to expect version-dependent fields.
pub(crate) read_at_parse_time: bool,
}
#[derive(Debug, Default, Clone)]
pub(crate) struct FieldAttrs {
pub(crate) docs: Vec<syn::Attribute>,
pub(crate) nullable: Option<syn::Path>,
pub(crate) conditional: Option<Attr<Condition>>,
pub(crate) skip_getter: Option<syn::Path>,
/// specify that an offset getter has a custom impl
pub(crate) offset_getter: Option<Attr<syn::Ident>>,
/// optionally a method on the parent type used to generate the offset data
/// source for this item.
pub(crate) offset_data: Option<Attr<syn::Ident>>,
/// If present, argument is an expression that evaluates to a u32, and is
/// used to adjust the write position of offsets.
//TODO: this could maybe be combined with offset_data?
pub(crate) offset_adjustment: Option<Attr<InlineExpr>>,
pub(crate) version: Option<syn::Path>,
pub(crate) format: Option<Attr<syn::LitInt>>,
pub(crate) count: Option<Attr<Count>>,
pub(crate) compile: Option<Attr<CustomCompile>>,
pub(crate) compile_with: Option<Attr<syn::Ident>>,
pub(crate) default: Option<Attr<syn::Expr>>,
pub(crate) compile_type: Option<Attr<syn::Type>>,
pub(crate) read_with_args: Option<Attr<FieldReadArgs>>,
pub(crate) read_offset_args: Option<Attr<FieldReadArgs>>,
/// If present, a custom method that returns a FieldType for this field,
/// during traversal.
pub(crate) traverse_with: Option<Attr<syn::Ident>>,
pub(crate) to_owned: Option<Attr<InlineExpr>>,
/// Custom validation behaviour
pub(crate) validate: Option<Attr<FieldValidation>>,
}
#[derive(Debug, Clone)]
pub(crate) struct Attr<T> {
pub(crate) name: syn::Ident,
pub(crate) attr: T,
}
impl<T> Attr<T> {
fn new(name: syn::Ident, attr: T) -> Self {
Attr { name, attr }
}
pub(crate) fn span(&self) -> Span {
self.name.span()
}
}
impl<T> std::ops::Deref for Attr<T> {
type Target = T;
fn deref(&self) -> &Self::Target {
&self.attr
}
}
impl<T: ToTokens> ToTokens for Attr<T> {
fn to_tokens(&self, tokens: &mut TokenStream) {
self.attr.to_tokens(tokens)
}
}
#[derive(Debug, Clone)]
pub(crate) struct FieldReadArgs {
pub(crate) inputs: Vec<syn::Ident>,
}
#[derive(Clone, Debug)]
pub(crate) enum Condition {
SinceVersion(VersionSpec),
IfFlag { field: syn::Ident, flag: syn::Path },
IfCond { xform: IfTransform },
}
#[derive(Clone, Debug)]
pub(crate) enum IfTransform {
/// any_flag(field, flag_a, ...):
///
/// Evaluates to true if field has at least one of the input flags set.
AnyFlag(syn::Ident, Vec<syn::Path>),
}
enum IfArg {
Field(syn::Ident),
Path(syn::Path),
}
#[derive(Clone, Debug)]
pub(crate) struct VersionSpec {
major: u16,
minor: Option<u16>,
}
/// Annotations for how to calculate the count of an array.
///
/// ```no_compile
/// #[count(1)] #[count(..)] #[count($hi)] // simple
/// #[count(subtract($field, 1))] // complex
/// ```
#[derive(Clone, Debug)]
pub(crate) enum Count {
// the field isn't used, but it's nice to hold onto if we want to print errors
// in the future
#[allow(dead_code)]
All(syn::token::DotDot),
SingleArg(CountArg),
Complicated {
args: Vec<CountArg>,
xform: CountTransform,
},
}
#[derive(Clone, Debug)]
pub(crate) enum CountArg {
Field(syn::Ident),
Literal(syn::LitInt),
}
#[derive(Clone, Copy, Debug)]
pub(crate) enum CountTransform {
/// requires exactly two args, defined as $arg1 - $arg2
Sub,
/// requires exactly two args, defined as $arg1 + $arg2
Add,
/// requires exactly three args, defined as ($arg1 + $arg2) * $arg3
AddMul,
/// requires exactly three args, defined as ($arg1 * $arg2) + $arg3
MulAdd,
/// requires exactly one arg. defined as $arg1 / 2
Half,
DeltaValueCount,
DeltaSetIndexData,
/// three args: the axis count, the tuple index, and a constant on that index
TupleLen,
/// only ItemVariationStore: requires item_count, word_delta_count, region_index_count
ItemVariationDataLen,
/// Number of bytes to hold a bitmap of N items
BitmapLen,
/// Number of bytes to hold a bitmap with max value N
MaxValueBitmapLen,
/// requires exactly two args, defined as $arg1 - $arg2 + 2
SubAddTwo,
/// requires exactly one arg. Get the count from the $arg1.`try_into::<usize>`().
TryInto,
}
/// Attributes for specifying how to compile a field
#[derive(Debug, Clone)]
pub(crate) enum CustomCompile {
/// this field is ignored
Skip,
/// an inline is provided for calculating this field's value
Expr(InlineExpr),
}
/// Attributes for specifying how to validate a field
#[derive(Debug, Clone)]
pub(crate) enum FieldValidation {
/// this field is not validated
Skip,
/// the field is validated with a custom method.
///
/// This must be a method with a &self param and a &mut ValidationCtx param.
Custom(syn::Ident),
}
/// an inline expression used in an attribute
///
/// this has one fancy quality: you can reference fields of the current
/// object by prepending a '$' to the field name, e.g.
///
/// `#[count( $num_items - 1 )]`
#[derive(Debug, Clone)]
pub(crate) struct InlineExpr {
pub(crate) expr: Box<syn::Expr>,
// the expression used in a compilation context. This resolves any referenced
// fields against `self`.
compile_expr: Option<Box<syn::Expr>>,
pub(crate) referenced_fields: Vec<syn::Ident>,
}
impl InlineExpr {
pub(crate) fn compile_expr(&self) -> &syn::Expr {
self.compile_expr.as_ref().unwrap_or(&self.expr)
}
}
#[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
}
}
/// A raw c-style enum
#[derive(Debug, Clone)]
pub(crate) struct RawEnum {
pub(crate) docs: Vec<syn::Attribute>,
pub(crate) name: syn::Ident,
pub(crate) typ: syn::Ident,
pub(crate) variants: Vec<RawVariant>,
}
/// A raw scalar variant
#[derive(Debug, Clone)]
pub(crate) struct RawVariant {
pub(crate) attrs: EnumVariantAttrs,
pub(crate) name: syn::Ident,
pub(crate) value: syn::LitInt,
}
#[derive(Debug, Default, Clone)]
pub(crate) struct EnumVariantAttrs {
pub(crate) docs: Vec<syn::Attribute>,
pub(crate) default: Option<syn::Path>,
}
/// A set of bit-flags
#[derive(Debug, Clone)]
pub(crate) struct BitFlags {
pub(crate) docs: Vec<syn::Attribute>,
pub(crate) name: syn::Ident,
pub(crate) typ: syn::Ident,
pub(crate) variants: Vec<RawVariant>,
}
#[derive(Debug, Clone)]
pub(crate) enum ExternType {
Scalar,
Record,
}
/// A scalar or record that the codegen user must define themselves
#[derive(Debug, Clone)]
pub(crate) struct Extern {
pub(crate) name: syn::Ident,
pub(crate) typ: ExternType,
}
mod kw {
syn::custom_keyword!(table);
syn::custom_keyword!(record);
syn::custom_keyword!(flags);
syn::custom_keyword!(format);
syn::custom_keyword!(group);
syn::custom_keyword!(skip);
syn::custom_keyword!(scalar);
}
impl Parse for Items {
fn parse(input: ParseStream) -> Result<Self, syn::Error> {
let mut items = IndexMap::new();
let parse_module_path = get_parse_module_path(input)?;
while !input.is_empty() {
let item = input.parse::<Item>()?;
items.insert(item.name().clone(), item);
}
Ok(Self {
items,
parse_module_path,
})
}
}
fn get_parse_module_path(input: ParseStream) -> syn::Result<syn::Path> {
let attrs = input.call(Attribute::parse_inner)?;
match attrs.as_slice() {
[one] if one.path().is_ident("parse_module") => one.parse_args(),
[one] => Err(logged_syn_error(one.span(), "unexpected attribute")),
[_, two, ..] => Err(logged_syn_error(
two.span(),
"expected at most one top-level attribute",
)),
[] => Err(logged_syn_error(
Span::call_site(),
"expected #![parse_module(..)] attribute",
)),
}
}
impl Parse for Item {
fn parse(input: ParseStream) -> Result<Self, syn::Error> {
let peek = input.fork();
// skip attributes
while peek.lookahead1().peek(Token![#]) {
Attribute::parse_outer(&peek)?;
}
let lookahead = peek.lookahead1();
if lookahead.peek(kw::table) {
Ok(Self::Table(input.parse()?))
} else if lookahead.peek(kw::record) {
Ok(Self::Record(input.parse()?))
} else if lookahead.peek(kw::flags) {
Ok(Self::Flags(input.parse()?))
} else if lookahead.peek(kw::format) {
Ok(Self::Format(input.parse()?))
} else if lookahead.peek(kw::group) {
Ok(Self::GenericGroup(input.parse()?))
} else if lookahead.peek(Token![enum]) {
Ok(Self::RawEnum(input.parse()?))
} else if lookahead.peek(Token![extern]) {
Ok(Self::Extern(input.parse()?))
} else {
Err(logged_syn_error(
input.span(),
"expected one of 'table' 'record' 'flags' 'format' 'enum', 'extern', or 'group'.",
))
}
}
}
impl Parse for Table {
fn parse(input: ParseStream) -> syn::Result<Self> {
let attrs: TableAttrs = input.parse()?;
let _table = input.parse::<kw::table>()?;
let name = input.parse::<syn::Ident>()?;
let mut fields: Fields = input.parse()?;
fields.read_args = attrs.read_args.clone().map(|attrs| attrs.attr);
Ok(Table {
attrs,
name,
fields,
})
}
}
impl Parse for Record {
fn parse(input: ParseStream) -> syn::Result<Self> {
let attrs: TableAttrs = input.parse()?;
let _kw = input.parse::<kw::record>()?;
let name = input.parse::<syn::Ident>()?;
let lifetime = input
.peek(Token![<])
.then(|| {
input.parse::<Token![<]>()?;
input.parse::<syn::Lifetime>()?;
input.parse::<Token![>]>().map(|_| quote!(<'a>))
})
.transpose()?;
let mut fields: Fields = input.parse()?;
fields.read_args = attrs.read_args.clone().map(|attrs| attrs.attr);
Ok(Record {
attrs,
lifetime,
name,
fields,
})
}
}
impl Parse for BitFlags {
fn parse(input: ParseStream) -> syn::Result<Self> {
let docs = get_optional_docs(input)?;
let _kw = input.parse::<kw::flags>()?;
let typ = input.parse::<syn::Ident>()?;
validate_ident(
&typ,
&["u8", "u16", "u32"],
"allowed bitflag types: u8, u16, u32",
)?;
let name = input.parse::<syn::Ident>()?;
let content;
let _ = braced!(content in input);
let variants = Punctuated::<RawVariant, Token![,]>::parse_terminated(&content)?
.into_iter()
.collect();
Ok(BitFlags {
docs,
name,
typ,
variants,
})
}
}
impl Parse for RawEnum {
fn parse(input: ParseStream) -> syn::Result<Self> {
let docs = get_optional_docs(input)?;
let _kw = input.parse::<Token![enum]>()?;
let typ = input.parse::<syn::Ident>()?;
validate_ident(&typ, &["u8", "u16"], "allowed enum types: u8, u16")?;
let name = input.parse::<syn::Ident>()?;
let content;
let _ = braced!(content in input);
let variants = Punctuated::<RawVariant, Token![,]>::parse_terminated(&content)?
.into_iter()
.collect();
Ok(RawEnum {
docs,
name,
typ,
variants,
})
}
}
impl Parse for Extern {
fn parse(input: ParseStream) -> syn::Result<Self> {
let _kw = input.parse::<Token![extern]>()?;
let lookahead = input.lookahead1();
let typ = if lookahead.peek(kw::scalar) {
input.parse::<kw::scalar>()?;
ExternType::Scalar
} else if lookahead.peek(kw::record) {
input.parse::<kw::record>()?;
ExternType::Record
} else {
return Err(lookahead.error());
};
let name = input.parse::<syn::Ident>()?;
let _ = input.parse::<Token![;]>();
Ok(Extern { name, typ })
}
}
impl Parse for TableFormat {
fn parse(input: ParseStream) -> syn::Result<Self> {
let attrs: TableAttrs = input.parse()?;
let _kw = input.parse::<kw::format>()?;
let format: syn::Ident = input.parse()?;
let format_offset = if input.peek(Token![@]) {
input.parse::<Token![@]>()?;
let offset = input.parse::<syn::LitInt>()?;
if offset.base10_parse::<u16>().is_err() {
return Err(syn::Error::new(
offset.span(),
"value must be an unsigned integer",
));
}
Some(offset)
} else {
None
};
validate_ident(
&format,
&["u8", "u16", "i16", "DeltaFormat"],
"unexpected format type",
)?;
let name = input.parse::<syn::Ident>()?;
let content;
let _ = braced!(content in input);
let variants = Punctuated::<FormatVariant, Token![,]>::parse_terminated(&content)?
.into_iter()
.collect();
Ok(TableFormat {
attrs,
format,
name,
variants,
format_offset,
})
}
}
impl Parse for GenericGroup {
fn parse(input: ParseStream) -> syn::Result<Self> {
let attrs = input.parse::<TableAttrs>()?;
let _kw = input.parse::<kw::group>()?;
let name = input.parse()?;
let content;
let _ = parenthesized!(content in input);
let inner_type = content.parse()?;
content.parse::<Token![,]>()?;
content.parse::<Token![$]>()?;
let inner_field = content.parse()?;
let content;
let _ = braced!(content in input);
let variants = Punctuated::<GroupVariant, Token![,]>::parse_terminated(&content)?
.into_iter()
.collect();
Ok(GenericGroup {
attrs,
name,
inner_type,
inner_field,
variants,
})
}
}
impl Parse for GroupVariant {
fn parse(input: ParseStream) -> syn::Result<Self> {
let type_id = input.parse()?;
input.parse::<Token![=>]>()?;
let name = input.parse()?;
let content;
let _ = parenthesized!(content in input);
let typ = content.parse()?;
Ok(GroupVariant { type_id, name, typ })
}
}
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
read_at_parse_time: false,
})
}
}
impl Parse for FieldType {
fn parse(input: ParseStream) -> syn::Result<Self> {
let type_: syn::Type = input.parse()?;
Self::from_syn_type(&type_)
}
}
// 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,
NameId,
MajorMinor,
}
impl 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),
"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(());
}
WellKnownScalar::from_str(path.ident.to_string().as_str())
}
}
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(),
})
}
}
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
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")),
}
}
impl Parse for FieldReadArgs {
fn parse(input: ParseStream) -> syn::Result<Self> {
let mut inputs = Vec::new();
while !input.is_empty() {
input.parse::<Token![$]>()?;
inputs.push(input.parse::<syn::Ident>()?);
if !input.is_empty() {
input.parse::<Token![,]>()?;
}
}
Ok(FieldReadArgs { inputs })
}
}
impl Parse for RawVariant {
fn parse(input: ParseStream) -> Result<Self, syn::Error> {
let attrs = input.parse()?;
let name = input.parse::<syn::Ident>()?;
let _ = input.parse::<Token![=]>()?;
let value: syn::LitInt = input.parse()?;
Ok(Self { attrs, name, value })
}
}
impl Parse for FormatVariant {
fn parse(input: ParseStream) -> Result<Self, syn::Error> {
let attrs = input.parse()?;
let name = input.parse::<syn::Ident>()?;
let content;
parenthesized!(content in input);
let typ = content.parse::<syn::Ident>()?;
Ok(Self { attrs, name, typ })
}
}
static MATCH_IF: &str = "match_if";
static WRITE_FONTS_ONLY: &str = "write_fonts_only";
impl Parse for VariantAttrs {
fn parse(input: ParseStream) -> syn::Result<Self> {
let mut this = VariantAttrs::default();
let attrs = Attribute::parse_outer(input)
.map_err(|e| syn::Error::new(e.span(), format!("hmm: '{e}'")))?;
for attr in attrs {
let ident = attr.path().get_ident().ok_or_else(|| {
syn::Error::new(
attr.path().span(),
"attr paths should be a single identifier",
)
})?;
if ident == DOC {
this.docs.push(attr);
} else if ident == MATCH_IF {
this.match_stmt = Some(Attr::new(ident.clone(), attr.parse_args()?));
} else if ident == WRITE_FONTS_ONLY {
this.write_only = Some(attr.path().clone());
} else {
return Err(logged_syn_error(
ident.span(),
format!("unknown variant attribute {ident}"),
));
}
}
Ok(this)
}
}
impl FieldAttrs {
// returns an error if multiple condition attributes are present, which I hope
// to not need to support
fn checked_set_condition(
&mut self,
ident: &syn::Ident,
condition: Condition,
) -> syn::Result<()> {
if let Some(existing) = &self.conditional {
return Err(syn::Error::new(
ident.span(),
format!(
"condition conflicts with existing condition {}",
existing.name
),
));
}
self.conditional = Some(Attr::new(ident.clone(), condition));
Ok(())
}
}
static DOC: &str = "doc";
static NULLABLE: &str = "nullable";
static SKIP_GETTER: &str = "skip_getter";
static COUNT: &str = "count";
static SINCE_VERSION: &str = "since_version";
static IF_COND: &str = "if_cond";
static IF_FLAG: &str = "if_flag";
static FORMAT: &str = "format";
static VERSION: &str = "version";
static OFFSET_GETTER: &str = "offset_getter";
static OFFSET_DATA: &str = "offset_data_method";
static OFFSET_ADJUSTMENT: &str = "offset_adjustment";
static COMPILE: &str = "compile";
static COMPILE_WITH: &str = "compile_with";
static COMPILE_TYPE: &str = "compile_type";
static DEFAULT: &str = "default";
static READ_WITH: &str = "read_with";
static READ_OFFSET_WITH: &str = "read_offset_with";
static TRAVERSE_WITH: &str = "traverse_with";
static TO_OWNED: &str = "to_owned";
static VALIDATE: &str = "validate";
impl Parse for FieldAttrs {
fn parse(input: ParseStream) -> syn::Result<Self> {
let mut this = FieldAttrs::default();
let attrs = Attribute::parse_outer(input)
.map_err(|e| syn::Error::new(e.span(), format!("hmm: '{e}'")))?;
for attr in attrs {
let ident = attr.path().get_ident().ok_or_else(|| {
syn::Error::new(
attr.path().span(),
"attr paths should be a single identifier",
)
})?;
if ident == DOC {
this.docs.push(attr);
} else if ident == NULLABLE {
this.nullable = Some(attr.path().clone());
} else if ident == SKIP_GETTER {
this.skip_getter = Some(attr.path().clone());
} else if ident == OFFSET_GETTER {
this.offset_getter = Some(Attr::new(ident.clone(), attr.parse_args()?));
} else if ident == OFFSET_DATA {
this.offset_data = Some(Attr::new(ident.clone(), attr.parse_args()?));
} else if ident == OFFSET_ADJUSTMENT {
this.offset_adjustment = Some(Attr::new(ident.clone(), attr.parse_args()?));
} else if ident == VERSION {
this.version = Some(attr.path().clone());
} else if ident == COUNT {
this.count = Some(Attr::new(ident.clone(), attr.parse_args()?));
} else if ident == COMPILE {
this.compile = Some(Attr::new(ident.clone(), attr.parse_args()?));
} else if ident == COMPILE_WITH {
this.compile_with = Some(Attr::new(ident.clone(), attr.parse_args()?));
} else if ident == COMPILE_TYPE {
this.compile_type = Some(Attr::new(ident.clone(), attr.parse_args()?));
} else if ident == DEFAULT {
this.default = Some(Attr::new(ident.clone(), attr.parse_args()?));
} else if ident == VALIDATE {
this.validate = Some(Attr::new(ident.clone(), attr.parse_args()?));
} else if ident == TO_OWNED {
this.to_owned = Some(Attr::new(ident.clone(), attr.parse_args()?));
} else if ident == SINCE_VERSION {
let spec = attr.parse_args()?;
this.checked_set_condition(ident, Condition::SinceVersion(spec))?;
} else if ident == IF_FLAG {
let condition = parse_if_flag(&attr)?;
this.checked_set_condition(ident, condition)?;
} else if ident == IF_COND {
let condition = parse_if_cond(&attr)?;
this.checked_set_condition(ident, condition)?;
} else if ident == READ_WITH {
this.read_with_args = Some(Attr::new(ident.clone(), attr.parse_args()?));
} else if ident == READ_OFFSET_WITH {
this.read_offset_args = Some(Attr::new(ident.clone(), attr.parse_args()?));
} else if ident == TRAVERSE_WITH {
this.traverse_with = Some(Attr::new(ident.clone(), attr.parse_args()?));
} else if ident == FORMAT {
this.format = Some(Attr::new(ident.clone(), parse_attr_eq_value(&attr)?))
} else {
return Err(logged_syn_error(
ident.span(),
format!("unknown field attribute {ident}"),
));
}
}
Ok(this)
}
}
static SKIP_FROM_OBJ: &str = "skip_from_obj";
static SKIP_FONT_WRITE: &str = "skip_font_write";
static SKIP_CONSTRUCTOR: &str = "skip_constructor";
static READ_ARGS: &str = "read_args";
static GENERIC_OFFSET: &str = "generic_offset";
static TAG: &str = "tag";
impl Parse for TableAttrs {
fn parse(input: ParseStream) -> syn::Result<Self> {
let mut this = TableAttrs::default();
let attrs = Attribute::parse_outer(input)
.map_err(|e| syn::Error::new(e.span(), format!("hmm: '{e}'")))?;
for attr in attrs {
let ident = attr.path().get_ident().ok_or_else(|| {
syn::Error::new(
attr.path().span(),
"attr paths should be a single identifier",
)
})?;
if ident == DOC {
this.docs.push(attr);
} else if ident == SKIP_FROM_OBJ {
this.skip_from_obj = Some(attr.path().clone());
} else if ident == SKIP_FONT_WRITE {
this.skip_font_write = Some(attr.path().clone());
} else if ident == SKIP_CONSTRUCTOR {
this.skip_constructor = Some(attr.path().clone());
} else if ident == WRITE_FONTS_ONLY {
this.write_only = Some(attr.path().clone());
} else if ident == READ_ARGS {
this.read_args = Some(Attr::new(ident.clone(), attr.parse_args()?));
} else if ident == GENERIC_OFFSET {
this.generic_offset = Some(Attr::new(ident.clone(), attr.parse_args()?));
} else if ident == TAG {
let tag: syn::LitStr = parse_attr_eq_value(&attr)?;
if let Err(e) = Tag::new_checked(tag.value().as_bytes()) {
return Err(logged_syn_error(tag.span(), format!("invalid tag: '{e}'")));
}
this.tag = Some(Attr::new(ident.clone(), tag))
} else if ident == VALIDATE {
this.validate = Some(Attr::new(ident.clone(), attr.parse_args()?));
} else {
return Err(logged_syn_error(
ident.span(),
format!("unknown table attribute {ident}"),
));
}
}
Ok(this)
}
}
impl Parse for EnumVariantAttrs {
fn parse(input: ParseStream) -> syn::Result<Self> {
let mut this = EnumVariantAttrs::default();
let attrs = Attribute::parse_outer(input)
.map_err(|e| syn::Error::new(e.span(), format!("hmm: '{e}'")))?;
for attr in attrs {
let ident = attr.path().get_ident().ok_or_else(|| {
syn::Error::new(
attr.path().span(),
"attr paths should be a single identifier",
)
})?;
if ident == DOC {
this.docs.push(attr);
} else if ident == DEFAULT {
this.default = Some(attr.path().clone());
} else {
return Err(logged_syn_error(
ident.span(),
format!("unknown field attribute {ident}"),
));
}
}
Ok(this)
}
}
impl Parse for TableReadArgs {
fn parse(input: ParseStream) -> syn::Result<Self> {
let args = Punctuated::<TableReadArg, Token![,]>::parse_separated_nonempty(input)?
.into_iter()
.collect();
Ok(TableReadArgs { args })
}
}
impl Parse for TableReadArg {
fn parse(input: ParseStream) -> syn::Result<Self> {
let ident = input.parse::<syn::Ident>()?;
input.parse::<Token![:]>()?;
let typ = input.parse()?;
Ok(TableReadArg { ident, typ })
}
}
fn resolve_ident<'a>(
known: &'a 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?",
))
}
}
fn resolve_field(
known: &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(())
}
impl Items {
pub(crate) fn sanity_check(&self, phase: Phase) -> syn::Result<()> {
for item in self.iter() {
item.sanity_check(phase)?;
}
Ok(())
}
pub(crate) fn iter(&self) -> impl Iterator<Item = &Item> + '_ {
self.items.values()
}
pub(crate) fn iter_mut(&mut self) -> impl Iterator<Item = &mut Item> + '_ {
self.items.values_mut()
}
pub(crate) fn get(&self, name: &syn::Ident) -> Option<&Item> {
self.items.get(name)
}
pub(crate) fn resolve_pending(&mut self) -> Result<(), syn::Error> {
// We should know what some stuff is now
// In theory we could repeat resolution until we succeed or stop learning
// but I don't think ever need that currently
let known = self.build_type_map();
known.iter().for_each(|(k, v)| trace!("{} => {:?}", k, v));
// Try to resolve everything pending against the known world
for item in self.iter_mut() {
let fields = match item {
Item::Record(item) => &mut item.fields.fields,
Item::Table(item) => &mut item.fields.fields,
_ => continue,
};
for field in fields {
resolve_field(&known, field)?;
}
}
Ok(())
}
// we return a new structure instead of resolving against self because
// resolution involves mutable access to self.
fn build_type_map(&self) -> HashMap<syn::Ident, FieldType> {
self.items
.iter()
.filter_map(|(key, value)| {
let value = match value {
Item::Table(_)
| Item::Record(_)
| Item::Extern(Extern {
typ: ExternType::Record,
..
}) => FieldType::Struct {
typ: value.name().clone(),
},
Item::Flags(_)
| Item::RawEnum(_)
| Item::Extern(Extern {
typ: ExternType::Scalar,
..
}) => FieldType::Scalar {
typ: value.name().clone(),
},
Item::Format(_) | Item::GenericGroup(_) => return None,
};
Some((key.clone(), value))
})
.collect()
}
}
impl Item {
fn name(&self) -> &syn::Ident {
match self {
Item::Table(table) => &table.name,
Item::Record(record) => &record.name,
Item::Format(group) => &group.name,
Item::GenericGroup(group) => &group.name,
Item::RawEnum(item) => &item.name,
Item::Flags(item) => &item.name,
Item::Extern(item) => &item.name,
}
}
fn sanity_check(&self, phase: Phase) -> syn::Result<()> {
match self {
Item::Table(item) => item.sanity_check(phase),
Item::Record(item) => item.sanity_check(phase),
Item::Format(_) => Ok(()),
Item::RawEnum(item) => item.sanity_check(phase),
Item::Flags(_) => Ok(()),
Item::GenericGroup(_) => Ok(()),
Item::Extern(..) => Ok(()),
}
}
}
impl RawEnum {
pub(crate) fn sanity_check(&self, _: Phase) -> syn::Result<()> {
let defaults: Vec<_> = self
.variants
.iter()
.filter_map(|v| v.attrs.default.as_ref().map(|_| &v.name))
.collect();
if defaults.len() > 1 {
return Err(logged_syn_error(
self.name.span(),
format!("multiple defaults {defaults:?}"),
));
}
Ok(())
}
}
impl Parse for CountArg {
fn parse(input: ParseStream) -> syn::Result<Self> {
if input.peek(Token![$]) {
input.parse::<Token![$]>()?;
input.parse().map(Self::Field)
} else {
let int = input.parse::<syn::LitInt>()?;
let digits = int.base10_digits();
if digits.starts_with('-') {
return Err(syn::Error::new(
input.span(),
"negative count is not supported",
));
}
//HACK: we ensure these literals always have an explicit type.
//Is this necessary? no clue.
if int.suffix() != "usize" {
let reparse = format!("{digits}_usize");
syn::parse_str(&reparse).map(Self::Literal)
} else {
Ok(Self::Literal(int))
}
}
}
}
impl Parse for Count {
fn parse(input: ParseStream) -> syn::Result<Self> {
if input.peek(Token![..]) {
input.parse().map(Count::All)
} else if input.peek(syn::Ident) {
// leading ident must be a function
let xform = input.parse()?;
let content;
let _ = parenthesized!(content in input);
let args = Punctuated::<CountArg, Token![,]>::parse_terminated(&content)?
.into_iter()
.collect();
Count::try_from_fancy_stuff(input.span(), xform, args)
} else {
input.parse().map(Self::SingleArg)
}
}
}
impl Parse for CountTransform {
fn parse(input: ParseStream) -> syn::Result<Self> {
let ident = input.parse::<syn::Ident>()?;
CountTransform::from_str(&ident.to_string())
.map_err(|err| syn::Error::new(ident.span(), err))
}
}
static TRANSFORM_IDENTS: &[(CountTransform, &str)] = &[
(CountTransform::Sub, "subtract"),
(CountTransform::Add, "add"),
(CountTransform::AddMul, "add_multiply"),
(CountTransform::MulAdd, "multiply_add"),
(CountTransform::Half, "half"),
(CountTransform::DeltaValueCount, "delta_value_count"),
(CountTransform::DeltaSetIndexData, "delta_set_index_data"),
(CountTransform::TupleLen, "tuple_len"),
(
CountTransform::ItemVariationDataLen,
"item_variation_data_len",
),
(CountTransform::BitmapLen, "bitmap_len"),
(CountTransform::MaxValueBitmapLen, "max_value_bitmap_len"),
(CountTransform::SubAddTwo, "subtract_add_two"),
(CountTransform::TryInto, "try_into"),
];
impl FromStr for CountTransform {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
TRANSFORM_IDENTS
.iter()
.find_map(|(var, ident)| (*ident == s).then_some(*var))
.ok_or_else(|| {
format!(
"invalid transform, expected one of {}",
TRANSFORM_IDENTS
.iter()
.map(|(_, ident)| format!("'{ident}'"))
.collect::<Vec<_>>()
.join(", ")
)
})
}
}
impl CountTransform {
fn arg_count(self) -> usize {
match self {
CountTransform::Sub => 2,
CountTransform::Add => 2,
CountTransform::AddMul => 3,
CountTransform::MulAdd => 3,
CountTransform::Half => 1,
CountTransform::DeltaValueCount => 3,
CountTransform::DeltaSetIndexData => 2,
CountTransform::TupleLen => 3,
CountTransform::ItemVariationDataLen => 3,
CountTransform::BitmapLen => 1,
CountTransform::MaxValueBitmapLen => 1,
CountTransform::SubAddTwo => 2,
CountTransform::TryInto => 1,
}
}
}
impl ToTokens for CountArg {
fn to_tokens(&self, tokens: &mut TokenStream) {
match self {
CountArg::Field(fld) => fld.to_tokens(tokens),
CountArg::Literal(lit) => lit.to_tokens(tokens),
}
}
}
impl Parse for VersionSpec {
fn parse(input: ParseStream) -> syn::Result<Self> {
let fork = input.fork();
if fork.parse::<syn::LitInt>().is_ok() && fork.is_empty() {
let major = input.parse::<syn::LitInt>()?;
let major: u16 = major.base10_parse()?;
return Ok(VersionSpec { major, minor: None });
}
let version = input.parse::<syn::LitFloat>()?;
let Some((major, minor)) = version.base10_digits().split_once('.') else {
return Err(syn::Error::new(version.span(), "version should be single integer major or major.minor (e.g. '1', '4', '1.1', '2.5')"));
};
let major = major.parse::<u16>();
let minor = minor.parse::<u16>();
major
.and_then(|major| {
minor.map(|minor| VersionSpec {
major,
minor: Some(minor),
})
})
.map_err(|_| syn::Error::new(version.span(), "could not parse major/minor version"))
}
}
impl ToTokens for VersionSpec {
fn to_tokens(&self, tokens: &mut TokenStream) {
let major = &self.major;
if let Some(minor) = &self.minor {
tokens.extend(quote!( (#major, #minor) ));
} else {
major.to_tokens(tokens);
}
}
}
impl Parse for CustomCompile {
fn parse(input: ParseStream) -> syn::Result<Self> {
let fork = input.fork();
if fork.parse::<kw::skip>().is_ok() && fork.is_empty() {
input.parse::<kw::skip>()?;
return Ok(Self::Skip);
}
input.parse().map(Self::Expr)
}
}
impl Parse for FieldValidation {
fn parse(input: ParseStream) -> syn::Result<Self> {
let fork = input.fork();
if fork.parse::<kw::skip>().is_ok() && fork.is_empty() {
input.parse::<kw::skip>()?;
return Ok(Self::Skip);
}
input.parse().map(Self::Custom)
}
}
impl Count {
fn try_from_fancy_stuff(
err_span: Span,
xform: CountTransform,
args: Vec<CountArg>,
) -> Result<Self, syn::Error> {
let expected_arg_count = xform.arg_count();
if args.len() != expected_arg_count {
return Err(syn::Error::new(
err_span,
format!("expected {expected_arg_count} arguments"),
));
}
Ok(Count::Complicated { args, xform })
}
pub(crate) fn single_field(&self) -> Option<&syn::Ident> {
if let Count::SingleArg(CountArg::Field(ident)) = self {
Some(ident)
} else {
None
}
}
pub(crate) fn iter_referenced_fields(&self) -> impl Iterator<Item = &syn::Ident> {
let (one, two) = match self {
Self::SingleArg(CountArg::Field(ident)) => (Some(ident), None),
Self::Complicated { args, .. } => (
None,
Some(args.iter().filter_map(|arg| match arg {
CountArg::Field(ident) => Some(ident),
_ => None,
})),
),
_ => (None, None),
};
// a trick so we return the exact sample iterator type from both match arms
one.into_iter().chain(two.into_iter().flatten())
}
pub(crate) fn count_expr(&self) -> TokenStream {
match self {
Count::All(_) => unreachable!("'all' count handled separately"),
Count::SingleArg(CountArg::Field(arg)) => quote!(#arg as usize),
Count::SingleArg(CountArg::Literal(arg)) => quote!(#arg),
Count::Complicated { args, xform } => match (xform, args.as_slice()) {
(CountTransform::Sub, [a, b]) => {
quote!(transforms::subtract(#a, #b))
}
(CountTransform::Add, [a, b]) => {
quote!(transforms::add(#a, #b))
}
(CountTransform::AddMul, [a, b, c]) => {
quote!(transforms::add_multiply(#a, #b, #c))
}
(CountTransform::MulAdd, [a, b, c]) => {
quote!(transforms::multiply_add(#a, #b, #c))
}
(CountTransform::Half, [a]) => {
quote!(transforms::half(#a))
}
(CountTransform::DeltaSetIndexData, [a, b]) => {
quote!(EntryFormat::map_size(#a, #b))
}
(CountTransform::DeltaValueCount, [a, b, c]) => {
quote!(DeltaFormat::value_count(#a, #b, #c))
}
(CountTransform::TupleLen, [a, b, c]) => {
quote!(TupleIndex::tuple_len(#a, #b, #c))
}
(CountTransform::ItemVariationDataLen, [a, b, c]) => {
quote!(ItemVariationData::delta_sets_len(#a, #b, #c))
}
(CountTransform::BitmapLen, [a]) => {
quote!(transforms::bitmap_len(#a))
}
(CountTransform::MaxValueBitmapLen, [a]) => {
quote!(transforms::max_value_bitmap_len(#a))
}
(CountTransform::SubAddTwo, [a, b]) => {
quote!(transforms::subtract_add_two(#a, #b))
}
(CountTransform::TryInto, [a]) => {
quote!(usize::try_from(#a).unwrap_or_default())
}
_ => unreachable!("validated before now"),
},
}
}
}
impl Parse for InlineExpr {
fn parse(input: ParseStream) -> syn::Result<Self> {
fn parse_inline_expr(tokens: TokenStream) -> syn::Result<InlineExpr> {
let span = tokens.span();
let s = tokens.to_string();
let mut idents = Vec::new();
let find_dollar_idents = regex::Regex::new(r"(\$) (\w+)").unwrap();
for ident in find_dollar_idents.captures_iter(&s) {
let text = ident.get(2).unwrap().as_str();
let ident = syn::parse_str::<syn::Ident>(text).map_err(|_| {
syn::Error::new(tokens.span(), format!("invalid ident '{text}'"))
})?;
idents.push(ident);
}
let expr: syn::Expr = if idents.is_empty() {
syn::parse2(tokens)
} else {
let new_source = find_dollar_idents.replace_all(&s, "$2");
syn::parse_str(&new_source)
}
.map_err(|_| syn::Error::new(span, "failed to parse expression"))?;
let compile_expr = (!idents.is_empty())
.then(|| {
let new_source =
find_dollar_idents.replace_all(&s, replace_field_with_compile_field);
syn::parse_str::<syn::Expr>(&new_source)
})
.transpose()?
.map(Box::new);
idents.sort_unstable();
idents.dedup();
Ok(InlineExpr {
expr: expr.into(),
compile_expr,
referenced_fields: idents,
})
}
let tokens: TokenStream = input.parse()?;
parse_inline_expr(tokens)
}
}
fn replace_field_with_compile_field(captures: &Captures) -> String {
let ident = captures.get(2).unwrap().as_str();
let ident = crate::fields::remove_offset_from_field_name(ident);
format!("&self.{ident}")
}
impl NeededWhen {
fn at_parsetime(&self) -> bool {
matches!(self, NeededWhen::Parse | NeededWhen::Both)
}
fn at_runtime(&self) -> bool {
matches!(self, NeededWhen::Runtime | NeededWhen::Both)
}
}
impl ReferencedFields {
pub(crate) fn needs_at_parsetime(&self, ident: &syn::Ident) -> bool {
self.0
.get(ident)
.map(NeededWhen::at_parsetime)
.unwrap_or(false)
}
pub(crate) fn needs_at_runtime(&self, ident: &syn::Ident) -> bool {
self.0
.get(ident)
.map(NeededWhen::at_runtime)
.unwrap_or(false)
}
}
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 inner.deref() {
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>)
}
}
}
}
impl FromIterator<(syn::Ident, NeededWhen)> for ReferencedFields {
fn from_iter<T: IntoIterator<Item = (syn::Ident, NeededWhen)>>(iter: T) -> Self {
let mut map = HashMap::new();
for (key, new_val) in iter {
let value = map.entry(key).or_insert(new_val);
// if a value is referenced by multiple fields, we combine them
*value = match (*value, new_val) {
(NeededWhen::Parse, NeededWhen::Parse) => NeededWhen::Parse,
(NeededWhen::Runtime, NeededWhen::Runtime) => NeededWhen::Runtime,
_ => NeededWhen::Both,
};
}
Self(map)
}
}
fn parse_attr_eq_value<T: Parse>(attr: &syn::Attribute) -> syn::Result<T> {
/// the tokens '= T' where 'T' is any `Parse`
struct EqualsThing<T>(T);
impl<T: Parse> Parse for EqualsThing<T> {
fn parse(input: ParseStream) -> syn::Result<Self> {
input.parse::<Token![=]>()?;
input.parse().map(EqualsThing)
}
}
let tokens = attr.meta.require_name_value()?.value.to_token_stream();
syn::parse2::<T>(tokens).map_err(|err| syn::Error::new(attr.meta.span(), err.to_string()))
}
fn parse_if_flag(attr: &syn::Attribute) -> syn::Result<Condition> {
struct IfFlag(syn::Ident, syn::Path);
impl Parse for IfFlag {
fn parse(input: ParseStream) -> syn::Result<Self> {
input.parse::<Token![$]>()?;
let ident = input.parse::<syn::Ident>()?;
input.parse::<Token![,]>()?;
let path = input.parse::<syn::Path>()?;
Ok(IfFlag(ident, path))
}
}
attr.parse_args::<IfFlag>()
.map(|IfFlag(field, flag)| Condition::IfFlag { field, flag })
.map_err(|e| {
syn::Error::new(
e.span(),
format!("expected #[if_flag($field_name, FlagType::SOME_FLAG)]: '{e}'"),
)
})
}
impl Parse for IfArg {
fn parse(input: ParseStream) -> syn::Result<Self> {
if input.peek(Token![$]) {
input.parse::<Token![$]>()?;
input.parse().map(Self::Field)
} else {
input.parse().map(Self::Path)
}
}
}
impl Parse for IfTransform {
fn parse(input: ParseStream) -> syn::Result<Self> {
let ident = input.parse::<syn::Ident>()?;
let content;
let _ = parenthesized!(content in input);
let args: Vec<IfArg> = Punctuated::<IfArg, Token![,]>::parse_terminated(&content)?
.into_iter()
.collect();
IfTransform::from_args(&ident.to_string(), args)
.map_err(|err| syn::Error::new(ident.span(), err))
}
}
impl IfTransform {
fn from_args(s: &str, args: Vec<IfArg>) -> Result<Self, String> {
match s {
"any_flag" => Self::any_flag(args),
_ => Err(format!("invalid if_cond transform function: {}", s)),
}
}
fn any_flag(args: Vec<IfArg>) -> Result<Self, String> {
let Some(IfArg::Field(field)) = args.first() else {
return Err("First argument to any_flag must be a field name.".to_string());
};
let mut flags: Vec<syn::Path> = vec![];
for arg in args.iter().skip(1) {
let IfArg::Path(flag) = arg else {
return Err(
"Arguments after the first argument to any_flag must be a flag names."
.to_string(),
);
};
flags.push(flag.clone());
}
Ok(IfTransform::AnyFlag(field.clone(), flags))
}
pub(crate) fn input_field(&self) -> Vec<syn::Ident> {
match self {
IfTransform::AnyFlag(field, _) => vec![field.clone()],
}
}
}
fn parse_if_cond(attr: &syn::Attribute) -> syn::Result<Condition> {
struct If(IfTransform);
impl Parse for If {
fn parse(input: ParseStream) -> syn::Result<Self> {
// leading ident must be a function
if !input.peek(syn::Ident) {
return Err(syn::Error::new(
input.span(),
"Non identifier argument to if_cond().",
));
}
let xform: IfTransform = input.parse()?;
Ok(If(xform))
}
}
attr.parse_args::<If>()
.map(|If(xform)| Condition::IfCond { xform })
.map_err(|e| {
syn::Error::new(
e.span(),
format!("expected #[if_cond(condition_function(...))]: '{e}'"),
)
})
}
fn validate_ident(ident: &syn::Ident, expected: &[&str], error: &str) -> Result<(), syn::Error> {
if !expected.iter().any(|exp| ident == exp) {
return Err(logged_syn_error(ident.span(), error));
}
Ok(())
}
fn get_optional_docs(input: ParseStream) -> Result<Vec<syn::Attribute>, syn::Error> {
let mut result = Vec::new();
while input.lookahead1().peek(Token![#]) {
result.extend(Attribute::parse_outer(input)?);
}
for attr in &result {
if !attr.path().is_ident("doc") {
return Err(logged_syn_error(attr.span(), "expected doc comment"));
}
}
Ok(result)
}
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")),
}
}
pub(crate) fn logged_syn_error<T: Display>(span: Span, message: T) -> syn::Error {
debug!("{}", Backtrace::capture());
syn::Error::new(span, message)
}
#[cfg(test)]
mod tests {
use quote::ToTokens;
use syn::parse_quote;
use super::*;
#[test]
fn parse_inline_expr_simple() {
let s = "div_me($hi * 5)";
let inline = syn::parse_str::<InlineExpr>(s).unwrap();
assert_eq!(inline.referenced_fields.len(), 1);
assert_eq!(
inline.expr.into_token_stream().to_string(),
"div_me (hi * 5)"
);
}
#[test]
fn parse_inline_expr_dedup() {
let s = "div_me($hi * 5 + $hi)";
let inline = syn::parse_str::<InlineExpr>(s).unwrap();
assert_eq!(inline.referenced_fields.len(), 1);
assert_eq!(
inline.expr.into_token_stream().to_string(),
"div_me (hi * 5 + hi)"
);
}
fn make_path_seg(s: &str) -> syn::PathSegment {
let path = syn::parse_str::<syn::Path>(s).unwrap();
path.segments.last().unwrap().clone()
}
fn parse_count(s: &str) -> Result<Count, syn::Error> {
syn::parse_str(s)
}
#[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());
}
#[test]
fn test_count_attr() {
assert!(matches!(
parse_count("$hello"),
Ok(Count::SingleArg(CountArg::Field(_)))
));
assert!(matches!(
parse_count("5"),
Ok(Count::SingleArg(CountArg::Literal(_)))
));
assert!(parse_count("hello").is_err());
assert!(parse_count("$5").is_err());
assert!(parse_count("5 - 2 as usize").is_err());
assert!(matches!(
parse_count("subtract(5, 2)"),
Ok(Count::Complicated {
xform: CountTransform::Sub,
..
})
));
assert!(parse_count("sub(5, 2)").is_err());
assert!(parse_count("subtract(5)").is_err());
}
#[test]
fn parse_version() {
fn parse(s: &str) -> Result<VersionSpec, syn::Error> {
syn::parse_str(s)
}
assert!(parse("32").unwrap().minor.is_none());
assert_eq!(parse("32").unwrap().major, 32);
assert!(parse("ab").is_err());
assert!(parse("MajorMinor::VERSION_1_0").is_err());
assert!(parse("1.3").unwrap().minor.is_some());
assert!(parse("1.2.3").is_err());
assert!(parse("1.'b'").is_err());
}
fn parse_format_group(s: &str) -> Result<TableFormat, syn::Error> {
syn::parse_str(s)
}
#[test]
fn parse_format_group_basic() {
let s = "format u16 MyThing {
FormatOne(SomeTable),
}";
let parsed = parse_format_group(s).unwrap();
assert_eq!(parsed.format.to_string(), "u16");
assert!(parsed.format_offset.is_none());
}
// just a sanity check
#[test]
fn parse_format_group_not_a_known_format() {
let s = "format Fixed MyThing {
FormatOne(SomeTable),
}";
assert!(parse_format_group(s).is_err());
}
#[test]
fn parse_format_group_with_format_offset() {
let s = "format u16@4 MyThing {
FormatOne(SomeTable),
}";
let parsed = parse_format_group(s).unwrap();
assert!(parsed.format_offset.is_some());
assert_eq!(
parsed.format_offset.unwrap().base10_parse::<u16>().ok(),
Some(4)
);
}
#[test]
#[should_panic(expected = "must be an unsigned")]
fn parse_format_group_with_negative_format_offset() {
let s = "format u16@-4 MyThing {
FormatOne(SomeTable),
}";
parse_format_group(s).unwrap();
}
#[test]
fn parse_tag_attr() {
let input: Table = parse_quote! {
#[tag = "hilo"]
table HiMom {}
};
assert_eq!(input.attrs.tag.unwrap().attr.value(), "hilo");
}
}