Skip to main content

css_ast/values/speech/
impls.rs

1use super::{VoicePitchStyleValue, VoiceRangeStyleValue};
2use crate::{CalcableValue, CssAtomSet, Frequency, KeywordValue, NonNegative, Percentage, Semitones, Value};
3use css_parse::{Cursor, Diagnostic, Parse, Parser, Peek, Result as ParseResult, T};
4
5macro_rules! voice_pitch_parse {
6	($ty:ident) => {
7		impl<'a> Parse<'a> for $ty<'a> {
8			fn parse<I>(p: &mut Parser<'a, I>) -> ParseResult<Self>
9			where
10				I: Iterator<Item = Cursor> + Clone,
11			{
12				let c = p.peek_n(1);
13				if <T![Ident]>::peek(p, c) && p.equals_atom(c, &CssAtomSet::Absolute) {
14					let absolute = p.parse::<KeywordValue<'a, T![Ident]>>()?;
15					return Ok(Self::FrequencyAbsolute(
16						p.parse::<CalcableValue<'a, NonNegative<Frequency>>>()?,
17						absolute,
18					));
19				}
20				let mut keyword = if <T![Ident]>::peek(p, c)
21					&& matches!(
22						p.to_atom::<CssAtomSet>(c),
23						CssAtomSet::XLow | CssAtomSet::Low | CssAtomSet::Medium | CssAtomSet::High | CssAtomSet::XHigh
24					) {
25					Some((p.to_atom::<CssAtomSet>(c), p.parse::<KeywordValue<'a, T![Ident]>>()?))
26				} else {
27					None
28				};
29				let mut frequency = None;
30				let mut semitones = None;
31				let mut percentage = None;
32				if p.peek::<CalcableValue<'a, NonNegative<Frequency>>>() {
33					frequency = Some(p.parse::<CalcableValue<'a, NonNegative<Frequency>>>()?);
34				} else if p.peek::<Value<'a, Semitones>>() {
35					semitones = Some(p.parse::<Value<'a, Semitones>>()?);
36				} else if p.peek::<CalcableValue<'a, Percentage>>() {
37					percentage = Some(p.parse::<CalcableValue<'a, Percentage>>()?);
38				}
39				let c = p.peek_n(1);
40				if keyword.is_none() && <T![Ident]>::peek(p, c) {
41					if frequency.is_some() && p.equals_atom(c, &CssAtomSet::Absolute) {
42						let absolute = p.parse::<KeywordValue<'a, T![Ident]>>()?;
43						return Ok(Self::FrequencyAbsolute(frequency.expect("checked above"), absolute));
44					}
45					if matches!(
46						p.to_atom::<CssAtomSet>(c),
47						CssAtomSet::XLow | CssAtomSet::Low | CssAtomSet::Medium | CssAtomSet::High | CssAtomSet::XHigh
48					) {
49						keyword = Some((p.to_atom::<CssAtomSet>(c), p.parse::<KeywordValue<'a, T![Ident]>>()?));
50					}
51				}
52				let (atom, keyword) = match keyword {
53					Some((atom, keyword)) => (atom, Some(keyword)),
54					None if frequency.is_none() && semitones.is_none() && percentage.is_none() => {
55						Err(Diagnostic::new(c, Diagnostic::unexpected))?
56					}
57					None => (CssAtomSet::XLow, None),
58				};
59				Ok(match atom {
60					CssAtomSet::XLow if semitones.is_some() => Self::XLow1 { x_low: keyword, semitones },
61					CssAtomSet::XLow if percentage.is_some() => Self::XLow2 { x_low: keyword, percentage },
62					CssAtomSet::XLow => Self::XLow { x_low: keyword, frequency },
63					CssAtomSet::Low if semitones.is_some() => Self::Low1 { low: keyword, semitones },
64					CssAtomSet::Low if percentage.is_some() => Self::Low2 { low: keyword, percentage },
65					CssAtomSet::Low => Self::Low { low: keyword, frequency },
66					CssAtomSet::Medium if semitones.is_some() => Self::Medium1 { medium: keyword, semitones },
67					CssAtomSet::Medium if percentage.is_some() => Self::Medium2 { medium: keyword, percentage },
68					CssAtomSet::Medium => Self::Medium { medium: keyword, frequency },
69					CssAtomSet::High if semitones.is_some() => Self::High1 { high: keyword, semitones },
70					CssAtomSet::High if percentage.is_some() => Self::High2 { high: keyword, percentage },
71					CssAtomSet::High => Self::High { high: keyword, frequency },
72					CssAtomSet::XHigh if semitones.is_some() => Self::XHigh1 { x_high: keyword, semitones },
73					CssAtomSet::XHigh if percentage.is_some() => Self::XHigh2 { x_high: keyword, percentage },
74					CssAtomSet::XHigh => Self::XHigh { x_high: keyword, frequency },
75					_ => Err(Diagnostic::new(c, Diagnostic::unexpected))?,
76				})
77			}
78		}
79	};
80}
81
82voice_pitch_parse!(VoicePitchStyleValue);
83voice_pitch_parse!(VoiceRangeStyleValue);
84
85#[cfg(test)]
86mod tests {
87	use super::super::*;
88	use crate::CssAtomSet;
89	use css_parse::{assert_parse, assert_parse_error, assert_peek_false};
90
91	#[test]
92	fn test_voice_family() {
93		assert_parse!(CssAtomSet::ATOMS, VoiceFamilyStyleValue, "preserve");
94		assert_parse!(CssAtomSet::ATOMS, VoiceFamilyStyleValue, "male");
95		assert_parse!(CssAtomSet::ATOMS, VoiceFamilyStyleValue, "female");
96		assert_parse!(CssAtomSet::ATOMS, VoiceFamilyStyleValue, "neutral");
97		assert_parse!(CssAtomSet::ATOMS, VoiceFamilyStyleValue, "child male");
98		assert_parse!(CssAtomSet::ATOMS, VoiceFamilyStyleValue, "old neutral");
99		assert_parse!(CssAtomSet::ATOMS, VoiceFamilyStyleValue, "Alice");
100		assert_parse!(CssAtomSet::ATOMS, VoiceFamilyStyleValue, "\"Alice\"");
101		assert_parse!(CssAtomSet::ATOMS, VoiceFamilyStyleValue, "Alice,male");
102		assert_peek_false!(CssAtomSet::ATOMS, VoiceFamilyStyleValue, "");
103	}
104
105	#[test]
106	fn test_voice_volume() {
107		assert_parse!(CssAtomSet::ATOMS, VoiceVolumeStyleValue, "silent");
108		assert_parse!(CssAtomSet::ATOMS, VoiceVolumeStyleValue, "x-soft");
109		assert_parse!(CssAtomSet::ATOMS, VoiceVolumeStyleValue, "soft");
110		assert_parse!(CssAtomSet::ATOMS, VoiceVolumeStyleValue, "medium");
111		assert_parse!(CssAtomSet::ATOMS, VoiceVolumeStyleValue, "loud");
112		assert_parse!(CssAtomSet::ATOMS, VoiceVolumeStyleValue, "x-loud");
113		assert_parse!(CssAtomSet::ATOMS, VoiceVolumeStyleValue, "soft 6db");
114		assert_parse!(CssAtomSet::ATOMS, VoiceVolumeStyleValue, "loud -3db");
115		assert_peek_false!(CssAtomSet::ATOMS, VoiceVolumeStyleValue, "");
116		assert_peek_false!(CssAtomSet::ATOMS, VoiceVolumeStyleValue, "1px");
117	}
118
119	#[test]
120	fn test_voice_rate() {
121		assert_parse!(CssAtomSet::ATOMS, VoiceRateStyleValue, "normal");
122		assert_parse!(CssAtomSet::ATOMS, VoiceRateStyleValue, "x-slow");
123		assert_parse!(CssAtomSet::ATOMS, VoiceRateStyleValue, "slow");
124		assert_parse!(CssAtomSet::ATOMS, VoiceRateStyleValue, "medium");
125		assert_parse!(CssAtomSet::ATOMS, VoiceRateStyleValue, "fast");
126		assert_parse!(CssAtomSet::ATOMS, VoiceRateStyleValue, "x-fast");
127		assert_parse!(CssAtomSet::ATOMS, VoiceRateStyleValue, "50%");
128		assert_parse!(CssAtomSet::ATOMS, VoiceRateStyleValue, "normal 50%");
129		assert_peek_false!(CssAtomSet::ATOMS, VoiceRateStyleValue, "");
130		assert_peek_false!(CssAtomSet::ATOMS, VoiceRateStyleValue, "1px");
131	}
132
133	#[test]
134	fn test_voice_pitch() {
135		assert_parse!(CssAtomSet::ATOMS, VoicePitchStyleValue, "absolute 200hz");
136		assert_parse!(CssAtomSet::ATOMS, VoicePitchStyleValue, "200hz absolute");
137		assert_parse!(CssAtomSet::ATOMS, VoicePitchStyleValue, "x-low");
138		assert_parse!(CssAtomSet::ATOMS, VoicePitchStyleValue, "low");
139		assert_parse!(CssAtomSet::ATOMS, VoicePitchStyleValue, "medium");
140		assert_parse!(CssAtomSet::ATOMS, VoicePitchStyleValue, "high");
141		assert_parse!(CssAtomSet::ATOMS, VoicePitchStyleValue, "x-high");
142		assert_parse!(CssAtomSet::ATOMS, VoicePitchStyleValue, "medium 6st");
143		assert_parse!(CssAtomSet::ATOMS, VoicePitchStyleValue, "high -2st");
144		assert_parse!(CssAtomSet::ATOMS, VoicePitchStyleValue, "low 20hz");
145		assert_parse!(CssAtomSet::ATOMS, VoicePitchStyleValue, "50%");
146		assert_parse!(CssAtomSet::ATOMS, VoicePitchStyleValue, "6st medium");
147		assert_parse!(CssAtomSet::ATOMS, VoicePitchStyleValue, "20hz");
148		assert_parse!(CssAtomSet::ATOMS, VoicePitchStyleValue, "6st");
149		assert_parse!(CssAtomSet::ATOMS, VoicePitchStyleValue, "calc(20hz*2)");
150		assert_peek_false!(CssAtomSet::ATOMS, VoicePitchStyleValue, "");
151		assert_peek_false!(CssAtomSet::ATOMS, VoicePitchStyleValue, "1px");
152		assert_parse_error!(CssAtomSet::ATOMS, VoicePitchStyleValue, "absolute -200hz");
153	}
154
155	#[test]
156	fn test_voice_range() {
157		assert_parse!(CssAtomSet::ATOMS, VoiceRangeStyleValue, "absolute 200hz");
158		assert_parse!(CssAtomSet::ATOMS, VoiceRangeStyleValue, "x-low");
159		assert_parse!(CssAtomSet::ATOMS, VoiceRangeStyleValue, "x-high 30%");
160		assert_parse!(CssAtomSet::ATOMS, VoiceRangeStyleValue, "6st");
161		assert_peek_false!(CssAtomSet::ATOMS, VoiceRangeStyleValue, "");
162		assert_peek_false!(CssAtomSet::ATOMS, VoiceRangeStyleValue, "1px");
163	}
164
165	#[test]
166	fn test_writes() {
167		assert_parse!(CssAtomSet::ATOMS, CueBeforeStyleValue, "none");
168		assert_parse!(CssAtomSet::ATOMS, CueBeforeStyleValue, "url(foo)");
169		assert_parse!(CssAtomSet::ATOMS, CueBeforeStyleValue, "url(foo)20db");
170		assert_parse!(CssAtomSet::ATOMS, CueAfterStyleValue, "none");
171		assert_parse!(CssAtomSet::ATOMS, CueAfterStyleValue, "url(foo)");
172		assert_parse!(CssAtomSet::ATOMS, CueAfterStyleValue, "url(foo)20db");
173	}
174}