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
123pub(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#[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 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}