rtlola_frontend/mir/
print.rs

1use std::fmt::{Display, Formatter, Result};
2
3use itertools::Itertools;
4use rtlola_hir::hir::OutputKind;
5
6use super::{
7    FixedTy, FloatTy, InputStream, InstanceSelection, IntTy, Mir, OutputStream, PacingType,
8    Trigger, UIntTy, Window, WindowOperation,
9};
10use crate::mir::{
11    ActivationCondition, ArithLogOp, Constant, Expression, ExpressionKind, Offset,
12    StreamAccessKind, Type,
13};
14
15impl Display for Constant {
16    fn fmt(&self, f: &mut Formatter<'_>) -> Result {
17        match self {
18            Constant::Bool(b) => write!(f, "{b}"),
19            Constant::UInt(u) => write!(f, "{u}"),
20            Constant::Int(i) => write!(f, "{i}"),
21            Constant::Float(fl) => write!(f, "{fl:?}"),
22            Constant::Str(s) => write!(f, "\"{s}\""),
23            Constant::Decimal(i) => write!(f, "{i}"),
24        }
25    }
26}
27
28impl Display for ArithLogOp {
29    fn fmt(&self, f: &mut Formatter<'_>) -> Result {
30        use ArithLogOp::*;
31        match self {
32            Not => write!(f, "!"),
33            Neg => write!(f, "~"),
34            Add => write!(f, "+"),
35            Sub => write!(f, "-"),
36            Mul => write!(f, "*"),
37            Div => write!(f, "/"),
38            Rem => write!(f, "%"),
39            Pow => write!(f, "^"),
40            And => write!(f, "∧"),
41            Or => write!(f, "∨"),
42            Eq => write!(f, "="),
43            Lt => write!(f, "<"),
44            Le => write!(f, "≤"),
45            Ne => write!(f, "≠"),
46            Ge => write!(f, "≥"),
47            Gt => write!(f, ">"),
48            BitNot => write!(f, "~"),
49            BitAnd => write!(f, "&"),
50            BitOr => write!(f, "|"),
51            BitXor => write!(f, "^"),
52            Shl => write!(f, "<<"),
53            Shr => write!(f, ">>"),
54        }
55    }
56}
57
58impl Display for Type {
59    fn fmt(&self, f: &mut Formatter<'_>) -> Result {
60        match self {
61            Type::Float(_) => write!(f, "Float{}", self.size().expect("Floats are sized.").0 * 8),
62            Type::UInt(_) => write!(f, "UInt{}", self.size().expect("UInts are sized.").0 * 8),
63            Type::Int(_) => write!(f, "Int{}", self.size().expect("Ints are sized.").0 * 8),
64            Type::Fixed(ty) => write!(f, "Fixed{ty}"),
65            Type::UFixed(ty) => write!(f, "UFixed{ty}"),
66            Type::Function { args, ret } => {
67                write_delim_list(f, args, "(", &format!(") -> {ret}"), ",")
68            }
69            Type::Tuple(elems) => write_delim_list(f, elems, "(", ")", ","),
70            Type::String => write!(f, "String"),
71            Type::Bytes => write!(f, "Bytes"),
72            Type::Option(inner) => write!(f, "Option<{inner}>"),
73            Type::Bool => write!(f, "Bool"),
74        }
75    }
76}
77
78impl Display for IntTy {
79    fn fmt(&self, f: &mut Formatter<'_>) -> Result {
80        match self {
81            IntTy::Int8 => write!(f, "8"),
82            IntTy::Int16 => write!(f, "16"),
83            IntTy::Int32 => write!(f, "32"),
84            IntTy::Int64 => write!(f, "64"),
85            IntTy::Int128 => write!(f, "128"),
86            IntTy::Int256 => write!(f, "256"),
87        }
88    }
89}
90
91impl Display for UIntTy {
92    fn fmt(&self, f: &mut Formatter<'_>) -> Result {
93        match self {
94            UIntTy::UInt8 => write!(f, "8"),
95            UIntTy::UInt16 => write!(f, "16"),
96            UIntTy::UInt32 => write!(f, "32"),
97            UIntTy::UInt64 => write!(f, "64"),
98            UIntTy::UInt128 => write!(f, "128"),
99            UIntTy::UInt256 => write!(f, "256"),
100        }
101    }
102}
103
104impl Display for FloatTy {
105    fn fmt(&self, f: &mut Formatter<'_>) -> Result {
106        match self {
107            FloatTy::Float32 => write!(f, "32"),
108            FloatTy::Float64 => write!(f, "64"),
109        }
110    }
111}
112
113impl Display for FixedTy {
114    fn fmt(&self, f: &mut Formatter<'_>) -> Result {
115        match self {
116            FixedTy::Fixed64_32 => write!(f, "64_32"),
117            FixedTy::Fixed32_16 => write!(f, "32_16"),
118            FixedTy::Fixed16_8 => write!(f, "16_8"),
119        }
120    }
121}
122
123/// Writes out the joined vector `v`, enclosed by the given strings `pref` and `suff`.
124/// Uses the formatter.
125pub(crate) fn write_delim_list<T: Display>(
126    f: &mut Formatter<'_>,
127    v: &[T],
128    pref: &str,
129    suff: &str,
130    join: &str,
131) -> Result {
132    write!(f, "{pref}")?;
133    if let Some(e) = v.first() {
134        write!(f, "{e}")?;
135        for b in &v[1..] {
136            write!(f, "{join}{b}")?;
137        }
138    }
139    write!(f, "{suff}")?;
140    Ok(())
141}
142
143impl Display for Offset {
144    fn fmt(&self, f: &mut Formatter<'_>) -> Result {
145        match self {
146            Offset::Past(u) => write!(f, "{u}"),
147            _ => unimplemented!(),
148        }
149    }
150}
151
152impl Display for WindowOperation {
153    fn fmt(&self, f: &mut Formatter<'_>) -> Result {
154        write!(
155            f,
156            "{}",
157            match self {
158                WindowOperation::Count => "count",
159                WindowOperation::Min => "min",
160                WindowOperation::Max => "max",
161                WindowOperation::Sum => "sum",
162                WindowOperation::Product => "product",
163                WindowOperation::Average => "average",
164                WindowOperation::Integral => "integral",
165                WindowOperation::Conjunction => "conjunction",
166                WindowOperation::Disjunction => "disjunction",
167                WindowOperation::Last => "last",
168                WindowOperation::Variance => "variance",
169                WindowOperation::Covariance => "covariance",
170                WindowOperation::StandardDeviation => "standard deviation",
171                WindowOperation::NthPercentile(_) => todo!(),
172            }
173        )
174    }
175}
176
177/// A lightweight wrapper around the Mir to provide a [Display] implementation for Mir struct `T`.
178#[derive(Debug, Clone, Copy)]
179pub struct RtLolaMirPrinter<'a, T> {
180    mir: &'a Mir,
181    inner: &'a T,
182}
183
184impl<'a, T> RtLolaMirPrinter<'a, T> {
185    pub(crate) fn new(mir: &'a Mir, target: &'a T) -> Self {
186        RtLolaMirPrinter { mir, inner: target }
187    }
188}
189
190impl<T: Display> Display for RtLolaMirPrinter<'_, T> {
191    fn fmt(&self, f: &mut Formatter<'_>) -> Result {
192        self.inner.fmt(f)
193    }
194}
195
196impl Display for RtLolaMirPrinter<'_, ActivationCondition> {
197    fn fmt(&self, f: &mut Formatter<'_>) -> Result {
198        match self.inner {
199            ActivationCondition::Conjunction(s) => {
200                let rs = s
201                    .iter()
202                    .map(|ac| RtLolaMirPrinter::new(self.mir, ac).to_string())
203                    .join(&ArithLogOp::And.to_string());
204                write!(f, "{rs}")
205            }
206            ActivationCondition::Disjunction(s) => {
207                let rs = s
208                    .iter()
209                    .map(|ac| RtLolaMirPrinter::new(self.mir, ac).to_string())
210                    .join(&ArithLogOp::Or.to_string());
211                write!(f, "{rs}")
212            }
213            ActivationCondition::Stream(s) => write!(f, "{}", self.mir.stream(*s).name()),
214            ActivationCondition::True => write!(f, "true"),
215        }
216    }
217}
218
219impl Display for RtLolaMirPrinter<'_, PacingType> {
220    fn fmt(&self, f: &mut Formatter<'_>) -> Result {
221        match self.inner {
222            PacingType::GlobalPeriodic(freq) => {
223                let s = freq
224                    .into_format_args(
225                        uom::si::frequency::hertz,
226                        uom::fmt::DisplayStyle::Abbreviation,
227                    )
228                    .to_string();
229                write!(f, "Global({}Hz)", &s[..s.len() - 3])
230            }
231            PacingType::LocalPeriodic(freq) => {
232                let s = freq
233                    .into_format_args(
234                        uom::si::frequency::hertz,
235                        uom::fmt::DisplayStyle::Abbreviation,
236                    )
237                    .to_string();
238                write!(f, "Local({}Hz)", &s[..s.len() - 3])
239            }
240            PacingType::Event(ac) => RtLolaMirPrinter::new(self.mir, ac).fmt(f),
241            PacingType::Constant => write!(f, "true"),
242        }
243    }
244}
245
246type Associativity = bool;
247
248fn precedence_level(op: &ArithLogOp) -> (u32, Associativity) {
249    // https://en.cppreference.com/w/c/language/operator_precedence
250    let precedence = match op {
251        ArithLogOp::Not | ArithLogOp::BitNot | ArithLogOp::Neg => 2,
252
253        ArithLogOp::Mul | ArithLogOp::Rem | ArithLogOp::Pow | ArithLogOp::Div => 3,
254
255        ArithLogOp::Add | ArithLogOp::Sub => 4,
256
257        ArithLogOp::Shl | ArithLogOp::Shr => 5,
258
259        ArithLogOp::Lt | ArithLogOp::Le | ArithLogOp::Ge | ArithLogOp::Gt => 6,
260
261        ArithLogOp::Eq | ArithLogOp::Ne => 7,
262
263        ArithLogOp::BitAnd => 8,
264        ArithLogOp::BitXor => 9,
265        ArithLogOp::BitOr => 10,
266        ArithLogOp::And => 11,
267        ArithLogOp::Or => 12,
268    };
269
270    let associativity = !matches!(op, ArithLogOp::Div | ArithLogOp::Sub);
271
272    (precedence, associativity)
273}
274
275pub(crate) fn display_expression(mir: &Mir, expr: &Expression, current_level: u32) -> String {
276    match &expr.kind {
277        ExpressionKind::LoadConstant(c) => c.to_string(),
278        ExpressionKind::ArithLog(op, exprs) => {
279            let (op_level, associative) = precedence_level(op);
280            let display_exprs = exprs
281                .iter()
282                .map(|expr| display_expression(mir, expr, op_level))
283                .collect::<Vec<_>>();
284            let display = match display_exprs.len() {
285                1 => format!("{}{}", op, display_exprs[0]),
286                2 => format!("{} {} {}", display_exprs[0], op, display_exprs[1]),
287                _ => unreachable!(),
288            };
289            if (associative && current_level < op_level
290                || !associative && current_level <= op_level)
291                && current_level != 0
292            {
293                format!("({display})")
294            } else {
295                display
296            }
297        }
298        ExpressionKind::StreamAccess {
299            target,
300            parameters,
301            access_kind,
302        } => {
303            let stream_name = mir.stream(*target).name();
304            let target_name = if !parameters.is_empty() {
305                let parameter_list = parameters
306                    .iter()
307                    .map(|parameter| display_expression(mir, parameter, 0))
308                    .collect::<Vec<_>>()
309                    .join(", ");
310                format!("{stream_name}({parameter_list})")
311            } else {
312                stream_name.into()
313            };
314
315            match access_kind {
316                StreamAccessKind::Sync => target_name,
317                StreamAccessKind::DiscreteWindow(w) => {
318                    let window = mir.discrete_window(*w);
319                    let target_name = mir.stream(window.target).name();
320                    let duration = window.duration;
321                    let op = &window.op;
322                    format!("{target_name}.aggregate(over_discrete: {duration}, using: {op})")
323                }
324                StreamAccessKind::SlidingWindow(w) => {
325                    let window = mir.sliding_window(*w);
326                    let target_name = mir.stream(window.target).name();
327                    let duration = window.duration.as_secs_f64().to_string();
328                    let op = &window.op;
329                    format!("{target_name}.aggregate(over: {duration}s, using: {op})")
330                }
331                StreamAccessKind::InstanceAggregation(w) => {
332                    let window = mir.instance_aggregation(*w);
333                    let target_name = mir.stream(window.target).name();
334                    let duration = mir.display(&window.selection);
335                    let op = &window.op().to_string();
336                    format!("{target_name}.aggregate(over_instances: {duration}, using: {op})")
337                }
338                StreamAccessKind::Hold => format!("{target_name}.hold()"),
339                StreamAccessKind::Offset(o) => format!("{target_name}.offset(by:-{o})"),
340                StreamAccessKind::Get => format!("{target_name}.get()"),
341                StreamAccessKind::Fresh => format!("{target_name}.fresh()"),
342            }
343        }
344        ExpressionKind::ParameterAccess(sref, parameter) => {
345            mir.output(*sref).params[*parameter].name.to_string()
346        }
347        ExpressionKind::LambdaParameterAccess { wref, pref } => mir
348            .instance_aggregation(*wref)
349            .selection
350            .parameters()
351            .unwrap()
352            .iter()
353            .find(|p| p.idx == *pref)
354            .unwrap()
355            .name
356            .to_string(),
357        ExpressionKind::Ite {
358            condition,
359            consequence,
360            alternative,
361        } => {
362            let display_condition = display_expression(mir, condition, 0);
363            let display_consequence = display_expression(mir, consequence, 0);
364            let display_alternative = display_expression(mir, alternative, 0);
365            format!("if {display_condition} then {display_consequence} else {display_alternative}")
366        }
367        ExpressionKind::Tuple(exprs) => {
368            let display_exprs = exprs
369                .iter()
370                .map(|expr| display_expression(mir, expr, 0))
371                .collect::<Vec<_>>()
372                .join(", ");
373            format!("({display_exprs})")
374        }
375        ExpressionKind::TupleAccess(expr, i) => {
376            let display_expr = display_expression(mir, expr, 20);
377            format!("{display_expr}({i})")
378        }
379        ExpressionKind::Function(name, args) => {
380            let display_args = args
381                .iter()
382                .map(|arg| display_expression(mir, arg, 0))
383                .collect::<Vec<_>>()
384                .join(", ");
385            format!("{name}({display_args})")
386        }
387        ExpressionKind::Convert { expr: inner_expr } => {
388            let inner_display = display_expression(mir, inner_expr, 0);
389            format!("cast<{},{}>({inner_display})", inner_expr.ty, expr.ty)
390        }
391        ExpressionKind::Default { expr, default } => {
392            let display_expr = display_expression(mir, expr, 0);
393            let display_default = display_expression(mir, default, 0);
394            format!("{display_expr}.defaults(to: {display_default})")
395        }
396    }
397}
398
399impl Display for RtLolaMirPrinter<'_, InstanceSelection> {
400    fn fmt(&self, f: &mut Formatter<'_>) -> Result {
401        match &self.inner {
402            InstanceSelection::Fresh => write!(f, "fresh"),
403            InstanceSelection::All => write!(f, "all"),
404            InstanceSelection::FilteredFresh { parameters, cond } => {
405                let parameters = parameters
406                    .iter()
407                    .map(|p| format!("{}: {}", &p.name, p.ty))
408                    .join(", ");
409                let cond = display_expression(self.mir, cond, 0);
410                write!(f, "fresh(where: ({parameters}) => {cond})")
411            }
412            InstanceSelection::FilteredAll { parameters, cond } => {
413                let parameters = parameters
414                    .iter()
415                    .map(|p| format!("{}: {}", &p.name, p.ty))
416                    .join(", ");
417                let cond = display_expression(self.mir, cond, 0);
418                write!(f, "all(where: ({parameters}) => {cond})")
419            }
420        }
421    }
422}
423
424impl Display for RtLolaMirPrinter<'_, Expression> {
425    fn fmt(&self, f: &mut Formatter<'_>) -> Result {
426        write!(f, "{}", display_expression(self.mir, self.inner, 0))
427    }
428}
429
430impl Display for InputStream {
431    fn fmt(&self, f: &mut Formatter<'_>) -> Result {
432        let name = &self.name;
433        let ty = &self.ty;
434        write!(f, "input {name} : {ty}")
435    }
436}
437
438impl Display for RtLolaMirPrinter<'_, OutputStream> {
439    fn fmt(&self, f: &mut Formatter<'_>) -> Result {
440        let OutputStream {
441            name: _,
442            ty,
443            spawn,
444            eval,
445            close,
446            params,
447            kind,
448            ..
449        } = self.inner;
450
451        let display_parameters = if !params.is_empty() {
452            let parameter_list = params
453                .iter()
454                .map(|parameter| format!("{} : {}", parameter.name, parameter.ty))
455                .join(", ");
456            format!("({parameter_list})")
457        } else {
458            "".into()
459        };
460
461        match kind {
462            OutputKind::NamedOutput(name) => write!(f, "output {name}{display_parameters} : {ty}")?,
463            OutputKind::Trigger(_) => write!(f, "trigger{display_parameters}")?,
464        }
465
466        if spawn.expression.is_some()
467            || spawn.condition.is_some()
468            || spawn.pacing != PacingType::Constant
469        {
470            let display_pacing = RtLolaMirPrinter::new(self.mir, &spawn.pacing).to_string();
471            write!(f, "\n  spawn @{display_pacing}")?;
472            if let Some(spawn_expr) = &spawn.expression {
473                let display_spawn_expr = display_expression(self.mir, spawn_expr, 0);
474                write!(f, " with {display_spawn_expr}")?;
475            }
476            if let Some(spawn_condition) = &spawn.condition {
477                let display_spawn_condition = display_expression(self.mir, spawn_condition, 0);
478                write!(f, " when {display_spawn_condition}")?;
479            }
480        }
481
482        for clause in &eval.clauses {
483            let display_pacing = RtLolaMirPrinter::new(self.mir, &eval.eval_pacing).to_string();
484            write!(f, "\n  eval @{display_pacing} ")?;
485            if let Some(eval_condition) = &clause.condition {
486                let display_eval_condition = display_expression(self.mir, eval_condition, 0);
487                write!(f, "when {display_eval_condition} ")?;
488            }
489            let display_eval_expr = display_expression(self.mir, &clause.expression, 0);
490            write!(f, "with {display_eval_expr}")?;
491        }
492
493        if let Some(close_condition) = &close.condition {
494            let display_pacing = RtLolaMirPrinter::new(self.mir, &close.pacing).to_string();
495            let display_close_condition = display_expression(self.mir, close_condition, 0);
496            write!(
497                f,
498                "\n  close @{display_pacing} when {display_close_condition}"
499            )?;
500        }
501
502        Ok(())
503    }
504}
505
506impl Display for RtLolaMirPrinter<'_, Trigger> {
507    fn fmt(&self, f: &mut Formatter<'_>) -> Result {
508        let output = self.mir.output(self.inner.output_reference);
509        RtLolaMirPrinter::new(self.mir, output).fmt(f)
510    }
511}
512
513impl Display for Mir {
514    fn fmt(&self, f: &mut Formatter<'_>) -> Result {
515        self.inputs.iter().try_for_each(|input| {
516            RtLolaMirPrinter::new(self, input).fmt(f)?;
517            write!(f, "\n\n")
518        })?;
519
520        self.outputs.iter().try_for_each(|output| {
521            RtLolaMirPrinter::new(self, output).fmt(f)?;
522            write!(f, "\n\n")
523        })?;
524        Ok(())
525    }
526}
527
528#[cfg(test)]
529mod tests {
530    use rtlola_parser::ParserConfig;
531
532    use super::display_expression;
533    use crate::parse;
534
535    macro_rules! test_display_expression {
536        ( $( $name:ident: $test:expr => $expected:literal, )+) => {
537            $(
538            #[test]
539            fn $name() {
540                let spec = format!("input a : UInt64\noutput b@a := {}", $test);
541                let config = ParserConfig::for_string(spec);
542                let mir = parse(&config).expect("should parse");
543                let expr = &mir.outputs[0].eval.clauses.get(0).expect("only one clause").expression;
544                let display_expr = display_expression(&mir, expr, 0);
545                assert_eq!(display_expr, $expected);
546            }
547            )+
548        }
549    }
550
551    test_display_expression! {
552        constant:
553        "1" => "1",
554        add:
555        "1+2" => "1 + 2",
556        mul:
557        "1*2" => "1 * 2",
558        add_after_mul:
559        "1+2*3" => "1 + 2 * 3",
560        mul_after_add:
561        "1*(2+3)" => "1 * (2 + 3)",
562        comparison1:
563        "(1 > 2) && !false || (2 != 2)" => "1 > 2 ∧ !false ∨ 2 ≠ 2",
564        comparison2:
565        "(true == (1 > 2 || false))" => "true = (1 > 2 ∨ false)",
566        associativity:
567        "1 - (2 - 3)" => "1 - (2 - 3)",
568        associativity2:
569        "1 + (2 + 3)" => "1 + 2 + 3",
570        sync_access:
571        "a + 5" => "a + 5",
572        hold_access:
573        "a.hold().defaults(to: 0)" => "a.hold().defaults(to: 0)",
574        offset_access:
575        "a.offset(by:-2).defaults(to: 2+2)" => "a.offset(by:-2).defaults(to: 2 + 2)",
576        floats:
577        "1.0 + 1.5" => "1.0 + 1.5",
578    }
579
580    #[test]
581    fn test() {
582        let example = "input a : UInt64
583        input b : UInt64
584        output c@(a&&b) := a + b.hold().defaults(to:0)
585        output d@10Hz := b.aggregate(over: 2s, using: sum) + c.hold().defaults(to:0)
586        output e(x)
587            spawn with a when b == 0
588            eval when x == a with e(x).offset(by:-1).defaults(to:0) + 1
589            close when x == a && e(x) == 0
590        output f
591            eval @a when a == 0 with 0
592            eval @a when a > 5 && a <= 10 with 1
593            eval @a when a > 10 with 2
594        trigger c > 5 \"message\"
595        ";
596
597        let config = ParserConfig::for_string(example.into());
598        let mir = parse(&config).expect("should parse");
599        let config = ParserConfig::for_string(mir.to_string());
600        parse(&config).expect("should also parse");
601    }
602
603    #[test]
604    fn test_instance_aggregation() {
605        let spec = "input a: Int32\n\
606        input a2: Int32\n\
607        output b (p1, p2) \
608            spawn with (a, a + 1) \
609            eval with p1 + p2 + a\n\
610        output c (p1) \
611            spawn with a \
612            eval with b.aggregate(over_instances: fresh(where: (p1, p2) => p2 = a2), using: Σ)";
613        let config = ParserConfig::for_string(spec.into());
614        let mir = parse(&config).expect("should parse");
615        let config = ParserConfig::for_string(mir.to_string());
616        parse(&config).expect("should also parse");
617    }
618
619    #[test]
620    fn test_cast() {
621        let spec = "input a: Int32\n\
622        output b : UInt32 := cast<Int32,UInt32>(a)";
623        let config = ParserConfig::for_string(spec.into());
624        let mir = parse(&config).expect("should parse");
625        let config = ParserConfig::for_string(mir.to_string());
626        parse(&config).expect("should also parse");
627    }
628}