1use std::collections::HashMap;
2
3use itertools::Itertools;
4use petgraph::algo::{all_simple_paths, has_path_connecting};
5use petgraph::graph::NodeIndex;
6use petgraph::stable_graph::StableGraph;
7use petgraph::visit::{IntoNeighbors, IntoNodeIdentifiers, Visitable};
8use petgraph::Outgoing;
9use rtlola_reporting::{Diagnostic, RtLolaError, Span};
10use serde::{Deserialize, Serialize};
11
12use super::{DepAna, DepAnaTrait, TypedTrait};
13use crate::config::MemoryBoundMode;
14use crate::hir::{
15 ConcretePacingType, Expression, ExpressionKind, FnExprKind, Hir, MemorizationBound, SRef,
16 SpawnDef, StreamAccessKind, WRef, WidenExprKind,
17};
18use crate::modes::HirMode;
19
20pub type DependencyGraph = StableGraph<SRef, EdgeWeight>;
26
27#[derive(Hash, Clone, Debug, PartialEq, Eq, Copy)]
29pub struct EdgeWeight {
30 pub origin: Origin,
32 pub kind: StreamAccessKind,
34}
35
36#[derive(Hash, Clone, Debug, PartialEq, Eq, Copy, Serialize, Deserialize)]
38pub enum Origin {
39 Spawn,
41 Filter(usize),
43 Eval(usize),
45 Close,
47}
48
49impl EdgeWeight {
50 pub(crate) fn new(kind: StreamAccessKind, origin: Origin) -> Self {
52 EdgeWeight { kind, origin }
53 }
54
55 pub(crate) fn window(&self) -> Option<WRef> {
57 match self.kind {
58 StreamAccessKind::Get
59 | StreamAccessKind::Fresh
60 | StreamAccessKind::Sync
61 | StreamAccessKind::Hold
62 | StreamAccessKind::Offset(_) => None,
63 StreamAccessKind::DiscreteWindow(wref)
64 | StreamAccessKind::SlidingWindow(wref)
65 | StreamAccessKind::InstanceAggregation(wref) => Some(wref),
66 }
67 }
68
69 pub(crate) fn as_memory_bound(&self, memory_bound_mode: MemoryBoundMode) -> MemorizationBound {
71 match self.kind {
72 StreamAccessKind::Sync
73 | StreamAccessKind::Get
74 | StreamAccessKind::Fresh
75 | StreamAccessKind::DiscreteWindow(_)
76 | StreamAccessKind::InstanceAggregation(_)
77 | StreamAccessKind::SlidingWindow(_) => {
78 MemorizationBound::default_value(memory_bound_mode)
79 }
80 StreamAccessKind::Hold => MemorizationBound::Bounded(1),
81 StreamAccessKind::Offset(o) => o.as_memory_bound(),
82 }
83 }
84}
85
86pub(crate) type StreamDependencies = HashMap<SRef, Vec<(SRef, Vec<(Origin, StreamAccessKind)>)>>;
88pub(crate) type TransitiveDependencies = HashMap<SRef, Vec<SRef>>;
90pub(crate) type WindowDependencies = HashMap<SRef, Vec<(SRef, Origin, WRef)>>;
92
93pub(crate) trait ExtendedDepGraph {
94 fn without_negative_offset_edges(self) -> Self;
96
97 fn without_different_pacing<M>(self, hir: &Hir<M>) -> Self
99 where
100 M: HirMode + TypedTrait;
101
102 fn has_negative_offset(e: &EdgeWeight) -> bool {
104 match e.kind {
105 StreamAccessKind::Sync
106 | StreamAccessKind::Get
107 | StreamAccessKind::Fresh
108 | StreamAccessKind::DiscreteWindow(_)
109 | StreamAccessKind::SlidingWindow(_)
110 | StreamAccessKind::InstanceAggregation(_)
111 | StreamAccessKind::Hold => false,
112 StreamAccessKind::Offset(o) => o.has_negative_offset(),
113 }
114 }
115
116 fn without_close(self) -> Self;
118
119 fn only_spawn(self) -> Self;
121
122 #[cfg(feature = "shift_layer")]
123 fn only_filter(self) -> Self;
125}
126
127impl ExtendedDepGraph for DependencyGraph {
128 fn without_negative_offset_edges(mut self) -> Self {
129 self.retain_edges(|graph, e| !Self::has_negative_offset(graph.edge_weight(e).unwrap()));
130 self
131 }
132
133 fn without_different_pacing<M>(mut self, hir: &Hir<M>) -> Self
134 where
135 M: HirMode + TypedTrait,
136 {
137 self.retain_edges(|g, e_i| {
138 let (lhs, rhs) = g.edge_endpoints(e_i).unwrap();
139 let w = g.edge_weight(e_i).unwrap();
140 let lhs_sr = *g.node_weight(lhs).unwrap();
141 let lhs = hir.stream_type(lhs_sr);
142 let rhs = hir.stream_type(*g.node_weight(rhs).unwrap());
143 let lhs_pt = match w.origin {
144 Origin::Spawn => lhs.spawn_pacing,
145 Origin::Filter(_) | Origin::Eval(_) => lhs.eval_pacing,
146 Origin::Close => lhs.close_pacing,
147 };
148 let rhs_pt = rhs.eval_pacing;
149 match (lhs_pt, rhs_pt) {
150 (ConcretePacingType::Event(_), ConcretePacingType::Event(_)) => true,
151 (ConcretePacingType::Event(_), _) | (_, ConcretePacingType::Event(_)) => false,
152 (
153 ConcretePacingType::FixedGlobalPeriodic(_),
154 ConcretePacingType::FixedGlobalPeriodic(_),
155 ) => true,
156 (
157 ConcretePacingType::FixedLocalPeriodic(_),
158 ConcretePacingType::FixedLocalPeriodic(_),
159 ) => true,
160 (
161 ConcretePacingType::FixedLocalPeriodic(_),
162 ConcretePacingType::FixedGlobalPeriodic(_),
163 )
164 | (
165 ConcretePacingType::FixedGlobalPeriodic(_),
166 ConcretePacingType::FixedLocalPeriodic(_),
167 ) => true,
168 _ => unreachable!(),
169 }
170 });
171 self
172 }
173
174 fn without_close(mut self) -> Self {
175 self.retain_edges(|g, e_i| g.edge_weight(e_i).unwrap().origin != Origin::Close);
176 self
177 }
178
179 fn only_spawn(mut self) -> Self {
180 self.retain_edges(|g, e_i| g.edge_weight(e_i).unwrap().origin == Origin::Spawn);
181 self
182 }
183
184 #[cfg(feature = "shift_layer")]
185 fn only_filter(mut self) -> Self {
186 self.retain_edges(|g, e_i| matches!(g.edge_weight(e_i).unwrap().origin, Origin::Filter(_)));
187 self
188 }
189}
190
191impl DepAnaTrait for DepAna {
192 fn direct_accesses(&self, who: SRef) -> Vec<SRef> {
193 self.direct_accesses
194 .get(&who)
195 .map_or(Vec::new(), |accesses| {
196 accesses.iter().map(|(sref, _)| *sref).collect()
197 })
198 }
199
200 fn direct_accesses_with(&self, who: SRef) -> Vec<(SRef, Vec<(Origin, StreamAccessKind)>)> {
201 self.direct_accesses
202 .get(&who)
203 .map_or(Vec::new(), |accesses| accesses.to_vec())
204 }
205
206 fn transitive_accesses(&self, who: SRef) -> Vec<SRef> {
207 self.transitive_accesses
208 .get(&who)
209 .map_or(Vec::new(), |accesses| accesses.to_vec())
210 }
211
212 fn direct_accessed_by(&self, who: SRef) -> Vec<SRef> {
213 self.direct_accessed_by
214 .get(&who)
215 .map_or(Vec::new(), |accessed_by| {
216 accessed_by.iter().map(|(sref, _)| *sref).collect()
217 })
218 }
219
220 fn direct_accessed_by_with(&self, who: SRef) -> Vec<(SRef, Vec<(Origin, StreamAccessKind)>)> {
221 self.direct_accessed_by
222 .get(&who)
223 .map_or(Vec::new(), |accessed_by| accessed_by.to_vec())
224 }
225
226 fn transitive_accessed_by(&self, who: SRef) -> Vec<SRef> {
227 self.transitive_accessed_by
228 .get(&who)
229 .map_or(Vec::new(), |accesses| accesses.to_vec())
230 }
231
232 fn aggregated_by(&self, who: SRef) -> Vec<(SRef, Origin, WRef)> {
233 self.aggregated_by
234 .get(&who)
235 .map_or(Vec::new(), |aggregated_by| {
236 aggregated_by
237 .iter()
238 .map(|(sref, origin, wref)| (*sref, *origin, *wref))
239 .collect::<Vec<_>>()
240 })
241 }
242
243 fn aggregates(&self, who: SRef) -> Vec<(SRef, Origin, WRef)> {
244 self.aggregates.get(&who).map_or(Vec::new(), |aggregates| {
245 aggregates
246 .iter()
247 .map(|(sref, origin, wref)| (*sref, *origin, *wref))
248 .collect::<Vec<_>>()
249 })
250 }
251
252 fn graph(&self) -> &DependencyGraph {
253 &self.graph
254 }
255}
256
257#[derive(Debug, Clone, Serialize, Deserialize)]
259pub enum DependencyErr {
260 WellFormedNess(Vec<SRef>),
264}
265
266impl DependencyErr {
267 pub(crate) fn into_diagnostic<M: HirMode>(self, hir: &Hir<M>) -> Diagnostic {
268 let names = hir.names();
269 let spans: HashMap<SRef, Span> = hir
270 .inputs()
271 .map(|i| (i.sr, i.span))
272 .chain(hir.outputs().map(|o| (o.sr, o.span)))
273 .collect();
274 match self {
275 DependencyErr::WellFormedNess(mut cycle) => {
276 if cycle.len() == 1
277 || cycle[0] != *cycle.last().expect("Cycle has at least one element")
278 {
279 cycle.push(cycle[0]);
280 }
281 let cycle_string = cycle.iter().map(|sr| &names[sr]).join(" -> ");
282 let mut diag = Diagnostic::error(&format!(
283 "Specification is not well-formed: Found dependency cycle: {cycle_string}",
284 ));
285 for stream in cycle.iter().take(cycle.len() - 1) {
286 diag = diag.add_span_with_label(
287 spans[stream],
288 Some(&format!("Stream {} found here", names[stream])),
289 true,
290 );
291 }
292 diag
293 }
294 }
295 }
296}
297
298impl DepAna {
299 pub(crate) fn analyze<M>(spec: &Hir<M>) -> Result<DepAna, RtLolaError>
304 where
305 M: HirMode + TypedTrait,
306 {
307 let edges_expr = spec
308 .outputs()
309 .map(|o| o.sr)
310 .flat_map(|sr| {
311 spec.eval_unchecked(sr)
312 .iter()
313 .enumerate()
314 .flat_map(|(i, eval)| {
315 Self::collect_edges(spec, sr, eval.expression)
316 .into_iter()
317 .map(move |a| (i, a))
318 })
319 .collect::<Vec<_>>()
320 })
321 .map(|(i, (src, w, tar))| (src, EdgeWeight::new(w, Origin::Eval(i)), tar));
322 let edges_spawn = spec
323 .outputs()
324 .map(|o| o.sr)
325 .flat_map(|sr| {
326 spec.spawn(sr).map(
327 |SpawnDef {
328 expression,
329 condition,
330 ..
331 }| {
332 expression
333 .map_or(Vec::new(), |spawn_expr| {
334 Self::collect_edges(spec, sr, spawn_expr)
335 })
336 .into_iter()
337 .chain(condition.map_or(Vec::new(), |spawn_cond| {
338 Self::collect_edges(spec, sr, spawn_cond)
339 }))
340 },
341 )
342 })
343 .flatten()
344 .map(|(src, w, tar)| (src, EdgeWeight::new(w, Origin::Spawn), tar));
345 let edges_filter = spec
346 .outputs()
347 .map(|o| o.sr)
348 .flat_map(|sr| {
349 spec.eval_unchecked(sr)
350 .iter()
351 .enumerate()
352 .flat_map(|(i, eval)| eval.condition.map(|cond| (i, cond)))
353 .flat_map(|(i, filter)| {
354 Self::collect_edges(spec, sr, filter)
355 .into_iter()
356 .map(move |a| (i, a))
357 })
358 .collect::<Vec<_>>()
359 })
360 .map(|(i, (src, w, tar))| (src, EdgeWeight::new(w, Origin::Filter(i)), tar));
361 let edges_close = spec
362 .outputs()
363 .map(|o| o.sr)
364 .flat_map(|sr| {
365 spec.close(sr)
366 .and_then(|cd| cd.condition)
367 .map(|close| Self::collect_edges(spec, sr, close))
368 })
369 .flatten()
370 .map(|(src, w, tar)| (src, EdgeWeight::new(w, Origin::Close), tar));
371 let edges = edges_expr
372 .chain(edges_spawn)
373 .chain(edges_filter)
374 .chain(edges_close)
375 .collect::<Vec<(SRef, EdgeWeight, SRef)>>();
376
377 let num_nodes = spec.num_inputs() + spec.num_outputs();
378 let num_edges = edges.len();
379 let mut graph: DependencyGraph = StableGraph::with_capacity(num_nodes, num_edges);
380
381 let node_mapping: HashMap<SRef, NodeIndex> = spec
383 .all_streams()
384 .map(|sr| (sr, graph.add_node(sr)))
385 .collect();
386 edges.iter().for_each(|(src, w, tar)| {
387 graph.add_edge(node_mapping[src], node_mapping[tar], *w);
388 });
389
390 Self::check_well_formedness(&graph, spec).map_err(|e| e.into_diagnostic(spec))?;
392 let mut direct_accesses: HashMap<SRef, Vec<(SRef, Origin, StreamAccessKind)>> =
394 spec.all_streams().map(|sr| (sr, Vec::new())).collect();
395 let mut direct_accessed_by: HashMap<SRef, Vec<(SRef, Origin, StreamAccessKind)>> =
396 spec.all_streams().map(|sr| (sr, Vec::new())).collect();
397 let mut aggregates: WindowDependencies =
398 spec.all_streams().map(|sr| (sr, Vec::new())).collect();
399 let mut aggregated_by: WindowDependencies =
400 spec.all_streams().map(|sr| (sr, Vec::new())).collect();
401 edges.iter().for_each(|(src, w, tar)| {
402 let cur_accesses = direct_accesses.get_mut(src).unwrap();
403 let access = (*tar, w.origin, w.kind);
404 if !cur_accesses.contains(&access) {
405 cur_accesses.push(access);
406 }
407 let cur_accessed_by = direct_accessed_by.get_mut(tar).unwrap();
408 let access = (*src, w.origin, w.kind);
409 if !cur_accessed_by.contains(&access) {
410 cur_accessed_by.push(access);
411 }
412 if let Some(wref) = w.window() {
413 let cur_aggregates = aggregates.get_mut(src).unwrap();
414 if !cur_aggregates.contains(&(*tar, w.origin, wref)) {
415 cur_aggregates.push((*tar, w.origin, wref));
416 }
417 let cur_aggregates_by = aggregated_by.get_mut(tar).unwrap();
418 if !cur_aggregates_by.contains(&(*src, w.origin, wref)) {
419 cur_aggregates_by.push((*src, w.origin, wref));
420 }
421 }
422 });
423 let direct_accesses = Self::group_access_kinds(direct_accesses);
424 let direct_accessed_by = Self::group_access_kinds(direct_accessed_by);
425 let transitive_accesses = graph
426 .node_indices()
427 .map(|from_index| {
428 let sr = *(graph.node_weight(from_index).unwrap());
429 let transitive_dependencies = graph
430 .node_indices()
431 .filter(|to_index| {
432 Self::has_transitive_connection(&graph, from_index, *to_index)
433 })
434 .map(|to_index| *(graph.node_weight(to_index).unwrap()))
435 .collect::<Vec<SRef>>();
436 (sr, transitive_dependencies)
437 })
438 .collect::<HashMap<SRef, Vec<SRef>>>();
439 let transitive_accessed_by = graph
440 .node_indices()
441 .map(|to_index| {
442 let sr = *(graph.node_weight(to_index).unwrap());
443 let transitive_dependencies = graph
444 .node_indices()
445 .filter(|from_index| {
446 Self::has_transitive_connection(&graph, *from_index, to_index)
447 })
448 .map(|from_index| *(graph.node_weight(from_index).unwrap()))
449 .collect::<Vec<SRef>>();
450 (sr, transitive_dependencies)
451 })
452 .collect::<HashMap<SRef, Vec<SRef>>>();
453 Ok(DepAna {
454 direct_accesses,
455 transitive_accesses,
456 direct_accessed_by,
457 transitive_accessed_by,
458 aggregated_by,
459 aggregates,
460 graph,
461 })
462 }
463
464 #[allow(clippy::type_complexity)]
465 fn group_access_kinds(
466 accesses: HashMap<SRef, Vec<(SRef, Origin, StreamAccessKind)>>,
467 ) -> HashMap<SRef, Vec<(SRef, Vec<(Origin, StreamAccessKind)>)>> {
468 accesses
469 .into_iter()
470 .map(|(sr, accesses)| {
471 let groups = accesses
472 .into_iter()
473 .sorted_by_key(|(target, _, _)| *target)
474 .group_by(|(target, _, _)| *target);
475 let targets = groups
476 .into_iter()
477 .map(|(target, accesses)| {
478 (
479 target,
480 accesses
481 .map(|(_, origin, kind)| (origin, kind))
482 .collect::<Vec<_>>(),
483 )
484 })
485 .collect();
486 (sr, targets)
487 })
488 .collect()
489 }
490
491 fn has_transitive_connection(graph: &DependencyGraph, from: NodeIndex, to: NodeIndex) -> bool {
492 if from != to {
493 has_path_connecting(&graph, from, to, None)
494 } else {
495 let self_check = graph
498 .edge_indices()
499 .filter(|ei| {
500 let (src, tar) = graph.edge_endpoints(*ei).unwrap();
501 src == tar
502 }) .find(|ei| {
504 let (src, _) = graph.edge_endpoints(*ei).unwrap();
505 src == from
506 });
507 let neighbor_check = graph
509 .neighbors_directed(from, Outgoing)
510 .any(|cur_neigbor| has_path_connecting(graph, cur_neigbor, to, None));
511 self_check.is_some() || neighbor_check
512 }
513 }
514
515 fn is_cyclic_directed<G>(g: G) -> Result<(), (G::NodeId, G::NodeId)>
516 where
517 G: IntoNodeIdentifiers + IntoNeighbors + Visitable,
518 {
519 use petgraph::visit::{depth_first_search, DfsEvent};
520
521 depth_first_search(g, g.node_identifiers(), |event| match event {
522 DfsEvent::BackEdge(start, end) => Err((start, end)),
523 _ => Ok(()),
524 })
525 }
526
527 fn check_well_formedness<M>(graph: &DependencyGraph, hir: &Hir<M>) -> Result<(), DependencyErr>
529 where
530 M: HirMode + TypedTrait,
531 {
532 let graph: &DependencyGraph = &graph
533 .clone()
534 .without_different_pacing(hir)
535 .without_negative_offset_edges()
536 .without_close();
537
538 Self::is_cyclic_directed(&graph).map_err(|(start, end)| {
540 let path: Vec<NodeIndex> = all_simple_paths(&graph, end, start, 0, None).next().expect(
541 "If there is a cycle with start and end, then there is a path between them",
542 );
543 let streams: Vec<SRef> = path.iter().map(|id| graph[*id]).collect();
544 DependencyErr::WellFormedNess(streams)
545 })
546 }
547
548 fn collect_edges<M>(
549 hir: &Hir<M>,
550 src: SRef,
551 expr: &Expression,
552 ) -> Vec<(SRef, StreamAccessKind, SRef)>
553 where
554 M: HirMode,
555 {
556 match &expr.kind {
557 ExpressionKind::StreamAccess(target, stream_access_kind, args) => {
558 let mut args = args
559 .iter()
560 .flat_map(|arg| Self::collect_edges(hir, src, arg))
561 .collect::<Vec<(SRef, StreamAccessKind, SRef)>>();
562 args.push((src, *stream_access_kind, *target));
563 if let StreamAccessKind::InstanceAggregation(wref) = stream_access_kind {
564 if let Some(condition) =
565 hir.single_instance_aggregation(*wref).selection.condition()
566 {
567 let edges = Self::collect_edges(hir, src, condition);
568 args.extend(edges);
569 }
570 }
571 args
572 }
573 ExpressionKind::ParameterAccess(_, _)
574 | ExpressionKind::LambdaParameterAccess { .. } => Vec::new(),
575 ExpressionKind::LoadConstant(_) => Vec::new(),
576 ExpressionKind::ArithLog(_op, args) => args
577 .iter()
578 .flat_map(|a| Self::collect_edges(hir, src, a).into_iter())
579 .collect(),
580 ExpressionKind::Tuple(content) => content
581 .iter()
582 .flat_map(|a| Self::collect_edges(hir, src, a))
583 .collect(),
584 ExpressionKind::Function(FnExprKind { args, .. }) => args
585 .iter()
586 .flat_map(|a| Self::collect_edges(hir, src, a))
587 .collect(),
588 ExpressionKind::Ite {
589 condition,
590 consequence,
591 alternative,
592 } => Self::collect_edges(hir, src, condition)
593 .into_iter()
594 .chain(Self::collect_edges(hir, src, consequence))
595 .chain(Self::collect_edges(hir, src, alternative))
596 .collect(),
597 ExpressionKind::TupleAccess(content, _n) => Self::collect_edges(hir, src, content),
598 ExpressionKind::Widen(WidenExprKind { expr: inner, .. }) => {
599 Self::collect_edges(hir, src, inner)
600 }
601 ExpressionKind::Default { expr, default } => Self::collect_edges(hir, src, expr)
602 .into_iter()
603 .chain(Self::collect_edges(hir, src, default))
604 .collect(),
605 }
606 }
607}
608
609#[cfg(test)]
610mod tests {
611 use rtlola_parser::{parse, ParserConfig};
612
613 use super::*;
614 use crate::config::FrontendConfig;
615 use crate::modes::BaseMode;
616
617 macro_rules! empty_vec_for_map {
618 ($name_map: ident) => {
619 $name_map
620 .values()
621 .clone()
622 .into_iter()
623 .map(|sr| (*sr, vec![]))
624 .collect::<HashMap<_, _>>()
625 };
626 }
627
628 macro_rules! checking_map {
629 ($name_map: ident, $([$source: expr,($($x:literal),* $(,)?)]),* $(,)?) => {
630 vec![$(($name_map[$source], vec![$($name_map[$x]),*])),*].into_iter().collect::<HashMap<_,_>>()
631 };
632 ($name_map: ident, $([$source: expr,($(($x:literal,$w:expr)),* $(,)?)]),* $(,)?) => {
633 vec![$(($name_map[$source], vec![$(($name_map[$x],$w)),*])),*].into_iter().collect::<HashMap<_,_>>()
634 };
635 }
636
637 fn check_graph_for_spec(
638 spec: &str,
639 dependencies: Option<(
640 HashMap<SRef, Vec<SRef>>,
641 HashMap<SRef, Vec<SRef>>,
642 HashMap<SRef, Vec<SRef>>,
643 HashMap<SRef, Vec<SRef>>,
644 HashMap<SRef, Vec<(SRef, WRef)>>,
645 HashMap<SRef, Vec<(SRef, WRef)>>,
646 )>,
647 ) {
648 let parser_config = ParserConfig::for_string(spec.to_string());
649 let frontend_config = FrontendConfig::from(&parser_config);
650 let ast = parse(&parser_config).unwrap_or_else(|e| panic!("{:?}", e));
651 let hir = Hir::<BaseMode>::from_ast(ast)
652 .unwrap()
653 .check_types(&frontend_config)
654 .unwrap();
655 let deps = DepAna::analyze(&hir);
656 if let Ok(deps) = deps {
657 let (
658 direct_accesses,
659 transitive_accesses,
660 direct_accessed_by,
661 transitive_accessed_by,
662 aggregates,
663 aggregated_by,
664 ) = dependencies.unwrap();
665 deps.direct_accesses.iter().for_each(|(sr, accesses_hir)| {
666 let accesses_reference = direct_accesses.get(sr).unwrap();
667 assert_eq!(accesses_hir.len(), accesses_reference.len(), "sr: {}", sr);
668 accesses_hir
669 .iter()
670 .for_each(|(sr, _)| assert!(accesses_reference.contains(&sr), "sr: {}", sr));
671 });
672 deps.transitive_accesses
673 .iter()
674 .for_each(|(sr, accesses_hir)| {
675 let accesses_reference = transitive_accesses.get(sr).unwrap();
676 assert_eq!(accesses_hir.len(), accesses_reference.len(), "sr: {}", sr);
677 accesses_hir
678 .iter()
679 .for_each(|sr| assert!(accesses_reference.contains(sr), "sr: {}", sr));
680 });
681 deps.direct_accessed_by
682 .iter()
683 .for_each(|(sr, accessed_by_hir)| {
684 let accessed_by_reference = direct_accessed_by.get(sr).unwrap();
685 assert_eq!(
686 accessed_by_hir.len(),
687 accessed_by_reference.len(),
688 "sr: {}",
689 sr
690 );
691 accessed_by_hir.iter().for_each(|sr| {
692 assert!(accessed_by_reference.contains(&sr.0), "sr: {}", sr.0)
693 });
694 });
695 deps.transitive_accessed_by
696 .iter()
697 .for_each(|(sr, accessed_by_hir)| {
698 let accessed_by_reference = transitive_accessed_by.get(sr).unwrap();
699 assert_eq!(
700 accessed_by_hir.len(),
701 accessed_by_reference.len(),
702 "sr: {}",
703 sr
704 );
705 accessed_by_hir
706 .iter()
707 .for_each(|sr| assert!(accessed_by_reference.contains(sr), "sr: {}", sr));
708 });
709 deps.aggregates.iter().for_each(|(sr, aggregates_hir)| {
710 let aggregates_reference = aggregates.get(sr).unwrap();
711 assert_eq!(aggregates_hir.len(), aggregates_reference.len(), "test");
712 aggregates_hir.iter().for_each(|(sr, _, wref)| {
713 assert!(aggregates_reference.contains(&(*sr, *wref)))
714 });
715 });
716 deps.aggregated_by
717 .iter()
718 .for_each(|(sr, aggregated_by_hir)| {
719 let aggregated_by_reference = aggregated_by.get(sr).unwrap();
720 assert_eq!(
721 aggregated_by_hir.len(),
722 aggregated_by_reference.len(),
723 "test"
724 );
725 aggregated_by_hir.iter().for_each(|(sr, _, wref)| {
726 assert!(aggregated_by_reference.contains(&(*sr, *wref)))
727 });
728 });
729 } else {
730 assert!(dependencies.is_none())
731 }
732 }
733
734 #[test]
735 fn self_loop_simple() {
736 let spec = "input a: Int8\noutput b: Int8 := a+b";
737 check_graph_for_spec(spec, None);
738 }
739
740 #[test]
741 fn self_loop_complex() {
742 let spec = "input a: Int8\n
743 output b: Int8 := a\n
744 output c: Int8 := b\n
745 output d: Int8 := c\n
746 output e: Int8 @1Hz := e";
747 check_graph_for_spec(spec, None)
748 }
749
750 #[test]
751 fn simple_loop() {
752 let spec = "input a: Int8\noutput b: Int8 := a+d\noutput c: Int8 := b\noutput d: Int8 := c";
753 check_graph_for_spec(spec, None);
754 }
755
756 #[test]
757 fn linear_dependencies() {
758 let spec = "input a: Int8\noutput b: Int8 := a\noutput c: Int8 := b\noutput d: Int8 := c";
759 let sname_to_sref = vec![
760 ("a", SRef::In(0)),
761 ("b", SRef::Out(0)),
762 ("c", SRef::Out(1)),
763 ("d", SRef::Out(2)),
764 ]
765 .into_iter()
766 .collect::<HashMap<&str, SRef>>();
767 let direct_accesses = checking_map!(
768 sname_to_sref,
769 ["a", ()],
770 ["b", ("a")],
771 ["c", ("b")],
772 ["d", ("c")]
773 );
774 let transitive_accesses = checking_map!(
775 sname_to_sref,
776 ["a", ()],
777 ["b", ("a")],
778 ["c", ("a", "b")],
779 ["d", ("a", "b", "c")]
780 );
781 let direct_accessed_by = checking_map!(
782 sname_to_sref,
783 ["a", ("b")],
784 ["b", ("c")],
785 ["c", ("d")],
786 ["d", ()]
787 );
788 let transitive_accessed_by = checking_map!(
789 sname_to_sref,
790 ["a", ("b", "c", "d")],
791 ["b", ("c", "d")],
792 ["c", ("d")],
793 ["d", ()]
794 );
795 let aggregates = empty_vec_for_map!(sname_to_sref);
796 let aggregated_by = empty_vec_for_map!(sname_to_sref);
797 check_graph_for_spec(
798 spec,
799 Some((
800 direct_accesses,
801 transitive_accesses,
802 direct_accessed_by,
803 transitive_accessed_by,
804 aggregates,
805 aggregated_by,
806 )),
807 );
808 }
809
810 #[test]
811 fn negative_loop() {
812 let spec = "output a: Int8 @1Hz := a.offset(by: -1).defaults(to: 0)";
813 let sname_to_sref = vec![("a", SRef::Out(0))]
814 .into_iter()
815 .collect::<HashMap<&str, SRef>>();
816 let direct_accesses = checking_map!(sname_to_sref, ["a", ("a")]);
817 let transitive_accesses = checking_map!(sname_to_sref, ["a", ("a")]);
818 let direct_accessed_by = checking_map!(sname_to_sref, ["a", ("a")]);
819 let transitive_accessed_by = checking_map!(sname_to_sref, ["a", ("a")]);
820 let aggregates = empty_vec_for_map!(sname_to_sref);
821 let aggregated_by = empty_vec_for_map!(sname_to_sref);
822 check_graph_for_spec(
823 spec,
824 Some((
825 direct_accesses,
826 transitive_accesses,
827 direct_accessed_by,
828 transitive_accessed_by,
829 aggregates,
830 aggregated_by,
831 )),
832 );
833 }
834
835 #[test]
836 fn negative_loop_different_offsets() {
837 let spec = "input a: Int8\noutput b: Int8 := a.offset(by: -1).defaults(to: 0) + d.offset(by:-2).defaults(to:0)\noutput c: Int8 := b.offset(by:-3).defaults(to: 0)\noutput d: Int8 := c.offset(by:-4).defaults(to: 0)";
838 let sname_to_sref = vec![
839 ("a", SRef::In(0)),
840 ("b", SRef::Out(0)),
841 ("c", SRef::Out(1)),
842 ("d", SRef::Out(2)),
843 ]
844 .into_iter()
845 .collect::<HashMap<&str, SRef>>();
846 let direct_accesses = checking_map!(
847 sname_to_sref,
848 ["a", ()],
849 ["b", ("a", "d")],
850 ["c", ("b")],
851 ["d", ("c")],
852 );
853 let transitive_accesses = checking_map!(
854 sname_to_sref,
855 ["a", ()],
856 ["b", ("a", "b", "c", "d")],
857 ["c", ("a", "b", "c", "d")],
858 ["d", ("a", "b", "c", "d")],
859 );
860 let direct_accessed_by = checking_map!(
861 sname_to_sref,
862 ["a", ("b")],
863 ["b", ("c")],
864 ["c", ("d")],
865 ["d", ("b")],
866 );
867 let transitive_accessed_by = checking_map!(
868 sname_to_sref,
869 ["a", ("b", "c", "d")],
870 ["b", ("b", "c", "d")],
871 ["c", ("b", "c", "d")],
872 ["d", ("b", "c", "d")],
873 );
874 let aggregates = empty_vec_for_map!(sname_to_sref);
875 let aggregated_by = empty_vec_for_map!(sname_to_sref);
876 check_graph_for_spec(
877 spec,
878 Some((
879 direct_accesses,
880 transitive_accesses,
881 direct_accessed_by,
882 transitive_accessed_by,
883 aggregates,
884 aggregated_by,
885 )),
886 );
887 }
888
889 #[test]
890 fn negative_and_positive_lookups_as_loop() {
891 let spec = "input a: Int8\noutput b: Int8 := a + d.offset(by:-1).defaults(to:0)\noutput c: Int8 := b\noutput d: Int8 := c";
892 let sname_to_sref = vec![
893 ("a", SRef::In(0)),
894 ("b", SRef::Out(0)),
895 ("c", SRef::Out(1)),
896 ("d", SRef::Out(2)),
897 ]
898 .into_iter()
899 .collect::<HashMap<&str, SRef>>();
900 let direct_accesses = checking_map!(
901 sname_to_sref,
902 ["a", ()],
903 ["b", ("a", "d")],
904 ["c", ("b")],
905 ["d", ("c")]
906 );
907 let transitive_accesses = checking_map!(
908 sname_to_sref,
909 ["a", ()],
910 ["b", ("a", "b", "c", "d")],
911 ["c", ("a", "b", "c", "d")],
912 ["d", ("a", "b", "c", "d")]
913 );
914 let direct_accessed_by = checking_map!(
915 sname_to_sref,
916 ["a", ("b")],
917 ["b", ("c")],
918 ["c", ("d")],
919 ["d", ("b")]
920 );
921 let transitive_accessed_by = checking_map!(
922 sname_to_sref,
923 ["a", ("b", "c", "d")],
924 ["b", ("b", "c", "d")],
925 ["c", ("b", "c", "d")],
926 ["d", ("b", "c", "d")]
927 );
928 let aggregates = empty_vec_for_map!(sname_to_sref);
929 let aggregated_by = empty_vec_for_map!(sname_to_sref);
930 check_graph_for_spec(
931 spec,
932 Some((
933 direct_accesses,
934 transitive_accesses,
935 direct_accessed_by,
936 transitive_accessed_by,
937 aggregates,
938 aggregated_by,
939 )),
940 );
941 }
942
943 #[test]
944 fn self_sliding_window() {
945 let spec = "output a @1Hz := a.aggregate(over: 1s, using: count)";
946 check_graph_for_spec(spec, None);
947 }
948
949 #[test]
950 fn sliding_window_loop() {
951 let spec = "input a: Int8\noutput b@10Hz := a.aggregate(over: 1s, using: sum) + d.aggregate(over: 1s, using: sum)\noutput c@2Hz := b.aggregate(over: 1s, using: sum)\noutput d@1Hz := c.aggregate(over: 1s, using: sum)";
952 check_graph_for_spec(spec, None);
953 }
954
955 #[test]
956 fn sliding_window_and_positive_lookups_loop() {
957 let spec = "input a: Int8\noutput b@10Hz := a.aggregate(over: 1s, using: sum) + d.hold().defaults(to: 0)\noutput c@2Hz := b.aggregate(over: 1s, using: sum)\noutput d@1Hz := c";
958 check_graph_for_spec(spec, None);
959 }
960
961 #[test]
962 fn sliding_windows_chain_and_hold_lookup() {
963 let spec = "input a: Int8\noutput b@1Hz := a.aggregate(over: 1s, using: sum) + d.offset(by: -1).defaults(to: 0)\noutput c@2Hz := b.aggregate(over: 1s, using: sum)\noutput d@2Hz := b.hold().defaults(to: 0)";
964 let sname_to_sref = vec![
965 ("a", SRef::In(0)),
966 ("b", SRef::Out(0)),
967 ("c", SRef::Out(1)),
968 ("d", SRef::Out(2)),
969 ]
970 .into_iter()
971 .collect::<HashMap<&str, SRef>>();
972 let direct_accesses = checking_map!(
973 sname_to_sref,
974 ["a", ()],
975 ["b", ("a", "d")],
976 ["c", ("b")],
977 ["d", ("b")]
978 );
979 let transitive_accesses = checking_map!(
980 sname_to_sref,
981 ["a", ()],
982 ["b", ("a", "b", "d")],
983 ["c", ("a", "b", "d")],
984 ["d", ("a", "b", "d")]
985 );
986 let direct_accessed_by = checking_map!(
987 sname_to_sref,
988 ["a", ("b")],
989 ["b", ("c", "d")],
990 ["c", ()],
991 ["d", ("b")]
992 );
993 let transitive_accessed_by = checking_map!(
994 sname_to_sref,
995 ["a", ("b", "c", "d")],
996 ["b", ("b", "c", "d")],
997 ["c", ()],
998 ["d", ("b", "c", "d")]
999 );
1000 let aggregates = checking_map!(
1001 sname_to_sref,
1002 ["a", ()],
1003 ["b", (("a", WRef::Sliding(0)))],
1004 ["c", (("b", WRef::Sliding(1)))],
1005 ["d", ()]
1006 );
1007 let aggregated_by = checking_map!(
1008 sname_to_sref,
1009 ["a", (("b", WRef::Sliding(0)))],
1010 ["b", (("c", WRef::Sliding(1)))],
1011 ["c", ()],
1012 ["d", ()]
1013 );
1014 check_graph_for_spec(
1015 spec,
1016 Some((
1017 direct_accesses,
1018 transitive_accesses,
1019 direct_accessed_by,
1020 transitive_accessed_by,
1021 aggregates,
1022 aggregated_by,
1023 )),
1024 );
1025 }
1026
1027 #[test]
1034 fn parallel_edges_in_a_loop() {
1035 let spec =
1036 "input a: Int8\noutput b: Int8 := a+d+d\noutput c: Int8 := b\noutput d: Int8 := c";
1037 check_graph_for_spec(spec, None);
1038 }
1039
1040 #[test]
1041 fn spawn_self_loop() {
1042 let spec = "input a: Int8\noutput b spawn @a when b.hold(or: 2) > 6 eval with a + 5";
1043 check_graph_for_spec(spec, None);
1044 }
1045
1046 #[test]
1047 fn filter_self_loop() {
1048 let spec = "input a: Int8\noutput b eval when b.hold(or: 2) > 6 with a + 5";
1049 check_graph_for_spec(spec, None);
1050 }
1051
1052 #[test]
1053 fn close_self_loop() {
1054 let spec = "input a: Int8\noutput b close when b > 6 eval with a + 5";
1055 let sname_to_sref = vec![("a", SRef::In(0)), ("b", SRef::Out(0))]
1056 .into_iter()
1057 .collect::<HashMap<&str, SRef>>();
1058 let direct_accesses = checking_map!(sname_to_sref, ["a", ()], ["b", ("a", "b")]);
1059 let transitive_accesses = checking_map!(sname_to_sref, ["a", ()], ["b", ("a", "b")]);
1060 let direct_accessed_by = checking_map!(sname_to_sref, ["a", ("b")], ["b", ("b")]);
1061 let transitive_accessed_by = checking_map!(sname_to_sref, ["a", ("b")], ["b", ("b")]);
1062 let aggregates = empty_vec_for_map!(sname_to_sref);
1063 let aggregated_by = empty_vec_for_map!(sname_to_sref);
1064 check_graph_for_spec(
1065 spec,
1066 Some((
1067 direct_accesses,
1068 transitive_accesses,
1069 direct_accessed_by,
1070 transitive_accessed_by,
1071 aggregates,
1072 aggregated_by,
1073 )),
1074 );
1075 }
1076
1077 #[test]
1078 fn simple_loop_with_parameter_event_based() {
1079 let spec = "input a: Int8\n
1080 output b(para) spawn with c eval @a with b(para).offset(by: -1).defaults(to: 0)\n
1081 output c @a := b(5).hold().defaults(to: 0)";
1082 check_graph_for_spec(spec, None);
1083 }
1084
1085 #[test]
1086 fn simple_loop_with_parameter_static_and_dynamic_periodic() {
1087 let spec = "input a: Int8\n
1088 output b(para) spawn with a eval @1Hz with b(para).offset(by: -1).defaults(to: 0)\n
1089 output c @1Hz := b(5).hold().defaults(to: 0)";
1090 let sname_to_sref = vec![("a", SRef::In(0)), ("b", SRef::Out(0)), ("c", SRef::Out(1))]
1091 .into_iter()
1092 .collect::<HashMap<&str, SRef>>();
1093 let direct_accesses =
1094 checking_map!(sname_to_sref, ["a", ()], ["b", ("a", "b")], ["c", ("b")]);
1095 let transitive_accesses = checking_map!(
1096 sname_to_sref,
1097 ["a", ()],
1098 ["b", ("a", "b")],
1099 ["c", ("b", "a")]
1100 );
1101 let direct_accessed_by =
1102 checking_map!(sname_to_sref, ["a", ("b")], ["b", ("b", "c")], ["c", ()]);
1103 let transitive_accessed_by = checking_map!(
1104 sname_to_sref,
1105 ["a", ("b", "c")],
1106 ["b", ("b", "c")],
1107 ["c", ()]
1108 );
1109 let aggregates = empty_vec_for_map!(sname_to_sref);
1110 let aggregated_by = empty_vec_for_map!(sname_to_sref);
1111 check_graph_for_spec(
1112 spec,
1113 Some((
1114 direct_accesses,
1115 transitive_accesses,
1116 direct_accessed_by,
1117 transitive_accessed_by,
1118 aggregates,
1119 aggregated_by,
1120 )),
1121 );
1122 }
1123
1124 #[test]
1125 fn simple_chain_with_parameter() {
1126 let spec = "input a: Int8\n
1127 output b := a + 5\n
1128 output c(para) spawn with b eval @1Hz with para + 5";
1129 let sname_to_sref = vec![("a", SRef::In(0)), ("b", SRef::Out(0)), ("c", SRef::Out(1))]
1130 .into_iter()
1131 .collect::<HashMap<&str, SRef>>();
1132 let direct_accesses = checking_map!(sname_to_sref, ["a", ()], ["b", ("a")], ["c", ("b")],);
1133 let transitive_accesses =
1134 checking_map!(sname_to_sref, ["a", ()], ["b", ("a")], ["c", ("a", "b")],);
1135 let direct_accessed_by =
1136 checking_map!(sname_to_sref, ["a", ("b")], ["b", ("c")], ["c", ()],);
1137 let transitive_accessed_by =
1138 checking_map!(sname_to_sref, ["a", ("b", "c")], ["b", ("c")], ["c", ()],);
1139 let aggregates = empty_vec_for_map!(sname_to_sref);
1140 let aggregated_by = empty_vec_for_map!(sname_to_sref);
1141 check_graph_for_spec(
1142 spec,
1143 Some((
1144 direct_accesses,
1145 transitive_accesses,
1146 direct_accessed_by,
1147 transitive_accessed_by,
1148 aggregates,
1149 aggregated_by,
1150 )),
1151 );
1152 }
1153
1154 #[test]
1155 fn parameter_loop() {
1156 let spec = "input a: Int8\n
1157 output b(para) spawn when d(para).hold(or: 2) > 6 with a eval with a + para\n
1158 output c(para) spawn when a > 6 with a eval with a + b(para).hold(or: 2)\n
1159 output d(para) spawn when a > 6 with a eval with a + c(para)";
1160 check_graph_for_spec(spec, None);
1161 }
1162
1163 #[test]
1164 fn lookup_chain_with_parametrization() {
1165 let spec = "input a: Int8\noutput b(para) spawn when a > 6 with a eval with a + para\noutput c(para) spawn when a > 6 with a eval with a + b(para)\noutput d(para) spawn when a > 6 with a eval with a + c(para)";
1166 let name_to_sref = vec![
1167 ("a", SRef::In(0)),
1168 ("b", SRef::Out(0)),
1169 ("c", SRef::Out(1)),
1170 ("d", SRef::Out(2)),
1171 ]
1172 .into_iter()
1173 .collect::<HashMap<&str, SRef>>();
1174 let direct_accesses = checking_map!(
1175 name_to_sref,
1176 ["a", ()],
1177 ["b", ("a",)],
1178 ["c", ("a", "b")],
1179 ["d", ("a", "c")],
1180 );
1181 let transitive_accesses = checking_map!(
1182 name_to_sref,
1183 ["a", ()],
1184 ["b", ("a",)],
1185 ["c", ("a", "b")],
1186 ["d", ("a", "b", "c")],
1187 );
1188 let direct_accessed_by = checking_map!(
1189 name_to_sref,
1190 ["a", ("b", "c", "d")],
1191 ["b", ("c",)],
1192 ["c", ("d")],
1193 ["d", ()],
1194 );
1195 let transitive_accessed_by = checking_map!(
1196 name_to_sref,
1197 ["a", ("b", "c", "d")],
1198 ["b", ("c", "d")],
1199 ["c", ("d")],
1200 ["d", ()],
1201 );
1202 let aggregates = empty_vec_for_map!(name_to_sref);
1203 let aggregated_by = empty_vec_for_map!(name_to_sref);
1204 check_graph_for_spec(
1205 spec,
1206 Some((
1207 direct_accesses,
1208 transitive_accesses,
1209 direct_accessed_by,
1210 transitive_accessed_by,
1211 aggregates,
1212 aggregated_by,
1213 )),
1214 );
1215 }
1216
1217 #[test]
1218 fn parameter_loop_with_different_lookup_types() {
1219 let spec = "input a: Int8\n
1220 input b: Int8\n
1221 output c(p) spawn when a < b with a eval with p + b + f(p).hold().defaults(to: 0)\n
1222 output d(p) spawn when c(4).hold().defaults(to: 0) > 5 with b eval with b + 5\n
1223 output e(p) spawn with b eval @a∧b with d(p).hold().defaults(to: 0) + 5\n
1224 output f(p) spawn with b eval when e(p).hold().defaults(to: 0) < 6 with b + 5";
1225 check_graph_for_spec(spec, None);
1226 }
1227
1228 #[test]
1229 fn parameter_loop_with_lookup_in_close() {
1230 let spec = "input a: Int8\n
1231 input b: Int8\n
1232 output c(p) spawn when a < b with a eval with p + b + g(p).hold().defaults(to: 0)\n
1233 output d(p) spawn when c(4).hold().defaults(to: 0) > 5 with b eval with b + 5\n
1234 output e(p) spawn with b eval @a∧b with d(p).hold().defaults(to: 0) + 5\n
1235 output f(p) spawn with b eval when e(p).hold().defaults(to: 0) < 6 with b + 5\n
1236 output g(p) spawn with b close @true when f(p).hold().defaults(to: 0) < 6 eval with b + 5";
1237 let sname_to_sref = vec![
1238 ("a", SRef::In(0)),
1239 ("b", SRef::In(1)),
1240 ("c", SRef::Out(0)),
1241 ("d", SRef::Out(1)),
1242 ("e", SRef::Out(2)),
1243 ("f", SRef::Out(3)),
1244 ("g", SRef::Out(4)),
1245 ]
1246 .into_iter()
1247 .collect::<HashMap<&str, SRef>>();
1248 let direct_accesses = checking_map!(
1249 sname_to_sref,
1250 ["a", ()],
1251 ["b", ()],
1252 ["c", ("a", "b", "g")],
1253 ["d", ("b", "c")],
1254 ["e", ("b", "d")],
1255 ["f", ("b", "e")],
1256 ["g", ("b", "f")],
1257 );
1258 let transitive_accesses = checking_map!(
1259 sname_to_sref,
1260 ["a", ()],
1261 ["b", ()],
1262 ["c", ("a", "b", "c", "d", "e", "f", "g")],
1263 ["d", ("a", "b", "c", "d", "e", "f", "g")],
1264 ["e", ("a", "b", "c", "d", "e", "f", "g")],
1265 ["f", ("a", "b", "c", "d", "e", "f", "g")],
1266 ["g", ("a", "b", "c", "d", "e", "f", "g")],
1267 );
1268 let direct_accessed_by = checking_map!(
1269 sname_to_sref,
1270 ["a", ("c")],
1271 ["b", ("c", "d", "e", "f", "g")],
1272 ["c", ("d")],
1273 ["d", ("e")],
1274 ["e", ("f")],
1275 ["f", ("g")],
1276 ["g", ("c")],
1277 );
1278 let transitive_accessed_by = checking_map!(
1279 sname_to_sref,
1280 ["a", ("c", "d", "e", "f", "g")],
1281 ["b", ("c", "d", "e", "f", "g")],
1282 ["c", ("c", "d", "e", "f", "g")],
1283 ["d", ("c", "d", "e", "f", "g")],
1284 ["e", ("c", "d", "e", "f", "g")],
1285 ["f", ("c", "d", "e", "f", "g")],
1286 ["g", ("c", "d", "e", "f", "g")],
1287 );
1288 let aggregates = empty_vec_for_map!(sname_to_sref);
1289 let aggregated_by = empty_vec_for_map!(sname_to_sref);
1290 check_graph_for_spec(
1291 spec,
1292 Some((
1293 direct_accesses,
1294 transitive_accesses,
1295 direct_accessed_by,
1296 transitive_accessed_by,
1297 aggregates,
1298 aggregated_by,
1299 )),
1300 );
1301 }
1302
1303 #[test]
1304 fn parameter_nested_lookup_implicit() {
1305 let spec = "input a: Int8\n input b: Int8\n output c(p) spawn with a eval with p + b\noutput d := c(c(b).hold().defaults(to: 0)).hold().defaults(to: 0)";
1306 let sname_to_sref = vec![
1307 ("a", SRef::In(0)),
1308 ("b", SRef::In(1)),
1309 ("c", SRef::Out(0)),
1310 ("d", SRef::Out(1)),
1311 ]
1312 .into_iter()
1313 .collect::<HashMap<&str, SRef>>();
1314 let direct_accesses = checking_map!(
1315 sname_to_sref,
1316 ["a", ()],
1317 ["b", ()],
1318 ["c", ("a", "b")],
1319 ["d", ("b", "c",)],
1320 );
1321 let transitive_accesses = checking_map!(
1322 sname_to_sref,
1323 ["a", ()],
1324 ["b", ()],
1325 ["c", ("a", "b")],
1326 ["d", ("a", "b", "c")]
1327 );
1328 let direct_accessed_by = checking_map!(
1329 sname_to_sref,
1330 ["a", ("c")],
1331 ["b", ("c", "d")],
1332 ["c", ("d")],
1333 ["d", ()]
1334 );
1335 let transitive_accessed_by = checking_map!(
1336 sname_to_sref,
1337 ["a", ("c", "d")],
1338 ["b", ("c", "d")],
1339 ["c", ("d")],
1340 ["d", ()]
1341 );
1342 let aggregates = empty_vec_for_map!(sname_to_sref);
1343 let aggregated_by = empty_vec_for_map!(sname_to_sref);
1344 check_graph_for_spec(
1345 spec,
1346 Some((
1347 direct_accesses,
1348 transitive_accesses,
1349 direct_accessed_by,
1350 transitive_accessed_by,
1351 aggregates,
1352 aggregated_by,
1353 )),
1354 );
1355 }
1356
1357 #[test]
1358 fn parameter_nested_lookup_explicit() {
1359 let spec = "input a: Int8\n input b: Int8\n output c(p) spawn with a eval with p + b\noutput d := c(b).hold().defaults(to: 0)\noutput e := c(d).hold().defaults(to: 0)";
1360 let sname_to_sref = vec![
1361 ("a", SRef::In(0)),
1362 ("b", SRef::In(1)),
1363 ("c", SRef::Out(0)),
1364 ("d", SRef::Out(1)),
1365 ("e", SRef::Out(2)),
1366 ]
1367 .into_iter()
1368 .collect::<HashMap<&str, SRef>>();
1369 let direct_accesses = checking_map!(
1370 sname_to_sref,
1371 ["a", ()],
1372 ["b", ()],
1373 ["c", ("a", "b")],
1374 ["d", ("b", "c")],
1375 ["e", ("c", "d")]
1376 );
1377 let transitive_accesses = checking_map!(
1378 sname_to_sref,
1379 ["a", ()],
1380 ["b", ()],
1381 ["c", ("a", "b")],
1382 ["d", ("a", "b", "c")],
1383 ["e", ("a", "b", "c", "d")]
1384 );
1385 let direct_accessed_by = checking_map!(
1386 sname_to_sref,
1387 ["a", ("c")],
1388 ["b", ("c", "d")],
1389 ["c", ("d", "e")],
1390 ["d", ("e")],
1391 ["e", ()]
1392 );
1393 let transitive_accessed_by = checking_map!(
1394 sname_to_sref,
1395 ["a", ("c", "d", "e")],
1396 ["b", ("c", "d", "e")],
1397 ["c", ("d", "e")],
1398 ["d", ("e")],
1399 ["e", ()]
1400 );
1401 let aggregates = empty_vec_for_map!(sname_to_sref);
1402 let aggregated_by = empty_vec_for_map!(sname_to_sref);
1403 check_graph_for_spec(
1404 spec,
1405 Some((
1406 direct_accesses,
1407 transitive_accesses,
1408 direct_accessed_by,
1409 transitive_accessed_by,
1410 aggregates,
1411 aggregated_by,
1412 )),
1413 );
1414 }
1415
1416 #[test]
1417 fn parameter_loop_with_parameter_lookup() {
1418 let spec = "input a: Int8\n input b: Int8\n output c(p) spawn with a eval with p + b + e\noutput d := c(b).hold().defaults(to: 0)\noutput e := c(d).hold().defaults(to: 0)";
1419 check_graph_for_spec(spec, None);
1420 }
1421
1422 #[test]
1423 fn parameter_cross_lookup() {
1424 let spec = "input a: Int8\n
1425 input b: Int8\n
1426 output c(p) spawn with a eval with p + b\n
1427 output d @1Hz := c(e).hold().defaults(to: 0)\n
1428 output e @1Hz := c(d).hold().defaults(to: 0)";
1429 check_graph_for_spec(spec, None);
1430 }
1431
1432 #[ignore = "This should be rejected. See Issue #33"]
1433 #[test]
1434 fn test_filter_self_lookup() {
1435 let spec = "input a: Int8\n\
1436 input b: Bool\n\
1437 output c eval when c.offset(by:-1).defaults(to: true) with b";
1438
1439 check_graph_for_spec(spec, None);
1440 }
1441
1442 #[test]
1443 fn delay() {
1444 let spec = "
1445 input x:Int8\n\
1446 output a spawn when x=42 close when if true then true else a eval @1Hz with x.aggregate(over: 1s, using: sum) > 1337
1447 ";
1448 let sname_to_sref = vec![("a", SRef::Out(0)), ("x", SRef::In(0))]
1449 .into_iter()
1450 .collect::<HashMap<&str, SRef>>();
1451 let direct_accesses = checking_map!(sname_to_sref, ["a", ("a", "x")], ["x", ()]);
1452 let transitive_accesses = checking_map!(sname_to_sref, ["a", ("a", "x")], ["x", ()]);
1453 let direct_accessed_by = checking_map!(sname_to_sref, ["a", ("a")], ["x", ("a")]);
1454 let transitive_accessed_by = checking_map!(sname_to_sref, ["a", ("a")], ["x", ("a")]);
1455 let aggregates = checking_map!(sname_to_sref, ["a", (("x", WRef::Sliding(0)))], ["x", ()]);
1456 let aggregated_by =
1457 checking_map!(sname_to_sref, ["a", ()], ["x", (("a", WRef::Sliding(0)))]);
1458 check_graph_for_spec(
1459 spec,
1460 Some((
1461 direct_accesses,
1462 transitive_accesses,
1463 direct_accessed_by,
1464 transitive_accessed_by,
1465 aggregates,
1466 aggregated_by,
1467 )),
1468 );
1469 }
1470
1471 #[test]
1472 fn instance_aggregation() {
1473 let spec = "input a: Int32\n\
1474 output b (p) spawn with a eval when a > 5 with b(p).offset(by: -1).defaults(to: 0) + 1\n\
1475 output c eval with b.aggregate(over_instances: fresh, using: Σ)\n";
1476 let sname_to_sref = vec![("b", SRef::Out(0)), ("c", SRef::Out(1)), ("a", SRef::In(0))]
1477 .into_iter()
1478 .collect::<HashMap<&str, SRef>>();
1479 let direct_accesses =
1480 checking_map!(sname_to_sref, ["a", ()], ["b", ("b", "a")], ["c", ("b")]);
1481 let transitive_accesses = checking_map!(
1482 sname_to_sref,
1483 ["a", ()],
1484 ["b", ("b", "a")],
1485 ["c", ("b", "a")]
1486 );
1487 let direct_accessed_by =
1488 checking_map!(sname_to_sref, ["a", ("b")], ["b", ("b", "c")], ["c", ()]);
1489 let transitive_accessed_by = checking_map!(
1490 sname_to_sref,
1491 ["a", ("b", "c")],
1492 ["b", ("b", "c")],
1493 ["c", ()]
1494 );
1495 let aggregates = checking_map!(
1496 sname_to_sref,
1497 ["a", ()],
1498 ["b", ()],
1499 ["c", (("b", WRef::Instance(0)))],
1500 );
1501 let aggregated_by = checking_map!(
1502 sname_to_sref,
1503 ["a", ()],
1504 ["b", (("c", WRef::Instance(0)))],
1505 ["c", ()],
1506 );
1507 check_graph_for_spec(
1508 spec,
1509 Some((
1510 direct_accesses,
1511 transitive_accesses,
1512 direct_accessed_by,
1513 transitive_accessed_by,
1514 aggregates,
1515 aggregated_by,
1516 )),
1517 );
1518 }
1519
1520 #[test]
1521 fn test_get_dep() {
1522 let spec = "
1523 input x:Int8\n\
1524 output a eval @x when x > 0 with x*x
1525 output b eval @x with a.get().defaults(to:0)
1526 ";
1527 let sname_to_sref = vec![("a", SRef::Out(0)), ("b", SRef::Out(1)), ("x", SRef::In(0))]
1528 .into_iter()
1529 .collect::<HashMap<&str, SRef>>();
1530 let direct_accesses: HashMap<SRef, Vec<SRef>> =
1531 checking_map!(sname_to_sref, ["a", ("x")], ["b", ("a")], ["x", ()]);
1532 let transitive_accesses =
1533 checking_map!(sname_to_sref, ["a", ("x")], ["b", ("a", "x")], ["x", ()]);
1534 let direct_accessed_by =
1535 checking_map!(sname_to_sref, ["a", ("b")], ["b", ()], ["x", ("a")]);
1536 let transitive_accessed_by =
1537 checking_map!(sname_to_sref, ["a", ("b")], ["b", ()], ["x", ("a", "b")]);
1538 let aggregates = empty_vec_for_map!(sname_to_sref);
1539 let aggregated_by = empty_vec_for_map!(sname_to_sref);
1540 check_graph_for_spec(
1541 spec,
1542 Some((
1543 direct_accesses,
1544 transitive_accesses,
1545 direct_accessed_by,
1546 transitive_accessed_by,
1547 aggregates,
1548 aggregated_by,
1549 )),
1550 );
1551 }
1552
1553 #[test]
1554 fn test_tick_dep() {
1555 let spec = "
1556 input x:Int8\n\
1557 output a eval @x when x > 0 with x*x
1558 output b eval @x with if a.is_fresh() then 1 else -1
1559 ";
1560 let sname_to_sref = vec![("a", SRef::Out(0)), ("b", SRef::Out(1)), ("x", SRef::In(0))]
1561 .into_iter()
1562 .collect::<HashMap<&str, SRef>>();
1563 let direct_accesses: HashMap<SRef, Vec<SRef>> =
1564 checking_map!(sname_to_sref, ["a", ("x")], ["b", ("a")], ["x", ()]);
1565 let transitive_accesses =
1566 checking_map!(sname_to_sref, ["a", ("x")], ["b", ("a", "x")], ["x", ()]);
1567 let direct_accessed_by =
1568 checking_map!(sname_to_sref, ["a", ("b")], ["b", ()], ["x", ("a")]);
1569 let transitive_accessed_by =
1570 checking_map!(sname_to_sref, ["a", ("b")], ["b", ()], ["x", ("a", "b")]);
1571 let aggregates = empty_vec_for_map!(sname_to_sref);
1572 let aggregated_by = empty_vec_for_map!(sname_to_sref);
1573 check_graph_for_spec(
1574 spec,
1575 Some((
1576 direct_accesses,
1577 transitive_accesses,
1578 direct_accessed_by,
1579 transitive_accessed_by,
1580 aggregates,
1581 aggregated_by,
1582 )),
1583 );
1584 }
1585
1586 #[test]
1587 fn test_filtered_instance_aggregation() {
1588 let spec = "input a: Int32\n\
1589 input a2: Int32\n\
1590 output b (p1, p2) \
1591 spawn with (a, a + 1) \
1592 eval with p1 + p2 + a\n\
1593 output c (p1) \
1594 spawn with a \
1595 eval with b.aggregate(over_instances: all(where: (p1,p2) => p2 = a2), using: Σ)\n";
1596 let sname_to_sref = vec![
1597 ("a", SRef::In(0)),
1598 ("a2", SRef::In(1)),
1599 ("b", SRef::Out(0)),
1600 ("c", SRef::Out(1)),
1601 ]
1602 .into_iter()
1603 .collect::<HashMap<&str, SRef>>();
1604 let direct_accesses: HashMap<SRef, Vec<SRef>> = checking_map!(
1605 sname_to_sref,
1606 ["a", ()],
1607 ["a2", ()],
1608 ["b", ("a")],
1609 ["c", ("a", "a2", "b")]
1610 );
1611 let transitive_accesses = checking_map!(
1612 sname_to_sref,
1613 ["a", ()],
1614 ["a2", ()],
1615 ["b", ("a")],
1616 ["c", ("a", "a2", "b")]
1617 );
1618 let direct_accessed_by = checking_map!(
1619 sname_to_sref,
1620 ["a", ("b", "c")],
1621 ["a2", ("c")],
1622 ["b", ("c")],
1623 ["c", ()]
1624 );
1625 let transitive_accessed_by = checking_map!(
1626 sname_to_sref,
1627 ["a", ("b", "c")],
1628 ["a2", ("c")],
1629 ["b", ("c")],
1630 ["c", ()]
1631 );
1632 let aggregates = checking_map!(
1633 sname_to_sref,
1634 ["a", ()],
1635 ["a2", ()],
1636 ["b", ()],
1637 ["c", (("b", WRef::Instance(0)))],
1638 );
1639 let aggregated_by = checking_map!(
1640 sname_to_sref,
1641 ["a", ()],
1642 ["a2", ()],
1643 ["b", (("c", WRef::Instance(0)))],
1644 ["c", ()],
1645 );
1646 check_graph_for_spec(
1647 spec,
1648 Some((
1649 direct_accesses,
1650 transitive_accesses,
1651 direct_accessed_by,
1652 transitive_accessed_by,
1653 aggregates,
1654 aggregated_by,
1655 )),
1656 );
1657 }
1658}