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