rtlola_hir/modes/
dependencies.rs

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
20/// Represents the Dependency Graph
21///
22/// The dependency graph represents all dependecies between streams.
23/// For this, the graph contains a node for each node in the specification and an edge from `source` to `target`, iff the stream `source` uses an stream value of `target`.
24/// The weight of each nodes is the stream reference representing the stream. The weight of the edges is the [kind](EdgeWeight) of the lookup.
25pub type DependencyGraph = StableGraph<SRef, EdgeWeight>;
26
27/// Represents the weights of the edges in the dependency graph
28#[derive(Hash, Clone, Debug, PartialEq, Eq, Copy)]
29pub struct EdgeWeight {
30    /// The [Origin] of the lookup
31    pub origin: Origin,
32    /// The [StreamAccessKind] of the lookup
33    pub kind: StreamAccessKind,
34}
35
36/// Represents the origin of a stream lookup
37#[derive(Hash, Clone, Debug, PartialEq, Eq, Copy, Serialize, Deserialize)]
38pub enum Origin {
39    /// The access occurs in the spawn declaration.
40    Spawn,
41    /// The access occurs in the filter expression.
42    Filter(usize),
43    /// The access occurs in the stream expression.
44    Eval(usize),
45    /// The access occurs in the close expression.
46    Close,
47}
48
49impl EdgeWeight {
50    /// Creates a new [EdgeWeight]
51    pub(crate) fn new(kind: StreamAccessKind, origin: Origin) -> Self {
52        EdgeWeight { kind, origin }
53    }
54
55    /// Returns the window reference if the [EdgeWeight] contains a sliding or discrete Aggregation or None otherwise.
56    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    /// Returns the memory bound of the [EdgeWeight]
70    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
86/// Represents all direct dependencies between streams
87pub(crate) type StreamDependencies = HashMap<SRef, Vec<(SRef, Vec<(Origin, StreamAccessKind)>)>>;
88/// Represents all transitive dependencies between streams
89pub(crate) type TransitiveDependencies = HashMap<SRef, Vec<SRef>>;
90/// Represents all dependencies between streams in which a window lookup is used
91pub(crate) type WindowDependencies = HashMap<SRef, Vec<(SRef, Origin, WRef)>>;
92
93pub(crate) trait ExtendedDepGraph {
94    /// Returns a new [dependency graph](DependencyGraph), in which all edges representing a negative offset lookup are deleted
95    fn without_negative_offset_edges(self) -> Self;
96
97    /// Returns a new [dependency graph](DependencyGraph), in which all edges between nodes with different pacing are deleted
98    fn without_different_pacing<M>(self, hir: &Hir<M>) -> Self
99    where
100        M: HirMode + TypedTrait;
101
102    /// Returns `true`, iff the edge weight `e` is a negative offset lookup
103    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    /// Returns a new [dependency graph](DependencyGraph), in which all edges representing a lookup that are used in the close condition are deleted
117    fn without_close(self) -> Self;
118
119    /// Returns a new [dependency graph](DependencyGraph), which only contains edges representing a lookup in the spawn condition
120    fn only_spawn(self) -> Self;
121
122    #[cfg(feature = "shift_layer")]
123    /// Returns a new [dependency graph](DependencyGraph), which only contains edges representing a lookup in the eval when condition
124    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/// Represents the error of the dependency analysis
258#[derive(Debug, Clone, Serialize, Deserialize)]
259pub enum DependencyErr {
260    /// Represents the error that the well-formedness condition is not satisfied.
261    ///
262    /// This error indicates that the given specification is not well-formed, i.e., the dependency graph contains a non-negative cycle.
263    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    /// Returns the result of the dependency analysis
300    ///
301    /// This function analyzes the dependencies of the given `spec`. It returns an [DependencyErr] if the specification is not well-formed.
302    /// Otherwise, the function returns the dependencies in the specification, including the dependency graph.
303    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        // add nodes and edges to graph
382        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        // Check well-formedness = no closed-walk with total weight of zero or positive
391        Self::check_well_formedness(&graph, spec).map_err(|e| e.into_diagnostic(spec))?;
392        // Describe dependencies in HashMaps
393        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            // Seprate case: has_path_connection alwas return true, if from = to
496            // First check if self edge is contained
497            let self_check = graph
498                .edge_indices()
499                .filter(|ei| {
500                    let (src, tar) = graph.edge_endpoints(*ei).unwrap();
501                    src == tar
502                }) // filter self references
503                .find(|ei| {
504                    let (src, _) = graph.edge_endpoints(*ei).unwrap();
505                    src == from
506                });
507            // Second check if one of the neighbors has a connection to the current node
508            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    /// Returns is the DP is well-formed, i.e., no closed-walk with total weight of zero or positive
528    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        // check if cyclic
539        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]
1028    // #[ignore] // Graph Analysis cannot handle positive edges; not required for this branch.
1029    // fn positive_loop_should_cause_a_warning() {
1030    //     chek_graph_for_spec("output a: Int8 := a[1]", false);
1031    // }
1032
1033    #[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}