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