Skip to main content

polydat_core/iteration/comprehension/surfaces/
scoped_stream.rs

1// Copyright 2024-2026 Jonathan Shook
2// SPDX-License-Identifier: Apache-2.0
3
4//! `ScopedKernelStream<K>` — second-order consumption surface
5//! (spec §9.5).
6//!
7//! Wraps a [`CoordinateStream`] + a parent `K`. Each
8//! `advance()` pulls one coord tuple from the underlying IR
9//! and applies `parent.scope(&coords)` to produce a
10//! [`ScopedKernelInstance`].
11//!
12//! Per spec §9.5.2's independence contract: this stream's
13//! cursor is **independent** of any other `CoordinateStream`
14//! or `ScopedKernelStream` instantiated from the same
15//! `CompiledComprehension`. Pulling from this stream does NOT
16//! advance any sibling.
17
18use std::sync::Arc;
19
20use crate::iteration::comprehension::ir::Program;
21
22use super::coord_stream::CoordinateStream;
23use super::instance::{KernelScope, ScopedKernelInstance};
24use super::scope_once::scope_once_with;
25
26/// Second-order stream. Each `advance()` yields one
27/// `ScopedKernelInstance<K::Scoped>` or `None`.
28///
29/// Construct via [`crate::iteration::comprehension::surfaces::CompiledComprehension::scoped_kernel_stream`].
30pub struct ScopedKernelStream<K: KernelScope> {
31    /// Underlying first-order stream. Owns its dispense
32    /// cursor; independent of any sibling streamer.
33    coord_stream: CoordinateStream,
34    /// Captured parent kernel. Each `advance` uses `scope`
35    /// against this same parent.
36    parent: K,
37}
38
39impl<K: KernelScope> ScopedKernelStream<K> {
40    pub(crate) fn new(program: Arc<Program>, parent: K) -> Self {
41        let coord_stream = CoordinateStream::new(program);
42        Self {
43            coord_stream,
44            parent,
45        }
46    }
47
48    /// Pull the next scoped instance. Internally:
49    /// 1. Pull one coord tuple from the underlying
50    ///    `CoordinateStream`.
51    /// 2. Apply `parent.scope(&coords)` (spec §9.5.3's
52    ///    `scope_once` semantic).
53    /// 3. Return the wrapped instance.
54    pub fn advance(&mut self) -> Option<ScopedKernelInstance<K::Scoped>> {
55        let coords = self.coord_stream.advance()?;
56        let instance = scope_once_with(&self.parent, &coords);
57        Some(instance)
58    }
59}
60
61impl<K: KernelScope> Iterator for ScopedKernelStream<K> {
62    type Item = ScopedKernelInstance<K::Scoped>;
63    fn next(&mut self) -> Option<Self::Item> {
64        self.advance()
65    }
66}
67
68#[cfg(test)]
69mod tests {
70    use super::*;
71    use crate::iteration::comprehension::ast::Comprehension;
72    use crate::iteration::comprehension::source::{LiteralValue, Source};
73    use crate::iteration::comprehension::strategies::{Tuple, TupleValue};
74    use crate::iteration::comprehension::surfaces::compile;
75
76    #[derive(Debug, Clone)]
77    struct MockKernel(String);
78
79    impl KernelScope for MockKernel {
80        type Scoped = (String, Vec<(String, TupleValue)>);
81        fn scope(&self, coords: &Tuple) -> Self::Scoped {
82            (self.0.clone(), coords.bindings.clone())
83        }
84    }
85
86    fn clause(name: &str, vs: &[i64]) -> Comprehension {
87        Comprehension::clause(
88            name,
89            Source::Literal {
90                values: vs.iter().map(|n| LiteralValue::Int(*n)).collect(),
91            },
92        )
93    }
94
95    #[test]
96    fn advance_produces_scoped_instances() {
97        let compiled = compile(&clause("k", &[1, 2, 3]));
98        let parent = MockKernel("p".into());
99        let mut stream = compiled.scoped_kernel_stream(parent);
100        let mut count = 0;
101        while let Some(inst) = stream.advance() {
102            assert_eq!(inst.scoped.0, "p");
103            assert_eq!(inst.coords.bindings.len(), 1);
104            count += 1;
105        }
106        assert_eq!(count, 3);
107    }
108
109    #[test]
110    fn dispense_order_matches_coord_stream() {
111        let compiled = compile(&clause("k", &[10, 20, 30]));
112        let parent = MockKernel("p".into());
113        let coord_values: Vec<TupleValue> = compiled
114            .coordinate_stream()
115            .map(|t| t.bindings[0].1.clone())
116            .collect();
117        let scoped_values: Vec<TupleValue> = compiled
118            .scoped_kernel_stream(parent)
119            .map(|inst| inst.coords.bindings[0].1.clone())
120            .collect();
121        assert_eq!(coord_values, scoped_values);
122    }
123}