1use crate::language::syntax::ast::DurationValue;
2use anyhow::{Result, anyhow};
3
4pub fn parse_duration_token(token: &str) -> Result<DurationValue> {
5 let token = token.trim();
6 if token.eq_ignore_ascii_case("auto") {
7 return Ok(DurationValue::Auto);
8 }
9
10 if let Some(value) = token.strip_suffix("ms") {
12 let ms: f32 = value
13 .parse()
14 .map_err(|_| anyhow!("invalid milliseconds duration: '{}'", token))?;
15 return Ok(DurationValue::Milliseconds(ms));
16 }
17
18 if let Some(result) = parse_temporal_duration(token) {
20 return Ok(result);
21 }
22
23 if let Some(fraction) = parse_fraction(token) {
25 return Ok(DurationValue::Beats(fraction));
26 }
27
28 if let Ok(number) = token.parse::<f32>() {
30 return Ok(DurationValue::Milliseconds(number));
31 }
32
33 Ok(DurationValue::Identifier(token.to_string()))
35}
36
37fn parse_temporal_duration(token: &str) -> Option<DurationValue> {
41 let parts_with_space: Vec<&str> = token.split_whitespace().collect();
43 if parts_with_space.len() == 2 {
44 if let Some(result) = try_parse_temporal(parts_with_space[0], parts_with_space[1]) {
45 return Some(result);
46 }
47 }
48
49 let split_pos = token.chars().position(|c| c.is_alphabetic())?;
52 if split_pos == 0 {
53 return None; }
55
56 let num_str = &token[..split_pos];
57 let unit_str = &token[split_pos..];
58
59 try_parse_temporal(num_str, unit_str)
60}
61
62fn try_parse_temporal(num_str: &str, unit_str: &str) -> Option<DurationValue> {
64 let count: f32 = num_str.trim().parse().ok()?;
65 let unit = unit_str.trim().to_lowercase();
66
67 let unit_singular = if unit.ends_with('s') && unit.len() > 1 {
69 &unit[..unit.len() - 1]
70 } else {
71 &unit
72 };
73
74 let multiplier = match unit_singular {
79 "step" => 0.25,
80 "beat" => 1.0,
81 "bar" => 4.0,
82 "measure" => 4.0,
83 _ => return None,
84 };
85
86 let beat_count = count * multiplier;
89 let identifier = format!("__temporal__{}_beat", beat_count);
90 Some(DurationValue::Identifier(identifier))
91}
92
93fn parse_fraction(token: &str) -> Option<f32> {
94 let mut split = token.split('/');
95 let numerator: f32 = split.next()?.trim().parse().ok()?;
96 let denominator: f32 = split.next()?.trim().parse().ok()?;
97 if denominator.abs() < f32::EPSILON {
98 return None;
99 }
100 Some(numerator / denominator)
101}
102
103#[cfg(test)]
104mod tests {
105 use super::*;
106
107 #[test]
108 fn test_duration_auto() {
109 assert!(matches!(
110 parse_duration_token("auto"),
111 Ok(DurationValue::Auto)
112 ));
113 assert!(matches!(
114 parse_duration_token("AUTO"),
115 Ok(DurationValue::Auto)
116 ));
117 }
118
119 #[test]
120 fn test_duration_milliseconds() {
121 match parse_duration_token("500") {
123 Ok(DurationValue::Milliseconds(ms)) => assert_eq!(ms, 500.0),
124 _ => panic!("Expected milliseconds"),
125 }
126
127 match parse_duration_token("500ms") {
129 Ok(DurationValue::Milliseconds(ms)) => assert_eq!(ms, 500.0),
130 _ => panic!("Expected milliseconds"),
131 }
132
133 match parse_duration_token("1000ms") {
134 Ok(DurationValue::Milliseconds(ms)) => assert_eq!(ms, 1000.0),
135 _ => panic!("Expected milliseconds"),
136 }
137 }
138
139 #[test]
140 fn test_duration_fractions() {
141 match parse_duration_token("1/4") {
143 Ok(DurationValue::Beats(b)) => assert_eq!(b, 0.25),
144 _ => panic!("Expected beats"),
145 }
146
147 match parse_duration_token("1/2") {
149 Ok(DurationValue::Beats(b)) => assert_eq!(b, 0.5),
150 _ => panic!("Expected beats"),
151 }
152
153 match parse_duration_token("1/1") {
155 Ok(DurationValue::Beats(b)) => assert_eq!(b, 1.0),
156 _ => panic!("Expected beats"),
157 }
158 }
159
160 #[test]
161 fn test_temporal_beat_with_space() {
162 match parse_duration_token("1 beat") {
164 Ok(DurationValue::Identifier(id)) => assert_eq!(id, "__temporal__1_beats"),
165 other => panic!("Unexpected result: {:?}", other),
166 }
167
168 match parse_duration_token("2 beat") {
170 Ok(DurationValue::Identifier(id)) => assert_eq!(id, "__temporal__2_beats"),
171 other => panic!("Unexpected result: {:?}", other),
172 }
173
174 match parse_duration_token("1 beats") {
176 Ok(DurationValue::Identifier(id)) => assert_eq!(id, "__temporal__1_beats"),
177 other => panic!("Unexpected result: {:?}", other),
178 }
179
180 match parse_duration_token("3 beats") {
182 Ok(DurationValue::Identifier(id)) => assert_eq!(id, "__temporal__3_beats"),
183 other => panic!("Unexpected result: {:?}", other),
184 }
185 }
186
187 #[test]
188 fn test_temporal_beat_without_space() {
189 match parse_duration_token("1beat") {
191 Ok(DurationValue::Identifier(id)) => assert_eq!(id, "__temporal__1_beats"),
192 other => panic!("Unexpected result: {:?}", other),
193 }
194
195 match parse_duration_token("2beat") {
197 Ok(DurationValue::Identifier(id)) => assert_eq!(id, "__temporal__2_beats"),
198 other => panic!("Unexpected result: {:?}", other),
199 }
200
201 match parse_duration_token("1beats") {
203 Ok(DurationValue::Identifier(id)) => assert_eq!(id, "__temporal__1_beats"),
204 other => panic!("Unexpected result: {:?}", other),
205 }
206
207 match parse_duration_token("3beats") {
209 Ok(DurationValue::Identifier(id)) => assert_eq!(id, "__temporal__3_beats"),
210 other => panic!("Unexpected result: {:?}", other),
211 }
212 }
213
214 #[test]
215 fn test_temporal_bar_with_space() {
216 match parse_duration_token("1 bar") {
218 Ok(DurationValue::Identifier(id)) => assert_eq!(id, "__temporal__4_bars"),
219 other => panic!("Unexpected result: {:?}", other),
220 }
221
222 match parse_duration_token("2 bar") {
224 Ok(DurationValue::Identifier(id)) => assert_eq!(id, "__temporal__8_bars"),
225 other => panic!("Unexpected result: {:?}", other),
226 }
227
228 match parse_duration_token("1 bars") {
230 Ok(DurationValue::Identifier(id)) => assert_eq!(id, "__temporal__4_bars"),
231 other => panic!("Unexpected result: {:?}", other),
232 }
233
234 match parse_duration_token("3 bars") {
236 Ok(DurationValue::Identifier(id)) => assert_eq!(id, "__temporal__12_bars"),
237 other => panic!("Unexpected result: {:?}", other),
238 }
239 }
240
241 #[test]
242 fn test_temporal_bar_without_space() {
243 match parse_duration_token("1bar") {
245 Ok(DurationValue::Identifier(id)) => assert_eq!(id, "__temporal__4_bars"),
246 other => panic!("Unexpected result: {:?}", other),
247 }
248
249 match parse_duration_token("2bar") {
251 Ok(DurationValue::Identifier(id)) => assert_eq!(id, "__temporal__8_bars"),
252 other => panic!("Unexpected result: {:?}", other),
253 }
254
255 match parse_duration_token("1bars") {
257 Ok(DurationValue::Identifier(id)) => assert_eq!(id, "__temporal__4_bars"),
258 other => panic!("Unexpected result: {:?}", other),
259 }
260
261 match parse_duration_token("3bars") {
263 Ok(DurationValue::Identifier(id)) => assert_eq!(id, "__temporal__12_bars"),
264 other => panic!("Unexpected result: {:?}", other),
265 }
266 }
267
268 #[test]
269 fn test_temporal_measure_with_space() {
270 match parse_duration_token("1 measure") {
272 Ok(DurationValue::Identifier(id)) => assert_eq!(id, "__temporal__4_measures"),
273 other => panic!("Unexpected result: {:?}", other),
274 }
275
276 match parse_duration_token("2 measure") {
278 Ok(DurationValue::Identifier(id)) => assert_eq!(id, "__temporal__8_measures"),
279 other => panic!("Unexpected result: {:?}", other),
280 }
281
282 match parse_duration_token("1 measures") {
284 Ok(DurationValue::Identifier(id)) => assert_eq!(id, "__temporal__4_measures"),
285 other => panic!("Unexpected result: {:?}", other),
286 }
287
288 match parse_duration_token("3 measures") {
290 Ok(DurationValue::Identifier(id)) => assert_eq!(id, "__temporal__12_measures"),
291 other => panic!("Unexpected result: {:?}", other),
292 }
293 }
294
295 #[test]
296 fn test_temporal_measure_without_space() {
297 match parse_duration_token("1measure") {
299 Ok(DurationValue::Identifier(id)) => assert_eq!(id, "__temporal__4_measures"),
300 other => panic!("Unexpected result: {:?}", other),
301 }
302
303 match parse_duration_token("2measure") {
305 Ok(DurationValue::Identifier(id)) => assert_eq!(id, "__temporal__8_measures"),
306 other => panic!("Unexpected result: {:?}", other),
307 }
308
309 match parse_duration_token("1measures") {
311 Ok(DurationValue::Identifier(id)) => assert_eq!(id, "__temporal__4_measures"),
312 other => panic!("Unexpected result: {:?}", other),
313 }
314
315 match parse_duration_token("3measures") {
317 Ok(DurationValue::Identifier(id)) => assert_eq!(id, "__temporal__12_measures"),
318 other => panic!("Unexpected result: {:?}", other),
319 }
320 }
321
322 #[test]
323 fn test_temporal_step_with_space() {
324 match parse_duration_token("1 step") {
326 Ok(DurationValue::Identifier(id)) => assert_eq!(id, "__temporal__0.25_steps"),
327 other => panic!("Unexpected result: {:?}", other),
328 }
329
330 match parse_duration_token("4 step") {
332 Ok(DurationValue::Identifier(id)) => assert_eq!(id, "__temporal__1_steps"),
333 other => panic!("Unexpected result: {:?}", other),
334 }
335
336 match parse_duration_token("1 steps") {
338 Ok(DurationValue::Identifier(id)) => assert_eq!(id, "__temporal__0.25_steps"),
339 other => panic!("Unexpected result: {:?}", other),
340 }
341
342 match parse_duration_token("16 steps") {
344 Ok(DurationValue::Identifier(id)) => assert_eq!(id, "__temporal__4_steps"),
345 other => panic!("Unexpected result: {:?}", other),
346 }
347 }
348
349 #[test]
350 fn test_temporal_step_without_space() {
351 match parse_duration_token("1step") {
353 Ok(DurationValue::Identifier(id)) => assert_eq!(id, "__temporal__0.25_steps"),
354 other => panic!("Unexpected result: {:?}", other),
355 }
356
357 match parse_duration_token("4step") {
359 Ok(DurationValue::Identifier(id)) => assert_eq!(id, "__temporal__1_steps"),
360 other => panic!("Unexpected result: {:?}", other),
361 }
362
363 match parse_duration_token("1steps") {
365 Ok(DurationValue::Identifier(id)) => assert_eq!(id, "__temporal__0.25_steps"),
366 other => panic!("Unexpected result: {:?}", other),
367 }
368
369 match parse_duration_token("16steps") {
371 Ok(DurationValue::Identifier(id)) => assert_eq!(id, "__temporal__4_steps"),
372 other => panic!("Unexpected result: {:?}", other),
373 }
374 }
375
376 #[test]
377 fn test_mixed_case() {
378 match parse_duration_token("1 BEAT") {
380 Ok(DurationValue::Identifier(id)) => assert_eq!(id, "__temporal__1_beats"),
381 other => panic!("Unexpected result: {:?}", other),
382 }
383
384 match parse_duration_token("2 Bar") {
385 Ok(DurationValue::Identifier(id)) => assert_eq!(id, "__temporal__8_bars"),
386 other => panic!("Unexpected result: {:?}", other),
387 }
388
389 match parse_duration_token("1MEASURE") {
390 Ok(DurationValue::Identifier(id)) => assert_eq!(id, "__temporal__4_measures"),
391 other => panic!("Unexpected result: {:?}", other),
392 }
393 }
394
395 #[test]
396 fn test_float_values() {
397 match parse_duration_token("0.5 beat") {
399 Ok(DurationValue::Identifier(id)) => assert_eq!(id, "__temporal__0.5_beats"),
400 other => panic!("Unexpected result: {:?}", other),
401 }
402
403 match parse_duration_token("1.5bar") {
405 Ok(DurationValue::Identifier(id)) => assert_eq!(id, "__temporal__6_bars"),
406 other => panic!("Unexpected result: {:?}", other),
407 }
408
409 match parse_duration_token("2.5 measures") {
411 Ok(DurationValue::Identifier(id)) => assert_eq!(id, "__temporal__10_measures"),
412 other => panic!("Unexpected result: {:?}", other),
413 }
414 }
415}