blob: 3f2ffeae6820868a4d0a598024e36ccf94cef685 [file] [edit]
//! raw parsing code
mod attrs;
mod fields;
pub(crate) use attrs::*;
pub(crate) use fields::*;
use std::{backtrace::Backtrace, collections::HashMap, fmt::Display};
use indexmap::IndexMap;
use log::{debug, trace};
use proc_macro2::{Span, TokenStream};
use quote::quote;
use syn::{
braced, parenthesized,
parse::{Parse, ParseStream},
punctuated::Punctuated,
spanned::Spanned,
Attribute, Token,
};
use crate::Phase;
#[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, Clone)]
pub(crate) struct Table {
pub(crate) attrs: TableAttrs,
pub(crate) name: syn::Ident,
pub(crate) fields: Fields,
}
impl Table {
// here for visibility reasons
pub(crate) fn raw_name(&self) -> &syn::Ident {
&self.name
}
/// Returns the table's format value if it has a `#[format(N)]` field.
pub(crate) fn format_value_and_width(&self) -> Option<(u32, u8)> {
let fld = self.fields.iter().find(|fld| fld.attrs.format.is_some())?;
let format = fld.attrs.format.as_ref().unwrap().base10_parse().ok()?;
let fld_tokens = fld.typ.cooked_type_tokens();
let width = if fld_tokens == "u8" {
1
} else if fld_tokens == "u16" {
2
} else {
panic!("only expect format fields to be u8 or u16");
};
Some((format, width))
}
}
#[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,
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,
}
impl FormatVariant {
pub(crate) fn type_name(&self) -> &syn::Ident {
&self.typ
}
}
/// 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,
}
/// 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!(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()?;
let content;
let _ = braced!(content in input);
let variants = Punctuated::<GroupVariant, Token![,]>::parse_terminated(&content)?
.into_iter()
.collect();
Ok(GenericGroup {
attrs,
name,
inner_type,
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 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 })
}
}
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.iter_mut() {
fields::resolve_field(&known, field)?;
}
// Mark fields as validated_at_parse if they have a known, fixed size
// and appear before any variable-length fields (arrays).
// Arrays return Some(empty token), so we stop when we see one.
for field in fields
.iter_mut()
.take_while(|fld| fld.known_min_size_stmt().is_some_and(|t| !t.is_empty()))
{
field.validated_at_parse = true;
}
}
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(()),
}
}
}
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)
}
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 parse_count(s: &str) -> Result<Count, syn::Error> {
syn::parse_str(s)
}
#[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");
}
}