Skip to main content

polydat_core/iteration/comprehension/surfaces/
compiled.rs

1// Copyright 2024-2026 Jonathan Shook
2// SPDX-License-Identifier: Apache-2.0
3
4//! `CompiledComprehension` — the entry point for the three
5//! consumption surfaces.
6//!
7//! Holds an `Arc<Program>` (immutable IR per spec §9.1). Each
8//! factory method on this handle (`coordinate_stream`,
9//! `scoped_kernel_stream`, `scope_once`) returns a fresh
10//! streamer with its own dispense state but shares the
11//! `Arc<Program>` — no recompilation across siblings (spec
12//! §9.5.2's "IR-sharing test" property).
13
14use std::sync::Arc;
15
16use crate::iteration::comprehension::ast::Comprehension;
17use crate::iteration::comprehension::ir::{Program, compile as compile_to_ir};
18
19use super::coord_stream::CoordinateStream;
20use super::instance::{KernelScope, ScopedKernelInstance};
21use super::scope_once::scope_once_with;
22use super::scoped_stream::ScopedKernelStream;
23
24/// A comprehension that has been compiled to immutable IR
25/// and is ready to dispense. The single source of truth for
26/// the underlying program across the consumption surfaces.
27///
28/// Construction is via [`from_ast`](Self::from_ast) (compiles once) or
29/// [`from_program`](Self::from_program) (when the IR was compiled elsewhere).
30/// Cloning a `CompiledComprehension` is cheap — just an
31/// `Arc::clone` on the program.
32#[derive(Debug, Clone)]
33pub struct CompiledComprehension {
34    program: Arc<Program>,
35}
36
37impl CompiledComprehension {
38    /// Compile an AST. Performs the AST → IR pass once.
39    pub fn from_ast(ast: &Comprehension) -> Self {
40        Self {
41            program: Arc::new(compile_to_ir(ast)),
42        }
43    }
44
45    /// Wrap an already-compiled program (tests use this for
46    /// hand-built IR).
47    pub fn from_program(program: Arc<Program>) -> Self {
48        Self { program }
49    }
50
51    /// Access the underlying compiled program (immutable per
52    /// spec §9.1).
53    pub fn program(&self) -> &Program {
54        &self.program
55    }
56
57    /// Clone the `Arc<Program>` for sharing with other
58    /// handles. Used internally by the streamer factories.
59    pub(crate) fn program_arc(&self) -> Arc<Program> {
60        Arc::clone(&self.program)
61    }
62
63    /// **First-order surface** (spec §9.5).
64    ///
65    /// Return a fresh [`CoordinateStream`]. Each call
66    /// allocates new per-streamer state; siblings share the
67    /// underlying IR but dispense independently per spec
68    /// §9.5.2's independence contract.
69    pub fn coordinate_stream(&self) -> CoordinateStream {
70        CoordinateStream::new(self.program_arc())
71    }
72
73    /// **Second-order surface** (spec §9.5).
74    ///
75    /// Return a fresh [`ScopedKernelStream`] wrapping the
76    /// supplied parent kernel. Each `advance()` pulls one
77    /// coord tuple from the underlying IR and applies
78    /// `parent.scope(&coords)` to produce a
79    /// [`ScopedKernelInstance`].
80    ///
81    /// Independence: pulling from this stream does NOT
82    /// advance any [`CoordinateStream`] obtained from the
83    /// same `CompiledComprehension`.
84    pub fn scoped_kernel_stream<K: KernelScope>(&self, parent: K) -> ScopedKernelStream<K> {
85        ScopedKernelStream::new(self.program_arc(), parent)
86    }
87
88    /// **One-shot surface** (spec §9.5.3).
89    ///
90    /// Apply `parent.scope(coords)` directly, without
91    /// constructing any streamer. Pure function — no
92    /// cursor consulted, no dispense state advanced. Used
93    /// for replay, debugging, and point queries where a
94    /// specific coord tuple is already known.
95    pub fn scope_once<K: KernelScope>(
96        &self,
97        parent: &K,
98        coords: &crate::iteration::comprehension::strategies::Tuple,
99    ) -> ScopedKernelInstance<K::Scoped> {
100        scope_once_with(parent, coords)
101    }
102}
103
104#[cfg(test)]
105mod tests {
106    use super::*;
107    use crate::iteration::comprehension::source::{LiteralValue, Source};
108
109    fn clause(name: &str, vs: &[i64]) -> Comprehension {
110        Comprehension::clause(
111            name,
112            Source::Literal {
113                values: vs.iter().map(|n| LiteralValue::Int(*n)).collect(),
114            },
115        )
116    }
117
118    #[test]
119    fn from_ast_compiles_once() {
120        let ast = clause("k", &[1, 2, 3]);
121        let compiled = CompiledComprehension::from_ast(&ast);
122        assert!(!compiled.program().is_empty());
123    }
124
125    #[test]
126    fn cloning_compiled_shares_arc() {
127        let ast = clause("k", &[1, 2, 3]);
128        let a = CompiledComprehension::from_ast(&ast);
129        let b = a.clone();
130        // Same Arc — strong_count goes up.
131        let count = Arc::strong_count(&a.program);
132        assert!(count >= 2, "expected shared Arc, count = {count}");
133        drop(b);
134    }
135
136    #[test]
137    fn two_coordinate_streams_share_program() {
138        let ast = clause("k", &[1, 2, 3]);
139        let compiled = CompiledComprehension::from_ast(&ast);
140        let _s1 = compiled.coordinate_stream();
141        let _s2 = compiled.coordinate_stream();
142        // Both streams hold an Arc; count is at least 3 (compiled +
143        // two streamers, possibly more if internal clones happen).
144        let count = Arc::strong_count(&compiled.program);
145        assert!(
146            count >= 3,
147            "expected shared program across streamers, count = {count}"
148        );
149    }
150}