Skip to main content

polydat_core/iteration/comprehension/
streamer_value.rs

1// Copyright 2024-2026 Jonathan Shook
2// SPDX-License-Identifier: Apache-2.0
3
4//! The value a producer wire carries (SRD 113 §3.1).
5//!
6//! `name := for ...` binds a comprehension as a value. The wire is a
7//! `Streamer`, realized as a reflected `Ext` value so it rides the
8//! existing type-erased extension point of the type plane. It carries
9//! the comprehension's text and validated algebra AST, and exposes
10//! `compiled()` (from which all three consumption surfaces hang) and
11//! `coordinate_stream()` as factories. Every factory call compiles
12//! a fresh stream, so streams obtained from one wire never share
13//! dispense state (§9.5.2 of Comprehension Forms).
14
15use serde::{Deserialize, Serialize};
16
17use crate::ast::{ReflectedValue, Value};
18use crate::iteration::comprehension::ast::Comprehension;
19use crate::iteration::comprehension::cardinality::CardinalityClass;
20use crate::iteration::comprehension::metadata::Metadata;
21use crate::iteration::comprehension::surfaces::{CompiledComprehension, CoordinateStream, compile};
22use crate::iteration::comprehension::validate::ValidationError;
23
24/// A comprehension bound as a value.
25#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
26pub struct StreamerValue {
27    /// The text after `for`, as the author wrote it (or, for a derived
28    /// producer, the derivation text).
29    pub text: String,
30    /// The comprehension, with any derivation already applied.
31    pub ast: Comprehension,
32}
33
34impl StreamerValue {
35    /// A streamer over `ast` with its source text.
36    pub fn new(text: impl Into<String>, ast: Comprehension) -> Self {
37        Self {
38            text: text.into(),
39            ast,
40        }
41    }
42
43    /// Element names in tuple order.
44    pub fn element_names(&self) -> Vec<String> {
45        self.ast.coordinate_names()
46    }
47
48    /// The algebra's metadata: cardinality, index addressability, and
49    /// natural order.
50    pub fn metadata(&self) -> Metadata {
51        self.ast.metadata()
52    }
53
54    /// The cardinality class of the tuple space.
55    pub fn cardinality(&self) -> CardinalityClass {
56        self.metadata().cardinality
57    }
58
59    /// Compile to the shared IR: validation, optimization, then the
60    /// AST → IR pass. Each call is independent. A comprehension the
61    /// `for` lowering resolved was validated then and fails here only
62    /// when a source needs a scope, which this surface has none of
63    /// (comprehension_forms.md §9.5.2);
64    /// one built programmatically is validated here.
65    pub fn compiled(&self) -> Result<CompiledComprehension, ValidationError> {
66        compile(&self.ast)
67    }
68
69    /// A fresh coordinate stream with its own dispense cursor.
70    pub fn coordinate_stream(&self) -> Result<CoordinateStream, ValidationError> {
71        Ok(self.compiled()?.coordinate_stream())
72    }
73
74    /// Serialize for transport through a const node argument. The
75    /// compiler lowers a producer binding to `streamer("<json>")`.
76    pub fn to_json(&self) -> String {
77        serde_json::to_string(self).expect("StreamerValue serializes")
78    }
79
80    /// Build from a node argument: either the JSON payload the compiler
81    /// emits for `for` expressions, or plain comprehension text such as
82    /// `k in 1..4` when an author calls `streamer` directly. Panics
83    /// with the parser's diagnostic when neither form applies.
84    pub fn from_json(payload: &str) -> Self {
85        let trimmed = payload.trim();
86        if trimmed.starts_with('{') {
87            match serde_json::from_str::<Self>(trimmed) {
88                Ok(v) => return v,
89                Err(e) => panic!("streamer: malformed comprehension payload: {e}"),
90            }
91        }
92        match Self::parse_text(trimmed) {
93            Ok(v) => v,
94            Err(e) => panic!("streamer: `{trimmed}` is not a comprehension: {e}"),
95        }
96    }
97
98    /// Parse canonical comprehension text into a streamer value.
99    pub fn parse_text(text: &str) -> Result<Self, String> {
100        let ast = crate::iteration::comprehension::spec::parse_comprehension_algebra(text)?;
101        Ok(Self::new(text, ast))
102    }
103}
104
105impl ReflectedValue for StreamerValue {
106    fn type_name(&self) -> &str {
107        "Streamer"
108    }
109
110    fn display(&self) -> String {
111        format!("for {}", self.text)
112    }
113
114    fn to_json_value(&self) -> serde_json::Value {
115        serde_json::json!({
116            "for": self.text,
117            "elements": self.element_names(),
118            "cardinality": format!("{:?}", self.cardinality()),
119        })
120    }
121
122    fn as_any(&self) -> &dyn std::any::Any {
123        self
124    }
125
126    fn clone_reflected(&self) -> Box<dyn ReflectedValue> {
127        Box::new(self.clone())
128    }
129}
130
131impl Value {
132    /// Wrap a [`StreamerValue`] as a `Value::Ext`.
133    pub fn from_streamer(s: StreamerValue) -> Self {
134        Value::Ext(Box::new(s))
135    }
136
137    /// Downcast to a [`StreamerValue`]. `None` if the value is not a
138    /// streamer.
139    pub fn as_streamer(&self) -> Option<&StreamerValue> {
140        match self {
141            Value::Ext(b) => b.as_any().downcast_ref::<StreamerValue>(),
142            _ => None,
143        }
144    }
145}