1use crate::algebra::BandExpression;
8use std::fmt;
9
10#[derive(Debug, Clone, PartialEq)]
12pub enum BandMathError {
13 UnexpectedToken {
15 pos: usize,
17 found: String,
19 },
20 UnknownFunction(String),
22 InvalidBandIndex(u32),
26 WrongArgCount {
28 function: String,
30 expected: usize,
32 actual: usize,
34 },
35 DivByConstantZero,
37 TrailingTokens(usize),
39 EmptyExpression,
41 UnmatchedParen,
43 InvalidNumber {
45 pos: usize,
47 text: String,
49 },
50}
51
52impl fmt::Display for BandMathError {
53 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
54 match self {
55 BandMathError::UnexpectedToken { pos, found } => {
56 write!(f, "unexpected token `{}` at position {}", found, pos)
57 }
58 BandMathError::UnknownFunction(name) => write!(f, "unknown function `{}`", name),
59 BandMathError::InvalidBandIndex(n) => {
60 write!(f, "invalid band index B{} (allowed range: 1..=999)", n)
61 }
62 BandMathError::WrongArgCount {
63 function,
64 expected,
65 actual,
66 } => write!(
67 f,
68 "function `{}` expects {} argument(s), got {}",
69 function, expected, actual
70 ),
71 BandMathError::DivByConstantZero => write!(f, "division by literal zero"),
72 BandMathError::TrailingTokens(pos) => {
73 write!(f, "trailing tokens after expression (position {})", pos)
74 }
75 BandMathError::EmptyExpression => write!(f, "empty expression"),
76 BandMathError::UnmatchedParen => write!(f, "unmatched parenthesis"),
77 BandMathError::InvalidNumber { pos, text } => {
78 write!(f, "invalid number literal `{}` at position {}", text, pos)
79 }
80 }
81 }
82}
83
84impl std::error::Error for BandMathError {}
85
86#[derive(Debug, Clone, PartialEq)]
88enum Token {
89 Number(f64),
91 Band(usize),
93 Op(char),
95 LParen,
97 RParen,
99 Comma,
101 Func(String),
103}
104
105struct Lexer<'a> {
107 src: &'a [u8],
108 pos: usize,
109}
110
111impl<'a> Lexer<'a> {
112 fn new(src: &'a str) -> Self {
113 Self {
114 src: src.as_bytes(),
115 pos: 0,
116 }
117 }
118
119 fn tokenize(mut self) -> Result<Vec<(Token, usize)>, BandMathError> {
120 let mut out: Vec<(Token, usize)> = Vec::new();
121 while self.pos < self.src.len() {
122 let b = self.src[self.pos];
123 if b.is_ascii_whitespace() {
124 self.pos += 1;
125 continue;
126 }
127 let start = self.pos;
128 if b.is_ascii_digit() || b == b'.' {
129 let (tok, new_pos) = self.lex_number(start)?;
130 out.push((tok, start));
131 self.pos = new_pos;
132 } else if b == b'B' && self.peek_is_digit(self.pos + 1) {
133 let (tok, new_pos) = self.lex_band(start)?;
134 out.push((tok, start));
135 self.pos = new_pos;
136 } else if b.is_ascii_alphabetic() || b == b'_' {
137 let (tok, new_pos) = self.lex_identifier(start)?;
138 out.push((tok, start));
139 self.pos = new_pos;
140 } else {
141 match b {
142 b'+' | b'-' | b'*' | b'/' | b'^' => {
143 out.push((Token::Op(b as char), start));
144 self.pos += 1;
145 }
146 b'(' => {
147 out.push((Token::LParen, start));
148 self.pos += 1;
149 }
150 b')' => {
151 out.push((Token::RParen, start));
152 self.pos += 1;
153 }
154 b',' => {
155 out.push((Token::Comma, start));
156 self.pos += 1;
157 }
158 _ => {
159 return Err(BandMathError::UnexpectedToken {
160 pos: start,
161 found: (b as char).to_string(),
162 });
163 }
164 }
165 }
166 }
167 Ok(out)
168 }
169
170 fn peek_is_digit(&self, idx: usize) -> bool {
171 idx < self.src.len() && self.src[idx].is_ascii_digit()
172 }
173
174 fn lex_number(&self, start: usize) -> Result<(Token, usize), BandMathError> {
175 let mut i = start;
176 let mut saw_dot = false;
177 let mut saw_digit = false;
178 while i < self.src.len() {
179 let c = self.src[i];
180 if c.is_ascii_digit() {
181 saw_digit = true;
182 i += 1;
183 } else if c == b'.' && !saw_dot {
184 saw_dot = true;
185 i += 1;
186 } else {
187 break;
188 }
189 }
190 if i < self.src.len() && (self.src[i] == b'e' || self.src[i] == b'E') {
192 i += 1;
193 if i < self.src.len() && (self.src[i] == b'+' || self.src[i] == b'-') {
194 i += 1;
195 }
196 let exp_start = i;
197 while i < self.src.len() && self.src[i].is_ascii_digit() {
198 i += 1;
199 }
200 if i == exp_start {
201 let text = std::str::from_utf8(&self.src[start..i])
202 .unwrap_or("")
203 .to_string();
204 return Err(BandMathError::InvalidNumber { pos: start, text });
205 }
206 }
207 if !saw_digit {
208 let text = std::str::from_utf8(&self.src[start..i])
210 .unwrap_or("")
211 .to_string();
212 return Err(BandMathError::InvalidNumber { pos: start, text });
213 }
214 let text = std::str::from_utf8(&self.src[start..i]).unwrap_or("");
215 let n: f64 = text.parse().map_err(|_| BandMathError::InvalidNumber {
216 pos: start,
217 text: text.to_string(),
218 })?;
219 Ok((Token::Number(n), i))
220 }
221
222 fn lex_band(&self, start: usize) -> Result<(Token, usize), BandMathError> {
223 let mut i = start + 1;
225 while i < self.src.len() && self.src[i].is_ascii_digit() {
226 i += 1;
227 }
228 let digits = std::str::from_utf8(&self.src[start + 1..i]).unwrap_or("");
229 let n: u32 = digits
230 .parse()
231 .map_err(|_| BandMathError::InvalidBandIndex(0))?;
232 if n == 0 || n > 999 {
233 return Err(BandMathError::InvalidBandIndex(n));
234 }
235 Ok((Token::Band((n - 1) as usize), i))
237 }
238
239 fn lex_identifier(&self, start: usize) -> Result<(Token, usize), BandMathError> {
240 let mut i = start;
241 while i < self.src.len() {
242 let c = self.src[i];
243 if c.is_ascii_alphanumeric() || c == b'_' {
244 i += 1;
245 } else {
246 break;
247 }
248 }
249 let name = std::str::from_utf8(&self.src[start..i]).unwrap_or("");
250 let mut j = i;
252 while j < self.src.len() && self.src[j].is_ascii_whitespace() {
253 j += 1;
254 }
255 if j < self.src.len() && self.src[j] == b'(' {
256 Ok((Token::Func(name.to_string()), i))
257 } else {
258 Err(BandMathError::UnexpectedToken {
259 pos: start,
260 found: name.to_string(),
261 })
262 }
263 }
264}
265
266struct Parser {
268 tokens: Vec<(Token, usize)>,
269 pos: usize,
270}
271
272impl Parser {
273 fn peek(&self) -> Option<&(Token, usize)> {
274 self.tokens.get(self.pos)
275 }
276
277 fn advance(&mut self) -> Option<(Token, usize)> {
278 let t = self.tokens.get(self.pos).cloned();
279 if t.is_some() {
280 self.pos += 1;
281 }
282 t
283 }
284
285 fn current_pos(&self) -> usize {
286 self.tokens
287 .get(self.pos)
288 .map(|(_, p)| *p)
289 .unwrap_or_else(|| self.tokens.last().map(|(_, p)| *p).unwrap_or(0))
290 }
291
292 fn parse_expr(&mut self) -> Result<BandExpression, BandMathError> {
293 let mut lhs = self.parse_term()?;
294 while let Some((Token::Op(op), _)) = self.peek().cloned() {
295 if op != '+' && op != '-' {
296 break;
297 }
298 self.advance();
299 let rhs = self.parse_term()?;
300 lhs = match op {
301 '+' => BandExpression::Add(Box::new(lhs), Box::new(rhs)),
302 '-' => BandExpression::Sub(Box::new(lhs), Box::new(rhs)),
303 _ => unreachable!("operator dispatch guarded above"),
304 };
305 }
306 Ok(lhs)
307 }
308
309 fn parse_term(&mut self) -> Result<BandExpression, BandMathError> {
310 let mut lhs = self.parse_factor()?;
311 while let Some((Token::Op(op), _)) = self.peek().cloned() {
312 if op != '*' && op != '/' {
313 break;
314 }
315 self.advance();
316 let rhs = self.parse_factor()?;
317 lhs = match op {
318 '*' => BandExpression::Mul(Box::new(lhs), Box::new(rhs)),
319 '/' => {
320 if let BandExpression::Constant(c) = &rhs {
323 if c.abs() < 1e-20 {
324 return Err(BandMathError::DivByConstantZero);
325 }
326 }
327 BandExpression::Div(Box::new(lhs), Box::new(rhs))
328 }
329 _ => unreachable!("operator dispatch guarded above"),
330 };
331 }
332 Ok(lhs)
333 }
334
335 fn parse_factor(&mut self) -> Result<BandExpression, BandMathError> {
336 self.parse_power()
337 }
338
339 fn parse_power(&mut self) -> Result<BandExpression, BandMathError> {
340 let lhs = self.parse_unary()?;
341 if let Some((Token::Op('^'), _)) = self.peek().cloned() {
342 self.advance();
343 let rhs = self.parse_power()?;
346 Ok(BandExpression::Pow(Box::new(lhs), Box::new(rhs)))
347 } else {
348 Ok(lhs)
349 }
350 }
351
352 fn parse_unary(&mut self) -> Result<BandExpression, BandMathError> {
353 if let Some((Token::Op('-'), _)) = self.peek().cloned() {
354 self.advance();
355 let inner = self.parse_unary()?;
356 return Ok(BandExpression::Neg(Box::new(inner)));
357 }
358 if let Some((Token::Op('+'), _)) = self.peek().cloned() {
359 self.advance();
361 return self.parse_unary();
362 }
363 self.parse_primary()
364 }
365
366 fn parse_primary(&mut self) -> Result<BandExpression, BandMathError> {
367 let (tok, pos) = self.advance().ok_or(BandMathError::EmptyExpression)?;
368 match tok {
369 Token::Number(n) => Ok(BandExpression::Constant(n as f32)),
370 Token::Band(idx) => Ok(BandExpression::Band(idx)),
371 Token::Func(name) => {
372 match self.advance() {
374 Some((Token::LParen, _)) => {}
375 Some((other, p)) => {
376 return Err(BandMathError::UnexpectedToken {
377 pos: p,
378 found: format!("{:?}", other),
379 });
380 }
381 None => return Err(BandMathError::UnmatchedParen),
382 }
383 let args = self.parse_args()?;
384 match self.advance() {
386 Some((Token::RParen, _)) => {}
387 Some((_, p)) => {
388 return Err(BandMathError::UnexpectedToken {
389 pos: p,
390 found: ")".to_string(),
391 });
392 }
393 None => return Err(BandMathError::UnmatchedParen),
394 }
395 self.dispatch_function(&name, args)
396 }
397 Token::LParen => {
398 let inner = self.parse_expr()?;
399 match self.advance() {
400 Some((Token::RParen, _)) => Ok(inner),
401 _ => Err(BandMathError::UnmatchedParen),
402 }
403 }
404 other => Err(BandMathError::UnexpectedToken {
405 pos,
406 found: format!("{:?}", other),
407 }),
408 }
409 }
410
411 fn parse_args(&mut self) -> Result<Vec<BandExpression>, BandMathError> {
412 let mut args: Vec<BandExpression> = Vec::new();
413 if let Some((Token::RParen, _)) = self.peek() {
415 return Ok(args);
416 }
417 args.push(self.parse_expr()?);
418 while let Some((Token::Comma, _)) = self.peek() {
419 self.advance();
420 args.push(self.parse_expr()?);
421 }
422 Ok(args)
423 }
424
425 fn dispatch_function(
426 &self,
427 name: &str,
428 mut args: Vec<BandExpression>,
429 ) -> Result<BandExpression, BandMathError> {
430 let lower = name.to_ascii_lowercase();
431 match lower.as_str() {
432 "log" | "ln" => {
433 self.check_arity(name, 1, &args)?;
434 Ok(BandExpression::Log(Box::new(args.remove(0))))
435 }
436 "exp" => {
437 self.check_arity(name, 1, &args)?;
438 Ok(BandExpression::Exp(Box::new(args.remove(0))))
439 }
440 "sqrt" => {
441 self.check_arity(name, 1, &args)?;
442 Ok(BandExpression::Sqrt(Box::new(args.remove(0))))
443 }
444 "abs" => {
445 self.check_arity(name, 1, &args)?;
446 Ok(BandExpression::Abs(Box::new(args.remove(0))))
447 }
448 "min" => {
449 self.check_arity(name, 2, &args)?;
450 let b = args.remove(1);
451 let a = args.remove(0);
452 Ok(BandExpression::Min(Box::new(a), Box::new(b)))
453 }
454 "max" => {
455 self.check_arity(name, 2, &args)?;
456 let b = args.remove(1);
457 let a = args.remove(0);
458 Ok(BandExpression::Max(Box::new(a), Box::new(b)))
459 }
460 "pow" => {
461 self.check_arity(name, 2, &args)?;
462 let b = args.remove(1);
463 let a = args.remove(0);
464 Ok(BandExpression::Pow(Box::new(a), Box::new(b)))
465 }
466 "clamp" => {
467 self.check_arity(name, 3, &args)?;
468 let hi = args.remove(2);
469 let lo = args.remove(1);
470 let value = args.remove(0);
471 Ok(BandExpression::Clamp {
472 value: Box::new(value),
473 lo: Box::new(lo),
474 hi: Box::new(hi),
475 })
476 }
477 _ => Err(BandMathError::UnknownFunction(name.to_string())),
478 }
479 }
480
481 fn check_arity(
482 &self,
483 name: &str,
484 expected: usize,
485 args: &[BandExpression],
486 ) -> Result<(), BandMathError> {
487 if args.len() == expected {
488 Ok(())
489 } else {
490 Err(BandMathError::WrongArgCount {
491 function: name.to_string(),
492 expected,
493 actual: args.len(),
494 })
495 }
496 }
497}
498
499pub fn parse_band_expression(s: &str) -> Result<BandExpression, BandMathError> {
505 let tokens = Lexer::new(s).tokenize()?;
506 if tokens.is_empty() {
507 return Err(BandMathError::EmptyExpression);
508 }
509 let mut p = Parser { tokens, pos: 0 };
510 let expr = p.parse_expr()?;
511 if p.pos < p.tokens.len() {
512 return Err(BandMathError::TrailingTokens(p.current_pos()));
513 }
514 Ok(expr)
515}
516
517#[cfg(test)]
518mod tests {
519 use super::*;
520
521 #[test]
522 fn test_lex_number_simple() {
523 let toks = Lexer::new("1.5 + 2").tokenize().expect("tokenize");
524 assert_eq!(toks.len(), 3);
525 assert_eq!(toks[0].0, Token::Number(1.5));
526 assert_eq!(toks[1].0, Token::Op('+'));
527 assert_eq!(toks[2].0, Token::Number(2.0));
528 }
529
530 #[test]
531 fn test_lex_band_reference() {
532 let toks = Lexer::new("B1 - B42").tokenize().expect("tokenize");
533 assert_eq!(toks[0].0, Token::Band(0));
534 assert_eq!(toks[2].0, Token::Band(41));
535 }
536
537 #[test]
538 fn test_lex_unknown_char_errors() {
539 let err = Lexer::new("B1 @ B2").tokenize().expect_err("must fail");
540 assert!(
541 matches!(err, BandMathError::UnexpectedToken { .. }),
542 "wrong variant: {:?}",
543 err
544 );
545 }
546
547 #[test]
548 fn test_parse_addition() {
549 let e = parse_band_expression("B1 + B2").expect("parse");
550 assert!(
551 matches!(e, BandExpression::Add(_, _)),
552 "expected Add, got {:?}",
553 e
554 );
555 }
556}