blob: 170b8063248f557f9363c5f498879a060b622529 [file] [log] [blame] [edit]
//! Generating types from the opentype spec
use std::backtrace::Backtrace;
use log::debug;
use proc_macro2::TokenStream;
use quote::quote;
mod error;
mod fields;
mod flags_enums;
mod formatting;
mod parsing;
mod record;
mod table;
use parsing::{Item, Items, Phase};
pub use error::ErrorReport;
/// Codegeneration mode.
#[derive(Debug, Clone, Copy, serde::Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum Mode {
/// Generate parsing code
Parse,
/// Generate compilation code
Compile,
}
pub fn generate_code(code_str: &str, mode: Mode) -> Result<String, syn::Error> {
// Generation is done in phases (https://github.com/googlefonts/fontations/issues/71):
// 1. Parse
debug!("Parse (mode {:?})", mode);
// This is the one step where we can't readily intercept the error with logged_syn_error
let mut items: Items = syn::parse_str(code_str).inspect_err(|_| {
debug!("{}", Backtrace::capture());
})?;
items.sanity_check(Phase::Parse)?;
// 2. Contemplate (semantic analysis)
debug!("Analyze (mode {:?})", mode);
items.resolve_pending()?;
items.sanity_check(Phase::Analysis)?;
// 3. Generate
debug!("Generate (mode {:?})", mode);
let tables = match &mode {
Mode::Parse => generate_parse_module(&items),
Mode::Compile => generate_compile_module(&items),
}?;
// 4. Touchup
debug!("Touchup (mode {:?})", mode);
let source_str = formatting::format(tables)?;
Ok(format!(
"\
// THIS FILE IS AUTOGENERATED.\n\
// Any changes to this file will be overwritten.\n\
// For more information about how codegen works, see font-codegen/README.md\n\n\
{source_str}",
))
}
pub(crate) fn generate_parse_module(items: &Items) -> Result<proc_macro2::TokenStream, syn::Error> {
let mut code = Vec::new();
for item in items.iter() {
let item_code = match item {
Item::Record(item) => record::generate(item, items)?,
Item::Table(item) => table::generate(item)?,
Item::GenericGroup(item) => table::generate_group(item)?,
Item::Format(item) => table::generate_format_group(item, items)?,
Item::RawEnum(item) => flags_enums::generate_raw_enum(item),
Item::Flags(item) => flags_enums::generate_flags(item),
Item::Extern(..) => Default::default(),
};
code.push(item_code);
}
Ok(quote! {
#[allow(unused_imports)]
use crate::codegen_prelude::*;
#(#code)*
})
}
pub(crate) fn generate_compile_module(
items: &Items,
) -> Result<proc_macro2::TokenStream, syn::Error> {
let code = items
.iter()
.map(|item| match item {
Item::Record(item) => record::generate_compile(item, &items.parse_module_path),
Item::Table(item) => table::generate_compile(item, &items.parse_module_path),
Item::GenericGroup(item) => {
table::generate_group_compile(item, &items.parse_module_path)
}
Item::Format(item) => table::generate_format_compile(item, items),
Item::RawEnum(item) => Ok(flags_enums::generate_raw_enum_compile(item)),
Item::Flags(item) => Ok(flags_enums::generate_flags_compile(item)),
Item::Extern(..) => Ok(TokenStream::new()),
})
.collect::<Result<Vec<_>, _>>()?;
let import_from_parse_mod = items.iter().filter_map(|item| match item {
Item::Flags(item) => Some(&item.name),
Item::RawEnum(item) => Some(&item.name),
_ => None,
});
let parse_mod_path = &items.parse_module_path;
Ok(quote! {
#[allow(unused_imports)]
use crate::codegen_prelude::*;
pub use #parse_mod_path::{ #( #import_from_parse_mod, )* };
#( #code )*
})
}
impl std::str::FromStr for Mode {
type Err = miette::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"parse" => Ok(Self::Parse),
"compile" => Ok(Self::Compile),
other => Err(miette::Error::msg(format!(
"expected one of 'parse' or 'compile' (found {other})"
))),
}
}
}