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