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#[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 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 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}