1use crate::error::{AgError, AgResult};
6
7const MAX_EXPRESSION_BYTES: usize = 4096;
8const MAX_EXPRESSION_TOKENS: usize = 256;
9
10#[derive(Debug, Clone, PartialEq)]
12pub enum Token {
13 Num(f64),
15 Band(usize),
17 Add,
19 Sub,
21 Mul,
23 Div,
25 Neg,
27}
28
29pub fn parse_expression(input: &str) -> AgResult<Vec<Token>> {
35 if input.len() > MAX_EXPRESSION_BYTES {
36 return Err(AgError::query("expression", "expression is too long"));
37 }
38 let toks = lex(input)?;
39 if toks.len() > MAX_EXPRESSION_TOKENS {
40 return Err(AgError::query(
41 "expression",
42 "expression has too many tokens",
43 ));
44 }
45 let mut p = Parser {
46 toks: &toks,
47 pos: 0,
48 out: Vec::new(),
49 depth: 0,
50 };
51 p.expr()?;
52 if p.pos != p.toks.len() {
53 return Err(AgError::query("expression", "unexpected trailing input"));
54 }
55 if p.out.is_empty() {
56 return Err(AgError::query("expression", "empty expression"));
57 }
58 Ok(p.out)
59}
60
61pub fn max_band_ref(rpn: &[Token]) -> Option<usize> {
63 rpn.iter()
64 .filter_map(|t| match t {
65 Token::Band(i) => Some(*i),
66 _ => None,
67 })
68 .max()
69}
70
71pub(crate) fn eval_rpn_with(rpn: &[Token], bands: &[f64], stack: &mut Vec<f64>) -> f64 {
74 stack.clear();
75 for t in rpn {
76 match t {
77 Token::Num(n) => stack.push(*n),
78 Token::Band(i) => stack.push(bands.get(*i).copied().unwrap_or(f64::NAN)),
79 Token::Neg => {
80 let a = stack.pop().unwrap_or(f64::NAN);
81 stack.push(-a);
82 }
83 op => {
84 let b = stack.pop().unwrap_or(f64::NAN);
85 let a = stack.pop().unwrap_or(f64::NAN);
86 stack.push(match op {
87 Token::Add => a + b,
88 Token::Sub => a - b,
89 Token::Mul => a * b,
90 Token::Div => a / b,
91 _ => f64::NAN,
92 });
93 }
94 }
95 }
96 stack.pop().unwrap_or(f64::NAN)
97}
98
99pub fn eval_rpn(rpn: &[Token], bands: &[f64]) -> f64 {
103 let mut stack = Vec::with_capacity(8);
104 eval_rpn_with(rpn, bands, &mut stack)
105}
106
107#[derive(Debug, Clone, PartialEq)]
108enum Lexeme {
109 Num(f64),
110 Band(usize),
111 Plus,
112 Minus,
113 Star,
114 Slash,
115 LParen,
116 RParen,
117}
118
119fn lex(s: &str) -> AgResult<Vec<Lexeme>> {
120 let b = s.as_bytes();
121 let mut i = 0;
122 let mut out = Vec::new();
123 while i < b.len() {
124 let c = b[i] as char;
125 match c {
126 ' ' | '\t' | '\n' | '\r' => i += 1,
127 '+' => {
128 out.push(Lexeme::Plus);
129 i += 1;
130 }
131 '-' => {
132 out.push(Lexeme::Minus);
133 i += 1;
134 }
135 '*' => {
136 out.push(Lexeme::Star);
137 i += 1;
138 }
139 '/' => {
140 out.push(Lexeme::Slash);
141 i += 1;
142 }
143 '(' => {
144 out.push(Lexeme::LParen);
145 i += 1;
146 }
147 ')' => {
148 out.push(Lexeme::RParen);
149 i += 1;
150 }
151 'b' | 'B' => {
152 let start = i + 1;
153 let mut j = start;
154 while j < b.len() && b[j].is_ascii_digit() {
155 j += 1;
156 }
157 if j == start {
158 return Err(AgError::query("expression", "band ref 'b' needs a number"));
159 }
160 let n: usize = s[start..j]
161 .parse()
162 .map_err(|_| AgError::query("expression", "invalid band number"))?;
163 if n == 0 {
164 return Err(AgError::query(
165 "expression",
166 "bands are 1-indexed (b1 is first)",
167 ));
168 }
169 out.push(Lexeme::Band(n - 1));
170 i = j;
171 }
172 c if c.is_ascii_digit() || c == '.' => {
173 let start = i;
174 let mut j = i;
175 while j < b.len() && (b[j].is_ascii_digit() || b[j] == b'.') {
176 j += 1;
177 }
178 let n: f64 = s[start..j]
179 .parse()
180 .map_err(|_| AgError::query("expression", "invalid number"))?;
181 if !n.is_finite() {
182 return Err(AgError::query("expression", "number must be finite"));
183 }
184 out.push(Lexeme::Num(n));
185 i = j;
186 }
187 other => {
188 return Err(AgError::query(
189 "expression",
190 format!("unexpected character {other:?}"),
191 ))
192 }
193 }
194 }
195 Ok(out)
196}
197
198struct Parser<'a> {
199 toks: &'a [Lexeme],
200 pos: usize,
201 out: Vec<Token>,
202 depth: usize,
203}
204
205impl<'a> Parser<'a> {
206 fn peek(&self) -> Option<&Lexeme> {
207 self.toks.get(self.pos)
208 }
209 fn expr(&mut self) -> AgResult<()> {
210 self.term()?;
211 while let Some(op) = self.peek() {
212 let t = match op {
213 Lexeme::Plus => Token::Add,
214 Lexeme::Minus => Token::Sub,
215 _ => break,
216 };
217 self.pos += 1;
218 self.term()?;
219 self.out.push(t);
220 }
221 Ok(())
222 }
223 fn term(&mut self) -> AgResult<()> {
224 self.factor()?;
225 while let Some(op) = self.peek() {
226 let t = match op {
227 Lexeme::Star => Token::Mul,
228 Lexeme::Slash => Token::Div,
229 _ => break,
230 };
231 self.pos += 1;
232 self.factor()?;
233 self.out.push(t);
234 }
235 Ok(())
236 }
237 fn factor(&mut self) -> AgResult<()> {
238 self.depth += 1;
239 if self.depth > 128 {
240 return Err(AgError::query("expression", "expression nesting too deep"));
241 }
242 let r = self.factor_inner();
243 self.depth -= 1;
244 r
245 }
246 fn factor_inner(&mut self) -> AgResult<()> {
247 match self.peek() {
248 Some(Lexeme::Num(n)) => {
249 let n = *n;
250 self.out.push(Token::Num(n));
251 self.pos += 1;
252 Ok(())
253 }
254 Some(Lexeme::Band(i)) => {
255 let i = *i;
256 self.out.push(Token::Band(i));
257 self.pos += 1;
258 Ok(())
259 }
260 Some(Lexeme::Minus) => {
261 self.pos += 1;
262 self.factor()?;
263 self.out.push(Token::Neg);
264 Ok(())
265 }
266 Some(Lexeme::LParen) => {
267 self.pos += 1;
268 self.expr()?;
269 match self.peek() {
270 Some(Lexeme::RParen) => {
271 self.pos += 1;
272 Ok(())
273 }
274 _ => Err(AgError::query("expression", "unbalanced parentheses")),
275 }
276 }
277 _ => Err(AgError::query(
278 "expression",
279 "expected a number, band, or '('",
280 )),
281 }
282 }
283}
284
285#[cfg(test)]
286mod tests {
287 use super::*;
288
289 fn rpn(s: &str) -> Vec<Token> {
290 parse_expression(s).unwrap()
291 }
292
293 #[test]
294 fn ndvi_value() {
295 let t = rpn("(b5-b4)/(b5+b4)");
296 let mut bands = vec![0.0; 5];
297 bands[4] = 0.5;
298 bands[3] = 0.1;
299 let v = eval_rpn(&t, &bands);
300 assert!((v - 0.6666667).abs() < 1e-5, "ndvi={v}");
301 }
302 #[test]
303 fn precedence_and_parens() {
304 assert_eq!(eval_rpn(&rpn("1+2*3"), &[]), 7.0);
305 assert_eq!(eval_rpn(&rpn("(1+2)*3"), &[]), 9.0);
306 }
307 #[test]
308 fn unary_minus() {
309 assert_eq!(eval_rpn(&rpn("-b1+3"), &[2.0]), 1.0);
310 }
311 #[test]
312 fn div_by_zero_is_nonfinite() {
313 let v = eval_rpn(&rpn("b1/b2"), &[1.0, 0.0]);
314 assert!(!v.is_finite(), "expected inf/nan, got {v}");
315 }
316 #[test]
317 fn max_band_ref_reports_highest() {
318 assert_eq!(max_band_ref(&rpn("(b5-b4)/(b5+b4)")), Some(4));
319 assert_eq!(max_band_ref(&rpn("1+2")), None);
320 }
321 #[test]
322 fn rejects_empty() {
323 assert!(matches!(
324 parse_expression(" "),
325 Err(AgError::Query { .. })
326 ));
327 }
328
329 #[test]
330 fn rejects_overflowing_numeric_literal() {
331 let literal = "9".repeat(400);
332 assert!(matches!(
333 parse_expression(&literal),
334 Err(AgError::Query { name, .. }) if name == "expression"
335 ));
336 }
337 #[test]
338 fn rejects_band_zero() {
339 assert!(matches!(
340 parse_expression("b0+1"),
341 Err(AgError::Query { .. })
342 ));
343 }
344 #[test]
345 fn rejects_bare_b() {
346 assert!(matches!(
347 parse_expression("b+1"),
348 Err(AgError::Query { .. })
349 ));
350 }
351 #[test]
352 fn rejects_trailing_garbage() {
353 assert!(matches!(
354 parse_expression("b1+1 xyz"),
355 Err(AgError::Query { .. })
356 ));
357 assert!(matches!(
358 parse_expression("b1)"),
359 Err(AgError::Query { .. })
360 ));
361 }
362 #[test]
363 fn rejects_unbalanced_parens() {
364 assert!(matches!(
365 parse_expression("(b1+1"),
366 Err(AgError::Query { .. })
367 ));
368 }
369
370 #[test]
371 fn left_associative() {
372 assert_eq!(eval_rpn(&rpn("1-2-3"), &[]), -4.0);
373 assert_eq!(eval_rpn(&rpn("8/2/2"), &[]), 2.0);
374 }
375
376 #[test]
377 fn unary_after_binary() {
378 assert_eq!(eval_rpn(&rpn("2*-3"), &[]), -6.0);
379 assert_eq!(eval_rpn(&rpn("3 - -2"), &[]), 5.0);
380 }
381
382 #[test]
383 fn rejects_empty_parens_and_leading_op() {
384 assert!(parse_expression("()").is_err());
385 assert!(parse_expression("*b1").is_err());
386 assert!(parse_expression("b1 b2").is_err());
387 }
388
389 #[test]
390 fn generic_ndvi_expression_evaluates() {
391 let tokens = rpn("(b5-b4)/(b5+b4)");
392 let mut bands = vec![0.0; 5];
393 bands[4] = 0.5;
394 bands[3] = 0.1;
395
396 assert!((eval_rpn(&tokens, &bands) - 0.6666667).abs() < 1e-5);
397 }
398
399 #[test]
400 fn rejects_deeply_nested() {
401 let deep = format!("{}b1{}", "(".repeat(500), ")".repeat(500));
402 assert!(matches!(
403 parse_expression(&deep),
404 Err(AgError::Query { .. })
405 ));
406 }
407
408 #[test]
409 fn rejects_excessively_long_expression() {
410 let long = ["b1"; 300].join("+");
411 assert!(matches!(
412 parse_expression(&long),
413 Err(AgError::Query { .. })
414 ));
415 }
416}