devalang_wasm/language/syntax/parser/driver/statements/
extended.rs1use super::super::duration::parse_duration_token;
2use crate::language::syntax::ast::{DurationValue, Statement, StatementKind, Value};
3use anyhow::{Result, anyhow};
4
5pub fn parse_fade(line: &str, line_number: usize) -> Result<Statement> {
8 let trimmed = line.trim_start();
9
10 let rest = if trimmed.starts_with("fade ") {
12 &trimmed[5..]
13 } else {
14 return Err(anyhow!("Invalid fade statement"));
15 };
16
17 let parts: Vec<&str> = rest.split_whitespace().collect();
19 if parts.len() < 5 {
20 return Err(anyhow!(
21 "fade requires format: fade <in|out> <entity>.<property> during <duration>"
22 ));
23 }
24
25 let direction = parts[0].to_string();
26 if direction != "in" && direction != "out" {
27 return Err(anyhow!(
28 "fade direction must be 'in' or 'out', got '{}'",
29 direction
30 ));
31 }
32
33 let target = parts[1].to_string();
34 if !target.contains('.') {
35 return Err(anyhow!(
36 "fade target must be in format entity.property, got '{}'",
37 target
38 ));
39 }
40
41 if parts[2] != "during" {
42 return Err(anyhow!("Expected 'during' keyword in fade statement"));
43 }
44
45 let duration_str = parts[3..].join(" ");
47 let duration = parse_duration_token(&duration_str)?;
48
49 Ok(Statement::new(
50 StatementKind::Fade {
51 direction,
52 target,
53 duration,
54 },
55 Value::Null,
56 0,
57 line_number,
58 1,
59 ))
60}
61
62pub fn parse_section(
66 parts: impl Iterator<Item = impl AsRef<str>>,
67 line_number: usize,
68) -> Result<Statement> {
69 let parts_vec: Vec<String> = parts.map(|p| p.as_ref().to_string()).collect();
70
71 if parts_vec.is_empty() {
72 return Err(anyhow!("section requires a name"));
73 }
74
75 let name = parts_vec[0].trim_end_matches(':').to_string();
76
77 let duration = if parts_vec.len() > 1 && parts_vec[1] == "during" {
79 if parts_vec.len() > 2 {
81 let duration_str = parts_vec[2..].join(" ").trim_end_matches(':').to_string();
83 Some(parse_duration_token(&duration_str)?)
84 } else {
85 return Err(anyhow!("section 'during' requires a duration value"));
86 }
87 } else {
88 None
90 };
91
92 Ok(Statement::new(
93 StatementKind::Section {
94 name,
95 duration,
96 body: Vec::new(), },
98 Value::Null,
99 0,
100 line_number,
101 1,
102 ))
103}
104
105pub fn parse_timeline(line: &str, line_number: usize) -> Result<Statement> {
108 let trimmed = line.trim_start();
109
110 let rest = if trimmed.starts_with("timeline ") {
112 &trimmed[9..]
113 } else {
114 return Err(anyhow!("Invalid timeline statement"));
115 };
116
117 if !rest.starts_with('[') || !rest.ends_with(']') {
119 return Err(anyhow!(
120 "timeline requires format: timeline [section_name, ...]"
121 ));
122 }
123
124 let inner = &rest[1..rest.len() - 1];
125 let mut sections = Vec::new();
126
127 for entry in inner.split(',') {
129 let entry = entry.trim();
130
131 if let Some(during_idx) = entry.find(" during ") {
133 let section_name = entry[..during_idx].trim().to_string();
134 let duration_str = entry[during_idx + 8..].trim();
135
136 let duration = parse_duration_token(duration_str)?;
137 sections.push((section_name, Some(duration)));
138 } else {
139 sections.push((entry.to_string(), None));
141 }
142 }
143
144 if sections.is_empty() {
145 return Err(anyhow!("timeline must have at least one section"));
146 }
147
148 Ok(Statement::new(
149 StatementKind::Timeline { sections },
150 Value::Null,
151 0,
152 line_number,
153 1,
154 ))
155}
156
157pub fn parse_schedule(_line: &str, line_number: usize) -> Result<Statement> {
168 Ok(Statement::new(
171 StatementKind::Schedule {
172 events: Vec::new(), },
174 Value::Null,
175 0,
176 line_number,
177 1,
178 ))
179}
180
181pub fn parse_stop(line: &str, line_number: usize) -> Result<Statement> {
190 let trimmed = line.trim_start();
191
192 let rest = if trimmed.starts_with("stop ") {
194 &trimmed[5..]
195 } else if trimmed.starts_with("silence") {
196 return Ok(Statement::new(
198 StatementKind::Stop {
199 target: None,
200 after: None,
201 },
202 Value::Null,
203 0,
204 line_number,
205 1,
206 ));
207 } else if trimmed.starts_with("mute ") {
208 &trimmed[5..]
209 } else {
210 return Err(anyhow!("Invalid stop statement"));
211 };
212
213 let parts: Vec<&str> = rest.split_whitespace().collect();
214
215 if parts.is_empty() {
216 return Err(anyhow!(
217 "stop requires format: stop <entity|all> [after <duration>]"
218 ));
219 }
220
221 if parts[0] == "all" {
223 let after = if parts.len() > 2 && parts[1] == "after" {
225 let duration_str = parts[2..].join(" ");
226 Some(parse_duration_token(&duration_str)?)
227 } else if parts.len() > 1 {
228 return Err(anyhow!(
229 "Invalid stop all syntax. Expected: stop all [after <duration>]"
230 ));
231 } else {
232 None
233 };
234
235 Ok(Statement::new(
236 StatementKind::Stop {
237 target: None,
238 after,
239 },
240 Value::Null,
241 0,
242 line_number,
243 1,
244 ))
245 } else {
246 let target = parts[0].to_string();
248
249 let after = if parts.len() > 2 && parts[1] == "after" {
251 let duration_str = parts[2..].join(" ");
252 Some(parse_duration_token(&duration_str)?)
253 } else if parts.len() > 1 {
254 return Err(anyhow!(
255 "Invalid stop syntax. Expected: stop <entity> [after <duration>]"
256 ));
257 } else {
258 None
259 };
260
261 Ok(Statement::new(
262 StatementKind::Stop {
263 target: Some(target),
264 after,
265 },
266 Value::Null,
267 0,
268 line_number,
269 1,
270 ))
271 }
272}
273
274pub fn parse_schedule_event_header(line: &str) -> Result<(DurationValue, Option<DurationValue>)> {
281 let trimmed = line.trim_start();
282
283 if !trimmed.starts_with("at ") {
284 return Err(anyhow!("Schedule event must start with 'at'"));
285 }
286
287 let rest = &trimmed[3..];
288 let rest = rest.trim_end_matches(':').trim();
289
290 if let Some(and_during_idx) = rest.find(" and during ") {
292 let start_str = rest[..and_during_idx].trim();
294 let duration_str = rest[and_during_idx + 12..].trim(); let start_time = parse_duration_token(start_str)?;
297 let optional_duration = parse_duration_token(duration_str)?;
298
299 Ok((start_time, Some(optional_duration)))
300 } else {
301 let start_time = parse_duration_token(rest)?;
303 Ok((start_time, None))
304 }
305}