oxigdal_algorithms/dsl/
variables.rs1use super::ast::{Expr, Type};
6use crate::error::{AlgorithmError, Result};
7use oxigdal_core::buffer::RasterBuffer;
8
9#[cfg(not(feature = "std"))]
10use alloc::{boxed::Box, collections::BTreeMap as HashMap, string::String, vec::Vec};
11
12#[cfg(feature = "std")]
13use std::collections::HashMap;
14
15#[derive(Debug, Clone)]
17pub enum Value {
18 Number(f64),
20 Bool(bool),
22 Raster(Box<RasterBuffer>),
24 Function {
26 params: Vec<String>,
28 body: Box<Expr>,
30 env: Environment,
32 },
33}
34
35impl Value {
36 pub fn get_type(&self) -> Type {
38 match self {
39 Value::Number(_) => Type::Number,
40 Value::Bool(_) => Type::Bool,
41 Value::Raster(_) => Type::Raster,
42 Value::Function { .. } => Type::Unknown,
43 }
44 }
45
46 pub fn as_number(&self) -> Result<f64> {
48 match self {
49 Value::Number(n) => Ok(*n),
50 Value::Bool(b) => Ok(if *b { 1.0 } else { 0.0 }),
51 _ => Err(AlgorithmError::InvalidParameter {
52 parameter: "value",
53 message: "Cannot convert to number".to_string(),
54 }),
55 }
56 }
57
58 pub fn as_bool(&self) -> Result<bool> {
60 match self {
61 Value::Bool(b) => Ok(*b),
62 Value::Number(n) => Ok(n.abs() > f64::EPSILON),
63 _ => Err(AlgorithmError::InvalidParameter {
64 parameter: "value",
65 message: "Cannot convert to bool".to_string(),
66 }),
67 }
68 }
69
70 pub fn as_raster(&self) -> Result<&RasterBuffer> {
72 match self {
73 Value::Raster(r) => Ok(r),
74 _ => Err(AlgorithmError::InvalidParameter {
75 parameter: "value",
76 message: "Not a raster".to_string(),
77 }),
78 }
79 }
80}
81
82#[derive(Debug, Clone)]
84pub struct Environment {
85 bindings: HashMap<String, Value>,
87 parent: Option<Box<Environment>>,
89}
90
91impl Default for Environment {
92 fn default() -> Self {
93 Self::new()
94 }
95}
96
97impl Environment {
98 pub fn new() -> Self {
100 Self {
101 bindings: HashMap::new(),
102 parent: None,
103 }
104 }
105
106 pub fn with_parent(parent: Environment) -> Self {
108 Self {
109 bindings: HashMap::new(),
110 parent: Some(Box::new(parent)),
111 }
112 }
113
114 pub fn define(&mut self, name: String, value: Value) {
116 self.bindings.insert(name, value);
117 }
118
119 pub fn lookup(&self, name: &str) -> Result<&Value> {
121 if let Some(value) = self.bindings.get(name) {
122 Ok(value)
123 } else if let Some(parent) = &self.parent {
124 parent.lookup(name)
125 } else {
126 Err(AlgorithmError::InvalidParameter {
127 parameter: "variable",
128 message: format!("Undefined variable: {name}"),
129 })
130 }
131 }
132
133 pub fn is_defined(&self, name: &str) -> bool {
135 self.bindings.contains_key(name) || self.parent.as_ref().is_some_and(|p| p.is_defined(name))
136 }
137
138 pub fn update(&mut self, name: &str, value: Value) -> Result<()> {
140 if self.bindings.contains_key(name) {
141 self.bindings.insert(name.to_string(), value);
142 Ok(())
143 } else if let Some(parent) = &mut self.parent {
144 parent.update(name, value)
145 } else {
146 Err(AlgorithmError::InvalidParameter {
147 parameter: "variable",
148 message: format!("Undefined variable: {name}"),
149 })
150 }
151 }
152
153 pub fn variables(&self) -> Vec<String> {
155 self.bindings.keys().cloned().collect()
156 }
157
158 pub fn len(&self) -> usize {
160 self.bindings.len()
161 }
162
163 pub fn is_empty(&self) -> bool {
165 self.bindings.is_empty()
166 }
167
168 pub fn merge(&mut self, other: Environment) {
170 for (name, value) in other.bindings {
171 self.bindings.insert(name, value);
172 }
173 }
174
175 pub fn snapshot(&self) -> HashMap<String, Value> {
177 let mut result = HashMap::new();
178
179 if let Some(parent) = &self.parent {
181 for (k, v) in parent.snapshot() {
182 result.insert(k, v);
183 }
184 }
185
186 for (k, v) in &self.bindings {
188 result.insert(k.clone(), v.clone());
189 }
190
191 result
192 }
193}
194
195#[derive(Debug, Clone)]
197pub struct BandContext<'a> {
198 bands: &'a [RasterBuffer],
199}
200
201impl<'a> BandContext<'a> {
202 pub fn new(bands: &'a [RasterBuffer]) -> Self {
204 Self { bands }
205 }
206
207 pub fn get_band(&self, index: usize) -> Result<&RasterBuffer> {
209 if index == 0 || index > self.bands.len() {
210 return Err(AlgorithmError::InvalidParameter {
211 parameter: "band",
212 message: format!("Band index {} out of range (1-{})", index, self.bands.len()),
213 });
214 }
215 Ok(&self.bands[index - 1])
216 }
217
218 pub fn num_bands(&self) -> usize {
220 self.bands.len()
221 }
222
223 pub fn is_valid_band(&self, index: usize) -> bool {
225 index > 0 && index <= self.bands.len()
226 }
227
228 pub fn all_bands(&self) -> &[RasterBuffer] {
230 self.bands
231 }
232}
233
234#[cfg(test)]
235mod tests {
236 use super::*;
237 use oxigdal_core::types::RasterDataType;
238
239 #[test]
240 fn test_environment_define_lookup() {
241 let mut env = Environment::new();
242 env.define("x".to_string(), Value::Number(42.0));
243
244 let val = env.lookup("x").expect("Should find x");
245 assert!(matches!(val, Value::Number(n) if (n - 42.0).abs() < 1e-10));
246 }
247
248 #[test]
249 fn test_environment_parent() {
250 let mut parent = Environment::new();
251 parent.define("x".to_string(), Value::Number(10.0));
252
253 let mut child = Environment::with_parent(parent);
254 child.define("y".to_string(), Value::Number(20.0));
255
256 assert!(child.lookup("x").is_ok());
257 assert!(child.lookup("y").is_ok());
258 assert!(child.lookup("z").is_err());
259 }
260
261 #[test]
262 fn test_environment_update() {
263 let mut env = Environment::new();
264 env.define("x".to_string(), Value::Number(10.0));
265
266 let result = env.update("x", Value::Number(20.0));
267 assert!(result.is_ok());
268
269 let val = env.lookup("x").expect("Should find x");
270 assert!(matches!(val, Value::Number(n) if (n - 20.0).abs() < 1e-10));
271 }
272
273 #[test]
274 fn test_band_context() {
275 let bands = vec![
276 RasterBuffer::zeros(10, 10, RasterDataType::Float32),
277 RasterBuffer::zeros(10, 10, RasterDataType::Float32),
278 ];
279
280 let ctx = BandContext::new(&bands);
281 assert_eq!(ctx.num_bands(), 2);
282 assert!(ctx.is_valid_band(1));
283 assert!(ctx.is_valid_band(2));
284 assert!(!ctx.is_valid_band(0));
285 assert!(!ctx.is_valid_band(3));
286 }
287
288 #[test]
289 fn test_value_conversions() {
290 let num = Value::Number(42.5);
291 assert!((num.as_number().expect("Should convert") - 42.5).abs() < 1e-10);
292
293 let bool_val = Value::Bool(true);
294 assert!(bool_val.as_bool().expect("Should convert"));
295
296 let zero = Value::Number(0.0);
297 assert!(!zero.as_bool().expect("Should convert"));
298 }
299
300 #[test]
301 fn test_environment_snapshot() {
302 let mut parent = Environment::new();
303 parent.define("x".to_string(), Value::Number(10.0));
304
305 let mut child = Environment::with_parent(parent);
306 child.define("y".to_string(), Value::Number(20.0));
307
308 let snapshot = child.snapshot();
309 assert_eq!(snapshot.len(), 2);
310 }
311}