use super::super::duration::parse_duration_token;
use crate::language::syntax::ast::{DurationValue, Statement, StatementKind, Value};
use anyhow::{Result, anyhow};
pub fn parse_fade(line: &str, line_number: usize) -> Result<Statement> {
let trimmed = line.trim_start();
let rest = if trimmed.starts_with("fade ") {
&trimmed[5..]
} else {
return Err(anyhow!("Invalid fade statement"));
};
let parts: Vec<&str> = rest.split_whitespace().collect();
if parts.len() < 5 {
return Err(anyhow!(
"fade requires format: fade <in|out> <entity>.<property> during <duration>"
));
}
let direction = parts[0].to_string();
if direction != "in" && direction != "out" {
return Err(anyhow!(
"fade direction must be 'in' or 'out', got '{}'",
direction
));
}
let target = parts[1].to_string();
if !target.contains('.') {
return Err(anyhow!(
"fade target must be in format entity.property, got '{}'",
target
));
}
if parts[2] != "during" {
return Err(anyhow!("Expected 'during' keyword in fade statement"));
}
let duration_str = parts[3..].join(" ");
let duration = parse_duration_token(&duration_str)?;
Ok(Statement::new(
StatementKind::Fade {
direction,
target,
duration,
},
Value::Null,
0,
line_number,
1,
))
}
pub fn parse_section(
parts: impl Iterator<Item = impl AsRef<str>>,
line_number: usize,
) -> Result<Statement> {
let parts_vec: Vec<String> = parts.map(|p| p.as_ref().to_string()).collect();
if parts_vec.is_empty() {
return Err(anyhow!("section requires a name"));
}
let name = parts_vec[0].trim_end_matches(':').to_string();
let duration = if parts_vec.len() > 1 && parts_vec[1] == "during" {
if parts_vec.len() > 2 {
let duration_str = parts_vec[2..].join(" ").trim_end_matches(':').to_string();
Some(parse_duration_token(&duration_str)?)
} else {
return Err(anyhow!("section 'during' requires a duration value"));
}
} else {
None
};
Ok(Statement::new(
StatementKind::Section {
name,
duration,
body: Vec::new(), },
Value::Null,
0,
line_number,
1,
))
}
pub fn parse_timeline(line: &str, line_number: usize) -> Result<Statement> {
let trimmed = line.trim_start();
let rest = if trimmed.starts_with("timeline ") {
&trimmed[9..]
} else {
return Err(anyhow!("Invalid timeline statement"));
};
if !rest.starts_with('[') || !rest.ends_with(']') {
return Err(anyhow!(
"timeline requires format: timeline [section_name, ...]"
));
}
let inner = &rest[1..rest.len() - 1];
let mut sections = Vec::new();
for entry in inner.split(',') {
let entry = entry.trim();
if let Some(during_idx) = entry.find(" during ") {
let section_name = entry[..during_idx].trim().to_string();
let duration_str = entry[during_idx + 8..].trim();
let duration = parse_duration_token(duration_str)?;
sections.push((section_name, Some(duration)));
} else {
sections.push((entry.to_string(), None));
}
}
if sections.is_empty() {
return Err(anyhow!("timeline must have at least one section"));
}
Ok(Statement::new(
StatementKind::Timeline { sections },
Value::Null,
0,
line_number,
1,
))
}
pub fn parse_schedule(_line: &str, line_number: usize) -> Result<Statement> {
Ok(Statement::new(
StatementKind::Schedule {
events: Vec::new(), },
Value::Null,
0,
line_number,
1,
))
}
pub fn parse_stop(line: &str, line_number: usize) -> Result<Statement> {
let trimmed = line.trim_start();
let rest = if trimmed.starts_with("stop ") {
&trimmed[5..]
} else if trimmed.starts_with("silence") {
return Ok(Statement::new(
StatementKind::Stop {
target: None,
after: None,
},
Value::Null,
0,
line_number,
1,
));
} else if trimmed.starts_with("mute ") {
&trimmed[5..]
} else {
return Err(anyhow!("Invalid stop statement"));
};
let parts: Vec<&str> = rest.split_whitespace().collect();
if parts.is_empty() {
return Err(anyhow!(
"stop requires format: stop <entity|all> [after <duration>]"
));
}
if parts[0] == "all" {
let after = if parts.len() > 2 && parts[1] == "after" {
let duration_str = parts[2..].join(" ");
Some(parse_duration_token(&duration_str)?)
} else if parts.len() > 1 {
return Err(anyhow!(
"Invalid stop all syntax. Expected: stop all [after <duration>]"
));
} else {
None
};
Ok(Statement::new(
StatementKind::Stop {
target: None,
after,
},
Value::Null,
0,
line_number,
1,
))
} else {
let target = parts[0].to_string();
let after = if parts.len() > 2 && parts[1] == "after" {
let duration_str = parts[2..].join(" ");
Some(parse_duration_token(&duration_str)?)
} else if parts.len() > 1 {
return Err(anyhow!(
"Invalid stop syntax. Expected: stop <entity> [after <duration>]"
));
} else {
None
};
Ok(Statement::new(
StatementKind::Stop {
target: Some(target),
after,
},
Value::Null,
0,
line_number,
1,
))
}
}
pub fn parse_schedule_event_header(line: &str) -> Result<(DurationValue, Option<DurationValue>)> {
let trimmed = line.trim_start();
if !trimmed.starts_with("at ") {
return Err(anyhow!("Schedule event must start with 'at'"));
}
let rest = &trimmed[3..];
let rest = rest.trim_end_matches(':').trim();
if let Some(and_during_idx) = rest.find(" and during ") {
let start_str = rest[..and_during_idx].trim();
let duration_str = rest[and_during_idx + 12..].trim();
let start_time = parse_duration_token(start_str)?;
let optional_duration = parse_duration_token(duration_str)?;
Ok((start_time, Some(optional_duration)))
} else {
let start_time = parse_duration_token(rest)?;
Ok((start_time, None))
}
}