devalang_wasm/language/syntax/parser/driver/
duration.rs1use 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 {
78 "beat" => 1.0,
79 "bar" => 4.0,
80 "measure" => 4.0,
81 _ => return None,
82 };
83
84 let identifier = format!("__temporal__{}_{}s", count * multiplier, unit_singular);
87 Some(DurationValue::Identifier(identifier))
88}
89
90fn parse_fraction(token: &str) -> Option<f32> {
91 let mut split = token.split('/');
92 let numerator: f32 = split.next()?.trim().parse().ok()?;
93 let denominator: f32 = split.next()?.trim().parse().ok()?;
94 if denominator.abs() < f32::EPSILON {
95 return None;
96 }
97 Some(numerator / denominator)
98}
99
100#[cfg(test)]
101mod tests {
102 use super::*;
103
104 #[test]
105 fn test_duration_auto() {
106 assert!(matches!(
107 parse_duration_token("auto"),
108 Ok(DurationValue::Auto)
109 ));
110 assert!(matches!(
111 parse_duration_token("AUTO"),
112 Ok(DurationValue::Auto)
113 ));
114 }
115
116 #[test]
117 fn test_duration_milliseconds() {
118 match parse_duration_token("500") {
120 Ok(DurationValue::Milliseconds(ms)) => assert_eq!(ms, 500.0),
121 _ => panic!("Expected milliseconds"),
122 }
123
124 match parse_duration_token("500ms") {
126 Ok(DurationValue::Milliseconds(ms)) => assert_eq!(ms, 500.0),
127 _ => panic!("Expected milliseconds"),
128 }
129
130 match parse_duration_token("1000ms") {
131 Ok(DurationValue::Milliseconds(ms)) => assert_eq!(ms, 1000.0),
132 _ => panic!("Expected milliseconds"),
133 }
134 }
135
136 #[test]
137 fn test_duration_fractions() {
138 match parse_duration_token("1/4") {
140 Ok(DurationValue::Beats(b)) => assert_eq!(b, 0.25),
141 _ => panic!("Expected beats"),
142 }
143
144 match parse_duration_token("1/2") {
146 Ok(DurationValue::Beats(b)) => assert_eq!(b, 0.5),
147 _ => panic!("Expected beats"),
148 }
149
150 match parse_duration_token("1/1") {
152 Ok(DurationValue::Beats(b)) => assert_eq!(b, 1.0),
153 _ => panic!("Expected beats"),
154 }
155 }
156
157 #[test]
158 fn test_temporal_beat_with_space() {
159 match parse_duration_token("1 beat") {
161 Ok(DurationValue::Identifier(id)) => assert_eq!(id, "__temporal__1_beats"),
162 other => panic!("Unexpected result: {:?}", other),
163 }
164
165 match parse_duration_token("2 beat") {
167 Ok(DurationValue::Identifier(id)) => assert_eq!(id, "__temporal__2_beats"),
168 other => panic!("Unexpected result: {:?}", other),
169 }
170
171 match parse_duration_token("1 beats") {
173 Ok(DurationValue::Identifier(id)) => assert_eq!(id, "__temporal__1_beats"),
174 other => panic!("Unexpected result: {:?}", other),
175 }
176
177 match parse_duration_token("3 beats") {
179 Ok(DurationValue::Identifier(id)) => assert_eq!(id, "__temporal__3_beats"),
180 other => panic!("Unexpected result: {:?}", other),
181 }
182 }
183
184 #[test]
185 fn test_temporal_beat_without_space() {
186 match parse_duration_token("1beat") {
188 Ok(DurationValue::Identifier(id)) => assert_eq!(id, "__temporal__1_beats"),
189 other => panic!("Unexpected result: {:?}", other),
190 }
191
192 match parse_duration_token("2beat") {
194 Ok(DurationValue::Identifier(id)) => assert_eq!(id, "__temporal__2_beats"),
195 other => panic!("Unexpected result: {:?}", other),
196 }
197
198 match parse_duration_token("1beats") {
200 Ok(DurationValue::Identifier(id)) => assert_eq!(id, "__temporal__1_beats"),
201 other => panic!("Unexpected result: {:?}", other),
202 }
203
204 match parse_duration_token("3beats") {
206 Ok(DurationValue::Identifier(id)) => assert_eq!(id, "__temporal__3_beats"),
207 other => panic!("Unexpected result: {:?}", other),
208 }
209 }
210
211 #[test]
212 fn test_temporal_bar_with_space() {
213 match parse_duration_token("1 bar") {
215 Ok(DurationValue::Identifier(id)) => assert_eq!(id, "__temporal__4_bars"),
216 other => panic!("Unexpected result: {:?}", other),
217 }
218
219 match parse_duration_token("2 bar") {
221 Ok(DurationValue::Identifier(id)) => assert_eq!(id, "__temporal__8_bars"),
222 other => panic!("Unexpected result: {:?}", other),
223 }
224
225 match parse_duration_token("1 bars") {
227 Ok(DurationValue::Identifier(id)) => assert_eq!(id, "__temporal__4_bars"),
228 other => panic!("Unexpected result: {:?}", other),
229 }
230
231 match parse_duration_token("3 bars") {
233 Ok(DurationValue::Identifier(id)) => assert_eq!(id, "__temporal__12_bars"),
234 other => panic!("Unexpected result: {:?}", other),
235 }
236 }
237
238 #[test]
239 fn test_temporal_bar_without_space() {
240 match parse_duration_token("1bar") {
242 Ok(DurationValue::Identifier(id)) => assert_eq!(id, "__temporal__4_bars"),
243 other => panic!("Unexpected result: {:?}", other),
244 }
245
246 match parse_duration_token("2bar") {
248 Ok(DurationValue::Identifier(id)) => assert_eq!(id, "__temporal__8_bars"),
249 other => panic!("Unexpected result: {:?}", other),
250 }
251
252 match parse_duration_token("1bars") {
254 Ok(DurationValue::Identifier(id)) => assert_eq!(id, "__temporal__4_bars"),
255 other => panic!("Unexpected result: {:?}", other),
256 }
257
258 match parse_duration_token("3bars") {
260 Ok(DurationValue::Identifier(id)) => assert_eq!(id, "__temporal__12_bars"),
261 other => panic!("Unexpected result: {:?}", other),
262 }
263 }
264
265 #[test]
266 fn test_temporal_measure_with_space() {
267 match parse_duration_token("1 measure") {
269 Ok(DurationValue::Identifier(id)) => assert_eq!(id, "__temporal__4_measures"),
270 other => panic!("Unexpected result: {:?}", other),
271 }
272
273 match parse_duration_token("2 measure") {
275 Ok(DurationValue::Identifier(id)) => assert_eq!(id, "__temporal__8_measures"),
276 other => panic!("Unexpected result: {:?}", other),
277 }
278
279 match parse_duration_token("1 measures") {
281 Ok(DurationValue::Identifier(id)) => assert_eq!(id, "__temporal__4_measures"),
282 other => panic!("Unexpected result: {:?}", other),
283 }
284
285 match parse_duration_token("3 measures") {
287 Ok(DurationValue::Identifier(id)) => assert_eq!(id, "__temporal__12_measures"),
288 other => panic!("Unexpected result: {:?}", other),
289 }
290 }
291
292 #[test]
293 fn test_temporal_measure_without_space() {
294 match parse_duration_token("1measure") {
296 Ok(DurationValue::Identifier(id)) => assert_eq!(id, "__temporal__4_measures"),
297 other => panic!("Unexpected result: {:?}", other),
298 }
299
300 match parse_duration_token("2measure") {
302 Ok(DurationValue::Identifier(id)) => assert_eq!(id, "__temporal__8_measures"),
303 other => panic!("Unexpected result: {:?}", other),
304 }
305
306 match parse_duration_token("1measures") {
308 Ok(DurationValue::Identifier(id)) => assert_eq!(id, "__temporal__4_measures"),
309 other => panic!("Unexpected result: {:?}", other),
310 }
311
312 match parse_duration_token("3measures") {
314 Ok(DurationValue::Identifier(id)) => assert_eq!(id, "__temporal__12_measures"),
315 other => panic!("Unexpected result: {:?}", other),
316 }
317 }
318
319 #[test]
320 fn test_mixed_case() {
321 match parse_duration_token("1 BEAT") {
323 Ok(DurationValue::Identifier(id)) => assert_eq!(id, "__temporal__1_beats"),
324 other => panic!("Unexpected result: {:?}", other),
325 }
326
327 match parse_duration_token("2 Bar") {
328 Ok(DurationValue::Identifier(id)) => assert_eq!(id, "__temporal__8_bars"),
329 other => panic!("Unexpected result: {:?}", other),
330 }
331
332 match parse_duration_token("1MEASURE") {
333 Ok(DurationValue::Identifier(id)) => assert_eq!(id, "__temporal__4_measures"),
334 other => panic!("Unexpected result: {:?}", other),
335 }
336 }
337
338 #[test]
339 fn test_float_values() {
340 match parse_duration_token("0.5 beat") {
342 Ok(DurationValue::Identifier(id)) => assert_eq!(id, "__temporal__0.5_beats"),
343 other => panic!("Unexpected result: {:?}", other),
344 }
345
346 match parse_duration_token("1.5bar") {
348 Ok(DurationValue::Identifier(id)) => assert_eq!(id, "__temporal__6_bars"),
349 other => panic!("Unexpected result: {:?}", other),
350 }
351
352 match parse_duration_token("2.5 measures") {
354 Ok(DurationValue::Identifier(id)) => assert_eq!(id, "__temporal__10_measures"),
355 other => panic!("Unexpected result: {:?}", other),
356 }
357 }
358}