Skip to main content

oxigdal_algorithms/dsl/
mod.rs

1//! Advanced Raster Algebra Domain-Specific Language (DSL)
2//!
3//! This module provides a comprehensive DSL for raster algebra operations with:
4//!
5//! - **Full expression grammar** with variables, functions, and control flow
6//! - **Type inference** for type-safe operations
7//! - **Optimization passes** including constant folding and algebraic simplifications
8//! - **Built-in function library** with 40+ mathematical, statistical, and spatial functions
9//! - **Compile-time macros** for zero-cost abstractions
10//! - **Runtime parser** for dynamic expression evaluation
11//!
12//! # Quick Start
13//!
14//! ## Simple Expression
15//!
16//! ```
17//! use oxigdal_algorithms::dsl::RasterDsl;
18//! use oxigdal_core::buffer::RasterBuffer;
19//! use oxigdal_core::types::RasterDataType;
20//!
21//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
22//! // Create sample bands
23//! let nir = RasterBuffer::zeros(100, 100, RasterDataType::Float32);
24//! let red = RasterBuffer::zeros(100, 100, RasterDataType::Float32);
25//!
26//! // Calculate NDVI
27//! let dsl = RasterDsl::new();
28//! let result = dsl.execute("(B1 - B2) / (B1 + B2)", &[nir, red])?;
29//! # Ok(())
30//! # }
31//! ```
32//!
33//! ## Complex Program
34//!
35//! ```
36//! use oxigdal_algorithms::dsl::RasterDsl;
37//!
38//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
39//! let program = r#"
40//!     let ndvi = (B8 - B4) / (B8 + B4);
41//!     let evi = 2.5 * ((B8 - B4) / (B8 + 6*B4 - 7.5*B2 + 1));
42//!
43//!     if ndvi > 0.6 && evi > 0.5 then
44//!         1.0
45//!     else if ndvi > 0.3 then
46//!         0.5
47//!     else
48//!         0.0
49//! "#;
50//!
51//! let dsl = RasterDsl::new();
52//! // let result = dsl.execute(program, bands)?;
53//! # Ok(())
54//! # }
55//! ```
56//!
57//! ## Built-in Functions
58//!
59//! The DSL includes a rich set of built-in functions:
60//!
61//! ### Mathematical
62//! - `sqrt`, `abs`, `floor`, `ceil`, `round`
63//! - `log`, `log10`, `log2`, `exp`
64//! - `sin`, `cos`, `tan`, `asin`, `acos`, `atan`, `atan2`
65//! - `pow`, `hypot`
66//!
67//! ### Statistical
68//! - `mean`, `median`, `mode`, `stddev`, `variance`
69//! - `sum`, `product`, `min`, `max`
70//! - `percentile`
71//!
72//! ### Spatial Filters
73//! - `gaussian(raster, sigma)` - Gaussian blur
74//! - `median_filter(raster, radius)` - Median filter
75//!
76//! ### Logical
77//! - `and`, `or`, `not`, `xor`
78//!
79//! ### Comparison
80//! - `eq`, `ne`, `lt`, `le`, `gt`, `ge`
81//!
82//! ### Utility
83//! - `clamp(value, min, max)` - Clamp to range
84//! - `select(condition, then, else)` - Conditional selection
85//!
86//! # Optimization
87//!
88//! The DSL includes several optimization levels:
89//!
90//! ```
91//! use oxigdal_algorithms::dsl::{RasterDsl, OptLevel};
92//!
93//! let mut dsl = RasterDsl::new();
94//! dsl.set_opt_level(OptLevel::Aggressive);
95//! ```
96//!
97//! - `None` - No optimization
98//! - `Basic` - Constant folding only
99//! - `Standard` - Basic + algebraic simplifications (default)
100//! - `Aggressive` - Standard + common subexpression elimination
101
102pub mod ast;
103pub mod compiler;
104pub mod functions;
105pub mod optimizer;
106pub mod parser;
107pub mod variables;
108
109#[cfg(feature = "dsl")]
110pub mod macro_support;
111
112use crate::error::Result;
113use oxigdal_core::buffer::RasterBuffer;
114
115pub use ast::{BinaryOp, Expr, Program, Statement, Type, UnaryOp};
116pub use compiler::CompiledProgram;
117pub use functions::FunctionRegistry;
118pub use optimizer::{OptLevel, Optimizer};
119pub use parser::{parse_expression, parse_program};
120pub use variables::{BandContext, Environment, Value};
121
122/// Main DSL interface
123pub struct RasterDsl {
124    optimizer: Optimizer,
125    func_registry: FunctionRegistry,
126}
127
128impl Default for RasterDsl {
129    fn default() -> Self {
130        Self::new()
131    }
132}
133
134impl RasterDsl {
135    /// Creates a new DSL instance with default settings
136    pub fn new() -> Self {
137        Self {
138            optimizer: Optimizer::new(OptLevel::Standard),
139            func_registry: FunctionRegistry::new(),
140        }
141    }
142
143    /// Sets the optimization level
144    pub fn set_opt_level(&mut self, level: OptLevel) {
145        self.optimizer = Optimizer::new(level);
146    }
147
148    /// Gets the current optimization level
149    pub fn opt_level(&self) -> OptLevel {
150        OptLevel::Standard // Return default, could be stored in optimizer
151    }
152
153    /// Parses and executes a DSL expression
154    ///
155    /// # Arguments
156    ///
157    /// * `expression` - The DSL expression or program to execute
158    /// * `bands` - Input raster bands (B1, B2, etc.)
159    ///
160    /// # Examples
161    ///
162    /// ```
163    /// use oxigdal_algorithms::dsl::RasterDsl;
164    /// use oxigdal_core::buffer::RasterBuffer;
165    /// use oxigdal_core::types::RasterDataType;
166    ///
167    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
168    /// let band1 = RasterBuffer::zeros(10, 10, RasterDataType::Float32);
169    /// let band2 = RasterBuffer::zeros(10, 10, RasterDataType::Float32);
170    ///
171    /// let dsl = RasterDsl::new();
172    /// let result = dsl.execute("B1 + B2", &[band1, band2])?;
173    /// # Ok(())
174    /// # }
175    /// ```
176    pub fn execute(&self, expression: &str, bands: &[RasterBuffer]) -> Result<RasterBuffer> {
177        // Try parsing as expression first
178        if let Ok(expr) = parse_expression(expression) {
179            let optimized = self.optimizer.optimize_expr(expr);
180            let program = Program {
181                statements: vec![Statement::Expr(Box::new(optimized))],
182            };
183            let compiled = CompiledProgram::new(program);
184            return compiled.execute(bands);
185        }
186
187        // Try parsing as program
188        let program = parse_program(expression)?;
189        let optimized = self.optimizer.optimize_program(program);
190        let compiled = CompiledProgram::new(optimized);
191        compiled.execute(bands)
192    }
193
194    /// Compiles a DSL expression to a reusable compiled program
195    pub fn compile(&self, expression: &str) -> Result<CompiledProgram> {
196        // Try parsing as expression first
197        if let Ok(expr) = parse_expression(expression) {
198            let optimized = self.optimizer.optimize_expr(expr);
199            let program = Program {
200                statements: vec![Statement::Expr(Box::new(optimized))],
201            };
202            return Ok(CompiledProgram::new(program));
203        }
204
205        // Try parsing as program
206        let program = parse_program(expression)?;
207        let optimized = self.optimizer.optimize_program(program);
208        Ok(CompiledProgram::new(optimized))
209    }
210
211    /// Gets the function registry
212    pub fn functions(&self) -> &FunctionRegistry {
213        &self.func_registry
214    }
215
216    /// Lists all available function names
217    pub fn list_functions(&self) -> Vec<&'static str> {
218        self.func_registry.function_names()
219    }
220}
221
222#[cfg(test)]
223mod tests {
224    use super::*;
225    use oxigdal_core::types::RasterDataType;
226
227    #[test]
228    fn test_dsl_simple_expression() {
229        let band1 = RasterBuffer::zeros(10, 10, RasterDataType::Float32);
230        let band2 = RasterBuffer::zeros(10, 10, RasterDataType::Float32);
231
232        let dsl = RasterDsl::new();
233        let result = dsl.execute("B1 + B2", &[band1, band2]);
234        assert!(result.is_ok());
235    }
236
237    #[test]
238    fn test_dsl_ndvi() {
239        let nir = RasterBuffer::zeros(10, 10, RasterDataType::Float32);
240        let red = RasterBuffer::zeros(10, 10, RasterDataType::Float32);
241
242        let dsl = RasterDsl::new();
243        let result = dsl.execute("(B1 - B2) / (B1 + B2)", &[nir, red]);
244        assert!(result.is_ok());
245    }
246
247    #[test]
248    fn test_dsl_program() {
249        let bands = vec![
250            RasterBuffer::zeros(10, 10, RasterDataType::Float32),
251            RasterBuffer::zeros(10, 10, RasterDataType::Float32),
252        ];
253
254        let program = r#"
255            let ndvi = (B1 - B2) / (B1 + B2);
256            ndvi;
257        "#;
258
259        let dsl = RasterDsl::new();
260        let result = dsl.execute(program, &bands);
261        assert!(result.is_ok());
262    }
263
264    #[test]
265    fn test_dsl_optimization() {
266        let band = RasterBuffer::zeros(10, 10, RasterDataType::Float32);
267
268        let mut dsl = RasterDsl::new();
269        dsl.set_opt_level(OptLevel::Aggressive);
270
271        let result = dsl.execute("B1 + 0", &[band]);
272        assert!(result.is_ok());
273    }
274
275    #[test]
276    fn test_dsl_compile() {
277        let dsl = RasterDsl::new();
278        let compiled = dsl.compile("(B1 - B2) / (B1 + B2)");
279        assert!(compiled.is_ok());
280    }
281
282    #[test]
283    fn test_function_list() {
284        let dsl = RasterDsl::new();
285        let functions = dsl.list_functions();
286        assert!(!functions.is_empty());
287        assert!(functions.contains(&"sqrt"));
288        assert!(functions.contains(&"sin"));
289    }
290}