rtlola_streamir/ir/
print.rs

1use std::{collections::HashMap, fmt::Write, time::Duration};
2
3use itertools::Itertools;
4use rtlola_frontend::mir::InputReference;
5
6use crate::formatter::{
7    expressions::{
8        DefaultConstantFormatter, DefaultExprFormatter, DefaultFunctionFormatter,
9        DefaultOperatorFormatter, ExprFormatter,
10    },
11    guards::{DefaultGuardFormatter, GuardFormatter},
12    statements::{DefaultStmtFormatter, StmtFormatter},
13    types::TypeFormatter,
14    StreamIrFormatter,
15};
16
17use super::{
18    Expr, Guard, LocalFreq, LocalFreqRef, OutputReference, Stmt, StreamIr, StreamReference, Type,
19    WindowReference,
20};
21
22/// A [StreamIrFormatter] trait implementation to display the StreamIR in a nicely formatted string
23#[derive(Debug, Clone)]
24pub struct DebugFormatter {
25    sr2name: HashMap<StreamReference, String>,
26    sr2parameter: HashMap<StreamReference, Vec<String>>,
27    window_targets: HashMap<WindowReference, StreamReference>,
28    lref2lfreq: HashMap<LocalFreqRef, LocalFreq>,
29}
30
31impl DebugFormatter {
32    /// Creates a new debug formatter
33    pub fn new(ir: &StreamIr) -> Self {
34        let sr2name = ir
35            .sr2memory
36            .iter()
37            .map(|(sr, mem)| (*sr, mem.name.clone()))
38            .collect();
39        let sr2parameter = ir
40            .sr2memory
41            .iter()
42            .map(|(sr, mem)| {
43                (
44                    *sr,
45                    mem.parameters()
46                        .map(|p| p.iter().map(|p| p.name.clone()).collect())
47                        .unwrap_or_else(Vec::new),
48                )
49            })
50            .collect();
51        let window_targets = ir
52            .wref2window
53            .iter()
54            .map(|(wref, memory)| (*wref, memory.target))
55            .collect();
56        let lref2lfreq = ir.lref2lfreq.clone();
57        Self {
58            sr2name,
59            sr2parameter,
60            window_targets,
61            lref2lfreq,
62        }
63    }
64}
65
66impl StreamIrFormatter for DebugFormatter {
67    type Return = String;
68
69    fn id(&self) -> String {
70        "DebugPrinter".into()
71    }
72
73    fn format(self, ir: StreamIr) -> Self::Return {
74        StreamIrPrinter::new(
75            &self.sr2name,
76            &self.sr2parameter,
77            &self.window_targets,
78            &self.lref2lfreq,
79        )
80        .stmt(ir.stmt)
81    }
82}
83
84struct StreamIrPrinter<'a> {
85    whitespace_counter: usize,
86    sr2name: &'a HashMap<StreamReference, String>,
87    sr2parameter: &'a HashMap<StreamReference, Vec<String>>,
88    window_targets: &'a HashMap<WindowReference, StreamReference>,
89    lref2lfreq: &'a HashMap<LocalFreqRef, LocalFreq>,
90}
91
92impl<'a> StreamIrPrinter<'a> {
93    fn new(
94        sr2name: &'a HashMap<StreamReference, String>,
95        sr2parameter: &'a HashMap<StreamReference, Vec<String>>,
96        window_targets: &'a HashMap<WindowReference, StreamReference>,
97        lref2lfreq: &'a HashMap<LocalFreqRef, LocalFreq>,
98    ) -> Self {
99        Self {
100            whitespace_counter: 0,
101            sr2name,
102            sr2parameter,
103            window_targets,
104            lref2lfreq,
105        }
106    }
107
108    fn indent(&self) -> Self {
109        Self {
110            whitespace_counter: self.whitespace_counter + 1,
111            sr2name: self.sr2name,
112            sr2parameter: self.sr2parameter,
113            window_targets: self.window_targets,
114            lref2lfreq: self.lref2lfreq,
115        }
116    }
117
118    fn name(&self, sr: StreamReference) -> &str {
119        &self.sr2name[&sr]
120    }
121
122    fn whitespace(&self, s: &str) -> String {
123        let ws = (0..self.whitespace_counter * 4)
124            .map(|_| " ")
125            .collect::<String>();
126        format!("{ws}{}", s)
127    }
128
129    fn parameter(&self, sr: StreamReference) -> String {
130        self.sr2parameter[&sr].iter().join(" ,")
131    }
132
133    fn stream_access(&self, sr: StreamReference, parameters: Vec<Expr>) -> String {
134        format!(
135            "{}({})",
136            self.name(sr),
137            parameters.into_iter().map(|p| self.expr(p)).join(",")
138        )
139    }
140}
141
142impl DefaultStmtFormatter for StreamIrPrinter<'_> {
143    fn skip(&self) -> String {
144        self.whitespace("skip")
145    }
146
147    fn shift(&self, sr: StreamReference) -> String {
148        self.whitespace(&format!("shift {}", self.name(sr)))
149    }
150
151    fn input(&self, sr: InputReference) -> String {
152        self.whitespace(&format!("input {}", self.name(StreamReference::In(sr))))
153    }
154
155    fn spawn(
156        &self,
157        sr: OutputReference,
158        with: Option<Vec<Expr>>,
159        _local_frequencies: Vec<LocalFreqRef>,
160        _windows: Vec<WindowReference>,
161    ) -> String {
162        let with = with
163            .map(|with| {
164                format!(
165                    "with ({})",
166                    with.into_iter().map(|with| self.expr(with)).join(",")
167                )
168            })
169            .unwrap_or_default();
170        self.whitespace(&format!("spawn {} {with}", self.name(sr.sr())))
171    }
172
173    fn eval(&self, sr: OutputReference, with: Expr, idx: usize) -> String {
174        self.whitespace(&format!(
175            "eval_{idx} {} with {}",
176            self.name(sr.sr()),
177            self.expr(with)
178        ))
179    }
180
181    fn close(
182        &self,
183        sr: OutputReference,
184        _local_frequencies: Vec<LocalFreqRef>,
185        _windows: Vec<WindowReference>,
186    ) -> String {
187        self.whitespace(&format!("close {}", self.name(sr.sr())))
188    }
189
190    fn r#if(&self, guard: Guard, cons: Stmt, alt: Option<Stmt>) -> String {
191        let inner_formater = self.indent();
192        let guard = self.guard(guard);
193        let cons = inner_formater.stmt(cons);
194        let alt = alt.map(|alt| inner_formater.stmt(alt));
195        let cons = self.whitespace(&format!("if {guard} then\n{cons}"));
196        if let Some(alt) = alt {
197            cons + &self.whitespace(&format!("\nelse\n{alt}"))
198        } else {
199            cons
200        }
201    }
202
203    fn iterate(&self, srs: Vec<OutputReference>, inner: Stmt) -> String {
204        let sr = srs[0].sr();
205        let inner_formatter = self.indent();
206        let inner = inner_formatter.stmt(inner);
207        let parameter = self.parameter(sr);
208        let names = srs.into_iter().map(|s| self.name(s.sr())).join(", ");
209        self.whitespace(&format!("({parameter}) <- iterate {names}\n{inner}",))
210    }
211
212    fn assign(&self, sr: Vec<OutputReference>, parameter_expr: Vec<Expr>, inner: Stmt) -> String {
213        let sr = sr[0].sr();
214        let inner_formatter = self.indent();
215        let inner = &inner_formatter.stmt(inner);
216        let parameter = self.parameter(sr);
217        let parameter_expr = parameter_expr.into_iter().map(|p| self.expr(p)).join(",");
218        self.whitespace(&format!(
219            "({parameter}) <- assign {parameter_expr}\n{inner}",
220        ))
221    }
222
223    fn seq(&self, inner: Vec<Stmt>) -> String {
224        let inner_strings = inner
225            .into_iter()
226            .map(|stmt| self.stmt(stmt))
227            .collect::<Vec<_>>();
228        let longest_line = inner_strings
229            .iter()
230            .map(|s| s.lines().map(|line| line.len()).max().unwrap())
231            .max()
232            .unwrap();
233        let leading_whitespace = inner_strings
234            .iter()
235            .map(|s| s.chars().take_while(|c| c.is_whitespace()).count())
236            .min()
237            .unwrap();
238        let separator =
239            " ".repeat(leading_whitespace) + &"-".repeat(longest_line - leading_whitespace);
240        let separator = format!("\n{separator}\n");
241        inner_strings.join(&separator)
242    }
243
244    fn parallel(&self, inner: Vec<Stmt>) -> String {
245        let inner_strings = inner
246            .into_iter()
247            .map(|stmt| self.stmt(stmt))
248            .collect::<Vec<_>>();
249        let leading_whitespace = inner_strings
250            .iter()
251            .map(|s| {
252                s.lines()
253                    .map(|l| l.chars().take_while(|c| c.is_whitespace()).count())
254                    .min()
255                    .unwrap()
256            })
257            .collect::<Vec<_>>();
258        let mut inner_lines = inner_strings
259            .iter()
260            .map(|stmt| stmt.lines())
261            .collect::<Vec<_>>();
262        let longest_line_per_stmt = inner_strings
263            .iter()
264            .map(|s| s.lines().map(|line| line.len()).max().unwrap())
265            .collect::<Vec<_>>();
266
267        let mut res = String::new();
268        for i in 0.. {
269            let current_lines = inner_lines
270                .iter_mut()
271                .map(|lines| lines.next())
272                .collect::<Vec<_>>();
273            if current_lines.iter().all(|line| line.is_none()) {
274                break;
275            }
276
277            if i != 0 {
278                writeln!(res).unwrap();
279            }
280
281            for (i, line) in current_lines.into_iter().enumerate() {
282                write!(
283                    &mut res,
284                    "{:width$}",
285                    if i == 0 {
286                        line.unwrap_or("")
287                    } else {
288                        line.map(|l| &l[leading_whitespace[i]..]).unwrap_or("")
289                    },
290                    width = if i == 0 {
291                        longest_line_per_stmt[i]
292                    } else {
293                        longest_line_per_stmt[i] - leading_whitespace[i]
294                    }
295                )
296                .expect("can't fail with strings");
297                if i != inner_lines.len() - 1 {
298                    write!(&mut res, " | ").unwrap();
299                }
300            }
301        }
302
303        res
304    }
305}
306
307impl DefaultGuardFormatter for StreamIrPrinter<'_> {
308    fn stream(&self, sr: StreamReference) -> String {
309        format!("@{}", self.name(sr))
310    }
311
312    fn alive(&self, sr: StreamReference) -> String {
313        format!("Alive({})", self.name(sr))
314    }
315
316    fn dynamic(&self, expr: Expr) -> String {
317        format!("( {} )", self.expr(expr))
318    }
319
320    fn global_freq(&self, duration: Duration) -> String {
321        format!("@Global({}s)", duration.as_secs_f64())
322    }
323
324    fn local_freq(&self, lref: LocalFreqRef) -> String {
325        let LocalFreq {
326            dur,
327            sr,
328            reference: _,
329        } = self.lref2lfreq[&lref];
330        format!("@Local({}s, {})", dur.as_secs_f64(), self.name(sr.sr()))
331    }
332
333    fn constant(&self, b: bool) -> String {
334        if b {
335            "⊤".into()
336        } else {
337            "⊥".into()
338        }
339    }
340
341    fn fast_and(&self, inner: Vec<StreamReference>) -> String {
342        let s = inner.into_iter().map(|s| self.name(s)).join("&&");
343        format!("@({s})")
344    }
345
346    fn fast_or(&self, inner: Vec<StreamReference>) -> String {
347        let s = inner.into_iter().map(|s| self.name(s)).join("||");
348        format!("@({s})")
349    }
350}
351
352impl DefaultExprFormatter for StreamIrPrinter<'_> {
353    fn sync_access(&self, sr: StreamReference, parameters: Vec<Expr>) -> String {
354        self.stream_access(sr, parameters)
355    }
356
357    fn offset_access(
358        &self,
359        sr: StreamReference,
360        offset: u32,
361        default: Expr,
362        parameters: Vec<Expr>,
363    ) -> String {
364        let sr = self.stream_access(sr, parameters);
365        let dft = self.expr(default);
366        format!("{sr}.offset(by: -{offset}).defaults(to: {dft})")
367    }
368
369    fn hold_access(&self, sr: StreamReference, default: Expr, parameters: Vec<Expr>) -> String {
370        let sr = self.stream_access(sr, parameters);
371        let dft = self.expr(default);
372        format!("{sr}.hold().defaults(to: {dft})")
373    }
374
375    fn get_access(&self, sr: StreamReference, default: Expr, parameters: Vec<Expr>) -> String {
376        let sr = self.stream_access(sr, parameters);
377        let dft = self.expr(default);
378        format!("{sr}.get().defaults(to: {dft})")
379    }
380
381    fn is_fresh(&self, sr: StreamReference, parameters: Vec<Expr>) -> String {
382        let sr = self.stream_access(sr, parameters);
383        format!("{sr}.is_fresh()")
384    }
385
386    fn sliding_window_access(&self, window_idx: usize, default: Option<Expr>) -> String {
387        let target_name = self.name(self.window_targets[&WindowReference::Sliding(window_idx)]);
388        let c = format!("{target_name}.aggregate_sliding({window_idx})");
389        if let Some(default) = default {
390            format!("{c}.defaults(to: {})", self.expr(default))
391        } else {
392            c
393        }
394    }
395
396    fn discrete_window_access(&self, window_idx: usize, default: Option<Expr>) -> String {
397        let target_name = self.name(self.window_targets[&WindowReference::Discrete(window_idx)]);
398        let c = format!("{target_name}.aggregate_discrete({window_idx})");
399        if let Some(default) = default {
400            format!("{c}.defaults(to: {})", self.expr(default))
401        } else {
402            c
403        }
404    }
405
406    fn instance_aggregation(&self, window_idx: usize, default: Option<Expr>) -> String {
407        let target_name = self.name(self.window_targets[&WindowReference::Instance(window_idx)]);
408        let c = format!("{target_name}.aggregate_instances({window_idx})");
409        if let Some(default) = default {
410            format!("{c}.defaults(to: {})", self.expr(default))
411        } else {
412            c
413        }
414    }
415
416    fn parameter_access(&self, sr: StreamReference, p: usize) -> String {
417        self.sr2parameter[&sr][p].clone()
418    }
419
420    fn lambda_parameter_access(&self, _wref: WindowReference, _idx: usize) -> String {
421        unreachable!("is never printed as we only print window references")
422    }
423
424    fn cast(&self, ty: Type, expr: Expr) -> String {
425        format!(
426            "cast<{},{}>({})",
427            self.ty(expr.ty.clone()),
428            self.ty(ty),
429            self.expr(expr)
430        )
431    }
432}
433
434impl DefaultFunctionFormatter for StreamIrPrinter<'_> {}
435
436impl DefaultOperatorFormatter for StreamIrPrinter<'_> {}
437
438impl DefaultConstantFormatter for StreamIrPrinter<'_> {}
439
440impl TypeFormatter for StreamIrPrinter<'_> {
441    type Return = String;
442
443    fn type_int(&self, bits: u16) -> Self::Return {
444        format!("Int{bits}")
445    }
446
447    fn type_uint(&self, bits: u16) -> Self::Return {
448        format!("UInt{bits}")
449    }
450
451    fn type_bool(&self) -> Self::Return {
452        "Bool".into()
453    }
454
455    fn type_string(&self) -> Self::Return {
456        "String".into()
457    }
458
459    fn type_float32(&self) -> Self::Return {
460        "Float32".into()
461    }
462
463    fn type_float64(&self) -> Self::Return {
464        "Float64".into()
465    }
466
467    fn type_option(&self, inner: Type) -> Self::Return {
468        format!("{}?", self.ty(inner))
469    }
470
471    fn type_tuple(&self, inner: Vec<Type>) -> Self::Return {
472        let inners = inner.into_iter().map(|inner| self.ty(inner)).join(", ");
473        format!("({inners})")
474    }
475
476    fn type_fixed(&self, bits: u16) -> Self::Return {
477        format!("Fixed{bits}")
478    }
479
480    fn type_ufixed(&self, bits: u16) -> Self::Return {
481        format!("UFixed{bits}")
482    }
483
484    fn type_bytes(&self) -> Self::Return {
485        "Bytes".into()
486    }
487}
488
489impl StreamIr {
490    /// Display the StreamIR using a debugging formatter
491    pub fn display(self) -> String {
492        DebugFormatter::new(&self).format(self)
493    }
494}
495
496#[cfg(test)]
497mod tests {
498    use rtlola_frontend::{parse, ParserConfig};
499
500    use crate::ir::StreamIr;
501
502    #[test]
503    fn test() {
504        let spec = "input a : UInt64
505        input c: UInt64
506        output b(p)
507            spawn with a eval when p == a with b(p).last(or: 0) + 1";
508        let mir = parse(&ParserConfig::for_string(spec.into())).unwrap();
509        let streamir: StreamIr = mir.try_into().unwrap();
510        println!("{}", streamir.display());
511    }
512}