Skip to main content

css_value_definition_parser/
lib.rs

1#![deny(warnings)]
2use heck::ToPascalCase;
3use proc_macro2::{Span, TokenStream};
4use quote::{ToTokens, TokenStreamExt, format_ident};
5use std::{
6	fmt::Display,
7	ops::{Deref, Range},
8};
9use syn::{
10	Error, Ident, Lit, LitFloat, LitInt, LitStr, Result, Token, braced, bracketed,
11	ext::IdentExt,
12	parenthesized,
13	parse::{Parse, ParseStream},
14	parse2, token,
15};
16
17#[cfg(test)]
18mod test;
19
20pub struct StrWrapped<T: Parse>(pub T);
21impl<T: Parse> Parse for StrWrapped<T> {
22	fn parse(input_raw: ParseStream) -> Result<Self> {
23		Ok(Self(parse2::<T>(
24			input_raw.parse::<LitStr>()?.value().replace("'", "\"").replace("∞", "").parse::<TokenStream>()?,
25		)?))
26	}
27}
28
29#[derive(Debug, PartialEq, Clone)]
30pub enum Def {
31	Ident(DefIdent),
32	Function(DefIdent, Box<Def>),
33	AutoOr(Box<Def>),
34	NoneOr(Box<Def>),
35	AutoNoneOr(Box<Def>),
36	NormalOr(Box<Def>),
37	Type(DefType),
38	StyleValue(DefType),
39	FunctionType(DefType),
40	Optional(Box<Def>), // ?
41	Combinator(Vec<Def>, DefCombinatorStyle),
42	Group(Box<Def>, DefGroupStyle),
43	Multiplier(Box<Def>, DefMultiplierSeparator, DefRange),
44	Punct(char),
45	IntLiteral(i32),
46	DimensionLiteral(f32, String),
47}
48
49#[derive(Debug, PartialEq, Copy, Clone)]
50pub enum DefGroupStyle {
51	None,         // [ ] - regular group notation
52	OneMustOccur, // [ ]! - at least one in the group must occur
53}
54
55#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Copy, Clone)]
56pub enum DefCombinatorStyle {
57	Ordered,      // <space>
58	AllMustOccur, // && - all must occur
59	Options,      // || - one or more must occur
60	Alternatives, // | - exactly one must occur
61}
62
63#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy)]
64pub enum DefMultiplierSeparator {
65	None,   // *, +, or {,}
66	Commas, // #, #? or #{,}
67}
68
69#[derive(Debug, PartialEq, Clone)]
70pub enum DefRange {
71	None,
72	Range(Range<f32>), // {A,B}
73	RangeFrom(f32),    // {A,}
74	RangeTo(f32),      // {,B}
75	Fixed(f32),        // {A}
76}
77
78#[derive(Debug, PartialEq, Clone)]
79pub struct DefIdent(pub String);
80
81#[derive(Debug, PartialEq, Clone)]
82pub struct DefType {
83	pub ident: DefIdent,
84	pub range: DefRange,
85}
86
87impl DefType {
88	pub fn new(str: &str, range: DefRange) -> Self {
89		DefType { ident: DefIdent(str.to_string()), range }
90	}
91
92	pub fn ident_str(&self) -> &str {
93		self.ident.0.as_str()
94	}
95
96	pub fn maybe_unsized(&self) -> bool {
97		// Check for specific types that require allocations
98		matches!(
99			self.ident_str(),
100			// Hand-written types that contain other allocating types
101			"Color"          // Color<'a>
102				| "Shadow"       // Shadow<'a> (contains Color<'a>)
103				| "SpreadShadow"   // SpreadShadow<'a> (contains Color<'a>)
104				| "Image"          // contains Gradient<'a>
105				| "Image1d"  // contains StripesFunction<'a>
106				| "ContentList"  // Vec<'a, ContentListItem<'a>>
107				| "ContentAltItem"  // contains Counter<'a> and AttrFunction<'a>
108				| "ContentReplacement"  // type alias for Image<'a>
109				| "Counter"  // Counter<'a> enum with CounterFunction/CountersFunction variants
110				| "CounterStyle"  // complex hand-written type
111				| "CursorImage"  // contains Image<'a>
112				| "EasingFunction"  // contains LinearFunction<'a> with CommaSeparated
113				// Types that reference other allocating types
114					| "LineWidthOrRepeat"  // contains Repeat<'a>
115					| "LineWidthList"  // contains LineWidthOrRepeat<'a>
116					| "AutoLineWidthList"  // contains Repeat<'a> and LineWidthOrRepeat<'a>
117					| "GapRuleList"  // contains Vec<'a, ...>
118					| "GapAutoRuleList"  // contains Vec<'a, ...>
119					| "FontFamilyName"  // may contain allocating elements
120				| "VoiceFamilyName"  // may contain allocating elements
121				| "BgImage"  // contains Image<'a>
122				| "MaskLayer"  // contains Image<'a>
123				| "MaskReference"  // contains Image<'a>
124				| "DynamicRangeLimit"  // contains DynamicRangeLimitMixFunction<'a>
125				| "DynamicRangeLimitMixFunction"  // contains allocating params
126				// Additional types that reference allocating types
127				| "Outline"
128				| "SingleTransition"
129				| "Symbol" // Symbol<'a>
130				| "TransformList"
131				| "FilterValueList"  // contains Vec<'a, FilterValue<'a>>
132				| "FilterValue"  // contains FilterFunction<'a>
133				| "FilterFunction" // contains DropShadowFunction<'a>
134			| "BgLayer"  // contains BgImage<'a> and Color<'a>
135			| "FinalBgLayer"  // type alias for BgLayer<'a>
136			| "BgPositionAndSize" // contains BgPosition which contains Position
137		)
138	}
139}
140
141impl Parse for Def {
142	fn parse(input: ParseStream) -> Result<Self> {
143		let mut root = if input.peek(Token![<]) {
144			input.parse::<Token![<]>()?;
145			let mut style_value = false;
146			let mut function = false;
147			let ident = if input.peek(LitStr) {
148				style_value = true;
149				input.parse::<StrWrapped<DefIdent>>()?.0.0
150			} else {
151				input.parse::<DefIdent>()?.0
152			}
153			.to_pascal_case();
154			let range = if input.peek(token::Bracket) {
155				let content;
156				bracketed!(content in input);
157				content.parse::<DefRange>()?
158			} else {
159				DefRange::None
160			};
161			if input.peek(token::Paren) {
162				let content;
163				parenthesized!(content in input);
164				if !content.is_empty() {
165					Err(Error::new(input.span(), "disallowed content inside deftype function"))?
166				}
167				debug_assert!(!style_value, "Can't be function and style value");
168				function = true;
169			}
170			debug_assert!(!(function && style_value), "Can't be function or style value and or-none");
171			let ty = if let Some(without_auto) = ident.strip_suffix("-or-auto") {
172				Self::AutoOr(Box::new(Def::Type(DefType { ident: DefIdent(without_auto.into()), range })))
173			} else if let Some(without_none) = ident.strip_suffix("-or-none") {
174				Self::NoneOr(Box::new(Def::Type(DefType { ident: DefIdent(without_none.into()), range })))
175			} else if function {
176				Self::FunctionType(DefType { ident: DefIdent(ident), range })
177			} else if style_value {
178				Self::StyleValue(DefType { ident: DefIdent(ident), range })
179			} else {
180				Self::Type(DefType { ident: DefIdent(ident), range })
181			};
182			input.parse::<Token![>]>()?;
183			ty
184		} else if input.peek(token::Bracket) {
185			let content;
186			bracketed!(content in input);
187			let inner = Box::new(content.parse::<Def>()?);
188			if input.peek(Token![!]) {
189				input.parse::<Token![!]>()?;
190				Self::Group(inner, DefGroupStyle::OneMustOccur)
191			} else if input.peek(Token![#]) {
192				input.parse::<Token![#]>()?;
193				Self::Multiplier(inner, DefMultiplierSeparator::Commas, DefRange::RangeFrom(1.))
194			} else if input.peek(Token![+]) {
195				input.parse::<Token![+]>()?;
196				Self::Multiplier(inner, DefMultiplierSeparator::None, DefRange::RangeFrom(1.))
197			} else if input.peek(token::Brace) {
198				let content;
199				braced!(content in input);
200				let range = content.parse::<DefRange>()?;
201				debug_assert!(matches!(range, DefRange::Range(_) | DefRange::Fixed(_)));
202				Self::Multiplier(inner, DefMultiplierSeparator::None, range)
203			} else {
204				Self::Group(inner, DefGroupStyle::None)
205			}
206		} else if input.peek(Ident::peek_any) {
207			let ident = input.parse::<DefIdent>()?;
208			if input.peek(token::Paren) {
209				let content;
210				parenthesized!(content in input);
211				Self::Function(ident, Box::new(content.parse::<Def>()?))
212			} else {
213				Self::Ident(ident)
214			}
215		} else if input.peek(Token![-]) && input.peek2(Ident::peek_any) {
216			input.parse::<Token![-]>()?;
217			let mut ident = input.parse::<DefIdent>()?;
218			ident.0.insert(0, '-');
219			Self::Ident(ident)
220		} else if input.peek(Lit) {
221			let lit = input.parse::<Lit>()?;
222			match lit {
223				Lit::Int(lit) => {
224					if lit.suffix() == "" {
225						Self::IntLiteral(lit.base10_parse::<i32>()?)
226					} else {
227						let unit = lit.suffix();
228						if unit.is_empty() {
229							Err(Error::new(lit.span(), "Invalid dimension unit"))?
230						}
231						Self::DimensionLiteral(lit.base10_parse::<f32>()?, unit.to_string())
232					}
233				}
234				Lit::Char(lit) => Self::Punct(lit.value()),
235				Lit::Str(lit) if lit.value().len() == 1 => Self::Punct(lit.value().chars().next().unwrap()),
236				_ => Err(Error::new(input.span(), "unknown token in Def parse"))?,
237			}
238		} else {
239			input.step(|cursor| {
240				if let Some((p, next)) = cursor.punct() {
241					return Ok((Self::Punct(p.as_char()), next));
242				}
243				Err(Error::new(input.span(), "unknown token in Def parse"))?
244			})?
245		}
246		.optimize();
247		loop {
248			if input.is_empty() {
249				return Ok(root);
250			} else if input.peek(Token![?]) {
251				input.parse::<Token![?]>()?;
252				let inner = root;
253				root = Self::Optional(Box::new(inner.optimize()));
254			} else if input.peek(Token![+])
255				|| input.peek(Token![#])
256				|| input.peek(token::Brace)
257				|| input.peek(Token![*])
258			{
259				let inner = root;
260				let (sep, range) = if input.peek(Token![*]) {
261					input.parse::<Token![*]>()?;
262					(DefMultiplierSeparator::None, DefRange::RangeFrom(0.))
263				} else if input.peek(Token![+]) {
264					input.parse::<Token![+]>()?;
265					(DefMultiplierSeparator::None, DefRange::RangeFrom(1.))
266				} else if input.peek(Token![#]) {
267					input.parse::<Token![#]>()?;
268					let range = if input.peek(token::Brace) {
269						let content;
270						braced!(content in input);
271						content.parse::<DefRange>()?
272					} else if input.peek(Token![?]) {
273						input.parse::<Token![?]>()?;
274						DefRange::RangeFrom(0.)
275					} else {
276						DefRange::RangeFrom(1.)
277					};
278					(DefMultiplierSeparator::Commas, range)
279				} else if input.peek(token::Brace) {
280					let content;
281					braced!(content in input);
282					(DefMultiplierSeparator::None, content.parse::<DefRange>()?)
283				} else {
284					Err(Error::new(input.span(), "Unknown token in DefMultiplierStyle parse!"))?
285				};
286				root = Self::Multiplier(Box::new(inner.optimize()), sep, range).optimize();
287			} else {
288				let style = if input.peek(Token![||]) {
289					input.parse::<Token![||]>()?;
290					DefCombinatorStyle::Options
291				} else if input.peek(Token![|]) {
292					input.parse::<Token![|]>()?;
293					DefCombinatorStyle::Alternatives
294				} else if input.peek(Token![&&]) {
295					input.parse::<Token![&&]>()?;
296					DefCombinatorStyle::AllMustOccur
297				} else {
298					DefCombinatorStyle::Ordered
299				};
300				let mut next = input.parse::<Def>()?;
301				match (&mut root, &mut next) {
302					(_, &mut Self::Combinator(ref mut children, ref s)) if s == &style => {
303						children.insert(0, root);
304						root = next;
305					}
306					(&mut Self::Combinator(ref mut children, ref s), _) if s == &style => {
307						children.push(next);
308					}
309					(_, &mut Self::Combinator(ref mut children, ref other_style)) if &style < other_style => {
310						let options = Self::Combinator(vec![root, children.remove(0)], style);
311						children.insert(0, options);
312						root = next;
313					}
314					(_, Self::Group(inner, DefGroupStyle::None)) => {
315						let children = vec![root, inner.as_ref().clone()];
316						root = Self::Combinator(children, style);
317					}
318					(Self::Group(inner, DefGroupStyle::None), _) => {
319						let children = vec![inner.as_ref().clone(), next];
320						root = Self::Combinator(children, style);
321					}
322					_ => {
323						let children = vec![root, next];
324						root = Self::Combinator(children, style);
325					}
326				}
327			}
328		}
329	}
330}
331
332impl Def {
333	/// Returns true if this type is unsized, in other words it requires heap allocations
334	/// to contain a full representation.
335	pub fn maybe_unsized(&self) -> bool {
336		match self {
337			Self::Ident(_) | Self::IntLiteral(_) | Self::DimensionLiteral(_, _) | Self::Punct(_) => false,
338			// Functions that contain multipliers or known allocating types
339			Self::Function(_, inner) => inner.maybe_unsized(),
340			Self::FunctionType(ty) => {
341				matches!(ty.ident_str(), "DynamicRangeLimitMix" | "Param" | "Repeat" | "Attr")
342			}
343			Self::Type(d) => d.maybe_unsized(),
344			Self::StyleValue(ty) => {
345				matches!(
346					ty.ident_str(),
347					"AnimationRangeEnd"
348						| "AnimationRangeStart"
349						| "BorderBlockStart"
350						| "BorderImageSource"
351						| "BorderTopClip" | "BorderTopColor"
352						| "CaretColor" | "ColumnRule"
353						| "ColumnRuleWidth"
354						| "Container" | "ContainerName"
355						| "DynamicRangeLimit"
356						| "EventTriggerName"
357						| "EventTriggerSource"
358						| "ListStyleImage" | "ListStyleType"
359						| "MaskBorderSource"
360						| "OutlineColor" | "PointerTimelineAxis"
361						| "PointerTimelineName"
362						| "PositionTryFallbacks"
363						| "RowRule" | "ScrollTimelineAxis"
364						| "ScrollTimelineName"
365						| "TextDecorationColor"
366						| "TextEmphasisColor"
367						| "TimelineTriggerActivationRange"
368						| "TimelineTriggerActivationRangeEnd"
369						| "TimelineTriggerActivationRangeStart"
370						| "TimelineTriggerActiveRange"
371						| "TimelineTriggerActiveRangeEnd"
372						| "TimelineTriggerActiveRangeStart"
373						| "TimelineTriggerName"
374						| "TimelineTriggerSource"
375						| "ViewTimelineAxis"
376						| "ViewTimelineInset"
377						| "ViewTimelineName"
378				)
379			}
380			Self::AutoOr(d) | Self::NoneOr(d) | Self::AutoNoneOr(d) | Self::NormalOr(d) => d.maybe_unsized(),
381			Self::Optional(d) => d.maybe_unsized(),
382			Self::Combinator(ds, _) => ds.iter().any(|d| d.maybe_unsized()),
383			Self::Group(d, _) => d.maybe_unsized(),
384			Self::Multiplier(inner, sep, range) => {
385				// Bounded ranges are optimized to Optionals combinators and only need 'a if the
386				// inner type does.
387				matches!(sep, DefMultiplierSeparator::Commas)
388					|| matches!(range, DefRange::RangeFrom(_) | DefRange::RangeTo(_))
389					|| inner.maybe_unsized()
390			}
391		}
392	}
393
394	pub fn suggested_data_type(&self) -> DataType {
395		match self {
396			Self::Combinator(_, DefCombinatorStyle::Alternatives) => DataType::Enum,
397			_ => DataType::SingleUnnamedStruct,
398		}
399	}
400
401	pub fn optimize(&self) -> Self {
402		if let Self::Combinator(defs, DefCombinatorStyle::Alternatives) = self {
403			let optimized: Vec<Def> = defs.iter().map(Self::optimize).collect();
404			if optimized.iter().any(|d| matches!(d, Def::Combinator(_, DefCombinatorStyle::Alternatives))) {
405				let flat: Vec<Def> = optimized
406					.into_iter()
407					.flat_map(|d| match d {
408						Def::Combinator(inner, DefCombinatorStyle::Alternatives) => inner,
409						other => vec![other],
410					})
411					.collect();
412				return Self::Combinator(flat, DefCombinatorStyle::Alternatives).optimize();
413			}
414		}
415		match self {
416			Self::Combinator(defs, DefCombinatorStyle::Alternatives)
417				if defs.iter().any(Self::has_distributable_group) =>
418			{
419				let distributed: Vec<Def> =
420					defs.iter()
421						.flat_map(|d| match d {
422							Def::Combinator(
423								children,
424								style @ (DefCombinatorStyle::Ordered | DefCombinatorStyle::AllMustOccur),
425							) => {
426								if let Some((group_idx, alts, wrap_optional)) =
427									children.iter().enumerate().find_map(|(i, c)| {
428										Self::extract_alternatives(c).map(|(alts, wrap)| (i, alts, wrap))
429									}) {
430									let prefix = &children[..group_idx];
431									let suffix = &children[group_idx + 1..];
432									let mut result: Vec<Def> = alts
433										.iter()
434										.map(|alt| {
435											let mut new_children: Vec<Def> = prefix.to_vec();
436											new_children.push(alt.clone());
437											new_children.extend_from_slice(suffix);
438											Def::Combinator(new_children, *style)
439										})
440										.collect();
441									if wrap_optional {
442										let mut absent: Vec<Def> = prefix.to_vec();
443										absent.extend_from_slice(suffix);
444										match absent.len() {
445											0 => {}
446											1 => {
447												let single = absent.into_iter().next().unwrap();
448												if let Some((alts, _)) = Self::extract_alternatives(&single) {
449													result.extend(alts.iter().cloned());
450												} else {
451													result.push(single);
452												}
453											}
454											_ => result.push(Def::Combinator(absent, *style)),
455										}
456									}
457									result
458								} else {
459									vec![d.clone()]
460								}
461							}
462							_ => vec![d.clone()],
463						})
464						.collect();
465				return Self::Combinator(distributed, DefCombinatorStyle::Alternatives).optimize();
466			}
467
468			Self::Combinator(defs, DefCombinatorStyle::Alternatives) if defs.len() == 2 => {
469				let [first, second] = defs.as_slice() else { panic!("defs.len() was 2!") };
470				match (first, second) {
471					// "none | AutoOr<X>" can become "AutoNoneOr<X>"
472					(Def::Ident(DefIdent(ident)), Def::AutoOr(def))
473					| (Def::AutoOr(def), Def::Ident(DefIdent(ident)))
474						if ident == "none" =>
475					{
476						Def::AutoNoneOr(Box::new(*def.clone()))
477					}
478					// "auto | NoneOr<X>" can become "AutoNoneOr<X>"
479					(Def::Ident(DefIdent(ident)), Def::NoneOr(def))
480					| (Def::NoneOr(def), Def::Ident(DefIdent(ident)))
481						if ident == "auto" =>
482					{
483						Def::AutoNoneOr(Box::new(*def.clone()))
484					}
485					// "<X> | auto" can be simplified to "AutoOr<X>"
486					(Def::Ident(DefIdent(ident)), def) | (def, Def::Ident(DefIdent(ident)))
487						if ident == "auto" &&
488						// Avoid AutoOr<Ident>, or AutoOr<NoneOr<>> though
489						!matches!(def, Def::Ident(_) | Def::AutoOr(_) | Def::NoneOr(_)) =>
490					{
491						Def::AutoOr(Box::new(def.clone()))
492					}
493					// "<X> | none" can be simplified to "NoneOr<X>"
494					(Def::Ident(DefIdent(ident)), def) | (def, Def::Ident(DefIdent(ident)))
495						if ident == "none"  &&
496						// Avoid NoneOr<Ident>, or NoneOr<AutoOr<>> though
497						!matches!(def, Def::Ident(_) | Def::AutoOr(_) | Def::NoneOr(_)) =>
498					{
499						Def::NoneOr(Box::new(def.clone()))
500					}
501					// "<X> | normal" can be simplified to "NormalOr<X>"
502					(Def::Ident(DefIdent(ident)), def) | (def, Def::Ident(DefIdent(ident)))
503						if ident == "normal" &&
504						// Avoid NormalOr<Ident>, or NormalOr<AutoOr<>> though
505						!matches!(def, Def::Ident(_) | Def::AutoOr(_) | Def::NoneOr(_) | Def::NormalOr(_)) =>
506					{
507						Def::NormalOr(Box::new(def.clone()))
508					}
509					// "<length-percentage> | <flex>" can be simplified to "<length-percentage-or-flex>"
510					(Def::Type(type1), Def::Type(type2)) => match (type1.ident_str(), type2.ident_str()) {
511						// "<gap-rule-list> | <gap-auto-rule-list>" can be flattened to "<gap-rule-list>"
512						("GapRuleList", "GapAutoRuleList") => {
513							Def::Type(DefType::new("GapRuleList", type1.range.clone()))
514						}
515						("GapAutoRuleList", "GapRuleList") => {
516							Def::Type(DefType::new("GapRuleList", type2.range.clone()))
517						}
518						("LengthPercentage", "Flex") | ("Flex", "LengthPercentage") => {
519							Def::Type(DefType::new("LengthPercentageOrFlex", type1.range.clone()))
520						}
521						("Number", "Percentage") | ("Percentage", "Number") => {
522							Def::Type(DefType::new("NumberPercentage", type1.range.clone()))
523						}
524						("Number", "Length") | ("Length", "Number") => {
525							Def::Type(DefType::new("NumberLength", type1.range.clone()))
526						}
527						_ => {
528							return Self::Combinator(
529								vec![first.optimize(), second.optimize()],
530								DefCombinatorStyle::Alternatives,
531							);
532						}
533					},
534					_ => {
535						return Self::Combinator(
536							vec![first.optimize(), second.optimize()],
537							DefCombinatorStyle::Alternatives,
538						);
539					}
540				}
541			}
542			Self::Combinator(defs, DefCombinatorStyle::Alternatives) if defs.len() == 3 => {
543				let [first, second, third] = defs.as_slice() else { panic!("defs.len() was 3!") };
544				match (first, second, third) {
545					// "auto | none | <X>" can be simplified to "AutoNoneOr<X>"
546					(def, Def::Ident(DefIdent(first)), Def::Ident(DefIdent(second)))
547					| (Def::Ident(DefIdent(first)), def, Def::Ident(DefIdent(second)))
548					| (Def::Ident(DefIdent(first)), Def::Ident(DefIdent(second)), def)
549						if matches!((first.as_str(), second.as_str()), ("auto", "none") | ("none", "auto")) &&
550						// Avoid AutoNoneOr<Ident>, or AutoNoneOr<AutoOr<>> though
551						!matches!(def, Def::Ident(_) | Def::AutoOr(_) | Def::NoneOr(_)) =>
552					{
553						Def::AutoNoneOr(Box::new(def.clone()))
554					}
555					// "<number> | <length> | auto" can be simplified to "AutoOr<NumberLength>"
556					(Def::Type(type1), Def::Type(type2), Def::Ident(DefIdent(ident)))
557					| (Def::Ident(DefIdent(ident)), Def::Type(type1), Def::Type(type2))
558					| (Def::Type(type1), Def::Ident(DefIdent(ident)), Def::Type(type2))
559						if ident == "auto" =>
560					{
561						match (type1.ident_str(), type2.ident_str()) {
562							("Number", "Length") | ("Length", "Number") => {
563								Def::AutoOr(Box::new(Def::Type(DefType::new("NumberLength", type1.range.clone()))))
564							}
565							("Percentage", "Length") | ("Length", "Percentage") => {
566								Def::AutoOr(Box::new(Def::Type(DefType::new("LengthPercentage", type1.range.clone()))))
567							}
568							("Number", "Percentage") | ("Percentage", "Number") => {
569								Def::AutoOr(Box::new(Def::Type(DefType::new("NumberPercentage", type1.range.clone()))))
570							}
571							("LengthPercentage", "Number") | ("Number", "LengthPercentage") => Def::AutoOr(Box::new(
572								Def::Type(DefType::new("LengthPercentageNumber", type1.range.clone())),
573							)),
574							_ => {
575								return Self::Combinator(
576									vec![first.optimize(), second.optimize(), third.optimize()],
577									DefCombinatorStyle::Alternatives,
578								);
579							}
580						}
581					}
582					// "<x> | <length-percentage> | <flex>" can be simplified to "<x> | <length-percentage-or-flex>"
583					// "<x> | <number> | <percentage>" can be simplified to "<number-or-percentage>"
584					(def, Def::Type(type1), Def::Type(type2))
585					| (Def::Type(type1), def, Def::Type(type2))
586					| (Def::Type(type1), Def::Type(type2), def) => match (type1.ident_str(), type2.ident_str()) {
587						("LengthPercentage", "Flex") | ("Flex", "LengthPercentage") => Def::Combinator(
588							vec![
589								def.optimize(),
590								Def::Type(DefType::new("LengthPercentageOrFlex", type1.range.clone())),
591							],
592							DefCombinatorStyle::Alternatives,
593						),
594						("Number", "Percentage") | ("Percentage", "Number") => Def::Combinator(
595							vec![def.optimize(), Def::Type(DefType::new("NumberPercentage", type1.range.clone()))],
596							DefCombinatorStyle::Alternatives,
597						),
598						("Number", "Length") | ("Length", "Number") => Def::Combinator(
599							vec![def.optimize(), Def::Type(DefType::new("NumberLength", type1.range.clone()))],
600							DefCombinatorStyle::Alternatives,
601						),
602						_ => {
603							return Self::Combinator(
604								vec![first.optimize(), second.optimize(), third.optimize()],
605								DefCombinatorStyle::Alternatives,
606							);
607						}
608					},
609					_ => {
610						return Self::Combinator(
611							vec![first.optimize(), second.optimize(), third.optimize()],
612							DefCombinatorStyle::Alternatives,
613						);
614					}
615				}
616			}
617			Self::Combinator(defs, style) => {
618				// First optimize all children.
619				let optimized: Vec<Def> = defs.iter().map(|d| d.optimize()).collect();
620				let is_repeated_multiplier = {
621					let extractable: Vec<_> = optimized.iter().filter_map(Self::extract_alternatives).collect();
622					extractable.len() == optimized.len() && {
623						let first = extractable[0].0;
624						extractable.iter().all(|(alts, _)| *alts == first)
625					}
626				};
627				if !matches!(style, DefCombinatorStyle::Alternatives)
628					&& !is_repeated_multiplier
629					&& let Some((group_idx, alts, wrap_optional)) = optimized
630						.iter()
631						.enumerate()
632						.find_map(|(i, c)| Self::extract_distributable(c, *style).map(|(alts, wrap)| (i, alts, wrap)))
633				{
634					let prefix = &optimized[..group_idx];
635					let suffix = &optimized[group_idx + 1..];
636					let mut distributed: Vec<Def> = alts
637						.iter()
638						.map(|alt| {
639							let mut new_children: Vec<Def> = prefix.to_vec();
640							new_children.push(alt.clone());
641							new_children.extend_from_slice(suffix);
642							Def::Combinator(new_children, *style)
643						})
644						.collect();
645					if wrap_optional {
646						let mut absent: Vec<Def> = prefix.to_vec();
647						absent.extend_from_slice(suffix);
648						match absent.len() {
649							0 => {}
650							1 => {
651								let single = absent.into_iter().next().unwrap();
652								if let Some((alts, _)) = Self::extract_alternatives(&single) {
653									distributed.extend(alts.iter().cloned());
654								} else {
655									distributed.push(single);
656								}
657							}
658							_ => distributed.push(Def::Combinator(absent, *style)),
659						}
660					}
661					return Self::Combinator(distributed, DefCombinatorStyle::Alternatives).optimize();
662				}
663				return Self::Combinator(optimized, *style);
664			}
665			// Optimize multiplier styles to avoid unnecessarily allocating.
666			// A Multiplier with a fixed range can be normalised to an Ordered combinator of the same value.
667			Self::Multiplier(inner, DefMultiplierSeparator::None, DefRange::Fixed(i)) => {
668				let opts: Vec<_> = (1..=*i as u32).map(|_| inner.deref().clone()).collect();
669				Self::Combinator(opts, DefCombinatorStyle::Ordered)
670			}
671			// Optimize multiplier styles to avoid unnecessarily allocating.
672			// A multiplier with a bounded range can be normalised to an Ordered combinator of some optionals.
673			Self::Multiplier(inner, DefMultiplierSeparator::None, DefRange::Range(Range { start, end })) => {
674				let opts: Vec<Def> = (1..=*end as i32)
675					.map(|i| if i <= (*start as i32) { inner.deref().clone() } else { Self::Optional(inner.clone()) })
676					.collect();
677				Self::Combinator(opts, DefCombinatorStyle::Ordered)
678			}
679			Self::Multiplier(inner, sep, range) => {
680				return Self::Multiplier(Box::new(inner.optimize()), *sep, range.clone());
681			}
682			Self::Optional(inner) => return Self::Optional(Box::new(inner.optimize())),
683			Self::Group(inner, style) => return Self::Group(Box::new(inner.optimize()), *style),
684			_ => return self.clone(),
685		}
686		.optimize()
687	}
688
689	fn extract_alternatives(def: &Def) -> Option<(&[Def], bool)> {
690		match def {
691			Def::Combinator(alts, DefCombinatorStyle::Alternatives) => Some((alts, false)),
692			Def::Group(inner, _) => {
693				if let Def::Combinator(alts, DefCombinatorStyle::Alternatives) = inner.as_ref() {
694					Some((alts, false))
695				} else {
696					None
697				}
698			}
699			Def::Optional(inner) => {
700				let inner_def = match inner.as_ref() {
701					Def::Group(g, _) => g.as_ref(),
702					other => other,
703				};
704				if let Def::Combinator(alts, DefCombinatorStyle::Alternatives) = inner_def {
705					Some((alts, true))
706				} else {
707					None
708				}
709			}
710			_ => None,
711		}
712	}
713
714	/// Extracts alternatives that need distribution for the given combinator style.
715	/// For top-level Ordered structs, all-keyword alternatives are handled by codegen's
716	/// `is_all_keywords()` → `keyword_ident` path and don't need distribution.
717	/// For AllMustOccur and other styles, all alternatives need distribution.
718	fn extract_distributable(def: &Def, style: DefCombinatorStyle) -> Option<(&[Def], bool)> {
719		Self::extract_alternatives(def).filter(|(alts, _)| {
720			!matches!(style, DefCombinatorStyle::Ordered) || !alts.iter().all(|d| matches!(d, Def::Ident(_)))
721		})
722	}
723
724	fn has_distributable_group(def: &Def) -> bool {
725		match def {
726			Def::Combinator(children, DefCombinatorStyle::Ordered | DefCombinatorStyle::AllMustOccur) => {
727				let extractable: Vec<_> = children.iter().filter_map(Self::extract_alternatives).collect();
728				if extractable.is_empty() {
729					return false;
730				}
731				if extractable.len() == children.len() {
732					let first_alts = extractable[0].0;
733					if extractable.iter().all(|(alts, _)| *alts == first_alts) {
734						return false;
735					}
736				}
737				true
738			}
739			_ => false,
740		}
741	}
742
743	/// Returns the keyword name if `self` is a keyword-optional-prefixed ordered sequence:
744	/// either `Group(Ordered([Optional(Ident(kw)), ...]))` or a bare
745	/// `Combinator(Ordered, [Optional(Ident(kw)), ...])` (groups are elided by optimize()).
746	/// These need a named helper struct so that `Peek` includes both the optional keyword
747	/// and the required subsequent tokens.
748	pub fn keyword_prefix_name(&self) -> Option<&str> {
749		let inner = match self {
750			Def::Group(inner, _) => inner.as_ref(),
751			Def::Combinator(_, DefCombinatorStyle::Ordered) => self,
752			_ => return None,
753		};
754		if let Def::Combinator(defs, DefCombinatorStyle::Ordered) = inner
755			&& let Some(Def::Optional(first)) = defs.first()
756			&& let Def::Ident(DefIdent(name)) = first.as_ref()
757		{
758			return Some(name.as_str());
759		}
760		None
761	}
762}
763
764impl Parse for DefIdent {
765	fn parse(input: ParseStream) -> Result<Self> {
766		let mut str = "".to_owned();
767		let mut last_was_ident = false;
768		loop {
769			if input.peek(Token![>]) || input.peek(token::Bracket) {
770				return Ok(Self(str));
771			} else if input.peek(Ident::peek_any) && !last_was_ident {
772				last_was_ident = true;
773				let ident = input.call(Ident::parse_any)?;
774				str.push_str(&ident.to_string());
775			// LitInt might pick up identifier parts like "3d"
776			} else if input.peek(LitInt) && last_was_ident {
777				last_was_ident = true;
778				let int = input.parse::<LitInt>()?;
779				str.push_str(&int.to_string());
780			} else if input.peek(Token![-]) {
781				last_was_ident = false;
782				input.parse::<Token![-]>()?;
783				str.push('-');
784			} else {
785				return Ok(Self(str));
786			}
787		}
788	}
789}
790
791impl Parse for DefRange {
792	fn parse(input: ParseStream) -> Result<Self> {
793		let mut lhs = None;
794		let mut rhs = None;
795		if input.peek(LitFloat) {
796			lhs = Some(input.parse::<LitFloat>()?.base10_parse()?);
797		} else if input.peek(LitInt) {
798			lhs = Some(input.parse::<LitInt>()?.base10_parse::<f32>()?);
799		}
800		if input.peek(Token![,]) {
801			input.parse::<Token![,]>()?;
802			if input.peek(LitFloat) {
803				rhs = Some(input.parse::<LitFloat>()?.base10_parse()?);
804			} else if input.peek(LitInt) {
805				rhs = Some(input.parse::<LitInt>()?.base10_parse::<f32>()?);
806			}
807		} else if let Some(lhs) = lhs {
808			return Ok(Self::Fixed(lhs));
809		}
810		Ok(match (lhs, rhs) {
811			(Some(start), Some(end)) => Self::Range(Range { start, end }),
812			(None, Some(end)) => Self::RangeTo(end),
813			(Some(start), None) => Self::RangeFrom(start),
814			(None, None) => Self::None,
815		})
816	}
817}
818
819pub enum DataType {
820	SingleUnnamedStruct,
821	Enum,
822}
823
824impl DataType {
825	pub fn is_struct(&self) -> bool {
826		matches!(self, Self::SingleUnnamedStruct)
827	}
828
829	pub fn is_enum(&self) -> bool {
830		matches!(self, Self::Enum)
831	}
832}
833
834impl Display for DefIdent {
835	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
836		self.0.fmt(f)
837	}
838}
839
840impl ToTokens for DefIdent {
841	fn to_tokens(&self, tokens: &mut TokenStream) {
842		tokens.append(Ident::new(&self.to_string(), Span::call_site()));
843	}
844}
845
846impl From<DefIdent> for Ident {
847	fn from(value: DefIdent) -> Self {
848		format_ident!("{}", value.0)
849	}
850}
851
852impl From<Ident> for DefIdent {
853	fn from(value: Ident) -> Self {
854		Self(value.to_string())
855	}
856}