rtlola_parser/
syntactic_sugar.rs

1use std::collections::HashSet;
2use std::hash::Hash;
3use std::rc::Rc;
4
5// List for all syntactic sugar transformer
6mod aggregation_method;
7mod builder;
8mod delta;
9mod implication;
10mod last;
11mod mirror;
12mod next_id;
13mod offset_or;
14mod probability;
15mod probability_aggregation;
16mod true_ratio;
17use aggregation_method::AggrMethodToWindow;
18use delta::Delta;
19use itertools::Itertools;
20use last::Last;
21use mirror::Mirror as SynSugMirror;
22use probability::Probability;
23use probability_aggregation::ProbabilityAggregation;
24use rtlola_reporting::RtLolaError;
25use true_ratio::TrueRatio;
26
27use self::implication::Implication;
28use self::offset_or::OffsetOr;
29use crate::ast::{
30    CloseSpec, EvalSpec, Expression, ExpressionKind, Input, InstanceSelection, LambdaExpr,
31    Mirror as AstMirror, NodeId, Output, RtLolaAst, SpawnSpec,
32};
33
34#[derive(Debug, Clone, Eq, PartialEq, Hash, PartialOrd, Ord)]
35enum ChangeInstruction {
36    #[allow(dead_code)] // currently unused
37    ReplaceExpr(NodeId, Expression),
38    RemoveStream(NodeId),
39    AddOutput(Box<Output>),
40}
41#[derive(Debug, Clone, Eq, PartialEq, Hash)]
42enum LocalChangeInstruction {
43    ReplaceExpr(Expression),
44}
45
46/// The ChangeSet collects change instructions for syntactic sugar removal.
47///
48/// Desugarization could replace the current expression or add global changes, like outsource to new streams.
49/// Adding change sets collects all global changes and allows at most one local change to be present.
50#[derive(Debug, Clone)]
51struct ChangeSet {
52    _local_applied_flag: bool,
53    local_instructions: Option<LocalChangeInstruction>,
54    global_instructions: HashSet<ChangeInstruction>,
55}
56
57#[derive(Clone, Copy, Debug)]
58enum ExprOrigin {
59    SpawnWhen,
60    SpawnWith,
61    #[allow(dead_code)]
62    EvalWhen(usize),
63    EvalWith(usize),
64    CloseWhen,
65}
66
67/// Syntactic Sugar has to implement this trait and override methods if needed.
68///
69/// The transformer gets every single expression passed once, in an bottom up order. Afterwards every top level stream object is passed once.
70/// The [ChangeSet] can hold a single local change, a change that immediately has to be applied to the expression passed in [SynSugar::desugarize_expr],
71/// and an arbitrary number of changes that will be applied after an iteration, which is identified by the [NodeId] of the object it wants to replace.
72#[allow(unused_variables)] // allow unused arguments in the default implementation without changing the name
73trait SynSugar {
74    /// Desugars a single expression.  Provided [RtLolaAst] and [Expression] are for reference, not modification.
75    ///
76    /// # Requirements
77    /// * Ids may NEVER be re-used.  Always generate new ones with ast.next_id() or increase the prime-counter of an existing [NodeId].
78    /// * When creating new nodes with a span, do not re-use the span of the old node.  Instead, create an indirect span refering to the old one.
79    fn desugarize_expr<'a>(
80        &self,
81        exp: &'a Expression,
82        ast: &'a RtLolaAst,
83        stream: usize,
84        origin: ExprOrigin,
85    ) -> Result<ChangeSet, RtLolaError> {
86        Ok(ChangeSet::empty())
87    }
88
89    /// Desugars a single output stream.  Provided [RtLolaAst] and [Output] are for reference, not modification.
90    ///
91    /// # Requirements
92    /// * Ids may NEVER be re-used.  Always generate new ones with ast.next_id() or increase the prime-counter of an existing [NodeId].
93    /// * When creating new nodes with a span, do not re-use the span of the old node.  Instead, create an indirect span refering to the old one.
94    fn desugarize_stream_out<'a>(
95        &self,
96        stream: &'a Output,
97        ast: &'a RtLolaAst,
98    ) -> Result<ChangeSet, RtLolaError> {
99        Ok(ChangeSet::empty())
100    }
101    /// Desugars a single input stream.  Provided [RtLolaAst] and [Input] are for reference, not modification.
102    ///
103    /// # Requirements
104    /// * Ids may NEVER be re-used.  Always generate new ones with ast.next_id() or increase the prime-counter of an existing [NodeId].
105    /// * When creating new nodes with a span, do not re-use the span of the old node.  Instead, create an indirect span refering to the old one.
106    fn desugarize_stream_in<'a>(
107        &self,
108        stream: &'a Input,
109        ast: &'a RtLolaAst,
110    ) -> Result<ChangeSet, RtLolaError> {
111        Ok(ChangeSet::empty())
112    }
113    /// Desugars a single mirror stream.  Provided [RtLolaAst] and [AstMirror] are for reference, not modification.
114    ///
115    /// # Requirements
116    /// * Ids may NEVER be re-used.  Always generate new ones with ast.next_id() or increase the prime-counter of an existing [NodeId].
117    /// * When creating new nodes with a span, do not re-use the span of the old node.  Instead, create an indirect span refering to the old one.
118    fn desugarize_stream_mirror<'a>(
119        &self,
120        stream: &'a AstMirror,
121        ast: &'a RtLolaAst,
122    ) -> Result<ChangeSet, RtLolaError> {
123        Ok(ChangeSet::empty())
124    }
125}
126
127/// The container for all syntactic sugar structs.
128///
129/// Contains the logic on how and when to remove syntactic sugar changes.
130/// A transformer just needs to implement the SynSugar trait and be registered in the internal vector, constructed in [Desugarizer::all].
131#[allow(missing_debug_implementations)] // Contains no relevant fields, only logic. Trait object vectors are hard to print.
132pub struct Desugarizer {
133    sugar_transformers: Vec<Box<dyn SynSugar>>,
134}
135
136impl Desugarizer {
137    /// Constructs a Desugarizer with all registered SynSugar transformations.
138    ///
139    /// All transformations registered in the internal vector will be applied on the ast.
140    /// New structs have to be added in this function.
141    pub fn all() -> Self {
142        let all_transformers: Vec<Box<dyn SynSugar>> = vec![
143            Box::new(Implication {}),
144            Box::new(AggrMethodToWindow {}),
145            Box::new(Last {}),
146            Box::new(SynSugMirror {}),
147            Box::new(Delta {}),
148            Box::new(OffsetOr {}),
149            Box::new(TrueRatio {}),
150            Box::new(Probability {}),
151            Box::new(ProbabilityAggregation {}),
152        ];
153        Self {
154            sugar_transformers: all_transformers,
155        }
156    }
157
158    /// Before calling this, all others Rcs on Ast nodes MUST be dropped.
159    ///
160    /// # panics
161    /// If an Rc has a strong count greater than one.
162    pub fn remove_syn_sugar(&self, mut ast: RtLolaAst) -> Result<RtLolaAst, RtLolaError> {
163        while {
164            // magic rust do-while loop
165            let (new_ast, change_flag) = self.desugarize_fix_point(ast)?;
166            ast = new_ast;
167            change_flag
168        } {} // do not remove! magic rust do-while loop
169        Ok(ast)
170    }
171
172    fn desugarize_fix_point(&self, mut ast: RtLolaAst) -> Result<(RtLolaAst, bool), RtLolaError> {
173        let mut change_flag = false;
174        for current_sugar in self.sugar_transformers.iter() {
175            let mut change_set = ChangeSet::empty();
176
177            for mirror in ast.mirrors.iter() {
178                change_set += self.desugarize_mirror(mirror, &ast, current_sugar)?;
179            }
180
181            for ix in 0..ast.outputs.len() {
182                let out = &ast.outputs[ix];
183                let out_clone: Output = Output::clone(out);
184                let Output {
185                    spawn, eval, close, ..
186                } = out_clone;
187                let new_spawn_spec = if let Some(spawn_spec) = spawn {
188                    let SpawnSpec {
189                        expression,
190                        condition,
191                        annotated_pacing,
192                        id,
193                        span,
194                    } = spawn_spec;
195                    let expression = expression
196                        .map(|expr| {
197                            let (new_expr, spawn_cs) = Self::desugarize_expression(
198                                expr,
199                                &ast,
200                                current_sugar,
201                                ix,
202                                ExprOrigin::SpawnWith,
203                            )?;
204                            change_set += spawn_cs;
205                            Ok::<_, RtLolaError>(new_expr)
206                        })
207                        .transpose()?;
208                    let condition = condition
209                        .map(|expr| {
210                            let (new_expr, spawn_cond_cs) = Self::desugarize_expression(
211                                expr,
212                                &ast,
213                                current_sugar,
214                                ix,
215                                ExprOrigin::SpawnWhen,
216                            )?;
217                            change_set += spawn_cond_cs;
218                            Ok::<_, RtLolaError>(new_expr)
219                        })
220                        .transpose()?;
221                    Some(SpawnSpec {
222                        expression,
223                        condition,
224                        annotated_pacing,
225                        id,
226                        span,
227                    })
228                } else {
229                    None
230                };
231
232                let transformed_eval = eval
233                    .into_iter()
234                    .enumerate()
235                    .map(|(i, eval_spec)| {
236                        let EvalSpec {
237                            annotated_pacing,
238                            condition,
239                            eval_expression,
240                            id,
241                            span,
242                        } = eval_spec;
243                        let new_eval = eval_expression
244                            .map(|e| {
245                                let (res, eval_cs) = Self::desugarize_expression(
246                                    e,
247                                    &ast,
248                                    current_sugar,
249                                    ix,
250                                    ExprOrigin::EvalWith(i),
251                                )?;
252                                change_set += eval_cs;
253                                Ok::<_, RtLolaError>(res)
254                            })
255                            .transpose()?;
256                        let new_condition = condition
257                            .map(|e| {
258                                let (res, cond_cs) = Self::desugarize_expression(
259                                    e,
260                                    &ast,
261                                    current_sugar,
262                                    ix,
263                                    ExprOrigin::EvalWhen(i),
264                                )?;
265                                change_set += cond_cs;
266                                Ok::<_, RtLolaError>(res)
267                            })
268                            .transpose()?;
269                        Ok::<_, RtLolaError>(Some(EvalSpec {
270                            annotated_pacing,
271                            condition: new_condition,
272                            eval_expression: new_eval,
273                            id,
274                            span,
275                        }))
276                    })
277                    .flatten_ok()
278                    .collect::<Result<_, _>>()?;
279
280                let new_close_spec = if let Some(close_spec) = close {
281                    let CloseSpec {
282                        condition,
283                        id,
284                        span,
285                        annotated_pacing,
286                    } = close_spec;
287                    let (new_condition, close_cs) = Self::desugarize_expression(
288                        condition,
289                        &ast,
290                        current_sugar,
291                        ix,
292                        ExprOrigin::CloseWhen,
293                    )?;
294                    change_set += close_cs;
295                    Some(CloseSpec {
296                        condition: new_condition,
297                        id,
298                        span,
299                        annotated_pacing,
300                    })
301                } else {
302                    None
303                };
304
305                let new_out = Output {
306                    spawn: new_spawn_spec,
307                    eval: transformed_eval,
308                    close: new_close_spec,
309                    ..out_clone
310                };
311                ast.outputs[ix] = Rc::new(new_out);
312            }
313            for input in ast.inputs.iter() {
314                change_set += self.desugarize_input(input, &ast, current_sugar)?;
315            }
316
317            for output in ast.outputs.iter() {
318                change_set += self.desugarize_output(output, &ast, current_sugar)?;
319            }
320
321            change_flag |=
322                change_set._local_applied_flag || !change_set.global_instructions.is_empty();
323            ast = self.apply_global_changes(change_set, ast);
324        }
325        Ok((ast, change_flag))
326    }
327
328    fn apply_global_changes(&self, c_s: ChangeSet, mut ast: RtLolaAst) -> RtLolaAst {
329        c_s.global_iter().for_each(|ci| match ci {
330            ChangeInstruction::AddOutput(o) => {
331                ast.outputs.push(Rc::new(*o));
332            }
333            ChangeInstruction::RemoveStream(id) => {
334                if let Some(idx) = ast.outputs.iter().position(|o| o.id == id) {
335                    assert_eq!(Rc::strong_count(&ast.outputs[idx]), 1);
336                    ast.outputs.remove(idx);
337                } else if let Some(idx) = ast.inputs.iter().position(|o| o.id == id) {
338                    assert_eq!(Rc::strong_count(&ast.inputs[idx]), 1);
339                    ast.inputs.remove(idx);
340                } else if let Some(idx) = ast.mirrors.iter().position(|o| o.id == id) {
341                    assert_eq!(Rc::strong_count(&ast.mirrors[idx]), 1);
342                    ast.mirrors.remove(idx);
343                } else {
344                    debug_assert!(false, "id in changeset does not belong to any stream");
345                }
346            }
347            ChangeInstruction::ReplaceExpr(_, expr) => {
348                for ix in 0..ast.outputs.len() {
349                    let out = &ast.outputs[ix];
350                    let out_clone: Output = Output::clone(out);
351                    let Output {
352                        spawn, eval, close, ..
353                    } = out_clone;
354                    let new_spawn_spec = if let Some(spawn_spec) = spawn {
355                        let SpawnSpec {
356                            expression,
357                            condition,
358                            annotated_pacing,
359                            id,
360                            span,
361                        } = spawn_spec;
362                        let expression = expression.map(|tar_expression| {
363                            Self::apply_expr_global_change(id, &expr, &tar_expression)
364                        });
365                        let condition = condition.map(|cond_expression| {
366                            Self::apply_expr_global_change(id, &expr, &cond_expression)
367                        });
368                        Some(SpawnSpec {
369                            expression,
370                            condition,
371                            annotated_pacing,
372                            id,
373                            span,
374                        })
375                    } else {
376                        None
377                    };
378
379                    let new_eval_spec = eval
380                        .into_iter()
381                        .flat_map(|eval_spec| {
382                            let EvalSpec {
383                                eval_expression,
384                                condition,
385                                annotated_pacing,
386                                id,
387                                span,
388                            } = eval_spec;
389                            let eval_expression = eval_expression
390                                .map(|e| Self::apply_expr_global_change(id, &expr, &e));
391                            let condition =
392                                condition.map(|e| Self::apply_expr_global_change(id, &expr, &e));
393                            Some(EvalSpec {
394                                annotated_pacing,
395                                condition,
396                                eval_expression,
397                                id,
398                                span,
399                            })
400                        })
401                        .collect();
402
403                    let new_close_spec = if let Some(close_spec) = close {
404                        let CloseSpec {
405                            condition,
406                            id,
407                            span,
408                            annotated_pacing,
409                        } = close_spec;
410                        let new_condition = Self::apply_expr_global_change(id, &expr, &condition);
411                        Some(CloseSpec {
412                            condition: new_condition,
413                            id,
414                            span,
415                            annotated_pacing,
416                        })
417                    } else {
418                        None
419                    };
420
421                    let new_out = Output {
422                        spawn: new_spawn_spec,
423                        eval: new_eval_spec,
424                        close: new_close_spec,
425                        ..out_clone
426                    };
427                    ast.outputs[ix] = Rc::new(new_out);
428                }
429            }
430        });
431
432        ast
433    }
434
435    fn apply_expr_global_change(
436        target_id: NodeId,
437        new_expr: &Expression,
438        ast_expr: &Expression,
439    ) -> Expression {
440        if ast_expr.id == target_id {
441            return new_expr.clone();
442        }
443        let span = ast_expr.span;
444
445        use ExpressionKind::*;
446        match &ast_expr.kind {
447            Lit(_) | Ident(_) | MissingExpression => ast_expr.clone(),
448            Unary(op, inner) => Expression {
449                kind: Unary(
450                    *op,
451                    Box::new(Self::apply_expr_global_change(target_id, new_expr, inner)),
452                ),
453                span,
454                ..*ast_expr
455            },
456            Field(inner, ident) => Expression {
457                kind: Field(
458                    Box::new(Self::apply_expr_global_change(target_id, new_expr, inner)),
459                    ident.clone(),
460                ),
461                span,
462                ..*ast_expr
463            },
464            StreamAccess(inner, acc_kind) => Expression {
465                kind: StreamAccess(
466                    Box::new(Self::apply_expr_global_change(target_id, new_expr, inner)),
467                    *acc_kind,
468                ),
469                span,
470                ..*ast_expr
471            },
472            Offset(inner, offset) => Expression {
473                kind: Offset(
474                    Box::new(Self::apply_expr_global_change(target_id, new_expr, inner)),
475                    *offset,
476                ),
477                span,
478                ..*ast_expr
479            },
480            ParenthesizedExpression(inner) => Expression {
481                kind: ParenthesizedExpression(Box::new(Self::apply_expr_global_change(
482                    target_id, new_expr, inner,
483                ))),
484                span,
485                ..*ast_expr
486            },
487            Binary(bin_op, left, right) => Expression {
488                kind: Binary(
489                    *bin_op,
490                    Box::new(Self::apply_expr_global_change(target_id, new_expr, left)),
491                    Box::new(Self::apply_expr_global_change(target_id, new_expr, right)),
492                ),
493                span,
494                ..*ast_expr
495            },
496            Default(left, right) => Expression {
497                kind: Default(
498                    Box::new(Self::apply_expr_global_change(target_id, new_expr, left)),
499                    Box::new(Self::apply_expr_global_change(target_id, new_expr, right)),
500                ),
501                span,
502                ..*ast_expr
503            },
504            DiscreteWindowAggregation {
505                expr: left,
506                duration: right,
507                wait,
508                aggregation,
509                ..
510            } => Expression {
511                kind: DiscreteWindowAggregation {
512                    expr: Box::new(Self::apply_expr_global_change(target_id, new_expr, left)),
513                    duration: Box::new(Self::apply_expr_global_change(target_id, new_expr, right)),
514                    wait: *wait,
515                    aggregation: *aggregation,
516                },
517                span,
518                ..*ast_expr
519            },
520            SlidingWindowAggregation {
521                expr: left,
522                duration: right,
523                wait,
524                aggregation,
525            } => Expression {
526                kind: SlidingWindowAggregation {
527                    expr: Box::new(Self::apply_expr_global_change(target_id, new_expr, left)),
528                    duration: Box::new(Self::apply_expr_global_change(target_id, new_expr, right)),
529                    wait: *wait,
530                    aggregation: *aggregation,
531                },
532                span,
533                ..*ast_expr
534            },
535            InstanceAggregation {
536                expr,
537                selection,
538                aggregation,
539            } => {
540                let selection = match selection {
541                    InstanceSelection::Fresh => InstanceSelection::Fresh,
542                    InstanceSelection::All => InstanceSelection::All,
543                    InstanceSelection::FilteredFresh(LambdaExpr {
544                        parameters,
545                        expr: cond,
546                    }) => InstanceSelection::FilteredFresh(LambdaExpr {
547                        parameters: parameters.clone(),
548                        expr: Box::new(Self::apply_expr_global_change(target_id, new_expr, cond)),
549                    }),
550                    InstanceSelection::FilteredAll(LambdaExpr {
551                        parameters,
552                        expr: cond,
553                    }) => InstanceSelection::FilteredAll(LambdaExpr {
554                        parameters: parameters.clone(),
555                        expr: Box::new(Self::apply_expr_global_change(target_id, new_expr, cond)),
556                    }),
557                };
558                Expression {
559                    kind: InstanceAggregation {
560                        expr: Box::new(Self::apply_expr_global_change(target_id, new_expr, expr)),
561                        selection,
562                        aggregation: *aggregation,
563                    },
564                    span,
565                    ..*ast_expr
566                }
567            }
568            Ite(condition, normal, alternative) => Expression {
569                kind: Ite(
570                    Box::new(Self::apply_expr_global_change(
571                        target_id, new_expr, condition,
572                    )),
573                    Box::new(Self::apply_expr_global_change(target_id, new_expr, normal)),
574                    Box::new(Self::apply_expr_global_change(
575                        target_id,
576                        new_expr,
577                        alternative,
578                    )),
579                ),
580                span,
581                ..*ast_expr
582            },
583            Tuple(entries) => Expression {
584                kind: Tuple(
585                    entries
586                        .iter()
587                        .map(|t_expr| Self::apply_expr_global_change(target_id, new_expr, t_expr))
588                        .collect(),
589                ),
590                span,
591                ..*ast_expr
592            },
593            Function(name, types, entries) => Expression {
594                kind: Function(
595                    name.clone(),
596                    types.clone(),
597                    entries
598                        .iter()
599                        .map(|t_expr| Self::apply_expr_global_change(target_id, new_expr, t_expr))
600                        .collect(),
601                ),
602                span,
603                ..*ast_expr
604            },
605            Method(base, name, types, arguments) => Expression {
606                kind: Method(
607                    Box::new(Self::apply_expr_global_change(target_id, new_expr, base)),
608                    name.clone(),
609                    types.clone(),
610                    arguments
611                        .iter()
612                        .map(|t_expr| Self::apply_expr_global_change(target_id, new_expr, t_expr))
613                        .collect(),
614                ),
615                span,
616                ..*ast_expr
617            },
618            Lambda(LambdaExpr {
619                parameters,
620                expr: cond,
621            }) => Expression {
622                kind: Lambda(LambdaExpr {
623                    parameters: parameters.clone(),
624                    expr: Box::new(Self::apply_expr_global_change(target_id, new_expr, cond)),
625                }),
626                ..*ast_expr
627            },
628        }
629    }
630
631    /// Climbs along the expression and applies desugarizers and their local changes, while collecting global changes.
632    ///
633    /// LocalChangeInstructions are directly replied and never returned.
634    #[allow(clippy::borrowed_box)]
635    fn desugarize_expression(
636        ast_expr: Expression,
637        ast: &RtLolaAst,
638        current_sugar: &Box<dyn SynSugar>,
639        stream: usize,
640        origin: ExprOrigin,
641    ) -> Result<(Expression, ChangeSet), RtLolaError> {
642        let mut return_cs = ChangeSet::empty();
643        use ExpressionKind::*;
644        let Expression { kind, id, span } = ast_expr;
645        let new_expr = match kind {
646            Lit(_) | Ident(_) | MissingExpression => Expression { kind, id, span },
647            Unary(op, inner) => {
648                let (inner, cs) =
649                    Self::desugarize_expression(*inner, ast, current_sugar, stream, origin)?;
650                return_cs += cs;
651                Expression {
652                    kind: Unary(op, Box::new(inner)),
653                    span,
654                    id,
655                }
656            }
657            Field(inner, ident) => {
658                let (inner, cs) =
659                    Self::desugarize_expression(*inner, ast, current_sugar, stream, origin)?;
660                return_cs += cs;
661                Expression {
662                    kind: Field(Box::new(inner), ident),
663                    span,
664                    id,
665                }
666            }
667            StreamAccess(inner, acc_kind) => {
668                let (inner, cs) =
669                    Self::desugarize_expression(*inner, ast, current_sugar, stream, origin)?;
670                return_cs += cs;
671                Expression {
672                    kind: StreamAccess(Box::new(inner), acc_kind),
673                    span,
674                    id,
675                }
676            }
677            Offset(inner, offset) => {
678                let (inner, cs) =
679                    Self::desugarize_expression(*inner, ast, current_sugar, stream, origin)?;
680                return_cs += cs;
681                Expression {
682                    kind: Offset(Box::new(inner), offset),
683                    span,
684                    id,
685                }
686            }
687            ParenthesizedExpression(inner) => {
688                let (inner, cs) =
689                    Self::desugarize_expression(*inner, ast, current_sugar, stream, origin)?;
690                return_cs += cs;
691                Expression {
692                    kind: ParenthesizedExpression(Box::new(inner)),
693                    span,
694                    id,
695                }
696            }
697            Binary(bin_op, left, right) => {
698                let (left, lcs) =
699                    Self::desugarize_expression(*left, ast, current_sugar, stream, origin)?;
700                return_cs += lcs;
701                let (right, rcs) =
702                    Self::desugarize_expression(*right, ast, current_sugar, stream, origin)?;
703                return_cs += rcs;
704                Expression {
705                    kind: Binary(bin_op, Box::new(left), Box::new(right)),
706                    span,
707                    id,
708                }
709            }
710            Default(left, right) => {
711                let (left, lcs) =
712                    Self::desugarize_expression(*left, ast, current_sugar, stream, origin)?;
713                return_cs += lcs;
714                let (right, rcs) =
715                    Self::desugarize_expression(*right, ast, current_sugar, stream, origin)?;
716                return_cs += rcs;
717                Expression {
718                    kind: Default(Box::new(left), Box::new(right)),
719                    span,
720                    id,
721                }
722            }
723            DiscreteWindowAggregation {
724                expr: left,
725                duration: right,
726                wait,
727                aggregation,
728                ..
729            } => {
730                let (expr, ecs) =
731                    Self::desugarize_expression(*left, ast, current_sugar, stream, origin)?;
732                return_cs += ecs;
733                let (dur, dcs) =
734                    Self::desugarize_expression(*right, ast, current_sugar, stream, origin)?;
735                return_cs += dcs;
736                Expression {
737                    kind: DiscreteWindowAggregation {
738                        expr: Box::new(expr),
739                        duration: Box::new(dur),
740                        wait,
741                        aggregation,
742                    },
743                    span,
744                    id,
745                }
746            }
747            SlidingWindowAggregation {
748                expr: left,
749                duration: right,
750                wait,
751                aggregation,
752            } => {
753                let (expr, ecs) =
754                    Self::desugarize_expression(*left, ast, current_sugar, stream, origin)?;
755                return_cs += ecs;
756                let (dur, dcs) =
757                    Self::desugarize_expression(*right, ast, current_sugar, stream, origin)?;
758                return_cs += dcs;
759                Expression {
760                    kind: SlidingWindowAggregation {
761                        expr: Box::new(expr),
762                        duration: Box::new(dur),
763                        wait,
764                        aggregation,
765                    },
766                    span,
767                    id,
768                }
769            }
770            InstanceAggregation {
771                expr,
772                selection,
773                aggregation,
774            } => {
775                let (expr, ecs) =
776                    Self::desugarize_expression(*expr, ast, current_sugar, stream, origin)?;
777                let (selection, scs) = match selection {
778                    selection @ (InstanceSelection::Fresh | InstanceSelection::All) => {
779                        (selection, ChangeSet::empty())
780                    }
781                    InstanceSelection::FilteredFresh(LambdaExpr {
782                        parameters,
783                        expr: cond,
784                    }) => {
785                        let (cond, cs) =
786                            Self::desugarize_expression(*cond, ast, current_sugar, stream, origin)?;
787                        (
788                            InstanceSelection::FilteredFresh(LambdaExpr {
789                                parameters,
790                                expr: Box::new(cond),
791                            }),
792                            cs,
793                        )
794                    }
795                    InstanceSelection::FilteredAll(LambdaExpr {
796                        parameters,
797                        expr: cond,
798                    }) => {
799                        let (cond, cs) =
800                            Self::desugarize_expression(*cond, ast, current_sugar, stream, origin)?;
801                        (
802                            InstanceSelection::FilteredAll(LambdaExpr {
803                                parameters,
804                                expr: Box::new(cond),
805                            }),
806                            cs,
807                        )
808                    }
809                };
810                return_cs = return_cs + ecs + scs;
811                Expression {
812                    kind: InstanceAggregation {
813                        expr: Box::new(expr),
814                        selection,
815                        aggregation,
816                    },
817                    span,
818                    id,
819                }
820            }
821            Ite(condition, normal, alternative) => {
822                let (condition, ccs) =
823                    Self::desugarize_expression(*condition, ast, current_sugar, stream, origin)?;
824                return_cs += ccs;
825                let (normal, ncs) =
826                    Self::desugarize_expression(*normal, ast, current_sugar, stream, origin)?;
827                return_cs += ncs;
828                let (alternative, acs) =
829                    Self::desugarize_expression(*alternative, ast, current_sugar, stream, origin)?;
830                return_cs += acs;
831                Expression {
832                    kind: Ite(Box::new(condition), Box::new(normal), Box::new(alternative)),
833                    span,
834                    id,
835                }
836            }
837            Tuple(entries) => {
838                let (v_expr, v_cs): (Vec<Expression>, Vec<ChangeSet>) = entries
839                    .into_iter()
840                    .map(|t_expr| {
841                        Self::desugarize_expression(t_expr, ast, current_sugar, stream, origin)
842                    })
843                    .collect::<Result<_, _>>()?;
844                return_cs += v_cs.into_iter().fold(ChangeSet::empty(), |acc, x| acc + x);
845                Expression {
846                    kind: Tuple(v_expr),
847                    span,
848                    id,
849                }
850            }
851            Function(name, types, entries) => {
852                let (v_expr, v_cs): (Vec<Expression>, Vec<ChangeSet>) = entries
853                    .into_iter()
854                    .map(|t_expr| {
855                        Self::desugarize_expression(t_expr, ast, current_sugar, stream, origin)
856                    })
857                    .collect::<Result<_, _>>()?;
858                return_cs += v_cs.into_iter().fold(ChangeSet::empty(), |acc, x| acc + x);
859                Expression {
860                    kind: Function(name, types, v_expr),
861                    span,
862                    id,
863                }
864            }
865            Method(base, name, types, arguments) => {
866                let (base_expr, ecs) =
867                    Self::desugarize_expression(*base, ast, current_sugar, stream, origin)?;
868                return_cs += ecs;
869                let (v_expr, v_cs): (Vec<Expression>, Vec<ChangeSet>) = arguments
870                    .into_iter()
871                    .map(|t_expr| {
872                        Self::desugarize_expression(t_expr, ast, current_sugar, stream, origin)
873                    })
874                    .collect::<Result<_, _>>()?;
875                return_cs += v_cs.into_iter().fold(ChangeSet::empty(), |acc, x| acc + x);
876                Expression {
877                    kind: Method(Box::new(base_expr), name, types, v_expr),
878                    span,
879                    id,
880                }
881            }
882            Lambda(LambdaExpr {
883                parameters,
884                expr: cond,
885            }) => {
886                let (cond, ecs) =
887                    Self::desugarize_expression(*cond, ast, current_sugar, stream, origin)?;
888                return_cs += ecs;
889                Expression {
890                    kind: Lambda(LambdaExpr {
891                        parameters,
892                        expr: Box::new(cond),
893                    }),
894                    span,
895                    id,
896                }
897            }
898        };
899
900        // apply transformation on current expression and replace if local change needed
901        let mut current_level_cs = current_sugar.desugarize_expr(&new_expr, ast, stream, origin)?;
902        let return_expr = if let Some(LocalChangeInstruction::ReplaceExpr(replace_expr)) =
903            current_level_cs.extract_local_change()
904        {
905            replace_expr
906        } else {
907            new_expr
908        };
909        let final_cs = current_level_cs + return_cs;
910
911        Ok((return_expr, final_cs))
912    }
913
914    #[allow(clippy::borrowed_box)]
915    fn desugarize_input(
916        &self,
917        input: &Input,
918        ast: &RtLolaAst,
919        current_sugar: &Box<dyn SynSugar>,
920    ) -> Result<ChangeSet, RtLolaError> {
921        current_sugar.desugarize_stream_in(input, ast)
922    }
923
924    #[allow(clippy::borrowed_box)]
925    fn desugarize_mirror(
926        &self,
927        mirror: &AstMirror,
928        ast: &RtLolaAst,
929        current_sugar: &Box<dyn SynSugar>,
930    ) -> Result<ChangeSet, RtLolaError> {
931        current_sugar.desugarize_stream_mirror(mirror, ast)
932    }
933
934    #[allow(clippy::borrowed_box)]
935    fn desugarize_output(
936        &self,
937        output: &Output,
938        ast: &RtLolaAst,
939        current_sugar: &Box<dyn SynSugar>,
940    ) -> Result<ChangeSet, RtLolaError> {
941        current_sugar.desugarize_stream_out(output, ast)
942    }
943}
944
945impl Default for Desugarizer {
946    fn default() -> Self {
947        Self::all()
948    }
949}
950
951impl ChangeSet {
952    /// Construct a new empty ChangeSet.
953    fn empty() -> Self {
954        Self {
955            _local_applied_flag: false,
956            local_instructions: None,
957            global_instructions: HashSet::new(),
958        }
959    }
960
961    #[allow(dead_code)] // currently unused
962    /// Adds a stream to the Ast.
963    pub(crate) fn add_output(stream: Output) -> Self {
964        let mut cs = ChangeSet::empty();
965        cs.global_instructions
966            .insert(ChangeInstruction::AddOutput(Box::new(stream)));
967        cs
968    }
969
970    #[allow(dead_code)] // currently unused
971    /// Replaces the expression with id 'target_id' with the given expression. Performs global ast search.
972    pub(crate) fn replace_expression(target_id: NodeId, expr: Expression) -> Self {
973        let mut cs = Self::empty();
974        cs.global_instructions
975            .insert(ChangeInstruction::ReplaceExpr(target_id, expr));
976        cs
977    }
978
979    #[allow(dead_code)] // currently unused
980    /// Removes the stream with id 'target_id'. Performs global ast search.
981    pub(crate) fn remove_stream(target_id: NodeId) -> Self {
982        let mut cs = Self::empty();
983        cs.global_instructions
984            .insert(ChangeInstruction::RemoveStream(target_id));
985        cs
986    }
987
988    /// Replaces the current expression.
989    /// Should only be called in <T: SynSugar> T.desugarize_expr(...)
990    /// Wanted local changes will not be applied if used in other desugarize functions!
991    pub(crate) fn replace_current_expression(expr: Expression) -> Self {
992        let mut cs = Self::empty();
993        cs.local_instructions = Some(LocalChangeInstruction::ReplaceExpr(expr));
994        cs._local_applied_flag = true;
995        cs
996    }
997
998    /// Replaces the stream with the given NodeId with the new stream. Performs global ast search.
999    pub(crate) fn replace_stream(target_stream: NodeId, new_stream: Output) -> Self {
1000        let mut cs = ChangeSet::empty();
1001        cs.global_instructions
1002            .insert(ChangeInstruction::RemoveStream(target_stream));
1003        cs.global_instructions
1004            .insert(ChangeInstruction::AddOutput(Box::new(new_stream)));
1005        cs
1006    }
1007
1008    /// Provides an [Iterator] over all global changes.
1009    fn global_iter<'a>(self) -> Box<dyn Iterator<Item = ChangeInstruction> + 'a> {
1010        Box::new(self.global_instructions.into_iter().sorted())
1011    }
1012
1013    /// Internal use: extracts the wanted local change and removes it from the struct.
1014    fn extract_local_change(&mut self) -> Option<LocalChangeInstruction> {
1015        let mut ret = None;
1016        std::mem::swap(&mut self.local_instructions, &mut ret);
1017        ret
1018    }
1019}
1020
1021impl std::ops::Add<ChangeSet> for ChangeSet {
1022    type Output = ChangeSet;
1023
1024    fn add(self, rhs: Self) -> Self {
1025        let set = self
1026            .global_instructions
1027            .union(&rhs.global_instructions)
1028            .cloned()
1029            .collect();
1030        assert!(self.local_instructions.is_none() || rhs.local_instructions.is_none());
1031        let local = self.local_instructions.or(rhs.local_instructions);
1032        Self {
1033            _local_applied_flag: self._local_applied_flag || rhs._local_applied_flag,
1034            local_instructions: local,
1035            global_instructions: set,
1036        }
1037    }
1038}
1039
1040impl std::ops::AddAssign<ChangeSet> for ChangeSet {
1041    fn add_assign(&mut self, rhs: Self) {
1042        let set = self
1043            .global_instructions
1044            .union(&rhs.global_instructions)
1045            .cloned()
1046            .collect();
1047        assert!(self.local_instructions.is_none() || rhs.local_instructions.is_none());
1048        let local = self.local_instructions.clone().or(rhs.local_instructions);
1049        *self = Self {
1050            _local_applied_flag: self._local_applied_flag || rhs._local_applied_flag,
1051            local_instructions: local,
1052            global_instructions: set,
1053        }
1054    }
1055}
1056
1057#[cfg(test)]
1058mod tests {
1059    use super::*;
1060    use crate::ast::{BinOp, Ident, LitKind, Literal, Offset, UnOp, WindowOperation};
1061
1062    #[test]
1063    fn test_impl_simpl_replace() {
1064        let spec = "input a:Bool\ninput b:Bool\noutput c eval with a -> b".to_string();
1065        let ast = crate::parse(&crate::ParserConfig::for_string(spec)).unwrap();
1066        let out_kind = ast.outputs[0].eval[0]
1067            .clone()
1068            .eval_expression
1069            .unwrap()
1070            .kind
1071            .clone();
1072        let inner_kind = if let ExpressionKind::Binary(op, lhs, _rhs) = out_kind {
1073            assert!(matches!(op, BinOp::Or));
1074            lhs.kind
1075        } else {
1076            unreachable!()
1077        };
1078        assert!(matches!(inner_kind, ExpressionKind::Unary(UnOp::Not, _)));
1079    }
1080
1081    #[test]
1082    fn test_impl_nested_replace() {
1083        let spec =
1084            "input a:Bool\ninput b:Bool\ninput c:Bool\noutput d eval with a -> b -> c".to_string();
1085        let ast = crate::parse(&crate::ParserConfig::for_string(spec)).unwrap();
1086        let out_kind = ast.outputs[0].eval[0]
1087            .clone()
1088            .eval_expression
1089            .unwrap()
1090            .kind
1091            .clone();
1092        let inner_kind = if let ExpressionKind::Binary(op, lhs, rhs) = out_kind {
1093            assert!(matches!(op, BinOp::Or));
1094            let inner = if let ExpressionKind::Unary(op, inner) = lhs.kind {
1095                assert!(matches!(op, UnOp::Not));
1096                inner
1097            } else {
1098                unreachable!()
1099            };
1100            assert_eq!(inner.to_string(), "a");
1101            rhs.kind
1102        } else {
1103            unreachable!();
1104        };
1105        let inner = if let ExpressionKind::Binary(op, lhs, rhs) = inner_kind {
1106            assert!(matches!(op, BinOp::Or));
1107            let inner = if let ExpressionKind::Unary(op, inner) = lhs.kind {
1108                assert!(matches!(op, UnOp::Not));
1109                inner
1110            } else {
1111                unreachable!()
1112            };
1113            assert_eq!(inner.to_string(), "b");
1114            rhs
1115        } else {
1116            unreachable!()
1117        };
1118        assert_eq!(inner.to_string(), "c");
1119    }
1120
1121    #[test]
1122    fn test_offsetor_replace() {
1123        let spec = "output x eval @5Hz with x.offset(by: -4, or: 5.0)".to_string();
1124        let ast = crate::parse(&crate::ParserConfig::for_string(spec)).unwrap();
1125        let out_kind = ast.outputs[0].eval[0]
1126            .clone()
1127            .eval_expression
1128            .unwrap()
1129            .kind
1130            .clone();
1131        let inner_kind = if let ExpressionKind::Default(inner, default) = out_kind {
1132            assert!(matches!(default.kind, ExpressionKind::Lit(_)));
1133            inner.kind
1134        } else {
1135            unreachable!()
1136        };
1137        assert!(matches!(
1138            inner_kind,
1139            ExpressionKind::Offset(_, Offset::Discrete(-4))
1140        ));
1141    }
1142
1143    #[test]
1144    fn test_offsetor_replace_nested() {
1145        let spec =
1146            "output x eval @5Hz with -x.offset(by: -4, or: x.offset(by: -1, or: 5))".to_string();
1147        let ast = crate::parse(&crate::ParserConfig::for_string(spec)).unwrap();
1148        let out_kind = ast.outputs[0].eval[0]
1149            .clone()
1150            .eval_expression
1151            .unwrap()
1152            .kind
1153            .clone();
1154        let inner_kind = if let ExpressionKind::Unary(UnOp::Neg, inner) = out_kind {
1155            inner.kind
1156        } else {
1157            unreachable!()
1158        };
1159        let (inner_kind, default_kind) = if let ExpressionKind::Default(inner, default) = inner_kind
1160        {
1161            (inner.kind, default.kind)
1162        } else {
1163            unreachable!()
1164        };
1165        assert!(matches!(
1166            inner_kind,
1167            ExpressionKind::Offset(_, Offset::Discrete(-4))
1168        ));
1169        let inner_kind = if let ExpressionKind::Default(inner, default) = default_kind {
1170            assert!(matches!(default.kind, ExpressionKind::Lit(_)));
1171            inner.kind
1172        } else {
1173            unreachable!()
1174        };
1175        assert!(matches!(
1176            inner_kind,
1177            ExpressionKind::Offset(_, Offset::Discrete(-1))
1178        ));
1179    }
1180
1181    #[test]
1182    fn test_aggr_replace() {
1183        let spec = "output x eval @5Hz with x.count(6s)".to_string();
1184        let ast = crate::parse(&crate::ParserConfig::for_string(spec)).unwrap();
1185        assert!(matches!(
1186            ast.outputs[0].eval[0].clone().eval_expression.unwrap().kind,
1187            ExpressionKind::SlidingWindowAggregation {
1188                aggregation: WindowOperation::Count,
1189                ..
1190            }
1191        ));
1192    }
1193
1194    #[test]
1195    fn test_aggr_replace_nested() {
1196        let spec = "output x eval @ 5hz with -x.sum(6s)".to_string();
1197        let ast = crate::parse(&crate::ParserConfig::for_string(spec)).unwrap();
1198        let out_kind = ast.outputs[0].eval[0]
1199            .clone()
1200            .eval_expression
1201            .unwrap()
1202            .kind
1203            .clone();
1204        assert!(matches!(out_kind, ExpressionKind::Unary(UnOp::Neg, _)));
1205        let inner_kind = if let ExpressionKind::Unary(UnOp::Neg, inner) = out_kind {
1206            inner.kind
1207        } else {
1208            unreachable!()
1209        };
1210        assert!(matches!(
1211            inner_kind,
1212            ExpressionKind::SlidingWindowAggregation {
1213                aggregation: WindowOperation::Sum,
1214                ..
1215            }
1216        ));
1217    }
1218
1219    #[test]
1220    fn test_aggr_replace_multiple() {
1221        let spec = "output x eval @5hz with x.avg(5s) - x.integral(2.5s)".to_string();
1222        let ast = crate::parse(&crate::ParserConfig::for_string(spec)).unwrap();
1223        let out_kind = ast.outputs[0].eval[0]
1224            .clone()
1225            .eval_expression
1226            .unwrap()
1227            .kind
1228            .clone();
1229        assert!(matches!(out_kind, ExpressionKind::Binary(BinOp::Sub, _, _)));
1230        let (left, right) = if let ExpressionKind::Binary(BinOp::Sub, left, right) = out_kind {
1231            (left.kind, right.kind)
1232        } else {
1233            unreachable!()
1234        };
1235        assert!(matches!(
1236            left,
1237            ExpressionKind::SlidingWindowAggregation {
1238                aggregation: WindowOperation::Average,
1239                ..
1240            }
1241        ));
1242        assert!(matches!(
1243            right,
1244            ExpressionKind::SlidingWindowAggregation {
1245                aggregation: WindowOperation::Integral,
1246                ..
1247            }
1248        ));
1249    }
1250
1251    #[test]
1252    fn test_last_replace() {
1253        let spec = "output x eval @5hz with x.last(or: 3)".to_string();
1254        let ast = crate::parse(&crate::ParserConfig::for_string(spec)).unwrap();
1255        let out_kind = ast.outputs[0].eval[0]
1256            .clone()
1257            .eval_expression
1258            .unwrap()
1259            .kind
1260            .clone();
1261        let (access, dft) = if let ExpressionKind::Default(access, dft) = out_kind {
1262            (access.kind, dft.kind)
1263        } else {
1264            panic!("Last should result in a top-level default access with its argument as default value")
1265        };
1266        assert!(
1267            matches!(
1268                &dft,
1269                &ExpressionKind::Lit(Literal {
1270                    kind: LitKind::Numeric(ref x, None),
1271                    ..
1272                }) if x == &String::from("3")
1273            ),
1274            "The argument of last should be the default expression."
1275        );
1276        let stream = if let ExpressionKind::Offset(stream, Offset::Discrete(-1)) = access {
1277            stream
1278        } else {
1279            panic!("expected an offset expression, but found {:?}", access);
1280        };
1281
1282        assert!(
1283            matches!(*stream, Expression { kind: ExpressionKind::Ident(Ident { name, .. }), ..} if name == String::from("x") )
1284        );
1285    }
1286
1287    #[test]
1288    fn test_delta_replace() {
1289        let spec = "output y eval with delta(x,dft:0)".to_string();
1290        let expected = "output y eval with x - x.offset(by: -1).defaults(to: 0)";
1291        let ast = crate::parse(&crate::ParserConfig::for_string(spec)).unwrap();
1292        assert_eq!(expected, format!("{}", ast).trim());
1293    }
1294
1295    #[test]
1296    fn test_delta_replace_float() {
1297        let spec = "output y eval with delta(x, or: 0.0)".to_string();
1298        let expected = "output y eval with x - x.offset(by: -1).defaults(to: 0.0)";
1299        let ast = crate::parse(&crate::ParserConfig::for_string(spec)).unwrap();
1300        assert_eq!(expected, format!("{}", ast).trim());
1301    }
1302
1303    #[test]
1304    fn test_mirror_replace_str_cmp() {
1305        let spec = "output x eval with 3 \noutput y mirrors x when x > 5".to_string();
1306        let expected = "output x eval with 3\noutput y eval when x > 5 with 3";
1307        let ast = crate::parse(&crate::ParserConfig::for_string(spec)).unwrap();
1308        assert_eq!(expected, format!("{}", ast).trim());
1309    }
1310
1311    #[test]
1312    fn test_mirror_replace_multiple_eval() {
1313        let spec = "output x eval when a > 0 with 3 eval when a < 0 with -3\noutput y mirrors x when x > 5".to_string();
1314        let expected = "output x eval when a > 0 with 3 eval when a < 0 with -3\noutput y eval when a > 0 ∧ x > 5 with 3 eval when a < 0 ∧ x > 5 with -3";
1315        let ast = crate::parse(&crate::ParserConfig::for_string(spec)).unwrap();
1316        assert_eq!(expected, format!("{}", ast).trim());
1317    }
1318
1319    #[test]
1320    fn test_true_ratio_aggregation() {
1321        let spec = "input a: Bool\n\
1322        output b eval @1Hz with a.aggregate(over: 1s, using: true_ratio)\n"
1323            .to_string();
1324        let expected = "input a: Bool\n\
1325            output b eval @1Hz with a'.aggregate(over: 1s, using: avg)\n\
1326            output a' eval with if a then 1.0 else 0.0";
1327        let ast = crate::parse(&crate::ParserConfig::for_string(spec)).unwrap();
1328        assert_eq!(expected, format!("{}", ast).trim());
1329    }
1330
1331    #[test]
1332    fn test_true_ratio_aggregation_from_output() {
1333        let spec = "input a: Bool\n\
1334        output b eval with a\n\
1335        output c eval @1Hz with b.aggregate(over: 1s, using: true_ratio)\n"
1336            .to_string();
1337        let expected = "input a: Bool\n\
1338            output b eval with a\n\
1339            output c eval @1Hz with b'.aggregate(over: 1s, using: avg)\n\
1340            output b' eval with if b then 1.0 else 0.0";
1341        let ast = crate::parse(&crate::ParserConfig::for_string(spec)).unwrap();
1342        assert_eq!(expected, format!("{}", ast).trim());
1343    }
1344
1345    #[test]
1346    fn test_true_ratio_parameterized() {
1347        let spec = "input a: Bool\n\
1348        output b (p) spawn with a eval @1Hz with a.aggregate(over: 1s, using: true_ratio)\n"
1349            .to_string();
1350        let expected = "input a: Bool\n\
1351            output b (p) spawn with a eval @1Hz with a'.aggregate(over: 1s, using: avg)\n\
1352            output a' eval with if a then 1.0 else 0.0";
1353        let ast = crate::parse(&crate::ParserConfig::for_string(spec)).unwrap();
1354        assert_eq!(expected, format!("{}", ast).trim());
1355    }
1356
1357    #[test]
1358    fn test_true_ratio_parameterized_2() {
1359        let spec = "input a: Bool\n\
1360        output b (p) spawn with a eval when a = p with a\n\
1361        output c (p) spawn with a eval @1Hz with b(p).aggregate(over: 1s, using: true_ratio)\n"
1362            .to_string();
1363        let expected = "input a: Bool\n\
1364            output b (p) spawn with a eval when a = p with a\n\
1365            output c (p) spawn with a eval @1Hz with b'(p).aggregate(over: 1s, using: avg)\n\
1366            output b' (p) spawn with a eval when a = p with if b(p) then 1.0 else 0.0";
1367        let ast = crate::parse(&crate::ParserConfig::for_string(spec)).unwrap();
1368        assert_eq!(expected, format!("{}", ast).trim());
1369    }
1370
1371    #[test]
1372    fn test_true_ratio_instances() {
1373        let spec = "input a: UInt8\n\
1374        output a' (p) spawn @a with a eval @a when a = p with a + p > 5\n\
1375        output b eval @1Hz with a'.aggregate(over_instances: all, using: true_ratio)\n"
1376            .to_string();
1377        let expected = "input a: UInt8\n\
1378            output a' (p) spawn @a with a eval @a when a = p with a + p > 5\n\
1379            output b eval @1Hz with a''.aggregate(over_instances: all, using: avg)\n\
1380            output a'' (p) spawn @a with a eval @a when a = p with if a'(p) then 1.0 else 0.0";
1381        let ast = crate::parse(&crate::ParserConfig::for_string(spec)).unwrap();
1382        assert_eq!(expected, format!("{}", ast).trim());
1383    }
1384
1385    #[test]
1386    fn test_true_ratio_name() {
1387        let spec = "input a: Boolean\n\
1388        input a': Boolean\n\
1389        input a'': Boolean\n\
1390        input a''': Boolean\n\
1391        output b eval @1Hz with a'.aggregate(over: 1s, using: true_ratio)"
1392            .to_string();
1393        let expected = "input a: Boolean\n\
1394        input a': Boolean\n\
1395        input a'': Boolean\n\
1396        input a''': Boolean\n\
1397        output b eval @1Hz with a''''.aggregate(over: 1s, using: avg)\n\
1398            output a'''' eval with if a' then 1.0 else 0.0";
1399        let ast = crate::parse(&crate::ParserConfig::for_string(spec)).unwrap();
1400        assert_eq!(expected, format!("{}", ast).trim());
1401    }
1402
1403    #[test]
1404    fn test_probability() {
1405        let spec = "input a : Bool\n\
1406        input b : Bool\n\
1407        output c := prob(of: a, given: b)"
1408            .to_string();
1409        let expected = "input a: Bool
1410input b: Bool
1411output c eval with if count_given' = 0.0 then 0.0 else count_both' / count_given'
1412output count_both' eval with count_both'.offset(by: -1).defaults(to: 0.0) + if a ∧ b then 1.0 else 0.0
1413output count_given' eval with count_given'.offset(by: -1).defaults(to: 0.0) + if b ∧ a = a then 1.0 else 0.0".to_string();
1414        let ast = crate::parse(&crate::ParserConfig::for_string(spec)).unwrap();
1415        assert_eq!(expected, format!("{}", ast).trim());
1416    }
1417
1418    #[test]
1419    fn test_probability2() {
1420        let spec = "input a : UInt64\n\
1421        input b : UInt64\n\
1422        output c := prob(of: a > 5, given: b < 10, prior: 0.5, confidence: 2.0)"
1423            .to_string();
1424        let expected = "input a: UInt64
1425input b: UInt64
1426output c eval with if (count_given' + 2.0) = 0.0 then 0.0 else (count_both' + 0.5 * 2.0) / (count_given' + 2.0)
1427output count_both' eval with count_both'.offset(by: -1).defaults(to: 0.0) + if a > 5 ∧ b < 10 then 1.0 else 0.0
1428output count_given' eval with count_given'.offset(by: -1).defaults(to: 0.0) + if b < 10 ∧ a > 5 = a > 5 then 1.0 else 0.0".to_string();
1429        let ast = crate::parse(&crate::ParserConfig::for_string(spec)).unwrap();
1430        assert_eq!(expected, format!("{}", ast).trim());
1431    }
1432
1433    #[test]
1434    fn test_probability3() {
1435        let spec = "input a : Bool\n\
1436        output c := prob(of: a)"
1437            .to_string();
1438        let expected = "input a: Bool
1439output c eval with if count_given' = 0.0 then 0.0 else count_both' / count_given'
1440output count_both' eval with count_both'.offset(by: -1).defaults(to: 0.0) + if a then 1.0 else 0.0
1441output count_given' eval with count_given'.offset(by: -1).defaults(to: 0.0) + if a = a then 1.0 else 0.0"
1442            .to_string();
1443        let ast = crate::parse(&crate::ParserConfig::for_string(spec)).unwrap();
1444        assert_eq!(expected, format!("{}", ast).trim());
1445    }
1446
1447    #[test]
1448    fn test_probability_spawn_close() {
1449        let spec = "input a : Bool\n\
1450        output c 
1451            spawn when a
1452            eval when a == 0 with prob(of: a)
1453            close when a"
1454            .to_string();
1455        let expected = "input a: Bool
1456output c spawn when a eval when a = 0 with if count_given' = 0.0 then 0.0 else count_both' / count_given' close when a
1457output count_both' spawn when a eval when a = 0 with count_both'.offset(by: -1).defaults(to: 0.0) + if a then 1.0 else 0.0 close when a
1458output count_given' spawn when a eval when a = 0 with count_given'.offset(by: -1).defaults(to: 0.0) + if a = a then 1.0 else 0.0 close when a"
1459            .to_string();
1460        let ast = crate::parse(&crate::ParserConfig::for_string(spec)).unwrap();
1461        assert_eq!(expected, format!("{}", ast).trim());
1462    }
1463
1464    #[test]
1465    fn test_probability_parameterized() {
1466        let spec = "input a: Bool\ninput i: UInt64
1467output c(p)
1468    spawn with i
1469    eval with prob(of: a, given: p == i)"
1470            .to_string();
1471        let expected = "input a: Bool
1472input i: UInt64
1473output c (p) spawn with i eval with if count_given'(p) = 0.0 then 0.0 else count_both'(p) / count_given'(p)
1474output count_both' (p) spawn with i eval with count_both'(p).offset(by: -1).defaults(to: 0.0) + if a ∧ p = i then 1.0 else 0.0
1475output count_given' (p) spawn with i eval with count_given'(p).offset(by: -1).defaults(to: 0.0) + if p = i ∧ a = a then 1.0 else 0.0"
1476            .to_string();
1477        let ast = crate::parse(&crate::ParserConfig::for_string(spec)).unwrap();
1478        assert_eq!(expected, format!("{}", ast).trim());
1479    }
1480
1481    #[test]
1482    fn probability_instance_aggregation_of() {
1483        let spec = "input a : UInt64
1484        output b(p)
1485            spawn with a
1486            eval when p == a with b(p).last(or: 0) + 1 
1487        output c := b.prob(of: p => p > 5)
1488        "
1489        .to_string();
1490        let expected = "input a: UInt64
1491output b (p) spawn with a eval when p = a with b(p).offset(by: -1).defaults(to: 0) + 1
1492output c eval with if b.aggregate(over_instances: all, using: #) = 0.0 then 0.0 else b.aggregate(over_instances: all(where: (p) => p > 5), using: #) / b.aggregate(over_instances: all, using: #)".to_string();
1493        let ast = crate::parse(&crate::ParserConfig::for_string(spec)).unwrap();
1494        assert_eq!(expected, format!("{}", ast).trim());
1495    }
1496
1497    #[test]
1498    fn probability_instance_aggregation_of_given() {
1499        let spec = "input a : UInt64
1500        output b(p)
1501            spawn with a
1502            eval when p == a with b(p).last(or: 0) + 1 
1503        output c := b.prob(of: p => p > 5, given: p => p < 10)
1504        "
1505        .to_string();
1506        let expected = "input a: UInt64
1507output b (p) spawn with a eval when p = a with b(p).offset(by: -1).defaults(to: 0) + 1
1508output c eval with if b.aggregate(over_instances: all(where: (p) => p < 10), using: #) = 0.0 then 0.0 else b.aggregate(over_instances: all(where: (p) => p > 5 ∧ p < 10), using: #) / b.aggregate(over_instances: all(where: (p) => p < 10), using: #)".to_string();
1509        let ast = crate::parse(&crate::ParserConfig::for_string(spec)).unwrap();
1510        assert_eq!(expected, format!("{}", ast).trim());
1511    }
1512
1513    #[test]
1514    fn probability_instance_aggregation_of_given_prior() {
1515        let spec = "input a : UInt64
1516        output b(p)
1517            spawn with a
1518            eval when p == a with b(p).last(or: 0) + 1 
1519        output c := b.prob(of: p => p > 5, given: p => p < 10, prior: 5.0, confidence: 10.0)
1520        "
1521        .to_string();
1522        let expected = "input a: UInt64
1523output b (p) spawn with a eval when p = a with b(p).offset(by: -1).defaults(to: 0) + 1
1524output c eval with if (b.aggregate(over_instances: all(where: (p) => p < 10), using: #) + 10.0) = 0.0 then 0.0 else (b.aggregate(over_instances: all(where: (p) => p > 5 ∧ p < 10), using: #) + 5.0 * 10.0) / (b.aggregate(over_instances: all(where: (p) => p < 10), using: #) + 10.0)".to_string();
1525        let ast = crate::parse(&crate::ParserConfig::for_string(spec)).unwrap();
1526        assert_eq!(expected, format!("{}", ast).trim());
1527    }
1528}