Skip to main content

oxigdal_algorithms/dsl/
compiler.rs

1//! AST Compiler and Executor
2//!
3//! This module compiles the AST into executable operations and provides
4//! runtime execution with type checking and optimization.
5
6use super::ast::{BinaryOp, Expr, Program, Statement, UnaryOp};
7use super::functions::FunctionRegistry;
8use super::variables::{BandContext, Environment, Value};
9use crate::error::{AlgorithmError, Result};
10use oxigdal_core::buffer::RasterBuffer;
11use oxigdal_core::types::RasterDataType;
12
13#[cfg(not(feature = "std"))]
14use alloc::{boxed::Box, string::String, vec::Vec};
15
16/// Compiled program ready for execution
17pub struct CompiledProgram {
18    program: Program,
19    func_registry: FunctionRegistry,
20}
21
22impl CompiledProgram {
23    /// Creates a new compiled program from AST
24    pub fn new(program: Program) -> Self {
25        Self {
26            program,
27            func_registry: FunctionRegistry::new(),
28        }
29    }
30
31    /// Executes the program with given bands
32    pub fn execute(&self, bands: &[RasterBuffer]) -> Result<RasterBuffer> {
33        if bands.is_empty() {
34            return Err(AlgorithmError::EmptyInput {
35                operation: "execute",
36            });
37        }
38
39        let width = bands[0].width();
40        let height = bands[0].height();
41
42        // Check all bands have same dimensions
43        for band in bands.iter().skip(1) {
44            if band.width() != width || band.height() != height {
45                return Err(AlgorithmError::InvalidDimensions {
46                    message: "All bands must have same dimensions",
47                    actual: band.width() as usize,
48                    expected: width as usize,
49                });
50            }
51        }
52
53        let mut env = Environment::new();
54        let band_ctx = BandContext::new(bands);
55        let mut executor = Executor::new(&self.func_registry);
56
57        // Execute all statements
58        for stmt in &self.program.statements {
59            executor.execute_statement(stmt, &mut env, &band_ctx)?;
60        }
61
62        // Get the last expression result or create a default raster
63        if let Some(Statement::Expr(expr)) = self.program.statements.last() {
64            let result = executor.evaluate_expr(expr, &env, &band_ctx)?;
65
66            match result {
67                Value::Raster(r) => Ok(*r),
68                Value::Number(n) => {
69                    let mut raster = RasterBuffer::zeros(width, height, RasterDataType::Float32);
70                    for y in 0..height {
71                        for x in 0..width {
72                            raster.set_pixel(x, y, n).map_err(AlgorithmError::Core)?;
73                        }
74                    }
75                    Ok(raster)
76                }
77                Value::Bool(b) => {
78                    let val = if b { 1.0 } else { 0.0 };
79                    let mut raster = RasterBuffer::zeros(width, height, RasterDataType::Float32);
80                    for y in 0..height {
81                        for x in 0..width {
82                            raster.set_pixel(x, y, val).map_err(AlgorithmError::Core)?;
83                        }
84                    }
85                    Ok(raster)
86                }
87                _ => Err(AlgorithmError::InvalidParameter {
88                    parameter: "result",
89                    message: "Program must return a raster or scalar".to_string(),
90                }),
91            }
92        } else {
93            Err(AlgorithmError::InvalidParameter {
94                parameter: "program",
95                message: "Program has no expression to evaluate".to_string(),
96            })
97        }
98    }
99
100    /// Executes a single expression with given bands
101    pub fn execute_expr(&self, expr: &Expr, bands: &[RasterBuffer]) -> Result<RasterBuffer> {
102        if bands.is_empty() {
103            return Err(AlgorithmError::EmptyInput {
104                operation: "execute_expr",
105            });
106        }
107
108        let width = bands[0].width();
109        let height = bands[0].height();
110
111        let env = Environment::new();
112        let band_ctx = BandContext::new(bands);
113        let mut executor = Executor::new(&self.func_registry);
114
115        let result = executor.evaluate_expr(expr, &env, &band_ctx)?;
116
117        match result {
118            Value::Raster(r) => Ok(*r),
119            Value::Number(n) => {
120                let mut raster = RasterBuffer::zeros(width, height, RasterDataType::Float32);
121                for y in 0..height {
122                    for x in 0..width {
123                        raster.set_pixel(x, y, n).map_err(AlgorithmError::Core)?;
124                    }
125                }
126                Ok(raster)
127            }
128            Value::Bool(b) => {
129                let val = if b { 1.0 } else { 0.0 };
130                let mut raster = RasterBuffer::zeros(width, height, RasterDataType::Float32);
131                for y in 0..height {
132                    for x in 0..width {
133                        raster.set_pixel(x, y, val).map_err(AlgorithmError::Core)?;
134                    }
135                }
136                Ok(raster)
137            }
138            _ => Err(AlgorithmError::InvalidParameter {
139                parameter: "result",
140                message: "Expression must return a raster or scalar".to_string(),
141            }),
142        }
143    }
144}
145
146/// Runtime executor for expressions
147struct Executor<'a> {
148    func_registry: &'a FunctionRegistry,
149}
150
151impl<'a> Executor<'a> {
152    fn new(func_registry: &'a FunctionRegistry) -> Self {
153        Self { func_registry }
154    }
155
156    fn execute_statement(
157        &mut self,
158        stmt: &Statement,
159        env: &mut Environment,
160        band_ctx: &BandContext,
161    ) -> Result<()> {
162        match stmt {
163            Statement::VariableDecl { name, value } => {
164                let val = self.evaluate_expr(value, env, band_ctx)?;
165                env.define(name.clone(), val);
166                Ok(())
167            }
168            Statement::FunctionDecl { name, params, body } => {
169                let func_val = Value::Function {
170                    params: params.clone(),
171                    body: body.clone(),
172                    env: env.clone(),
173                };
174                env.define(name.clone(), func_val);
175                Ok(())
176            }
177            Statement::Return(_) => Err(AlgorithmError::InvalidParameter {
178                parameter: "return",
179                message: "Return statements not supported in top-level".to_string(),
180            }),
181            Statement::Expr(expr) => {
182                let _ = self.evaluate_expr(expr, env, band_ctx)?;
183                Ok(())
184            }
185        }
186    }
187
188    fn evaluate_expr(
189        &mut self,
190        expr: &Expr,
191        env: &Environment,
192        band_ctx: &BandContext,
193    ) -> Result<Value> {
194        match expr {
195            Expr::Number(n) => Ok(Value::Number(*n)),
196            Expr::Band(b) => {
197                let band = band_ctx.get_band(*b)?;
198                Ok(Value::Raster(Box::new(band.clone())))
199            }
200            Expr::Variable(name) => env.lookup(name).cloned(),
201            Expr::Binary {
202                left, op, right, ..
203            } => self.evaluate_binary(left, *op, right, env, band_ctx),
204            Expr::Unary {
205                op, expr: inner, ..
206            } => self.evaluate_unary(*op, inner, env, band_ctx),
207            Expr::Call { name, args, .. } => self.evaluate_call(name, args, env, band_ctx),
208            Expr::Conditional {
209                condition,
210                then_expr,
211                else_expr,
212                ..
213            } => self.evaluate_conditional(condition, then_expr, else_expr, env, band_ctx),
214            Expr::Block {
215                statements, result, ..
216            } => self.evaluate_block(statements, result.as_deref(), env, band_ctx),
217            Expr::ForLoop {
218                var,
219                start,
220                end,
221                body,
222                ..
223            } => self.evaluate_for_loop(var, start, end, body, env, band_ctx),
224        }
225    }
226
227    fn evaluate_binary(
228        &mut self,
229        left: &Expr,
230        op: BinaryOp,
231        right: &Expr,
232        env: &Environment,
233        band_ctx: &BandContext,
234    ) -> Result<Value> {
235        let left_val = self.evaluate_expr(left, env, band_ctx)?;
236        let right_val = self.evaluate_expr(right, env, band_ctx)?;
237
238        match (left_val, right_val) {
239            (Value::Number(l), Value::Number(r)) => {
240                let result = match op {
241                    BinaryOp::Add => l + r,
242                    BinaryOp::Subtract => l - r,
243                    BinaryOp::Multiply => l * r,
244                    BinaryOp::Divide => {
245                        if r.abs() < f64::EPSILON {
246                            f64::NAN
247                        } else {
248                            l / r
249                        }
250                    }
251                    BinaryOp::Modulo => l % r,
252                    BinaryOp::Power => l.powf(r),
253                    BinaryOp::Equal => return Ok(Value::Bool((l - r).abs() < f64::EPSILON)),
254                    BinaryOp::NotEqual => return Ok(Value::Bool((l - r).abs() >= f64::EPSILON)),
255                    BinaryOp::Less => return Ok(Value::Bool(l < r)),
256                    BinaryOp::LessEqual => return Ok(Value::Bool(l <= r)),
257                    BinaryOp::Greater => return Ok(Value::Bool(l > r)),
258                    BinaryOp::GreaterEqual => return Ok(Value::Bool(l >= r)),
259                    BinaryOp::And | BinaryOp::Or => {
260                        return Err(AlgorithmError::InvalidParameter {
261                            parameter: "operator",
262                            message: "Logical operators require boolean operands".to_string(),
263                        });
264                    }
265                };
266                Ok(Value::Number(result))
267            }
268            (Value::Bool(l), Value::Bool(r)) => {
269                let result = match op {
270                    BinaryOp::And => l && r,
271                    BinaryOp::Or => l || r,
272                    BinaryOp::Equal => l == r,
273                    BinaryOp::NotEqual => l != r,
274                    _ => {
275                        return Err(AlgorithmError::InvalidParameter {
276                            parameter: "operator",
277                            message: format!("Operator {:?} not supported for booleans", op),
278                        });
279                    }
280                };
281                Ok(Value::Bool(result))
282            }
283            (Value::Raster(l), Value::Raster(r)) => self.evaluate_raster_binary(&l, op, &r),
284            (Value::Raster(l), Value::Number(r)) => {
285                self.evaluate_raster_scalar_binary(&l, op, r, false)
286            }
287            (Value::Number(l), Value::Raster(r)) => {
288                self.evaluate_raster_scalar_binary(&r, op, l, true)
289            }
290            _ => Err(AlgorithmError::InvalidParameter {
291                parameter: "operands",
292                message: "Incompatible operand types".to_string(),
293            }),
294        }
295    }
296
297    fn evaluate_raster_binary(
298        &self,
299        left: &RasterBuffer,
300        op: BinaryOp,
301        right: &RasterBuffer,
302    ) -> Result<Value> {
303        if left.width() != right.width() || left.height() != right.height() {
304            return Err(AlgorithmError::InvalidDimensions {
305                message: "Rasters must have same dimensions",
306                actual: right.width() as usize,
307                expected: left.width() as usize,
308            });
309        }
310
311        let mut result = RasterBuffer::zeros(left.width(), left.height(), left.data_type());
312
313        for y in 0..left.height() {
314            for x in 0..left.width() {
315                let l = left.get_pixel(x, y).map_err(AlgorithmError::Core)?;
316                let r = right.get_pixel(x, y).map_err(AlgorithmError::Core)?;
317
318                let val = match op {
319                    BinaryOp::Add => l + r,
320                    BinaryOp::Subtract => l - r,
321                    BinaryOp::Multiply => l * r,
322                    BinaryOp::Divide => {
323                        if r.abs() < f64::EPSILON {
324                            f64::NAN
325                        } else {
326                            l / r
327                        }
328                    }
329                    BinaryOp::Modulo => l % r,
330                    BinaryOp::Power => l.powf(r),
331                    BinaryOp::Equal => {
332                        if (l - r).abs() < f64::EPSILON {
333                            1.0
334                        } else {
335                            0.0
336                        }
337                    }
338                    BinaryOp::NotEqual => {
339                        if (l - r).abs() >= f64::EPSILON {
340                            1.0
341                        } else {
342                            0.0
343                        }
344                    }
345                    BinaryOp::Less => {
346                        if l < r {
347                            1.0
348                        } else {
349                            0.0
350                        }
351                    }
352                    BinaryOp::LessEqual => {
353                        if l <= r {
354                            1.0
355                        } else {
356                            0.0
357                        }
358                    }
359                    BinaryOp::Greater => {
360                        if l > r {
361                            1.0
362                        } else {
363                            0.0
364                        }
365                    }
366                    BinaryOp::GreaterEqual => {
367                        if l >= r {
368                            1.0
369                        } else {
370                            0.0
371                        }
372                    }
373                    BinaryOp::And => {
374                        // Treat rasters as boolean rasters: 0 is false, non-zero is true
375                        let l_bool = l.abs() > f64::EPSILON;
376                        let r_bool = r.abs() > f64::EPSILON;
377                        if l_bool && r_bool { 1.0 } else { 0.0 }
378                    }
379                    BinaryOp::Or => {
380                        // Treat rasters as boolean rasters: 0 is false, non-zero is true
381                        let l_bool = l.abs() > f64::EPSILON;
382                        let r_bool = r.abs() > f64::EPSILON;
383                        if l_bool || r_bool { 1.0 } else { 0.0 }
384                    }
385                };
386
387                result.set_pixel(x, y, val).map_err(AlgorithmError::Core)?;
388            }
389        }
390
391        Ok(Value::Raster(Box::new(result)))
392    }
393
394    fn evaluate_raster_scalar_binary(
395        &self,
396        raster: &RasterBuffer,
397        op: BinaryOp,
398        scalar: f64,
399        scalar_left: bool,
400    ) -> Result<Value> {
401        let mut result = RasterBuffer::zeros(raster.width(), raster.height(), raster.data_type());
402
403        for y in 0..raster.height() {
404            for x in 0..raster.width() {
405                let r = raster.get_pixel(x, y).map_err(AlgorithmError::Core)?;
406
407                let val = if scalar_left {
408                    match op {
409                        BinaryOp::Add => scalar + r,
410                        BinaryOp::Subtract => scalar - r,
411                        BinaryOp::Multiply => scalar * r,
412                        BinaryOp::Divide => {
413                            if r.abs() < f64::EPSILON {
414                                f64::NAN
415                            } else {
416                                scalar / r
417                            }
418                        }
419                        BinaryOp::Modulo => scalar % r,
420                        BinaryOp::Power => scalar.powf(r),
421                        BinaryOp::Equal => {
422                            if (scalar - r).abs() < f64::EPSILON {
423                                1.0
424                            } else {
425                                0.0
426                            }
427                        }
428                        BinaryOp::NotEqual => {
429                            if (scalar - r).abs() >= f64::EPSILON {
430                                1.0
431                            } else {
432                                0.0
433                            }
434                        }
435                        BinaryOp::Less => {
436                            if scalar < r {
437                                1.0
438                            } else {
439                                0.0
440                            }
441                        }
442                        BinaryOp::LessEqual => {
443                            if scalar <= r {
444                                1.0
445                            } else {
446                                0.0
447                            }
448                        }
449                        BinaryOp::Greater => {
450                            if scalar > r {
451                                1.0
452                            } else {
453                                0.0
454                            }
455                        }
456                        BinaryOp::GreaterEqual => {
457                            if scalar >= r {
458                                1.0
459                            } else {
460                                0.0
461                            }
462                        }
463                        BinaryOp::And | BinaryOp::Or => {
464                            return Err(AlgorithmError::InvalidParameter {
465                                parameter: "operator",
466                                message: "Logical operators require boolean operands".to_string(),
467                            });
468                        }
469                    }
470                } else {
471                    match op {
472                        BinaryOp::Add => r + scalar,
473                        BinaryOp::Subtract => r - scalar,
474                        BinaryOp::Multiply => r * scalar,
475                        BinaryOp::Divide => {
476                            if scalar.abs() < f64::EPSILON {
477                                f64::NAN
478                            } else {
479                                r / scalar
480                            }
481                        }
482                        BinaryOp::Modulo => r % scalar,
483                        BinaryOp::Power => r.powf(scalar),
484                        BinaryOp::Equal => {
485                            if (r - scalar).abs() < f64::EPSILON {
486                                1.0
487                            } else {
488                                0.0
489                            }
490                        }
491                        BinaryOp::NotEqual => {
492                            if (r - scalar).abs() >= f64::EPSILON {
493                                1.0
494                            } else {
495                                0.0
496                            }
497                        }
498                        BinaryOp::Less => {
499                            if r < scalar {
500                                1.0
501                            } else {
502                                0.0
503                            }
504                        }
505                        BinaryOp::LessEqual => {
506                            if r <= scalar {
507                                1.0
508                            } else {
509                                0.0
510                            }
511                        }
512                        BinaryOp::Greater => {
513                            if r > scalar {
514                                1.0
515                            } else {
516                                0.0
517                            }
518                        }
519                        BinaryOp::GreaterEqual => {
520                            if r >= scalar {
521                                1.0
522                            } else {
523                                0.0
524                            }
525                        }
526                        BinaryOp::And | BinaryOp::Or => {
527                            return Err(AlgorithmError::InvalidParameter {
528                                parameter: "operator",
529                                message: "Logical operators require boolean operands".to_string(),
530                            });
531                        }
532                    }
533                };
534
535                result.set_pixel(x, y, val).map_err(AlgorithmError::Core)?;
536            }
537        }
538
539        Ok(Value::Raster(Box::new(result)))
540    }
541
542    fn evaluate_unary(
543        &mut self,
544        op: UnaryOp,
545        expr: &Expr,
546        env: &Environment,
547        band_ctx: &BandContext,
548    ) -> Result<Value> {
549        let val = self.evaluate_expr(expr, env, band_ctx)?;
550
551        match val {
552            Value::Number(n) => {
553                let result = match op {
554                    UnaryOp::Negate => -n,
555                    UnaryOp::Plus => n,
556                    UnaryOp::Not => {
557                        return Err(AlgorithmError::InvalidParameter {
558                            parameter: "operator",
559                            message: "Not operator requires boolean".to_string(),
560                        });
561                    }
562                };
563                Ok(Value::Number(result))
564            }
565            Value::Bool(b) => match op {
566                UnaryOp::Not => Ok(Value::Bool(!b)),
567                _ => Err(AlgorithmError::InvalidParameter {
568                    parameter: "operator",
569                    message: "Operator not supported for booleans".to_string(),
570                }),
571            },
572            Value::Raster(raster) => {
573                let mut result =
574                    RasterBuffer::zeros(raster.width(), raster.height(), raster.data_type());
575
576                for y in 0..raster.height() {
577                    for x in 0..raster.width() {
578                        let val = raster.get_pixel(x, y).map_err(AlgorithmError::Core)?;
579                        let new_val = match op {
580                            UnaryOp::Negate => -val,
581                            UnaryOp::Plus => val,
582                            UnaryOp::Not => {
583                                return Err(AlgorithmError::InvalidParameter {
584                                    parameter: "operator",
585                                    message: "Not operator requires boolean operands".to_string(),
586                                });
587                            }
588                        };
589                        result
590                            .set_pixel(x, y, new_val)
591                            .map_err(AlgorithmError::Core)?;
592                    }
593                }
594
595                Ok(Value::Raster(Box::new(result)))
596            }
597            _ => Err(AlgorithmError::InvalidParameter {
598                parameter: "operand",
599                message: "Incompatible operand type for unary operator".to_string(),
600            }),
601        }
602    }
603
604    fn evaluate_call(
605        &mut self,
606        name: &str,
607        args: &[Expr],
608        env: &Environment,
609        band_ctx: &BandContext,
610    ) -> Result<Value> {
611        // Check if it's a user-defined function
612        if let Ok(func_val) = env.lookup(name) {
613            if let Value::Function {
614                params,
615                body,
616                env: func_env,
617            } = func_val
618            {
619                if params.len() != args.len() {
620                    return Err(AlgorithmError::InvalidParameter {
621                        parameter: "arguments",
622                        message: format!("Expected {} arguments, got {}", params.len(), args.len()),
623                    });
624                }
625
626                // Create new environment with parameters bound
627                let mut new_env = Environment::with_parent(func_env.clone());
628                for (param, arg) in params.iter().zip(args.iter()) {
629                    let arg_val = self.evaluate_expr(arg, env, band_ctx)?;
630                    new_env.define(param.clone(), arg_val);
631                }
632
633                return self.evaluate_expr(body, &new_env, band_ctx);
634            }
635        }
636
637        // Check if it's a built-in function
638        if let Some((func, arity)) = self.func_registry.lookup(name) {
639            if arity > 0 && args.len() != arity {
640                return Err(AlgorithmError::InvalidParameter {
641                    parameter: "arguments",
642                    message: format!("Expected {arity} arguments, got {}", args.len()),
643                });
644            }
645
646            let arg_vals: Result<Vec<Value>> = args
647                .iter()
648                .map(|arg| self.evaluate_expr(arg, env, band_ctx))
649                .collect();
650
651            func(&arg_vals?)
652        } else {
653            Err(AlgorithmError::InvalidParameter {
654                parameter: "function",
655                message: format!("Unknown function: {name}"),
656            })
657        }
658    }
659
660    fn evaluate_conditional(
661        &mut self,
662        condition: &Expr,
663        then_expr: &Expr,
664        else_expr: &Expr,
665        env: &Environment,
666        band_ctx: &BandContext,
667    ) -> Result<Value> {
668        let cond_val = self.evaluate_expr(condition, env, band_ctx)?;
669
670        match cond_val {
671            Value::Bool(b) => {
672                if b {
673                    self.evaluate_expr(then_expr, env, band_ctx)
674                } else {
675                    self.evaluate_expr(else_expr, env, band_ctx)
676                }
677            }
678            Value::Number(n) => {
679                if n.abs() > f64::EPSILON {
680                    self.evaluate_expr(then_expr, env, band_ctx)
681                } else {
682                    self.evaluate_expr(else_expr, env, band_ctx)
683                }
684            }
685            Value::Raster(cond_raster) => {
686                // Pixel-wise conditional evaluation
687                let then_val = self.evaluate_expr(then_expr, env, band_ctx)?;
688                let else_val = self.evaluate_expr(else_expr, env, band_ctx)?;
689
690                let width = cond_raster.width();
691                let height = cond_raster.height();
692                let mut result = RasterBuffer::zeros(width, height, cond_raster.data_type());
693
694                for y in 0..height {
695                    for x in 0..width {
696                        let cond = cond_raster.get_pixel(x, y).map_err(AlgorithmError::Core)?;
697                        let is_true = cond.abs() > f64::EPSILON;
698
699                        let val = if is_true {
700                            match &then_val {
701                                Value::Raster(r) => {
702                                    r.get_pixel(x, y).map_err(AlgorithmError::Core)?
703                                }
704                                Value::Number(n) => *n,
705                                Value::Bool(b) => {
706                                    if *b {
707                                        1.0
708                                    } else {
709                                        0.0
710                                    }
711                                }
712                                _ => {
713                                    return Err(AlgorithmError::InvalidParameter {
714                                        parameter: "then_expr",
715                                        message: "Then expression must be raster or scalar"
716                                            .to_string(),
717                                    });
718                                }
719                            }
720                        } else {
721                            match &else_val {
722                                Value::Raster(r) => {
723                                    r.get_pixel(x, y).map_err(AlgorithmError::Core)?
724                                }
725                                Value::Number(n) => *n,
726                                Value::Bool(b) => {
727                                    if *b {
728                                        1.0
729                                    } else {
730                                        0.0
731                                    }
732                                }
733                                _ => {
734                                    return Err(AlgorithmError::InvalidParameter {
735                                        parameter: "else_expr",
736                                        message: "Else expression must be raster or scalar"
737                                            .to_string(),
738                                    });
739                                }
740                            }
741                        };
742
743                        result.set_pixel(x, y, val).map_err(AlgorithmError::Core)?;
744                    }
745                }
746
747                Ok(Value::Raster(Box::new(result)))
748            }
749            _ => Err(AlgorithmError::InvalidParameter {
750                parameter: "condition",
751                message: "Condition must be boolean, number, or raster".to_string(),
752            }),
753        }
754    }
755
756    fn evaluate_for_loop(
757        &mut self,
758        var: &str,
759        start: &Expr,
760        end: &Expr,
761        body: &Expr,
762        env: &Environment,
763        band_ctx: &BandContext,
764    ) -> Result<Value> {
765        let start_val = self.evaluate_expr(start, env, band_ctx)?.as_number()?;
766        let end_val = self.evaluate_expr(end, env, band_ctx)?.as_number()?;
767
768        // Guard against degenerate or excessively large loops to prevent OOM
769        const MAX_ITERATIONS: i64 = 1_000_000;
770        let start_i = start_val.floor() as i64;
771        let end_i = end_val.floor() as i64;
772        let iterations = (end_i - start_i).max(0);
773
774        if iterations > MAX_ITERATIONS {
775            return Err(AlgorithmError::InvalidParameter {
776                parameter: "for",
777                message: format!(
778                    "For loop would execute {iterations} iterations (max {MAX_ITERATIONS})"
779                ),
780            });
781        }
782
783        // Execute body for each integer step in [start, end)
784        let mut last_value = Value::Number(0.0);
785        let mut loop_env = Environment::with_parent(env.clone());
786
787        for i in start_i..end_i {
788            loop_env.define(var.to_string(), Value::Number(i as f64));
789            last_value = self.evaluate_expr(body, &loop_env, band_ctx)?;
790        }
791
792        Ok(last_value)
793    }
794
795    fn evaluate_block(
796        &mut self,
797        statements: &[Statement],
798        result: Option<&Expr>,
799        env: &Environment,
800        band_ctx: &BandContext,
801    ) -> Result<Value> {
802        let mut block_env = Environment::with_parent(env.clone());
803
804        for stmt in statements {
805            self.execute_statement(stmt, &mut block_env, band_ctx)?;
806        }
807
808        if let Some(expr) = result {
809            self.evaluate_expr(expr, &block_env, band_ctx)
810        } else {
811            Ok(Value::Number(0.0))
812        }
813    }
814}
815
816#[cfg(test)]
817mod tests {
818    use super::*;
819    use crate::dsl::parser::parse_expression;
820    use oxigdal_core::types::RasterDataType;
821
822    #[test]
823    fn test_compile_number() {
824        let expr = parse_expression("42").expect("Should parse");
825        let program = Program {
826            statements: vec![Statement::Expr(Box::new(expr))],
827        };
828        let compiled = CompiledProgram::new(program);
829
830        let bands = vec![RasterBuffer::zeros(10, 10, RasterDataType::Float32)];
831        let result = compiled.execute(&bands);
832        assert!(result.is_ok());
833    }
834
835    #[test]
836    fn test_compile_band() {
837        let expr = parse_expression("B1").expect("Should parse");
838        let program = Program {
839            statements: vec![Statement::Expr(Box::new(expr))],
840        };
841        let compiled = CompiledProgram::new(program);
842
843        let bands = vec![RasterBuffer::zeros(10, 10, RasterDataType::Float32)];
844        let result = compiled.execute(&bands);
845        assert!(result.is_ok());
846    }
847
848    #[test]
849    fn test_compile_arithmetic() {
850        let expr = parse_expression("B1 + B2").expect("Should parse");
851        let program = Program {
852            statements: vec![Statement::Expr(Box::new(expr))],
853        };
854        let compiled = CompiledProgram::new(program);
855
856        let bands = vec![
857            RasterBuffer::zeros(10, 10, RasterDataType::Float32),
858            RasterBuffer::zeros(10, 10, RasterDataType::Float32),
859        ];
860        let result = compiled.execute(&bands);
861        assert!(result.is_ok());
862    }
863}