1use sva_ast::{LogUnit, SpanUnit, Token, TokenKind, tokenize};
4use sva_engine::{Cmp, Term, Until};
5
6use crate::cli_error::CliError;
7
8pub fn until(text: &str, rate: u32, seconds_per_bar: Option<f64>) -> Result<Until, CliError> {
10 let tokens = tokenize(text).map_err(|d| refused(text, &d.message))?;
11 let mut reading = Reading {
12 text,
13 tokens: &tokens,
14 at: 0,
15 rate,
16 seconds_per_bar,
17 };
18 let held = reading.either()?;
19 if reading.at != tokens.len() {
20 return Err(reading.wrong("the condition ended"));
21 }
22 held.checked().map_err(|why| refused(text, &why))?;
23 Ok(held)
24}
25
26struct Reading<'a> {
27 text: &'a str,
28 tokens: &'a [Token],
29 at: usize,
30 rate: u32,
31 seconds_per_bar: Option<f64>,
32}
33
34impl Reading<'_> {
35 fn peek(&self) -> Option<&TokenKind> {
36 self.tokens.get(self.at).map(|t| &t.kind)
37 }
38
39 fn word(&self, word: &str) -> bool {
40 matches!(self.peek(), Some(TokenKind::Ident(w)) if w == word)
41 }
42
43 fn take(&mut self, kind: &TokenKind) -> Result<(), CliError> {
44 match self.peek() {
45 Some(k) if k == kind => {
46 self.at += 1;
47 Ok(())
48 }
49 _ => Err(self.wrong(&format!("`{}`", spelled(kind)))),
50 }
51 }
52
53 fn either(&mut self) -> Result<Until, CliError> {
54 let mut held = self.both()?;
55 while self.word("or") {
56 self.at += 1;
57 held = Until::Any(Box::new(held), Box::new(self.both()?));
58 }
59 Ok(held)
60 }
61
62 fn both(&mut self) -> Result<Until, CliError> {
63 let mut held = self.one()?;
64 while self.word("and") {
65 self.at += 1;
66 held = Until::All(Box::new(held), Box::new(self.one()?));
67 }
68 Ok(held)
69 }
70
71 fn one(&mut self) -> Result<Until, CliError> {
72 if self.peek() == Some(&TokenKind::LParen) {
73 self.at += 1;
74 let held = self.either()?;
75 self.take(&TokenKind::RParen)?;
76 return Ok(held);
77 }
78 let from = self.at;
79 let left = self.term()?;
80 let left_bare = self.bare_number(from);
81 let cmp = match self.peek() {
82 Some(TokenKind::Lt) => Cmp::Lt,
83 Some(TokenKind::Le) => Cmp::Le,
84 Some(TokenKind::Gt) => Cmp::Gt,
85 Some(TokenKind::Ge) => Cmp::Ge,
86 _ => return Err(self.wrong("a comparison, `<`, `<=`, `>` or `>=`")),
87 };
88 self.at += 1;
89 let from = self.at;
90 let right = self.term()?;
91 let bare = match (level(&left), level(&right)) {
92 (true, false) => self.bare_number(from),
93 (false, true) => left_bare,
94 _ => None,
95 };
96 if let Some((start, end)) = bare {
97 return Err(refused(
98 self.text,
99 &format!(
100 "`{}` (at bytes {start}..{end}) is compared with a level and has no level \
101 unit; write it in `db`",
102 &self.text[start..end]
103 ),
104 ));
105 }
106 Ok(Until::Holds(left, cmp, right))
107 }
108
109 fn bare_number(&self, from: usize) -> Option<(usize, usize)> {
112 let read = &self.tokens[from..self.at];
113 let unitless = read
114 .iter()
115 .any(|t| matches!(t.kind, TokenKind::Num(n) if n != 0.0));
116 match (unitless, read.first(), read.last()) {
117 (true, Some(first), Some(last)) => Some((first.span.start, last.span.end)),
118 _ => None,
119 }
120 }
121
122 fn term(&mut self) -> Result<Term, CliError> {
123 if self.word("t") {
124 self.at += 1;
125 return Ok(Term::Time);
126 }
127 if self.word("envelope") {
128 self.at += 1;
129 self.take(&TokenKind::LParen)?;
130 if !self.word("t") {
131 return Err(self.wrong("`t`"));
132 }
133 self.at += 1;
134 self.take(&TokenKind::RParen)?;
135 return Ok(Term::Envelope);
136 }
137 self.number().map(Term::Number)
138 }
139
140 fn number(&mut self) -> Result<f64, CliError> {
142 let sign = match self.peek() {
143 Some(TokenKind::Minus) => {
144 self.at += 1;
145 -1.0
146 }
147 _ => 1.0,
148 };
149 let value = match self.peek() {
150 Some(TokenKind::Num(n)) => sign * n,
151 Some(TokenKind::Time(n, SpanUnit::Seconds)) => sign * n,
152 Some(TokenKind::Samples(n)) => sign * n / f64::from(self.rate),
153 Some(TokenKind::Time(n, SpanUnit::Bars)) => match self.seconds_per_bar {
154 Some(per) => sign * n * per,
155 None => {
156 return Err(CliError::BadTempo(format!(
157 "`{}` is written in bars and nothing here declares a tempo; state \
158 `variables/bpm` and `variables/meter`, or write seconds",
159 self.text
160 )));
161 }
162 },
163 Some(TokenKind::Log(n, LogUnit::Decibels)) => LogUnit::Decibels.resolve(sign * n),
164 _ => return Err(self.wrong("a number, a time or a level")),
165 };
166 self.at += 1;
167 Ok(value)
168 }
169
170 fn wrong(&self, wanted: &str) -> CliError {
171 let found = self.tokens.get(self.at).map_or("the end".to_string(), |t| {
172 format!("`{}`", &self.text[t.span.start..t.span.end])
173 });
174 refused(self.text, &format!("expected {wanted}, found {found}"))
175 }
176}
177
178fn level(term: &Term) -> bool {
179 matches!(term, Term::Envelope)
180}
181
182fn spelled(kind: &TokenKind) -> &'static str {
183 match kind {
184 TokenKind::LParen => "(",
185 TokenKind::RParen => ")",
186 _ => "?",
187 }
188}
189
190fn refused(text: &str, why: &str) -> CliError {
191 CliError::Usage(format!(
192 "`{text}` is no condition: {why}. write comparisons over `t` and `envelope(t)`, \
193 joined by `and`/`or`, as `envelope(t) < -60db and t > 1s`"
194 ))
195}