1use std::collections::HashMap;
21use std::sync::Arc;
22
23use crate::ast::Value;
24use crate::dsl::traversal::Traversal;
25use crate::iteration::comprehension::runtime::{RuntimeTuple, evaluate_for_iteration};
26use crate::iteration::cursor_partition::{cursor_extent_on, cursor_over_partitions_on};
27use crate::kernel::Kernel;
28
29use super::{PolydatKernel, PolydatProgram};
30
31#[derive(Debug, Clone, PartialEq, Eq)]
33pub struct CursorSlice {
34 pub cursor: String,
36 pub start: u64,
38 pub end: u64,
40}
41
42impl CursorSlice {
43 pub fn len(&self) -> u64 {
45 self.end.saturating_sub(self.start)
46 }
47
48 pub fn is_empty(&self) -> bool {
50 self.len() == 0
51 }
52}
53
54pub struct Activation<K = PolydatKernel> {
60 pub index: u64,
63 pub coords: Vec<(String, Value)>,
65 pub kernel: K,
67 pub cursor: Option<CursorSlice>,
69}
70
71impl std::fmt::Debug for Activation {
72 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
73 f.debug_struct("Activation")
74 .field("index", &self.index)
75 .field("coords", &self.coords)
76 .field("cursor", &self.cursor)
77 .field("program_nodes", &self.kernel.program().node_count())
78 .finish()
79 }
80}
81
82impl std::fmt::Debug for Activation<Box<dyn Kernel>> {
83 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
84 f.debug_struct("Activation")
85 .field("index", &self.index)
86 .field("coords", &self.coords)
87 .field("cursor", &self.cursor)
88 .field("engine", &self.kernel.engine())
89 .finish()
90 }
91}
92
93impl<K> Activation<K> {
94 pub fn cycle_count(&self) -> u64 {
97 match &self.cursor {
98 Some(slice) => slice.len(),
99 None => 1,
100 }
101 }
102
103 pub fn coord(&self, name: &str) -> Option<&Value> {
105 self.coords.iter().find(|(n, _)| n == name).map(|(_, v)| v)
106 }
107}
108
109impl Activation<Box<dyn Kernel>> {
110 pub fn cycle(&mut self, i: u64) -> &mut dyn Kernel {
113 self.kernel.set_inputs(&[i]);
114 if let Some(slice) = &self.cursor {
115 let ordinal = slice.start.saturating_add(i);
116 let slot = format!("{}__ordinal", slice.cursor);
117 let _ = self.kernel.set_input(&slot, Value::U64(ordinal));
119 }
120 self.kernel.as_mut()
121 }
122
123 pub fn for_each_cycle(&mut self, mut f: impl FnMut(u64, &mut dyn Kernel)) {
125 for i in 0..self.cycle_count() {
126 let kernel = self.cycle(i);
127 f(i, kernel);
128 }
129 }
130}
131
132impl Activation {
133 pub fn cycle(&mut self, i: u64) -> &mut PolydatKernel {
137 self.kernel.set_inputs(&[i]);
138 if let Some(slice) = &self.cursor {
139 let ordinal = slice.start.saturating_add(i);
140 let slot = format!("{}__ordinal", slice.cursor);
141 if let Some(idx) = self.kernel.program().find_input(&slot) {
142 self.kernel.state().set_input(idx, Value::U64(ordinal));
143 }
144 }
145 &mut self.kernel
146 }
147
148 pub fn for_each_cycle(&mut self, mut f: impl FnMut(u64, &mut PolydatKernel)) {
150 for i in 0..self.cycle_count() {
151 let kernel = self.cycle(i);
152 f(i, kernel);
153 }
154 }
155}
156
157pub struct TraversalStream {
159 traversal: Traversal,
160 tuples: Vec<RuntimeTuple>,
161 cascade: Vec<(String, Value)>,
162 next: usize,
163}
164
165impl TraversalStream {
166 pub fn len(&self) -> usize {
168 self.tuples.len()
169 }
170
171 pub fn is_empty(&self) -> bool {
173 self.tuples.is_empty()
174 }
175
176 pub fn traversal(&self) -> &Traversal {
178 &self.traversal
179 }
180
181 pub fn seek(&mut self, index: usize) {
184 self.next = index.min(self.tuples.len());
185 }
186
187 pub fn position(&self) -> usize {
189 self.next
190 }
191
192 pub fn advance(&mut self) -> Result<Option<Activation>, String> {
194 if self.next >= self.tuples.len() {
195 return Ok(None);
196 }
197 let i = self.next;
198 self.next += 1;
199 self.activation(i).map(Some)
200 }
201
202 pub fn activation(&self, index: usize) -> Result<Activation, String> {
206 let tuple = self.tuples.get(index).ok_or_else(|| {
207 format!(
208 "activation index {index} is out of range; traversal has {} tuples",
209 self.tuples.len()
210 )
211 })?;
212 let program = self.traversal.program.clone();
213 let mut kernel = PolydatKernel::from_program(program);
214 bind_by_name(&mut kernel, tuple);
215 bind_by_name(&mut kernel, &self.cascade);
216 let cursor = narrow_cursors(&mut kernel)?;
217 Ok(Activation {
218 index: index as u64,
219 coords: tuple.clone(),
220 kernel,
221 cursor,
222 })
223 }
224
225 pub fn activate(&self, index: usize) -> Result<Activation<Box<dyn Kernel>>, String> {
229 self.activation_on(index, crate::Engine::default())
230 }
231
232 pub fn activation_on(
238 &self,
239 index: usize,
240 engine: crate::Engine,
241 ) -> Result<Activation<Box<dyn Kernel>>, String> {
242 let tuple = self.tuples.get(index).ok_or_else(|| {
243 format!(
244 "activation index {index} is out of range; traversal has {} tuples",
245 self.tuples.len()
246 )
247 })?;
248 let program = self
249 .traversal
250 .program_on(engine)
251 .map_err(|e| e.to_string())?;
252 let mut kernel = program.create_kernel();
253 bind_by_name_on(kernel.as_mut(), tuple)?;
254 bind_by_name_on(kernel.as_mut(), &self.cascade)?;
255 let cursor = narrow_cursors_on(kernel.as_mut())?;
256 Ok(Activation {
257 index: index as u64,
258 coords: tuple.clone(),
259 kernel,
260 cursor,
261 })
262 }
263}
264
265fn bind_by_name_on(kernel: &mut dyn Kernel, values: &[(String, Value)]) -> Result<(), String> {
267 let declared: std::collections::HashSet<String> = kernel.input_names().into_iter().collect();
268 for (name, value) in values {
269 if declared.contains(name) {
270 kernel.set_input(name, value.clone())?;
271 }
272 }
273 Ok(())
274}
275
276fn bind_by_name(kernel: &mut PolydatKernel, values: &[(String, Value)]) {
277 for (name, value) in values {
278 if let Some(idx) = kernel.program().find_input(name) {
279 kernel.state().set_input(idx, value.clone());
280 }
281 }
282}
283
284fn narrow_cursors(kernel: &mut PolydatKernel) -> Result<Option<CursorSlice>, String> {
289 narrow_cursors_on(kernel)
290}
291
292fn narrow_cursors_on(kernel: &mut dyn Kernel) -> Result<Option<CursorSlice>, String> {
293 let schemas: Vec<crate::iteration::source::SourceSchema> = kernel.cursor_schemas().to_vec();
294 let mut narrowest: Option<CursorSlice> = None;
295 for schema in &schemas {
296 let slice = if schema.partition_output.is_some() {
297 let parts = cursor_over_partitions_on(kernel, schema)?;
298 let partition = match parts.len() {
299 1 => parts[0],
300 0 => {
301 return Err(format!(
302 "cursor '{}': its `over` value resolved to no partitions",
303 schema.name
304 ));
305 }
306 n => {
307 return Err(format!(
308 "cursor '{}': its `over` value resolved to {n} partitions; inside a traversal, bind the list \
309 with an enclosing `for p in ...` and declare the cursor `over p`",
310 schema.name
311 ));
312 }
313 };
314 kernel.set_cursor(&schema.name, &partition)?;
315 CursorSlice {
316 cursor: schema.name.clone(),
317 start: partition.start_ord,
318 end: partition.end_ord,
319 }
320 } else {
321 let extent = cursor_extent_on(kernel, schema);
322 CursorSlice {
323 cursor: schema.name.clone(),
324 start: 0,
325 end: extent,
326 }
327 };
328 narrowest = Some(match narrowest {
329 Some(prev) if prev.len() <= slice.len() => prev,
330 _ => slice,
331 });
332 }
333 Ok(narrowest)
334}
335
336impl PolydatKernel {
337 pub fn over(program: Arc<PolydatProgram>) -> Self {
340 PolydatKernel::from_program(program)
341 }
342
343 pub fn traverse(&mut self, index: usize) -> Result<TraversalStream, String> {
350 let program = self.program().clone();
351 let traversal = program.traversals().get(index).cloned().ok_or_else(|| {
352 format!(
353 "no traversal at index {index}; the program declares {}",
354 program.traversals().len()
355 )
356 })?;
357 open_traversal(self, traversal)
358 }
359}
360
361pub fn program_identity(kernel: &PolydatKernel) -> *const PolydatProgram {
364 Arc::as_ptr(kernel.program())
365}
366
367pub fn open_traversal(
375 parent: &mut dyn Kernel,
376 traversal: Traversal,
377) -> Result<TraversalStream, String> {
378 let mut cascade = Vec::with_capacity(traversal.cascade.len());
379 for (name, _) in &traversal.cascade {
380 let value = if parent.output_type(name).is_some() {
381 parent.pull(name)
382 } else {
383 parent.input_value(name).unwrap_or(Value::None)
384 };
385 cascade.push((name.clone(), value));
386 }
387 let mut canonical = PolydatKernel::from_program(traversal.program.clone());
388 bind_by_name(&mut canonical, &cascade);
389 let params: HashMap<String, String> = HashMap::new();
390 let tuples = evaluate_for_iteration(&traversal.comprehension, &canonical, ¶ms, |_| Ok(()))
391 .map_err(|e| {
392 format!(
393 "`for {}` at line {}, col {}: {e}",
394 traversal.source_text, traversal.span.line, traversal.span.col
395 )
396 })?;
397 Ok(TraversalStream {
398 traversal,
399 tuples,
400 cascade,
401 next: 0,
402 })
403}