Skip to main content

polydat_core/iteration/comprehension/surfaces/
polydat_kernel.rs

1// Copyright 2024-2026 Jonathan Shook
2// SPDX-License-Identifier: Apache-2.0
3
4//! Bridge between the algebra-layer [`KernelScope`] surface
5//! and polydat's [`PolydatKernel`] scope-binding primitives.
6//!
7//! Implements `KernelScope` for a `PolydatKernelScope` wrapper that
8//! holds a `(canonical, parent)` `PolydatKernel` pair. Each
9//! `scope(coords)` call delegates to
10//! [`PolydatKernel::for_iteration`] — the established polydat
11//! primitive for "materialize a fresh per-iteration child of
12//! `parent` based on `canonical` with these bindings."
13//!
14//! This is the load-bearing bridge for the PR 9 migration:
15//! once `KernelScope for PolydatKernelScope` exists, the algebra-
16//! layer `ScopedKernelStream<PolydatKernelScope>` can replace
17//! the legacy `ComprehensionIter` everywhere nb-activity
18//! drives comprehension dispatch.
19
20use std::sync::Arc;
21
22use crate::ast::Value;
23use crate::iteration::comprehension::strategies::{Tuple, TupleValue};
24use crate::kernel::PolydatKernel;
25
26use super::instance::KernelScope;
27
28/// Wrapper around a `(canonical, parent)` `PolydatKernel` pair that
29/// implements [`KernelScope`].
30///
31/// - `canonical` is the comprehension's prototype kernel —
32///   built once at scope-synthesis time, materialized fresh
33///   per iteration via `for_iteration`.
34/// - `parent` is the enclosing scope's kernel — provides the
35///   outer scope chain that `materialize_subscope` wires
36///   into every iteration's child.
37///
38/// `scope(coords)` converts the algebra-layer `Tuple` to a
39/// polydat `[(String, Value)]` bindings slice and calls
40/// `PolydatKernel::for_iteration(&canonical, &parent, &bindings)`.
41#[derive(Debug, Clone)]
42pub struct PolydatKernelScope {
43    canonical: Arc<PolydatKernel>,
44    parent: Arc<PolydatKernel>,
45}
46
47impl PolydatKernelScope {
48    /// Construct the scope wrapper from the comprehension's
49    /// canonical kernel and the enclosing parent kernel.
50    pub fn new(canonical: Arc<PolydatKernel>, parent: Arc<PolydatKernel>) -> Self {
51        Self { canonical, parent }
52    }
53
54    /// Access the canonical kernel — useful when the consumer
55    /// needs to share metadata (input manifest, scope
56    /// coordinates) without invoking `scope`.
57    pub fn canonical(&self) -> &Arc<PolydatKernel> {
58        &self.canonical
59    }
60
61    /// Access the parent kernel.
62    pub fn parent(&self) -> &Arc<PolydatKernel> {
63        &self.parent
64    }
65}
66
67impl KernelScope for PolydatKernelScope {
68    /// Each scope produces a fresh `Arc<PolydatKernel>` — the
69    /// per-iteration child kernel. Consumers can clone the Arc
70    /// cheaply if they need multiple references.
71    type Scoped = Arc<PolydatKernel>;
72
73    fn scope(&self, coords: &Tuple) -> Arc<PolydatKernel> {
74        let bindings: Vec<(String, Value)> = coords
75            .bindings
76            .iter()
77            .map(|(name, val)| (name.clone(), tuple_value_to_polydat_value(val)))
78            .collect();
79        PolydatKernel::for_iteration(&self.canonical, &self.parent, &bindings)
80    }
81}
82
83/// Convert an algebra-layer [`TupleValue`] to a polydat
84/// [`Value`].
85///
86/// The algebra layer's `TupleValue::I64` doesn't exist in
87/// polydat's `Value`; integers there are unconditionally
88/// `U64`. We bitcast `i64 as u64` — for the comprehension
89/// use case (iteration coordinates, typically non-negative
90/// integers), this preserves the bit pattern; consumers that
91/// care about signedness should use `as i64` to recover.
92pub fn tuple_value_to_polydat_value(val: &TupleValue) -> Value {
93    match val {
94        TupleValue::U64(n) => Value::U64(*n),
95        TupleValue::I64(n) => Value::U64(*n as u64),
96        TupleValue::F64(f) => Value::F64(*f),
97        TupleValue::Str(s) => Value::Str(Arc::from(s.as_str())),
98        TupleValue::Bool(b) => Value::Bool(*b),
99    }
100}
101
102/// Reverse conversion — polydat `Value` to algebra-layer
103/// `TupleValue`. Only the subset of `Value` variants that
104/// have a corresponding `TupleValue` are converted; richer
105/// `Value` variants (`Bytes`, `Json`, `Ext`, `Handle`,
106/// `Vec*`, etc.) return `None` so the caller can handle
107/// the unsupported case explicitly.
108pub fn polydat_value_to_tuple_value(val: &Value) -> Option<TupleValue> {
109    match val {
110        Value::U64(n) => Some(TupleValue::U64(*n)),
111        Value::F64(f) => Some(TupleValue::F64(*f)),
112        Value::Bool(b) => Some(TupleValue::Bool(*b)),
113        Value::Str(s) => Some(TupleValue::Str(s.to_string())),
114        _ => None,
115    }
116}
117
118#[cfg(test)]
119mod tests {
120    use super::*;
121
122    #[test]
123    fn tuple_value_to_polydat_round_trips_u64() {
124        let tv = TupleValue::U64(42);
125        let pv = tuple_value_to_polydat_value(&tv);
126        let back = polydat_value_to_tuple_value(&pv).unwrap();
127        assert_eq!(tv, back);
128    }
129
130    #[test]
131    fn tuple_value_to_polydat_round_trips_f64() {
132        let tv = TupleValue::F64(3.14);
133        let pv = tuple_value_to_polydat_value(&tv);
134        let back = polydat_value_to_tuple_value(&pv).unwrap();
135        assert_eq!(tv, back);
136    }
137
138    #[test]
139    fn tuple_value_to_polydat_round_trips_string() {
140        let tv = TupleValue::Str("hello".into());
141        let pv = tuple_value_to_polydat_value(&tv);
142        let back = polydat_value_to_tuple_value(&pv).unwrap();
143        assert_eq!(tv, back);
144    }
145
146    #[test]
147    fn tuple_value_to_polydat_round_trips_bool() {
148        let tv = TupleValue::Bool(true);
149        let pv = tuple_value_to_polydat_value(&tv);
150        let back = polydat_value_to_tuple_value(&pv).unwrap();
151        assert_eq!(tv, back);
152    }
153
154    #[test]
155    fn i64_converts_via_bitcast() {
156        let tv = TupleValue::I64(123);
157        let pv = tuple_value_to_polydat_value(&tv);
158        match pv {
159            Value::U64(n) => assert_eq!(n, 123u64),
160            other => panic!("expected U64, got {other:?}"),
161        }
162    }
163
164    #[test]
165    fn polydat_value_returns_none_for_unsupported_variants() {
166        let pv = Value::Bytes(Arc::from(&b"abc"[..]));
167        assert!(polydat_value_to_tuple_value(&pv).is_none());
168    }
169}