## Coverage Report

#### Created: 2025-09-08 21:26

next uncovered line (L), next uncovered region (R), next uncovered branch (B)

    /home/noah/src/ruchy/src/runtime/lazy.rs

    Line
    Count
    Source
    1
    //! Lazy evaluation support for pipelines and operations
    2
    //!
    3
    //! This module implements lazy evaluation for performance optimization,
    4
    //! allowing operations to be composed without immediate execution.
    5
    6
    use std::cell::RefCell;
    7
    use std::rc::Rc;
    8
    9
    /// Represents a lazily evaluated value
    10
    pub enum LazyValue {
    11
        /// Already computed value
    12
        Computed(Value),
    13
        /// Deferred computation (using Rc for shared ownership of closure)
    14
        Deferred(Rc<RefCell<Option<Value>>>, Rc<dyn Fn() -> Result<Value>>),
    15
        /// Lazy pipeline stage
    16
        Pipeline {
    17
            source: Box<LazyValue>,
    18
            transform: Rc<dyn Fn(Value) -> Result<Value>>,
    19
        },
    20
    }
    21
    22
    impl Clone for LazyValue {
    23
    0
        fn clone(&self) -> Self {
    24
    0
            match self {
    25
    0
                LazyValue::Computed(v) => LazyValue::Computed(v.clone()),
    26
    0
                LazyValue::Deferred(cache, computation) => {
    27
    0
                    LazyValue::Deferred(Rc::clone(cache), Rc::clone(computation))
    28
                }
    29
    0
                LazyValue::Pipeline { source, transform } => LazyValue::Pipeline {
    30
    0
                    source: source.clone(),
    31
    0
                    transform: Rc::clone(transform),
    32
    0
                },
    33
            }
    34
    0
        }
    35
    }
    36
    37
    use crate::runtime::repl::Value;
    38
    use anyhow::Result;
    39
    40
    impl LazyValue {
    41
        /// Create a new computed lazy value
    42
    0
        pub fn computed(value: Value) -> Self {
    43
    0
            LazyValue::Computed(value)
    44
    0
        }
    45
    46
        /// Create a new deferred lazy value
    47
    0
        pub fn deferred<F>(computation: F) -> Self
    48
    0
        where
    49
    0
            F: Fn() -> Result<Value> + 'static,
    50
        {
    51
    0
            LazyValue::Deferred(Rc::new(RefCell::new(None)), Rc::new(computation))
    52
    0
        }
    53
    54
        /// Create a pipeline transformation
    55
    0
        pub fn pipeline<F>(source: LazyValue, transform: F) -> Self
    56
    0
        where
    57
    0
            F: Fn(Value) -> Result<Value> + 'static,
    58
        {
    59
    0
            LazyValue::Pipeline {
    60
    0
                source: Box::new(source),
    61
    0
                transform: Rc::new(transform),
    62
    0
            }
    63
    0
        }
    64
    65
        /// Force evaluation of the lazy value
    66
        ///
    67
        /// # Errors
    68
        ///
    69
        /// Returns an error if the computation fails
    70
    0
        pub fn force(&self) -> Result<Value> {
    71
    0
            match self {
    72
    0
                LazyValue::Computed(value) => Ok(value.clone()),
    73
    0
                LazyValue::Deferred(cache, computation) => {
    74
                    // Check if already computed
    75
    0
                    if let Some(cached) = cache.borrow().as_ref() {
    76
    0
                        return Ok(cached.clone());
    77
    0
                    }
    78
    79
                    // Compute and cache
    80
    0
                    let result = computation()?;
    81
    0
                    *cache.borrow_mut() = Some(result.clone());
    82
    0
                    Ok(result)
    83
                }
    84
    0
                LazyValue::Pipeline { source, transform } => {
    85
    0
                    let source_value = source.force()?;
    86
    0
                    transform(source_value)
    87
                }
    88
            }
    89
    0
        }
    90
    91
        /// Check if the value has been computed
    92
    0
        pub fn is_computed(&self) -> bool {
    93
    0
            match self {
    94
    0
                LazyValue::Computed(_) => true,
    95
    0
                LazyValue::Deferred(cache, _) => cache.borrow().is_some(),
    96
    0
                LazyValue::Pipeline { .. } => false,
    97
            }
    98
    0
        }
    99
    }
    100
    101
    /// Type alias for filter predicates
    102
    type FilterPredicate = Box<dyn Fn(&Value) -> Result<bool>>;
    103
    /// Type alias for map transforms
    104
    type MapTransform = Box<dyn Fn(Value) -> Result<Value>>;
    105
    106
    /// Lazy iterator for efficient collection processing
    107
    pub struct LazyIterator {
    108
        /// Current state of the iterator
    109
        state: RefCell<LazyIterState>,
    110
    }
    111
    112
    enum LazyIterState {
    113
        /// Source collection
    114
        Source(Vec<Value>),
    115
        /// Map transformation
    116
        Map {
    117
            source: Box<LazyIterator>,
    118
            transform: MapTransform,
    119
        },
    120
        /// Filter transformation
    121
        Filter {
    122
            source: Box<LazyIterator>,
    123
            predicate: FilterPredicate,
    124
        },
    125
        /// Take n elements
    126
        Take {
    127
            source: Box<LazyIterator>,
    128
            count: usize,
    129
        },
    130
        /// Skip n elements
    131
        Skip {
    132
            source: Box<LazyIterator>,
    133
            count: usize,
    134
        },
    135
    }
    136
    137
    impl LazyIterator {
    138
        /// Create a new lazy iterator from a collection
    139
    0
        pub fn from_vec(values: Vec<Value>) -> Self {
    140
    0
            LazyIterator {
    141
    0
                state: RefCell::new(LazyIterState::Source(values)),
    142
    0
            }
    143
    0
        }
    144
    145
        /// Map transformation
    146
        #[must_use]
    147
    0
        pub fn map<F>(self, transform: F) -> Self
    148
    0
        where
    149
    0
            F: Fn(Value) -> Result<Value> + 'static,
    150
        {
    151
    0
            LazyIterator {
    152
    0
                state: RefCell::new(LazyIterState::Map {
    153
    0
                    source: Box::new(self),
    154
    0
                    transform: Box::new(transform),
    155
    0
                }),
    156
    0
            }
    157
    0
        }
    158
    159
        /// Filter transformation
    160
        #[must_use]
    161
    0
        pub fn filter<F>(self, predicate: F) -> Self
    162
    0
        where
    163
    0
            F: Fn(&Value) -> Result<bool> + 'static,
    164
        {
    165
    0
            LazyIterator {
    166
    0
                state: RefCell::new(LazyIterState::Filter {
    167
    0
                    source: Box::new(self),
    168
    0
                    predicate: Box::new(predicate),
    169
    0
                }),
    170
    0
            }
    171
    0
        }
    172
    173
        /// Take first n elements
    174
        #[must_use]
    175
    0
        pub fn take(self, count: usize) -> Self {
    176
    0
            LazyIterator {
    177
    0
                state: RefCell::new(LazyIterState::Take {
    178
    0
                    source: Box::new(self),
    179
    0
                    count,
    180
    0
                }),
    181
    0
            }
    182
    0
        }
    183
    184
        /// Skip first n elements
    185
        #[must_use]
    186
    0
        pub fn skip(self, count: usize) -> Self {
    187
    0
            LazyIterator {
    188
    0
                state: RefCell::new(LazyIterState::Skip {
    189
    0
                    source: Box::new(self),
    190
    0
                    count,
    191
    0
                }),
    192
    0
            }
    193
    0
        }
    194
    195
        /// Collect the iterator into a vector (forces evaluation)
    196
        ///
    197
        /// # Errors
    198
        ///
    199
        /// Returns an error if any transformation fails
    200
    0
        pub fn collect(&self) -> Result<Vec<Value>> {
    201
    0
            match &*self.state.borrow() {
    202
    0
                LazyIterState::Source(values) => Ok(values.clone()),
    203
    0
                LazyIterState::Map { source, transform } => {
    204
    0
                    let source_values = source.collect()?;
    205
    0
                    source_values
    206
    0
                        .into_iter()
    207
    0
                        .map(transform)
    208
    0
                        .collect::<Result<Vec<_>>>()
    209
                }
    210
    0
                LazyIterState::Filter { source, predicate } => {
    211
    0
                    let source_values = source.collect()?;
    212
    0
                    let mut result = Vec::new();
    213
    0
                    for value in source_values {
    214
    0
                        if predicate(&value)? {
    215
    0
                            result.push(value);
    216
    0
                        }
    217
                    }
    218
    0
                    Ok(result)
    219
                }
    220
    0
                LazyIterState::Take { source, count } => {
    221
    0
                    let source_values = source.collect()?;
    222
    0
                    Ok(source_values.into_iter().take(*count).collect())
    223
                }
    224
    0
                LazyIterState::Skip { source, count } => {
    225
    0
                    let source_values = source.collect()?;
    226
    0
                    Ok(source_values.into_iter().skip(*count).collect())
    227
                }
    228
            }
    229
    0
        }
    230
    231
        /// Get the first element (forces minimal evaluation)
    232
        ///
    233
        /// # Errors
    234
        ///
    235
        /// Returns an error if evaluation fails
    236
    0
        pub fn first(&self) -> Result<Option<Value>> {
    237
    0
            let values = self.collect()?;
    238
    0
            Ok(values.into_iter().next())
    239
    0
        }
    240
    241
        /// Count elements (optimized to avoid full materialization where possible)
    242
        ///
    243
        /// # Errors
    244
        ///
    245
        /// Returns an error if evaluation fails
    246
    0
        pub fn count(&self) -> Result<usize> {
    247
    0
            match &*self.state.borrow() {
    248
    0
                LazyIterState::Source(values) => Ok(values.len()),
    249
    0
                _ => self.collect().map(|v| v.len()),
    250
            }
    251
    0
        }
    252
    }
    253
    254
    /// Lazy evaluation cache for memoization
    255
    pub struct LazyCache {
    256
        cache: RefCell<std::collections::HashMap<String, Value>>,
    257
    }
    258
    259
    impl LazyCache {
    260
        /// Create a new lazy cache
    261
    0
        pub fn new() -> Self {
    262
    0
            LazyCache {
    263
    0
                cache: RefCell::new(std::collections::HashMap::new()),
    264
    0
            }
    265
    0
        }
    266
    267
        /// Get or compute a value
    268
        ///
    269
        /// # Errors
    270
        ///
    271
        /// Returns an error if computation fails
    272
    0
        pub fn get_or_compute<F>(&self, key: &str, compute: F) -> Result<Value>
    273
    0
        where
    274
    0
            F: FnOnce() -> Result<Value>,
    275
        {
    276
    0
            if let Some(value) = self.cache.borrow().get(key) {
    277
    0
                return Ok(value.clone());
    278
    0
            }
    279
    280
    0
            let value = compute()?;
    281
    0
            self.cache
    282
    0
                .borrow_mut()
    283
    0
                .insert(key.to_string(), value.clone());
    284
    0
            Ok(value)
    285
    0
        }
    286
    287
        /// Clear the cache
    288
    0
        pub fn clear(&self) {
    289
    0
            self.cache.borrow_mut().clear();
    290
    0
        }
    291
    292
        /// Get cache size
    293
    0
        pub fn size(&self) -> usize {
    294
    0
            self.cache.borrow().len()
    295
    0
        }
    296
    }
    297
    298
    impl Default for LazyCache {
    299
    0
        fn default() -> Self {
    300
    0
            Self::new()
    301
    0
        }
    302
    }
    303
    304
    #[cfg(test)]
    305
    #[allow(clippy::unwrap_used)]
    306
    mod tests {
    307
        use super::*;
    308
    309
        #[test]
    310
        fn test_lazy_value_computed() {
    311
            let lazy = LazyValue::computed(Value::Int(42));
    312
            assert!(lazy.is_computed());
    313
            assert_eq!(lazy.force().unwrap(), Value::Int(42));
    314
        }
    315
    316
        #[test]
    317
        fn test_lazy_value_deferred() {
    318
            let counter = Rc::new(RefCell::new(0));
    319
            let counter_clone = Rc::clone(&counter);
    320
    321
            let lazy = LazyValue::deferred(move || {
    322
                *counter_clone.borrow_mut() += 1;
    323
                Ok(Value::Int(42))
    324
            });
    325
    326
            assert!(!lazy.is_computed());
    327
            assert_eq!(*counter.borrow(), 0);
    328
    329
            // First force computes
    330
            assert_eq!(lazy.force().unwrap(), Value::Int(42));
    331
            assert_eq!(*counter.borrow(), 1);
    332
    333
            // Second force uses cache
    334
            assert_eq!(lazy.force().unwrap(), Value::Int(42));
    335
            assert_eq!(*counter.borrow(), 1); // Not incremented again
    336
        }
    337
    338
        #[test]
    339
        fn test_lazy_iterator_map() {
    340
            let values = vec![Value::Int(1), Value::Int(2), Value::Int(3)];
    341
            let lazy = LazyIterator::from_vec(values).map(|v| {
    342
                if let Value::Int(n) = v {
    343
                    Ok(Value::Int(n * 2))
    344
                } else {
    345
                    Ok(v)
    346
                }
    347
            });
    348
    349
            let result = lazy.collect().unwrap();
    350
            assert_eq!(result, vec![Value::Int(2), Value::Int(4), Value::Int(6)]);
    351
        }
    352
    353
        #[test]
    354
        fn test_lazy_iterator_filter() {
    355
            let values = vec![Value::Int(1), Value::Int(2), Value::Int(3), Value::Int(4)];
    356
            let lazy = LazyIterator::from_vec(values).filter(|v| {
    357
                if let Value::Int(n) = v {
    358
                    Ok(n % 2 == 0)
    359
                } else {
    360
                    Ok(false)
    361
                }
    362
            });
    363
    364
            let result = lazy.collect().unwrap();
    365
            assert_eq!(result, vec![Value::Int(2), Value::Int(4)]);
    366
        }
    367
    368
        #[test]
    369
        fn test_lazy_cache() {
    370
            let cache = LazyCache::new();
    371
            let counter = Rc::new(RefCell::new(0));
    372
    373
            // First call computes
    374
            let counter_clone = Rc::clone(&counter);
    375
            let result = cache
    376
                .get_or_compute("key", || {
    377
                    *counter_clone.borrow_mut() += 1;
    378
                    Ok(Value::Int(42))
    379
                })
    380
                .unwrap();
    381
            assert_eq!(result, Value::Int(42));
    382
            assert_eq!(*counter.borrow(), 1);
    383
    384
            // Second call uses cache
    385
            let counter_clone = Rc::clone(&counter);
    386
            let result = cache
    387
                .get_or_compute("key", || {
    388
                    *counter_clone.borrow_mut() += 1;
    389
                    Ok(Value::Int(100))
    390
                })
    391
                .unwrap();
    392
            assert_eq!(result, Value::Int(42)); // Cached value
    393
            assert_eq!(*counter.borrow(), 1); // Not incremented
    394
        }
    395
    }