use crate::type_renames::get_type_rename;
use css_value_definition_parser::*;
use heck::{ToPascalCase, ToSnakeCase};
use itertools::Itertools;
use proc_macro2::{Punct, Spacing, TokenStream};
use quote::{format_ident, quote};
use std::ops::{Deref, Range};
use syn::{Error, Generics, Ident, Visibility, parse_quote};
pub fn pluralize(str: String) -> String {
if str.ends_with("s") { str.clone() } else { format!("{str}s") }
}
pub fn keyword_to_pascal(s: &str) -> String {
let pascal = s.to_lowercase().to_pascal_case();
if s.starts_with('-') { format!("_{pascal}") } else { pascal }
}
fn collision_suffix(def: &Def) -> Option<&'static str> {
match def {
Def::Combinator(children, DefCombinatorStyle::Ordered) => {
if children.iter().any(|c| matches!(c, Def::Combinator(_, DefCombinatorStyle::AllMustOccur))) {
Some("AnyOrder")
} else {
None
}
}
_ => None,
}
}
pub trait DefExt {
fn single_ident(ident: &Ident) -> Ident;
fn keyword_ident(ident: &Ident) -> Ident;
fn options_ident(ident: &Ident) -> Ident;
fn should_skip_visit(&self) -> bool;
fn type_attributes(&self, derives_parse: bool, derives_visitable: bool) -> TokenStream;
fn is_all_keywords(&self) -> bool;
fn get_generics(&self) -> Generics;
fn gather_keywords(&self) -> Vec<&Self>;
fn rewrite_options_type(&self, replacement: &TokenStream) -> TokenStream;
fn generate_additional_types(&self, vis: &Visibility, ident: &Ident, generics: &Generics) -> TokenStream;
}
pub trait DefTypeExt {
fn get_generics(&self) -> Generics;
}
pub trait GenerateDefinition {
fn generate_definition(
&self,
vis: &Visibility,
ident: &Ident,
generics: &Generics,
derives_parse: bool,
derives_visitable: bool,
) -> TokenStream;
}
pub trait ToFieldName {
fn to_variant_name(&self, size_hint: usize) -> Ident;
fn to_member_name(&self, size_hint: usize) -> Ident {
format_ident!("{}", self.to_variant_name(size_hint).to_string().to_snake_case())
}
}
pub trait ToType {
fn to_type(&self) -> TokenStream {
let types = self.to_types();
if types.len() == 1 {
quote! { #(#types)* }
} else {
quote! { (#(#types,)*) }
}
}
fn to_types(&self) -> Vec<TokenStream>;
}
impl ToFieldName for DefIdent {
fn to_variant_name(&self, size_hint: usize) -> Ident {
let pascal = keyword_to_pascal(&self.0);
format_ident!("{}", if size_hint > 0 { pluralize(pascal) } else { pascal })
}
}
impl ToFieldName for DefType {
fn to_variant_name(&self, size_hint: usize) -> Ident {
let str = self.ident.to_string();
format_ident!("{}", if size_hint > 0 { pluralize(str) } else { str })
}
}
impl ToFieldName for Def {
fn to_variant_name(&self, size_hint: usize) -> Ident {
match self {
Self::Ident(v) => v.to_variant_name(size_hint),
Self::Type(v) => v.to_variant_name(size_hint),
Self::StyleValue(v) => v.to_variant_name(size_hint),
Self::FunctionType(v) => format_ident!("{}Function", v.to_variant_name(size_hint)),
Self::AutoOr(ty) => format_ident!("AutoOr{}", ty.deref().to_variant_name(size_hint)),
Self::NoneOr(ty) => format_ident!("NoneOr{}", ty.deref().to_variant_name(size_hint)),
Self::AutoNoneOr(ty) => format_ident!("AutoNoneOr{}", ty.deref().to_variant_name(size_hint)),
Self::NormalOr(ty) => format_ident!("NormalOr{}", ty.deref().to_variant_name(size_hint)),
Self::Function(v, _) => format_ident!("{}Function", v.0.to_pascal_case()),
Self::Multiplier(v, _, _) => v.deref().to_variant_name(2),
Self::Group(def, _) => def.deref().to_variant_name(size_hint),
Self::Optional(def) => def.deref().to_variant_name(size_hint),
Self::IntLiteral(v) => format_ident!("Literal{}", v.to_string()),
Self::DimensionLiteral(int, dim) => format_ident!("Literal{int}{dim}"),
Self::Combinator(ds, DefCombinatorStyle::Ordered) => {
let non_optional: Vec<(String, &Def)> = ds
.iter()
.filter(|d| !matches!(d, Def::Optional(_) | Def::Punct(_)))
.map(|d| (d.to_variant_name(0).to_string(), d))
.collect();
let distinct_count = {
let mut uniq: Vec<&str> = non_optional.iter().map(|(s, _)| s.as_str()).collect();
uniq.dedup();
uniq.len()
};
let has_multiple_ident_children =
non_optional.len() > 1 && non_optional.iter().any(|(_, d)| matches!(d, Def::Ident(_)));
if distinct_count > 1 || has_multiple_ident_children {
let name: String = ds
.iter()
.filter(|d| !matches!(d, Def::Punct(_)))
.map(|d| d.to_variant_name(0).to_string())
.collect();
format_ident!("{}", name)
} else {
let (optional, others): (Vec<&Def>, Vec<&Def>) =
ds.iter().filter(|d| !matches!(d, Def::Punct(_))).partition(|d| matches!(d, Def::Optional(_)));
let logical_first = others.first().or(optional.first());
logical_first.expect("At least one Def is required").to_variant_name(0)
}
}
Self::Combinator(ds, DefCombinatorStyle::Options) => {
let auto_generated_name: String = ds
.iter()
.filter(|d| !matches!(d, Def::Punct(_)))
.map(|d| d.to_variant_name(0).to_string())
.collect();
format_ident!("{}", get_type_rename(&auto_generated_name).unwrap_or(&auto_generated_name))
}
Self::Combinator(ds, DefCombinatorStyle::AllMustOccur) => {
let name: String = ds
.iter()
.filter(|d| !matches!(d, Def::Punct(_)))
.map(|d| d.to_variant_name(0).to_string())
.collect();
format_ident!("{}", name)
}
Self::Combinator(ds, DefCombinatorStyle::Alternatives) => {
let auto_generated_name: String = ds
.iter()
.filter(|d| !matches!(d, Def::Punct(_)))
.map(|d| d.to_variant_name(0).to_string())
.collect();
format_ident!("{}", get_type_rename(&auto_generated_name).unwrap_or(&auto_generated_name))
}
Self::Punct(c) => panic!("Punct('{c}') has no variant name; filter before calling to_variant_name"),
}
}
}
impl ToType for DefIdent {
fn to_types(&self) -> Vec<TokenStream> {
vec![quote! { ::css_parse::T![Ident] }]
}
}
impl ToType for Def {
fn to_types(&self) -> Vec<TokenStream> {
match self {
Self::Ident(v) => v.to_types(),
Self::Type(v) => v.to_types(),
Self::StyleValue(ty) => {
let ident = format_ident!("{}StyleValue", ty.ident.0);
let generics = self.get_generics();
vec![quote! { crate::#ident #generics }]
}
Self::FunctionType(ty) => {
let ident = format_ident!("{}Function", ty.ident.0);
let generics = self.get_generics();
vec![quote! { crate::#ident #generics }]
}
Self::AutoOr(ty) => {
let ty = ty.to_type();
vec![quote! { crate::AutoOr<#ty> }]
}
Self::NoneOr(ty) => {
let ty = ty.to_type();
vec![quote! { crate::NoneOr<#ty> }]
}
Self::AutoNoneOr(ty) => {
let ty = ty.to_type();
vec![quote! { crate::AutoNoneOr<#ty> }]
}
Self::NormalOr(ty) => {
let ty = ty.to_type();
vec![quote! { crate::NormalOr<#ty> }]
}
Self::Optional(v) => {
let ty = v.to_type();
vec![quote! { Option<#ty> }]
}
Self::Function(_, _) => {
let func_name = self.to_variant_name(0);
let generics = self.get_generics();
vec![quote! { crate::#func_name #generics }]
}
Self::Combinator(ds, DefCombinatorStyle::Ordered) => ds.iter().map(|d| d.to_type()).collect(),
Self::Combinator(ds, DefCombinatorStyle::Alternatives) => {
let non_kw: Vec<&Def> = ds.iter().filter(|d| !matches!(d, Def::Ident(_))).collect();
if non_kw.len() == 2 && non_kw.len() == ds.len() {
let left = non_kw[0].to_type();
let right = non_kw[1].to_type();
vec![quote! { ::css_parse::Either<#left, #right> }]
} else {
let ident = self.to_variant_name(0);
let generics = self.get_generics();
vec![quote! { crate::#ident #generics }]
}
}
Self::Combinator(ds, DefCombinatorStyle::Options) => {
let types = ds.iter().map(|d| d.to_type());
vec![quote! { ::css_parse::Optionals![#(#types),*] }]
}
Self::Combinator(ds, DefCombinatorStyle::AllMustOccur) => {
let types: Vec<_> = ds.iter().map(|d| d.to_type()).collect();
if types.len() == 1 { types } else { vec![quote! { (#(#types),*) }] }
}
Self::Multiplier(def, DefMultiplierSeparator::Commas, range) => {
let ty = def.deref().to_type();
let min = match range {
DefRange::Range(Range { start, .. }) if *start != 1.0 => Some(*start as usize),
DefRange::RangeFrom(f) if *f != 1.0 => Some(*f as usize),
DefRange::Fixed(f) if *f != 1.0 => Some(*f as usize),
_ => None,
};
vec![quote! { ::css_parse::CommaSeparated<'a, #ty, #min> }]
}
Self::Multiplier(def, DefMultiplierSeparator::None, range) => {
let ty = def.deref().to_type();
match range {
DefRange::RangeFrom(f) if *f == 0.0 => {
vec![quote! { Option<::bumpalo::collections::Vec<'a, #ty>> }]
}
_ => {
vec![quote! { ::bumpalo::collections::Vec<'a, #ty> }]
}
}
}
Self::IntLiteral(value) => {
let val = *value;
vec![quote! { crate::Exact<crate::CSSInt, #val> }]
}
Self::DimensionLiteral(value, _) => {
let val = *value as i32;
vec![quote! { crate::Exact<::css_parse::T![Dimension], #val> }]
}
Self::Punct(char) => {
let punct = Punct::new(*char, Spacing::Alone);
vec![quote! { ::css_parse::T![#punct] }]
}
Self::Group(inner, _) => inner.deref().to_types(),
}
}
}
impl ToType for DefType {
fn to_types(&self) -> Vec<TokenStream> {
let ty = &self.ident;
let type_name = quote! { crate::#ty };
let generics = self.get_generics();
let base_type = quote! { #type_name #generics };
let wrapped_type = match self.range {
DefRange::None | DefRange::Fixed(_) => base_type,
DefRange::Range(Range { start, end }) => {
if start == end {
let value = start as i32;
quote! { crate::Exact<#base_type, #value> }
} else {
let min = start as i32;
let max = end as i32;
quote! { crate::Ranged<#base_type, #min, #max> }
}
}
DefRange::RangeFrom(start) => {
if start == 0.0 {
quote! { crate::NonNegative<#base_type> }
} else if start > 0.0 && start <= 1.0 {
quote! { crate::Positive<#base_type> }
} else {
let min = start as i32;
let max = i32::MAX;
quote! { crate::Ranged<#base_type, #min, #max> }
}
}
DefRange::RangeTo(end) => {
let min = i32::MIN;
let max = end as i32;
quote! { crate::Ranged<#base_type, #min, #max> }
}
};
vec![wrapped_type]
}
}
fn find_options_with_keywords(def: &Def) -> Vec<&Def> {
fn is_options_with_keywords(def: &Def) -> bool {
if let Def::Combinator(defs, DefCombinatorStyle::Options) = def {
defs.iter().any(|d| !matches!(d, Def::Type(_) | Def::StyleValue(_)))
} else {
false
}
}
fn unwrap_to_options(def: &Def) -> Option<&Def> {
match def {
Def::Group(inner, _) => unwrap_to_options(inner),
d if is_options_with_keywords(d) => Some(d),
_ => None,
}
}
match def {
Def::Combinator(children, DefCombinatorStyle::Alternatives) => {
children.iter().filter_map(unwrap_to_options).collect()
}
Def::NoneOr(inner) | Def::AutoOr(inner) | Def::NormalOr(inner) | Def::AutoNoneOr(inner) => {
unwrap_to_options(inner).into_iter().collect()
}
_ => vec![],
}
}
fn distinguishing_keyword_names(siblings: &[&Def]) -> Vec<Option<Vec<String>>> {
if siblings.len() < 2 {
return siblings
.iter()
.map(|sibling| match sibling {
Def::Combinator(children, DefCombinatorStyle::Options) => {
let kws: Vec<String> = children
.iter()
.filter_map(|d| if let Def::Ident(DefIdent(s)) = d { Some(s.clone()) } else { None })
.collect();
if kws.is_empty() { None } else { Some(kws) }
}
_ => None,
})
.collect();
}
let keyword_sets: Vec<Vec<String>> = siblings
.iter()
.map(|sibling| match sibling {
Def::Combinator(children, DefCombinatorStyle::Options) => children
.iter()
.filter_map(|d| if let Def::Ident(DefIdent(s)) = d { Some(s.clone()) } else { None })
.collect(),
_ => vec![],
})
.collect();
keyword_sets
.iter()
.enumerate()
.map(|(i, mine)| {
let unique: Vec<String> = mine
.iter()
.filter(|kw| keyword_sets.iter().enumerate().any(|(j, other)| j != i && !other.contains(kw)))
.cloned()
.collect();
if unique.is_empty() { None } else { Some(unique) }
})
.collect()
}
impl DefExt for Def {
fn single_ident(ident: &Ident) -> Ident {
let ident = ident.to_string();
let ident = ident.strip_prefix("Single").unwrap_or(&ident);
format_ident!("Single{}", ident)
}
fn keyword_ident(ident: &Ident) -> Ident {
let ident = ident.to_string();
let ident = ident.strip_prefix("Single").unwrap_or(&ident);
format_ident!("{}Keywords", ident)
}
fn options_ident(ident: &Ident) -> Ident {
let ident = ident.to_string();
let ident = ident.strip_prefix("Single").unwrap_or(&ident);
format_ident!("{}Options", ident)
}
fn should_skip_visit(&self) -> bool {
match self {
Self::Ident(_) => true,
Self::IntLiteral(_) => true,
Self::DimensionLiteral(_, _) => true,
Self::Function(_, _) => false,
Self::AutoOr(ty) => ty.as_ref().should_skip_visit(),
Self::NoneOr(ty) => ty.as_ref().should_skip_visit(),
Self::AutoNoneOr(ty) => ty.as_ref().should_skip_visit(),
Self::NormalOr(ty) => ty.as_ref().should_skip_visit(),
Self::Type(DefType { ident, .. }) => ident.0.ends_with("Keywords"),
Self::StyleValue(_) => false,
Self::FunctionType(_) => false,
Self::Optional(d) => d.should_skip_visit(),
Self::Combinator(d, _) => d.iter().all(|d| d.should_skip_visit()),
Self::Group(d, _) => d.should_skip_visit(),
Self::Multiplier(d, _, _) => d.should_skip_visit(),
Self::Punct(_) => false,
}
}
fn type_attributes(&self, derives_parse: bool, derives_visitable: bool) -> TokenStream {
let skip = if derives_visitable && self.should_skip_visit() {
quote! { #[cfg_attr(feature = "visitable", visit(skip))] }
} else {
quote! {}
};
let atom = match self {
Def::Type(ty) => match ty.ident_str() {
"Decibel" => quote! { #[atom(CssAtomSet::Db)] },
_ => quote! {},
},
Def::DimensionLiteral(_, unit) if derives_parse => {
let name = format_ident!("{}", unit.to_pascal_case());
quote! { #[atom(CssAtomSet::#name)] }
}
Def::Ident(DefIdent(str)) if derives_parse => {
let name = format_ident!("{}", keyword_to_pascal(str));
quote! { #[atom(CssAtomSet::#name)] }
}
Def::Optional(inner) => match inner.as_ref() {
Def::Ident(DefIdent(str)) if derives_parse => {
let name = format_ident!("{}", keyword_to_pascal(str));
quote! { #[atom(CssAtomSet::#name)] }
}
_ => quote! {},
},
_ => quote! {},
};
quote! { #skip #atom }
}
fn is_all_keywords(&self) -> bool {
match self {
Self::Ident(_) => true,
Self::IntLiteral(_) => false,
Self::DimensionLiteral(_, _) => false,
Self::Function(_, _) => false,
Self::Type(DefType { ident, .. }) => ident.0.ends_with("Keywords"),
Self::FunctionType(_) => false,
Self::StyleValue(_) => false,
Self::AutoOr(def) => def.deref().is_all_keywords(),
Self::NoneOr(def) => def.deref().is_all_keywords(),
Self::AutoNoneOr(def) => def.deref().is_all_keywords(),
Self::NormalOr(def) => def.deref().is_all_keywords(),
Self::Optional(def) => match def.as_ref() {
Self::Ident(_) => false,
_ => def.deref().is_all_keywords(),
},
Self::Combinator(defs, _) => defs.iter().all(Self::is_all_keywords),
Self::Group(def, _) => def.deref().is_all_keywords(),
Self::Multiplier(def, _, _) => def.deref().is_all_keywords(),
Self::Punct(_) => false,
}
}
fn gather_keywords(&self) -> Vec<&Self> {
match self {
Self::Ident(_) => vec![],
Self::Function(_, _) => vec![],
Self::AutoOr(_) => vec![],
Self::NoneOr(_) => vec![],
Self::AutoNoneOr(_) => vec![],
Self::NormalOr(_) => vec![],
Self::StyleValue(_) => vec![],
Self::FunctionType(_) => vec![],
Self::Type(_) => vec![],
Self::Optional(def) => {
if matches!(def.as_ref(), Self::Ident(_)) { vec![] } else { def.gather_keywords() }
}
Self::Combinator(opts, DefCombinatorStyle::Alternatives)
| Self::Combinator(opts, DefCombinatorStyle::Options) => {
opts.iter().filter(|def| matches!(def, Self::Ident(_))).collect()
}
Self::Combinator(opts, DefCombinatorStyle::Ordered) => {
opts.iter().flat_map(Self::gather_keywords).collect()
}
Self::Combinator(opts, DefCombinatorStyle::AllMustOccur) => {
opts.iter().flat_map(Self::gather_keywords).collect()
}
Self::Group(def, _) => def.gather_keywords(),
Self::Multiplier(def, _, _) => def.gather_keywords(),
Self::Punct(_) => vec![],
Self::IntLiteral(_) => vec![],
Self::DimensionLiteral(_, _) => vec![],
}
}
fn get_generics(&self) -> Generics {
if self.maybe_unsized()
&& !matches!(self, Def::NoneOr(_) | Def::AutoOr(_) | Def::AutoNoneOr(_) | Def::NormalOr(_))
{
parse_quote!(<'a>)
} else {
Default::default()
}
}
fn rewrite_options_type(&self, replacement: &TokenStream) -> TokenStream {
match self {
Self::NoneOr(_) => quote! { crate::NoneOr<#replacement> },
Self::AutoOr(_) => quote! { crate::AutoOr<#replacement> },
Self::NormalOr(_) => quote! { crate::NormalOr<#replacement> },
Self::AutoNoneOr(_) => quote! { crate::AutoNoneOr<#replacement> },
Self::Group(inner, _) => inner.rewrite_options_type(replacement),
Self::Combinator(_, DefCombinatorStyle::Options) => replacement.clone(),
_ => self.to_type(),
}
}
fn generate_additional_types(&self, vis: &Visibility, ident: &Ident, _generics: &Generics) -> TokenStream {
let needs_keyword_type = match self {
Self::Combinator(defs, DefCombinatorStyle::Ordered) => defs.iter().any(|def| def.is_all_keywords()),
Self::Multiplier(def, _, _) => match def.deref() {
Self::Combinator(defs, DefCombinatorStyle::Alternatives) => {
defs.iter().all(|def| matches!(def, Def::Ident(_)))
}
_ => false,
},
_ => false,
};
let keyword_type = if needs_keyword_type {
let keywords: Vec<TokenStream> = self
.gather_keywords()
.iter()
.unique_by(|def| if let Self::Ident(DefIdent(str)) = def { str } else { "" })
.filter_map(|def| {
if let Self::Ident(def) = def {
let ident = format_ident!("{}", keyword_to_pascal(&def.to_string()));
let ty = def.to_type();
Some(quote! { #[atom(CssAtomSet::#ident)] #ident(#ty), })
} else {
None
}
})
.collect();
let keyword_name = Self::keyword_ident(ident);
quote! {
#[derive(
::csskit_derives::Parse,
::csskit_derives::Peek,
::csskit_derives::ToCursors,
::csskit_derives::ToSpan,
::csskit_derives::SemanticEq,
Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
#[cfg_attr(feature = "visitable", derive(::csskit_derives::Visitable), visit(skip))]
pub enum #keyword_name {
#(#keywords)*
}
}
} else {
quote! {}
};
let single_inner: Option<&Def> = match self {
Self::Multiplier(defs, _, range) => match defs.deref() {
Def::Combinator(defs, DefCombinatorStyle::Alternatives)
if defs.iter().all(|def| matches!(def, Def::Ident(_))) =>
{
None
}
Def::Combinator(ds, DefCombinatorStyle::Alternatives)
if ds.len() == 2 && ds.iter().all(|d| !matches!(d, Def::Ident(_))) =>
{
None
}
Def::Combinator(_, _) if matches!(range, DefRange::RangeFrom(_) | DefRange::RangeTo(_)) => {
Some(defs.deref())
}
_ => None,
},
Self::Combinator(defs, DefCombinatorStyle::Ordered) => defs
.iter()
.find_map(|def| {
if def.keyword_prefix_name().is_some() {
Some(def)
} else if let Def::Optional(inner) = def {
if inner.keyword_prefix_name().is_some() { Some(inner.as_ref()) } else { None }
} else {
None
}
})
.map(|def| match def {
Def::Group(inner, _) => inner.as_ref(),
other => other,
}),
_ => None,
};
let single_type = if let Some(inner) = single_inner {
let single_ident = Self::single_ident(ident);
let generics = inner.get_generics();
let def = inner.generate_definition(vis, &single_ident, &generics, true, true);
quote! {
#[derive(
::csskit_derives::Parse,
::csskit_derives::Peek,
::csskit_derives::ToSpan,
::csskit_derives::ToCursors,
::csskit_derives::SemanticEq,
Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
#[cfg_attr(feature = "visitable", derive(::csskit_derives::Visitable), visit(children))]
#def
}
} else {
quote! {}
};
let options_children = if matches!(self, Self::Combinator(_, DefCombinatorStyle::Alternatives)) {
vec![]
} else {
find_options_with_keywords(self)
};
let options_types = if options_children.is_empty() {
quote! {}
} else {
let mut result = quote! {};
for (i, inner) in options_children.iter().enumerate() {
let opts_ident = if options_children.len() == 1 {
Self::options_ident(ident)
} else {
format_ident!("{}Options{}", ident, i + 1)
};
let generics = inner.get_generics();
let def = inner.generate_definition(vis, &opts_ident, &generics, true, true);
result.extend(quote! {
#[derive(
::csskit_derives::Parse,
::csskit_derives::Peek,
::csskit_derives::ToSpan,
::csskit_derives::ToCursors,
::csskit_derives::SemanticEq,
Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
#[cfg_attr(feature = "visitable", derive(::csskit_derives::Visitable), visit(children))]
#def
});
}
result
};
quote! {
#keyword_type
#single_type
#options_types
}
}
}
impl GenerateDefinition for Def {
fn generate_definition(
&self,
vis: &Visibility,
ident: &Ident,
generics: &Generics,
derives_parse: bool,
derives_visitable: bool,
) -> TokenStream {
let (_, type_generics, where_clause) = generics.split_for_impl();
match self.suggested_data_type() {
DataType::SingleUnnamedStruct => {
let mut struct_attrs = quote! {};
let members = match self {
Self::Combinator(_, DefCombinatorStyle::Alternatives) => {
Error::new(ident.span(), "cannot generate alternative combinators in struct")
.into_compile_error()
}
Self::Combinator(defs, DefCombinatorStyle::Options) => {
let members = defs.iter().map(|def| {
let name = def.to_member_name(0);
let ty = def.to_type();
let attrs = def.type_attributes(derives_parse, derives_visitable);
quote! { #attrs pub #name: Option<#ty> }
});
if derives_parse {
struct_attrs.extend(quote! { #[parse(one_must_occur)] })
}
quote! { { #(#members),* } }
}
Self::Combinator(defs, DefCombinatorStyle::Ordered) => {
let single_ident = Self::single_ident(ident);
let types = defs.iter().map(|def| {
let ty = if let Self::Optional(inner) = def {
if matches!(inner.as_ref(), Self::Ident(_)) {
def.to_type()
} else if inner.keyword_prefix_name().is_some() {
quote! { Option<#single_ident> }
} else if inner.is_all_keywords() {
let keyword_name = Self::keyword_ident(ident);
quote! { Option<#keyword_name> }
} else {
def.to_type()
}
} else if def.keyword_prefix_name().is_some() {
quote! { #single_ident }
} else if def.is_all_keywords() {
let keyword_name = Self::keyword_ident(ident);
quote! { #keyword_name }
} else {
def.to_type()
};
let attrs = def.type_attributes(derives_parse, derives_visitable);
quote! { #attrs pub #ty }
});
quote! { ( #(#types),* ); }
}
Self::Combinator(defs, DefCombinatorStyle::AllMustOccur) => {
struct_attrs.extend(quote! { #[parse(all_must_occur)] });
let types = defs.iter().map(|def| {
let ty = def.to_type();
let attrs = def.type_attributes(derives_parse, derives_visitable);
quote! { #attrs pub #ty }
});
quote! { ( #(#types),* ); }
}
Self::Multiplier(def, sep, range) => match def.deref() {
Self::Combinator(defs, DefCombinatorStyle::Alternatives)
if defs.iter().all(|def| matches!(def, Def::Ident(_))) =>
{
let keyword_name = Self::keyword_ident(ident);
let phantom_type = Self::Multiplier(
Box::new(Def::Type(DefType::new(&keyword_name.to_string(), DefRange::None))),
*sep,
range.clone(),
);
let ty = phantom_type.to_type();
quote! { ( pub #ty ); }
}
Self::Combinator(ds, DefCombinatorStyle::Alternatives)
if !ds.iter().all(|d| matches!(d, Def::Ident(_)))
&& ds.iter().filter(|d| !matches!(d, Def::Ident(_))).count() == 2
&& ds.len() == 2 =>
{
let ty = self.to_type();
quote! { ( pub #ty ); }
}
Self::Combinator(_, _) if matches!(range, DefRange::RangeFrom(_) | DefRange::RangeTo(_)) => {
let ty_ident = Self::single_ident(ident);
let needs_lifetime = def.maybe_unsized();
let generics = if needs_lifetime {
quote! { <'a> }
} else {
quote! {}
};
let inner_type_ref = quote! { crate::#ty_ident #generics };
let ty = match sep {
DefMultiplierSeparator::Commas => {
let min = match range {
DefRange::Range(Range { start, .. }) if *start != 1.0 => Some(*start as usize),
DefRange::RangeFrom(f) if *f != 1.0 => Some(*f as usize),
DefRange::Fixed(f) if *f != 1.0 => Some(*f as usize),
_ => None,
};
vec![quote! { ::css_parse::CommaSeparated<'a, #inner_type_ref, #min> }]
}
DefMultiplierSeparator::None => match range {
DefRange::Range(Range { start, .. }) if *start == 0.0 => {
vec![quote! { Option<::bumpalo::collections::Vec<'a, #inner_type_ref>> }]
}
_ => {
dbg!(range);
vec![quote! { Option<::bumpalo::collections::Vec<'a, #inner_type_ref> }]
}
},
};
quote! { ( #(pub #ty),* ); }
}
_ => {
let ty = self.to_types();
let attrs = self.type_attributes(derives_parse, derives_visitable);
quote! { ( #(#attrs pub #ty),* ); }
}
},
_ => {
let options_children = find_options_with_keywords(self);
if !options_children.is_empty() {
let opts_ident = Self::options_ident(ident);
let opts_generics = options_children[0].get_generics();
let ty = self.rewrite_options_type("e! { crate::#opts_ident #opts_generics });
quote! { ( pub #ty ); }
} else {
let ty = self.to_types();
let attrs = self.type_attributes(derives_parse, derives_visitable);
quote! { ( #(#attrs pub #ty),* ); }
}
}
};
quote! { #struct_attrs #vis struct #ident #type_generics #where_clause #members }
}
DataType::Enum => match self {
Self::Combinator(children, DefCombinatorStyle::Alternatives) => {
let options_children_refs: Vec<(usize, &Def)> = children
.iter()
.enumerate()
.filter_map(|(i, d)| {
let inner = match d {
Def::Group(inner, _) => inner.as_ref(),
other => other,
};
if let Def::Combinator(defs, DefCombinatorStyle::Options) = inner
&& defs.iter().any(|d| !matches!(d, Def::Type(_) | Def::StyleValue(_)))
{
return Some((i, inner));
}
None
})
.collect();
let options_indices: Vec<usize> = options_children_refs.iter().map(|(i, _)| *i).collect();
let options_inner_defs: Vec<&Def> = options_children_refs.iter().map(|(_, d)| *d).collect();
let distinguishing = distinguishing_keyword_names(&options_inner_defs);
let base_names: Vec<String> = children
.iter()
.enumerate()
.map(|(child_idx, d)| {
let options_helper_idx = options_indices.iter().position(|&i| i == child_idx);
if let Some(idx) = options_helper_idx {
let inner = options_inner_defs[idx];
if let Some(keywords) = &distinguishing[idx] {
keywords.iter().map(|k| k.to_pascal_case()).collect::<String>()
} else {
inner.to_variant_name(0).to_string()
}
} else {
d.to_variant_name(0).to_string()
}
})
.collect();
let mut seen: std::collections::HashMap<String, usize> = std::collections::HashMap::new();
let resolved_names: Vec<Ident> = base_names
.iter()
.zip(children.iter())
.enumerate()
.map(|(child_idx, (base, d))| {
let options_helper_idx = options_indices.iter().position(|&i| i == child_idx);
let raw = if *seen.get(base.as_str()).unwrap_or(&0) == 0 {
base.clone()
} else {
let suffix = if options_helper_idx.is_some() { None } else { collision_suffix(d) };
if let Some(s) = suffix {
let candidate = format!("{base}{s}");
if *seen.get(candidate.as_str()).unwrap_or(&0) == 0 {
candidate
} else {
let n = seen.get(candidate.as_str()).copied().unwrap_or(1);
format!("{candidate}{n}")
}
} else {
let n = seen.get(base.as_str()).copied().unwrap_or(1);
format!("{base}{n}")
}
};
*seen.entry(base.clone()).or_insert(0) += 1;
format_ident!("{}", raw)
})
.collect();
let variants: TokenStream = children
.iter()
.enumerate()
.map(|(child_idx, d)| {
let mut attrs = Some(d.type_attributes(derives_parse, derives_visitable));
let name = resolved_names[child_idx].clone();
// Locate this child in the Options list (if it is one).
let options_helper_idx = options_indices.iter().position(|&i| i == child_idx);
if let Some(idx) = options_helper_idx {
// Inline as struct variant with #[parse(one_must_occur)].
let inner = options_inner_defs[idx];
let Def::Combinator(opts_children, DefCombinatorStyle::Options) = inner else {
unreachable!("filtered above");
};
let name_str = name.to_string();
let name = format_ident!("{}", get_type_rename(&name_str).unwrap_or(&name_str));
let members: Vec<_> = opts_children
.iter()
.flat_map(|child| {
if let Def::Combinator(nested, DefCombinatorStyle::Options) = child {
nested
.iter()
.map(|nc| {
let member_name = nc.to_member_name(0);
let ty = nc.to_type();
let field_attrs =
nc.type_attributes(derives_parse, derives_visitable);
quote! { #field_attrs #member_name: Option<#ty> }
})
.collect::<Vec<_>>()
} else {
let member_name = child.to_member_name(0);
let ty = child.to_type();
let field_attrs = child.type_attributes(derives_parse, derives_visitable);
vec![quote! { #field_attrs #member_name: Option<#ty> }]
}
})
.collect();
let variant_attrs = if derives_parse {
quote! { #[parse(one_must_occur)] }
} else {
quote! {}
};
quote! { #variant_attrs #name { #(#members),* }, }
} else {
let types = match d {
Self::Combinator(defs, DefCombinatorStyle::Ordered) => defs
.iter()
.map(|d| {
let ty = d.to_type();
let attrs = d.type_attributes(derives_parse, derives_visitable);
quote! { #attrs #ty }
})
.collect(),
Self::Combinator(defs, DefCombinatorStyle::AllMustOccur) => {
if derives_parse {
attrs = Some(quote! { #[parse(all_must_occur)] });
}
defs.iter()
.map(|d| {
let ty = d.to_type();
let a = d.type_attributes(derives_parse, derives_visitable);
quote! { #a #ty }
})
.collect()
}
Self::Ident(_) => d.to_types(),
Self::IntLiteral(_) | Self::DimensionLiteral(_, _) => {
let attrs = attrs.take().unwrap();
let ty = d.to_type();
vec![quote! { #attrs #ty }]
}
Self::Type(_) => {
let attrs = attrs.take().unwrap();
let ty = d.to_type();
vec![quote! { #attrs #ty }]
}
Self::Optional(inner) if matches!(inner.deref(), Def::Type(_)) => {
let attrs = attrs.take().unwrap();
let ty = d.to_type();
vec![quote! { #attrs #ty }]
}
_ => d.to_types(),
};
quote! { #attrs #name(#(#types),*), }
}
})
.collect();
quote! { #vis enum #ident #type_generics #where_clause { #variants } }
}
Self::Combinator(_, _) => {
Error::new(ident.span(), "cannot generate non-Alternatives combinators in enum")
.into_compile_error()
}
_ => {
dbg!("TODO non union enum", self);
todo!("non union enum")
}
},
}
}
}
impl DefTypeExt for DefType {
fn get_generics(&self) -> Generics {
if self.maybe_unsized() { parse_quote!(<'a>) } else { Default::default() }
}
}