Skip to main content

css_ast/types/
generic_voice.rs

1use super::prelude::*;
2use crate::NumericValue;
3
4/// <https://drafts.csswg.org/css-speech-1/#typedef-voice-family-age>
5///
6/// ```text,ignore
7/// <age> = child | young | old
8/// ```
9#[node]
10#[derive(Parse, Peek, ToCursors, ToSpan, SemanticEq, Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
11#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
12#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit(skip))]
13#[derive(csskit_derives::NodeWithMetadata)]
14pub enum VoiceAge {
15	#[atom(CssAtomSet::Child)]
16	Child(T![Ident]),
17	#[atom(CssAtomSet::Young)]
18	Young(T![Ident]),
19	#[atom(CssAtomSet::Old)]
20	Old(T![Ident]),
21}
22
23/// <https://drafts.csswg.org/css-speech-1/#typedef-voice-family-gender>
24///
25/// ```text,ignore
26/// <gender> = male | female | neutral
27/// ```
28#[node]
29#[derive(Parse, Peek, ToCursors, ToSpan, SemanticEq, Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
30#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
31#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit(skip))]
32#[derive(csskit_derives::NodeWithMetadata)]
33pub enum VoiceGender {
34	#[atom(CssAtomSet::Male)]
35	Male(T![Ident]),
36	#[atom(CssAtomSet::Female)]
37	Female(T![Ident]),
38	#[atom(CssAtomSet::Neutral)]
39	Neutral(T![Ident]),
40}
41
42/// <https://drafts.csswg.org/css-speech-1/#typedef-generic-voice>
43///
44/// ```text,ignore
45/// <generic-voice> = <age>? <gender> <integer>?
46/// ```
47#[node]
48#[derive(ToCursors, ToSpan, SemanticEq, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
49#[cfg_attr(feature = "serde", derive(serde::Serialize), serde())]
50#[cfg_attr(feature = "visitable", derive(csskit_derives::Visitable), visit)]
51#[derive(csskit_derives::NodeWithMetadata)]
52pub struct GenericVoice<'a> {
53	pub age: Option<VoiceAge>,
54	pub gender: VoiceGender,
55	pub variant: Option<NumericValue<'a, T![Number]>>,
56}
57
58impl<'a> Peek<'a> for GenericVoice<'a> {
59	const PEEK_KINDSET: KindSet = VoiceAge::PEEK_KINDSET.combine(VoiceGender::PEEK_KINDSET);
60
61	#[inline(always)]
62	fn peek<I>(p: &Parser<'a, I>, c: Cursor) -> bool
63	where
64		I: Iterator<Item = Cursor> + Clone,
65	{
66		VoiceAge::peek(p, c) || VoiceGender::peek(p, c)
67	}
68}
69
70impl<'a> Parse<'a> for GenericVoice<'a> {
71	fn parse<I>(p: &mut Parser<'a, I>) -> ParserResult<Self>
72	where
73		I: Iterator<Item = Cursor> + Clone,
74	{
75		let age = p.parse_if_peek::<VoiceAge>()?;
76		let gender = p.parse::<VoiceGender>()?;
77		let variant = p.parse_if_peek::<NumericValue<T![Number]>>()?;
78		Ok(Self { age, gender, variant })
79	}
80}
81
82#[cfg(test)]
83mod tests {
84	use super::*;
85	use crate::CssAtomSet;
86	use css_parse::{assert_parse, assert_parse_error, assert_peek_false};
87
88	#[test]
89	fn test_parses() {
90		assert_parse!(CssAtomSet::ATOMS, GenericVoice, "male");
91		assert_parse!(CssAtomSet::ATOMS, GenericVoice, "female");
92		assert_parse!(CssAtomSet::ATOMS, GenericVoice, "neutral");
93		assert_parse!(CssAtomSet::ATOMS, GenericVoice, "child male");
94		assert_parse!(CssAtomSet::ATOMS, GenericVoice, "young female 2");
95		assert_parse!(CssAtomSet::ATOMS, GenericVoice, "old neutral 1");
96	}
97
98	#[test]
99	fn test_substitution() {
100		assert_parse!(CssAtomSet::ATOMS, GenericVoice, "male var(--n)");
101		assert_parse!(CssAtomSet::ATOMS, GenericVoice, "young female calc(1 + 1)");
102	}
103
104	#[test]
105	fn test_errors() {
106		assert_peek_false!(CssAtomSet::ATOMS, GenericVoice, "");
107		assert_parse_error!(CssAtomSet::ATOMS, GenericVoice, "child");
108		assert_peek_false!(CssAtomSet::ATOMS, GenericVoice, "auto");
109	}
110}