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 (for_traversal.md §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 (comprehension_forms.md §9.5.2).
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};
22use crate::iteration::comprehension::validate::{Mode, 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    /// The names the comprehension reads from the scope its wire is
33    /// bound in: each is a name of that scope, which a traversal
34    /// captures when it opens and a stream has none of. Empty for a
35    /// streamer built from text alone.
36    #[serde(default, skip_serializing_if = "Vec::is_empty")]
37    pub outer: Vec<String>,
38}
39
40impl StreamerValue {
41    /// A streamer over `ast` with its source text, bound in no scope.
42    pub fn new(text: impl Into<String>, ast: Comprehension) -> Self {
43        Self {
44            text: text.into(),
45            ast,
46            outer: Vec::new(),
47        }
48    }
49
50    /// A streamer over `ast` bound to a wire of a scope that has the
51    /// names `has` answers true for. [`Self::outer`] holds the names the
52    /// comprehension reads, does not bind, and the scope has; a name it
53    /// reads that the scope does not have is resolved nowhere and reads
54    /// None (comprehension_forms.md §5 V3).
55    pub fn in_scope(
56        text: impl Into<String>,
57        ast: Comprehension,
58        has: &dyn Fn(&str) -> bool,
59    ) -> Self {
60        let mut outer: Vec<String> = crate::iteration::comprehension::validate::outer_reads(&ast)
61            .into_iter()
62            .filter(|r| !r.bare && has(&r.name))
63            .map(|r| r.name)
64            .collect();
65        outer.sort();
66        outer.dedup();
67        Self {
68            text: text.into(),
69            ast,
70            outer,
71        }
72    }
73
74    /// Element names in tuple order.
75    pub fn element_names(&self) -> Vec<String> {
76        self.ast.coordinate_names()
77    }
78
79    /// The algebra's metadata: cardinality, index addressability, and
80    /// natural order.
81    pub fn metadata(&self) -> Metadata {
82        self.ast.metadata()
83    }
84
85    /// The cardinality class of the tuple space.
86    pub fn cardinality(&self) -> CardinalityClass {
87        self.metadata().cardinality
88    }
89
90    /// Compile to the shared IR: validation, optimization, then the
91    /// AST → IR pass. Each call is independent. A comprehension the
92    /// `for` lowering resolved was validated then and fails here only
93    /// when it reads a name of the scope its wire is bound in
94    /// ([`Self::outer`]), which this surface has none of
95    /// (`ContextRequired`, `PredicateContextRequired`,
96    /// comprehension_forms.md §9.5.2); one built programmatically is
97    /// validated here. A name it reads that nothing binds reads None
98    /// (§5 V3).
99    pub fn compiled(&self) -> Result<CompiledComprehension, ValidationError> {
100        CompiledComprehension::from_ast_in(&self.ast, Mode::Permissive, &|name| {
101            self.outer.iter().any(|n| n == name)
102        })
103        .map(|(compiled, _)| compiled)
104    }
105
106    /// A fresh coordinate stream with its own dispense cursor.
107    pub fn coordinate_stream(&self) -> Result<CoordinateStream, ValidationError> {
108        Ok(self.compiled()?.coordinate_stream())
109    }
110
111    /// Serialize for transport through a const node argument. The
112    /// compiler lowers a producer binding to `streamer("<json>")`.
113    pub fn to_json(&self) -> String {
114        serde_json::to_string(self).expect("StreamerValue serializes")
115    }
116
117    /// Build from a node argument: either the JSON payload the compiler
118    /// emits for `for` expressions, or plain comprehension text such as
119    /// `k in 1..4` when an author calls `streamer` directly. Panics
120    /// with the parser's diagnostic when neither form applies.
121    pub fn from_json(payload: &str) -> Self {
122        let trimmed = payload.trim();
123        if trimmed.starts_with('{') {
124            match serde_json::from_str::<Self>(trimmed) {
125                Ok(v) => return v,
126                Err(e) => panic!("streamer: malformed comprehension payload: {e}"),
127            }
128        }
129        match Self::parse_text(trimmed) {
130            Ok(v) => v,
131            Err(e) => panic!("streamer: `{trimmed}` is not a comprehension: {e}"),
132        }
133    }
134
135    /// Parse canonical comprehension text into a streamer value.
136    pub fn parse_text(text: &str) -> Result<Self, String> {
137        let ast = crate::iteration::comprehension::spec::parse_comprehension_algebra(text)?;
138        Ok(Self::new(text, ast))
139    }
140}
141
142impl ReflectedValue for StreamerValue {
143    fn type_name(&self) -> &str {
144        "Streamer"
145    }
146
147    fn display(&self) -> String {
148        format!("for {}", self.text)
149    }
150
151    fn to_json_value(&self) -> serde_json::Value {
152        serde_json::json!({
153            "for": self.text,
154            "elements": self.element_names(),
155            "cardinality": format!("{:?}", self.cardinality()),
156        })
157    }
158
159    fn as_any(&self) -> &dyn std::any::Any {
160        self
161    }
162
163    fn clone_reflected(&self) -> Box<dyn ReflectedValue> {
164        Box::new(self.clone())
165    }
166}
167
168impl Value {
169    /// Wrap a [`StreamerValue`] as a `Value::Ext`.
170    pub fn from_streamer(s: StreamerValue) -> Self {
171        Value::Ext(Box::new(s))
172    }
173
174    /// Downcast to a [`StreamerValue`]. `None` if the value is not a
175    /// streamer.
176    pub fn as_streamer(&self) -> Option<&StreamerValue> {
177        match self {
178            Value::Ext(b) => b.as_any().downcast_ref::<StreamerValue>(),
179            _ => None,
180        }
181    }
182}