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}