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}