blob: cd0ab76fa03f790203a76a0f5eec013289ae51bf [file] [edit]
//! Generating schema files from our internal types
use std::collections::{BTreeMap, HashSet};
use serde::{Deserialize, Serialize};
use crate::parsing::{
self, BitFlags, Field as RawField, FieldType, Item, Items, RawEnum as RawRawRnum, RawVariant,
Record as RawRecord, Table as RawTable, TableFormat, TableReadArgs,
};
#[derive(Clone, Debug, Deserialize, Serialize)]
struct Type(String);
#[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(tag = "type")]
enum SchemaItem {
Table(Table),
Record(Record),
Flags(Flags),
RawEnum(RawEnum),
}
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
struct Table {
name: String,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
sfnt_tag: Option<String>,
short_doc: String,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
long_doc: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
doc_link: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
input_args: Option<Vec<InputArgument>>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
version: Option<String>,
#[serde(skip_serializing_if = "Vec::is_empty")]
#[serde(default)]
formats: Vec<FormatTable>,
#[serde(skip_serializing_if = "Vec::is_empty")]
#[serde(default)]
fields: Vec<Field>,
}
#[derive(Clone, Debug, Default, Deserialize, Serialize)]
struct Record {
name: String,
short_doc: String,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
input_args: Option<Vec<InputArgument>>,
#[serde(skip_serializing_if = "Vec::is_empty")]
#[serde(default)]
fields: Vec<Field>,
}
#[derive(Clone, Debug, Deserialize, Serialize)]
struct InputArgument {
name: String,
#[serde(rename = "type")]
type_: Type,
}
#[derive(Clone, Debug, Deserialize, Serialize)]
enum OutputArgument {
Field(String),
Literal(String), //FIXME: what type here?
}
#[derive(Clone, Debug, Deserialize, Serialize)]
struct FormatTable {
format_type: Type,
format: i64,
table: Table,
}
#[derive(Clone, Debug, Deserialize, Serialize)]
struct Field {
name: String,
#[serde(rename = "type")]
type_: Type,
doc: String,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
offset: Option<OffsetInfo>,
/// Presence of this field indicates this is an array.
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
count: Option<CountInfo>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
available: Option<AvailableInfo>,
hidden: bool,
}
#[derive(Clone, Debug, Deserialize, Serialize)]
struct OffsetInfo {
nullable: bool,
target: OffsetTarget,
}
#[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(tag = "target_kind")]
enum OffsetTarget {
Table(OffsetTargetType),
Array(OffsetTargetType),
Map(OffsetTargetMap),
}
#[derive(Clone, Debug, Deserialize, Serialize)]
struct OffsetTargetType {
target: Type,
arguments: Vec<OutputArgument>,
}
#[derive(Clone, Debug, Deserialize, Serialize)]
struct OffsetTargetMap {
argument: InputArgument,
target_map: BTreeMap<String, OffsetTargetType>,
}
#[derive(Clone, Debug, Deserialize, Serialize)]
enum CountInfo {
All,
Computed(ComputedCount),
}
#[derive(Clone, Debug, Deserialize, Serialize)]
enum CountArg {
Literal(u32),
Field(String),
}
#[derive(Clone, Debug, Deserialize, Serialize)]
struct Flags {
name: String,
short_doc: String,
// must be an integer
raw_type: Type,
values: Vec<NamedValue>,
}
//HMM: this is structurally identical to the above, it just has different semantics
#[derive(Clone, Debug, Deserialize, Serialize)]
struct RawEnum {
name: String,
short_doc: String,
// must be an integer
raw_type: Type,
values: Vec<NamedValue>,
}
// shared between enums and flags
#[derive(Clone, Debug, Deserialize, Serialize)]
struct NamedValue {
name: String,
doc: String,
value: u32,
}
impl From<parsing::CountArg> for CountArg {
fn from(src: parsing::CountArg) -> Self {
match src {
parsing::CountArg::Field(ident) => CountArg::Field(ident.to_string()),
parsing::CountArg::Literal(lit) => CountArg::Literal(lit.base10_parse().unwrap()),
}
}
}
#[derive(Clone, Debug, Deserialize, Serialize)]
struct ComputedCount {
/// named fields of the parent
inputs: Vec<CountArg>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
transform: Option<parsing::CountTransform>,
}
impl Serialize for parsing::CountTransform {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
serializer.serialize_str(self.as_str())
}
}
impl<'de> Deserialize<'de> for parsing::CountTransform {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let s = String::deserialize(deserializer)?;
s.parse().map_err(serde::de::Error::custom)
}
}
#[derive(Clone, Debug, Deserialize, Serialize)]
struct AvailableInfo {
major: u32,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
minor: Option<u32>,
}
pub(crate) fn generate(items: &Items) -> Result<String, syn::Error> {
let mut done = HashSet::new();
let mut out = Vec::new();
// first we do groups
for item in items.iter().filter_map(|item| match item {
crate::parsing::Item::Format(group) => Some(group),
_ => None,
}) {
done.insert(item.name.clone());
let table = generate_table_group(item, items, &mut done);
out.push(SchemaItem::Table(table));
}
out.extend(items.iter().filter_map(|item| match item {
Item::Table(table) if done.insert(table.raw_name().clone()) => {
Some(SchemaItem::Table(generate_table(table)))
}
Item::Record(record) => Some(SchemaItem::Record(generate_record(record))),
Item::RawEnum(raw) => Some(SchemaItem::RawEnum(generate_raw_enum(raw))),
Item::Flags(raw) => Some(SchemaItem::Flags(generate_flags(raw))),
//Item::GenericGroup(_) => todo!(),
//Item::Extern(_) => todo!(),
_ => None,
}));
Ok(serde_yaml::to_string(&out).unwrap())
}
fn generate_table_group(
item: &TableFormat,
items: &Items,
done: &mut HashSet<syn::Ident>,
) -> Table {
let format_type = Type(item.format.to_string());
let sfnt_tag = None;
let short_doc = doc_attrs_to_string(&item.attrs.docs);
let formats = item
.variants
.iter()
.map(|variant| {
let Some(Item::Table(table)) = items.get(variant.type_name()) else {
panic!("missing table '{}'", variant.type_name());
};
assert!(done.insert(table.raw_name().clone())); // should never already be visited
let format = table
.fields
.iter()
.find_map(|fld| {
fld.attrs
.format
.as_deref()
.map(|format| format.base10_parse::<i64>().unwrap())
})
.expect("missing format field");
let table = generate_table(table);
FormatTable {
format_type: format_type.clone(),
format,
table,
}
})
.collect();
Table {
name: item.name.to_string(),
sfnt_tag,
short_doc,
formats,
..Default::default()
}
}
fn generate_table(item: &RawTable) -> Table {
let name = item.raw_name().to_string();
let sfnt_tag = None;
let short_doc = doc_attrs_to_string(&item.attrs.docs);
let input_args = item.attrs.read_args.as_deref().map(generate_input_args);
let version = item
.fields
.iter()
.find_map(|fld| fld.attrs.version.is_some().then(|| fld.name.to_string()));
let fields = item.fields.iter().map(generate_field).collect();
Table {
name,
sfnt_tag,
short_doc,
input_args,
version,
fields,
long_doc: None,
doc_link: None,
formats: Default::default(),
}
}
fn generate_record(item: &RawRecord) -> Record {
Record {
name: item.name.to_string(),
short_doc: doc_attrs_to_string(&item.attrs.docs),
input_args: item.attrs.read_args.as_deref().map(generate_input_args),
fields: item.fields.iter().map(generate_field).collect(),
}
}
fn generate_input_args(args: &TableReadArgs) -> Vec<InputArgument> {
args.args
.iter()
.map(|arg| InputArgument {
name: arg.ident.to_string(),
type_: Type(arg.typ.to_string()),
})
.collect()
}
fn generate_raw_enum(raw: &RawRawRnum) -> RawEnum {
RawEnum {
name: raw.name.to_string(),
short_doc: doc_attrs_to_string(&raw.docs),
raw_type: Type(raw.typ.to_string()),
values: raw.variants.iter().map(generate_value).collect(),
}
}
fn generate_flags(raw: &BitFlags) -> Flags {
Flags {
name: raw.name.to_string(),
short_doc: doc_attrs_to_string(&raw.docs),
raw_type: Type(raw.typ.to_string()),
values: raw.variants.iter().map(generate_value).collect(),
}
}
fn generate_value(from: &RawVariant) -> NamedValue {
NamedValue {
name: from.name.to_string(),
doc: doc_attrs_to_string(&from.docs),
value: from.value.base10_parse().unwrap(),
}
}
fn generate_field(field: &RawField) -> Field {
let name = field.name.to_string();
let type_ = match &field.typ {
FieldType::Offset { typ, .. } | FieldType::Scalar { typ } | FieldType::Struct { typ } => {
Type(typ.to_string())
}
FieldType::Array { inner_typ } => Type(inner_typ.cooked_type_tokens().to_string()),
FieldType::ComputedArray(arr) | FieldType::VarLenArray(arr) => {
Type(arr.raw_inner_type().to_string())
}
FieldType::PendingResolution { .. } => panic!("resolved before now"),
};
let doc = doc_attrs_to_string(&field.attrs.docs);
let offset = match &field.typ {
FieldType::Offset { target, .. } => {
let arguments = field
.attrs
.read_offset_args
.as_deref()
.map(|args| {
args.inputs
.iter()
.map(|inp| OutputArgument::Field(inp.to_string()))
.collect()
})
.unwrap_or_default();
let target = match target {
crate::parsing::OffsetTarget::Table(ident) => {
OffsetTarget::Table(OffsetTargetType {
target: Type(ident.to_string()),
arguments,
})
}
crate::parsing::OffsetTarget::Array(inner) => {
OffsetTarget::Array(OffsetTargetType {
target: Type(inner.cooked_type_tokens().to_string()),
arguments,
})
}
};
Some(OffsetInfo {
nullable: field.attrs.nullable.is_some(),
target,
})
}
_ => None,
};
let count = field.attrs.count.as_deref().map(|count| match count {
parsing::Count::All(_) => CountInfo::All,
parsing::Count::SingleArg(arg) => CountInfo::Computed(ComputedCount {
inputs: vec![arg.clone().into()],
transform: None,
}),
parsing::Count::Complicated { args, xform } => CountInfo::Computed(ComputedCount {
inputs: args.iter().cloned().map(Into::into).collect(),
transform: Some(*xform),
}),
});
let available = field.attrs.available.as_deref().map(|avail| AvailableInfo {
major: avail.major.base10_parse().unwrap(),
minor: avail.minor.as_ref().map(|v| v.base10_parse().unwrap()),
});
let hidden = false;
Field {
name,
type_,
doc,
offset,
count,
available,
hidden,
}
}
fn doc_attrs_to_string(docs: &[syn::Attribute]) -> String {
let mut out = String::new();
for doc in docs {
let as_str = doc.tokens.to_string();
let as_str = as_str.trim_matches(['=', ' ', '"'].as_slice());
if !out.is_empty() {
out.push(' ');
}
out.push_str(as_str)
}
out
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn roundtrip_enum() {
let an_enum = RawEnum {
name: "Hello".into(),
short_doc: "I am a useful type that performs many important tasks".into(),
raw_type: Type("u32".into()),
values: vec![NamedValue {
name: "INFLAMMIBLE".into(),
doc: "Ugh I always forget which is which".into(),
value: 1,
}],
};
let to_str = serde_yaml::to_string(&an_enum).unwrap();
let _back_to_typeland: RawEnum = serde_yaml::from_str(&to_str).unwrap();
}
}