nu_protocol/ir/
display.rs

1use super::{DataSlice, Instruction, IrBlock, Literal, RedirectMode};
2use crate::{DeclId, VarId, ast::Pattern, engine::EngineState};
3use std::fmt::{self};
4
5pub struct FmtIrBlock<'a> {
6    pub(super) engine_state: &'a EngineState,
7    pub(super) ir_block: &'a IrBlock,
8}
9
10impl fmt::Display for FmtIrBlock<'_> {
11    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
12        let plural = |count| if count == 1 { "" } else { "s" };
13        writeln!(
14            f,
15            "# {} register{}, {} instruction{}, {} byte{} of data",
16            self.ir_block.register_count,
17            plural(self.ir_block.register_count as usize),
18            self.ir_block.instructions.len(),
19            plural(self.ir_block.instructions.len()),
20            self.ir_block.data.len(),
21            plural(self.ir_block.data.len()),
22        )?;
23        if self.ir_block.file_count > 0 {
24            writeln!(
25                f,
26                "# {} file{} used for redirection",
27                self.ir_block.file_count,
28                plural(self.ir_block.file_count as usize)
29            )?;
30        }
31        for (index, instruction) in self.ir_block.instructions.iter().enumerate() {
32            let formatted = format!(
33                "{:-4}: {}",
34                index,
35                FmtInstruction {
36                    engine_state: self.engine_state,
37                    instruction,
38                    data: &self.ir_block.data,
39                }
40            );
41            let comment = &self.ir_block.comments[index];
42            if comment.is_empty() {
43                writeln!(f, "{formatted}")?;
44            } else {
45                writeln!(f, "{formatted:40} # {comment}")?;
46            }
47        }
48        Ok(())
49    }
50}
51
52pub struct FmtInstruction<'a> {
53    pub(super) engine_state: &'a EngineState,
54    pub(super) instruction: &'a Instruction,
55    pub(super) data: &'a [u8],
56}
57
58impl fmt::Display for FmtInstruction<'_> {
59    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
60        const WIDTH: usize = 22;
61
62        match self.instruction {
63            Instruction::Unreachable => {
64                write!(f, "{:WIDTH$}", "unreachable")
65            }
66            Instruction::LoadLiteral { dst, lit } => {
67                let lit = FmtLiteral {
68                    literal: lit,
69                    data: self.data,
70                };
71                write!(f, "{:WIDTH$} {dst}, {lit}", "load-literal")
72            }
73            Instruction::LoadValue { dst, val } => {
74                let val = val.to_debug_string();
75                write!(f, "{:WIDTH$} {dst}, {val}", "load-value")
76            }
77            Instruction::Move { dst, src } => {
78                write!(f, "{:WIDTH$} {dst}, {src}", "move")
79            }
80            Instruction::Clone { dst, src } => {
81                write!(f, "{:WIDTH$} {dst}, {src}", "clone")
82            }
83            Instruction::Collect { src_dst } => {
84                write!(f, "{:WIDTH$} {src_dst}", "collect")
85            }
86            Instruction::Span { src_dst } => {
87                write!(f, "{:WIDTH$} {src_dst}", "span")
88            }
89            Instruction::Drop { src } => {
90                write!(f, "{:WIDTH$} {src}", "drop")
91            }
92            Instruction::Drain { src } => {
93                write!(f, "{:WIDTH$} {src}", "drain")
94            }
95            Instruction::DrainIfEnd { src } => {
96                write!(f, "{:WIDTH$} {src}", "drain-if-end")
97            }
98            Instruction::LoadVariable { dst, var_id } => {
99                let var = FmtVar::new(self.engine_state, *var_id);
100                write!(f, "{:WIDTH$} {dst}, {var}", "load-variable")
101            }
102            Instruction::StoreVariable { var_id, src } => {
103                let var = FmtVar::new(self.engine_state, *var_id);
104                write!(f, "{:WIDTH$} {var}, {src}", "store-variable")
105            }
106            Instruction::DropVariable { var_id } => {
107                let var = FmtVar::new(self.engine_state, *var_id);
108                write!(f, "{:WIDTH$} {var}", "drop-variable")
109            }
110            Instruction::LoadEnv { dst, key } => {
111                let key = FmtData(self.data, *key);
112                write!(f, "{:WIDTH$} {dst}, {key}", "load-env")
113            }
114            Instruction::LoadEnvOpt { dst, key } => {
115                let key = FmtData(self.data, *key);
116                write!(f, "{:WIDTH$} {dst}, {key}", "load-env-opt")
117            }
118            Instruction::StoreEnv { key, src } => {
119                let key = FmtData(self.data, *key);
120                write!(f, "{:WIDTH$} {key}, {src}", "store-env")
121            }
122            Instruction::PushPositional { src } => {
123                write!(f, "{:WIDTH$} {src}", "push-positional")
124            }
125            Instruction::AppendRest { src } => {
126                write!(f, "{:WIDTH$} {src}", "append-rest")
127            }
128            Instruction::PushFlag { name } => {
129                let name = FmtData(self.data, *name);
130                write!(f, "{:WIDTH$} {name}", "push-flag")
131            }
132            Instruction::PushShortFlag { short } => {
133                let short = FmtData(self.data, *short);
134                write!(f, "{:WIDTH$} {short}", "push-short-flag")
135            }
136            Instruction::PushNamed { name, src } => {
137                let name = FmtData(self.data, *name);
138                write!(f, "{:WIDTH$} {name}, {src}", "push-named")
139            }
140            Instruction::PushShortNamed { short, src } => {
141                let short = FmtData(self.data, *short);
142                write!(f, "{:WIDTH$} {short}, {src}", "push-short-named")
143            }
144            Instruction::PushParserInfo { name, info } => {
145                let name = FmtData(self.data, *name);
146                write!(f, "{:WIDTH$} {name}, {info:?}", "push-parser-info")
147            }
148            Instruction::RedirectOut { mode } => {
149                write!(f, "{:WIDTH$} {mode}", "redirect-out")
150            }
151            Instruction::RedirectErr { mode } => {
152                write!(f, "{:WIDTH$} {mode}", "redirect-err")
153            }
154            Instruction::CheckErrRedirected { src } => {
155                write!(f, "{:WIDTH$} {src}", "check-err-redirected")
156            }
157            Instruction::OpenFile {
158                file_num,
159                path,
160                append,
161            } => {
162                write!(
163                    f,
164                    "{:WIDTH$} file({file_num}), {path}, append = {append:?}",
165                    "open-file"
166                )
167            }
168            Instruction::WriteFile { file_num, src } => {
169                write!(f, "{:WIDTH$} file({file_num}), {src}", "write-file")
170            }
171            Instruction::CloseFile { file_num } => {
172                write!(f, "{:WIDTH$} file({file_num})", "close-file")
173            }
174            Instruction::Call { decl_id, src_dst } => {
175                let decl = FmtDecl::new(self.engine_state, *decl_id);
176                write!(f, "{:WIDTH$} {decl}, {src_dst}", "call")
177            }
178            Instruction::StringAppend { src_dst, val } => {
179                write!(f, "{:WIDTH$} {src_dst}, {val}", "string-append")
180            }
181            Instruction::GlobFrom { src_dst, no_expand } => {
182                let no_expand = if *no_expand { "no-expand" } else { "expand" };
183                write!(f, "{:WIDTH$} {src_dst}, {no_expand}", "glob-from",)
184            }
185            Instruction::ListPush { src_dst, item } => {
186                write!(f, "{:WIDTH$} {src_dst}, {item}", "list-push")
187            }
188            Instruction::ListSpread { src_dst, items } => {
189                write!(f, "{:WIDTH$} {src_dst}, {items}", "list-spread")
190            }
191            Instruction::RecordInsert { src_dst, key, val } => {
192                write!(f, "{:WIDTH$} {src_dst}, {key}, {val}", "record-insert")
193            }
194            Instruction::RecordSpread { src_dst, items } => {
195                write!(f, "{:WIDTH$} {src_dst}, {items}", "record-spread")
196            }
197            Instruction::Not { src_dst } => {
198                write!(f, "{:WIDTH$} {src_dst}", "not")
199            }
200            Instruction::BinaryOp { lhs_dst, op, rhs } => {
201                write!(f, "{:WIDTH$} {lhs_dst}, {op:?}, {rhs}", "binary-op")
202            }
203            Instruction::FollowCellPath { src_dst, path } => {
204                write!(f, "{:WIDTH$} {src_dst}, {path}", "follow-cell-path")
205            }
206            Instruction::CloneCellPath { dst, src, path } => {
207                write!(f, "{:WIDTH$} {dst}, {src}, {path}", "clone-cell-path")
208            }
209            Instruction::UpsertCellPath {
210                src_dst,
211                path,
212                new_value,
213            } => {
214                write!(
215                    f,
216                    "{:WIDTH$} {src_dst}, {path}, {new_value}",
217                    "upsert-cell-path"
218                )
219            }
220            Instruction::Jump { index } => {
221                write!(f, "{:WIDTH$} {index}", "jump")
222            }
223            Instruction::BranchIf { cond, index } => {
224                write!(f, "{:WIDTH$} {cond}, {index}", "branch-if")
225            }
226            Instruction::BranchIfEmpty { src, index } => {
227                write!(f, "{:WIDTH$} {src}, {index}", "branch-if-empty")
228            }
229            Instruction::Match {
230                pattern,
231                src,
232                index,
233            } => {
234                let pattern = FmtPattern {
235                    engine_state: self.engine_state,
236                    pattern,
237                };
238                write!(f, "{:WIDTH$} ({pattern}), {src}, {index}", "match")
239            }
240            Instruction::CheckMatchGuard { src } => {
241                write!(f, "{:WIDTH$} {src}", "check-match-guard")
242            }
243            Instruction::Iterate {
244                dst,
245                stream,
246                end_index,
247            } => {
248                write!(f, "{:WIDTH$} {dst}, {stream}, end {end_index}", "iterate")
249            }
250            Instruction::OnError { index } => {
251                write!(f, "{:WIDTH$} {index}", "on-error")
252            }
253            Instruction::OnErrorInto { index, dst } => {
254                write!(f, "{:WIDTH$} {index}, {dst}", "on-error-into")
255            }
256            Instruction::PopErrorHandler => {
257                write!(f, "{:WIDTH$}", "pop-error-handler")
258            }
259            Instruction::ReturnEarly { src } => {
260                write!(f, "{:WIDTH$} {src}", "return-early")
261            }
262            Instruction::Return { src } => {
263                write!(f, "{:WIDTH$} {src}", "return")
264            }
265        }
266    }
267}
268
269struct FmtDecl<'a>(DeclId, &'a str);
270
271impl<'a> FmtDecl<'a> {
272    fn new(engine_state: &'a EngineState, decl_id: DeclId) -> Self {
273        FmtDecl(decl_id, engine_state.get_decl(decl_id).name())
274    }
275}
276
277impl fmt::Display for FmtDecl<'_> {
278    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
279        write!(f, "decl {} {:?}", self.0.get(), self.1)
280    }
281}
282
283struct FmtVar<'a>(VarId, Option<&'a str>);
284
285impl<'a> FmtVar<'a> {
286    fn new(engine_state: &'a EngineState, var_id: VarId) -> Self {
287        // Search for the name of the variable
288        let name: Option<&str> = engine_state
289            .active_overlays(&[])
290            .flat_map(|overlay| overlay.vars.iter())
291            .find(|(_, v)| **v == var_id)
292            .map(|(k, _)| std::str::from_utf8(k).unwrap_or("<utf-8 error>"));
293        FmtVar(var_id, name)
294    }
295}
296
297impl fmt::Display for FmtVar<'_> {
298    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
299        if let Some(name) = self.1 {
300            write!(f, "var {} {:?}", self.0.get(), name)
301        } else {
302            write!(f, "var {}", self.0.get())
303        }
304    }
305}
306
307impl fmt::Display for RedirectMode {
308    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
309        match self {
310            RedirectMode::Pipe => write!(f, "pipe"),
311            RedirectMode::PipeSeparate => write!(f, "pipe separate"),
312            RedirectMode::Value => write!(f, "value"),
313            RedirectMode::Null => write!(f, "null"),
314            RedirectMode::Inherit => write!(f, "inherit"),
315            RedirectMode::Print => write!(f, "print"),
316            RedirectMode::File { file_num } => write!(f, "file({file_num})"),
317            RedirectMode::Caller => write!(f, "caller"),
318        }
319    }
320}
321
322struct FmtData<'a>(&'a [u8], DataSlice);
323
324impl fmt::Display for FmtData<'_> {
325    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
326        if let Ok(s) = std::str::from_utf8(&self.0[self.1]) {
327            // Write as string
328            write!(f, "{s:?}")
329        } else {
330            // Write as byte array
331            write!(f, "0x{:x?}", self.0)
332        }
333    }
334}
335
336struct FmtLiteral<'a> {
337    literal: &'a Literal,
338    data: &'a [u8],
339}
340
341impl fmt::Display for FmtLiteral<'_> {
342    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
343        match self.literal {
344            Literal::Bool(b) => write!(f, "bool({b:?})"),
345            Literal::Int(i) => write!(f, "int({i:?})"),
346            Literal::Float(fl) => write!(f, "float({fl:?})"),
347            Literal::Filesize(q) => write!(f, "filesize({q}b)"),
348            Literal::Duration(q) => write!(f, "duration({q}ns)"),
349            Literal::Binary(b) => write!(f, "binary({})", FmtData(self.data, *b)),
350            Literal::Block(id) => write!(f, "block({})", id.get()),
351            Literal::Closure(id) => write!(f, "closure({})", id.get()),
352            Literal::RowCondition(id) => write!(f, "row_condition({})", id.get()),
353            Literal::Range {
354                start,
355                step,
356                end,
357                inclusion,
358            } => write!(f, "range({start}, {step}, {end}, {inclusion:?})"),
359            Literal::List { capacity } => write!(f, "list(capacity = {capacity})"),
360            Literal::Record { capacity } => write!(f, "record(capacity = {capacity})"),
361            Literal::Filepath { val, no_expand } => write!(
362                f,
363                "filepath({}, no_expand = {no_expand:?})",
364                FmtData(self.data, *val)
365            ),
366            Literal::Directory { val, no_expand } => write!(
367                f,
368                "directory({}, no_expand = {no_expand:?})",
369                FmtData(self.data, *val)
370            ),
371            Literal::GlobPattern { val, no_expand } => write!(
372                f,
373                "glob-pattern({}, no_expand = {no_expand:?})",
374                FmtData(self.data, *val)
375            ),
376            Literal::String(s) => write!(f, "string({})", FmtData(self.data, *s)),
377            Literal::RawString(rs) => write!(f, "raw-string({})", FmtData(self.data, *rs)),
378            Literal::CellPath(p) => write!(f, "cell-path({p})"),
379            Literal::Date(dt) => write!(f, "date({dt})"),
380            Literal::Nothing => write!(f, "nothing"),
381        }
382    }
383}
384
385struct FmtPattern<'a> {
386    engine_state: &'a EngineState,
387    pattern: &'a Pattern,
388}
389
390impl fmt::Display for FmtPattern<'_> {
391    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
392        match self.pattern {
393            Pattern::Record(bindings) => {
394                f.write_str("{")?;
395                for (name, pattern) in bindings {
396                    write!(
397                        f,
398                        "{}: {}",
399                        name,
400                        FmtPattern {
401                            engine_state: self.engine_state,
402                            pattern: &pattern.pattern,
403                        }
404                    )?;
405                }
406                f.write_str("}")
407            }
408            Pattern::List(bindings) => {
409                f.write_str("[")?;
410                for pattern in bindings {
411                    write!(
412                        f,
413                        "{}",
414                        FmtPattern {
415                            engine_state: self.engine_state,
416                            pattern: &pattern.pattern
417                        }
418                    )?;
419                }
420                f.write_str("]")
421            }
422            Pattern::Expression(expr) => {
423                let string =
424                    String::from_utf8_lossy(self.engine_state.get_span_contents(expr.span));
425                f.write_str(&string)
426            }
427            Pattern::Value(value) => {
428                f.write_str(&value.to_parsable_string(", ", &self.engine_state.config))
429            }
430            Pattern::Variable(var_id) => {
431                let variable = FmtVar::new(self.engine_state, *var_id);
432                write!(f, "{variable}")
433            }
434            Pattern::Or(patterns) => {
435                for (index, pattern) in patterns.iter().enumerate() {
436                    if index > 0 {
437                        f.write_str(" | ")?;
438                    }
439                    write!(
440                        f,
441                        "{}",
442                        FmtPattern {
443                            engine_state: self.engine_state,
444                            pattern: &pattern.pattern
445                        }
446                    )?;
447                }
448                Ok(())
449            }
450            Pattern::Rest(var_id) => {
451                let variable = FmtVar::new(self.engine_state, *var_id);
452                write!(f, "..{variable}")
453            }
454            Pattern::IgnoreRest => f.write_str(".."),
455            Pattern::IgnoreValue => f.write_str("_"),
456            Pattern::Garbage => f.write_str("<garbage>"),
457        }
458    }
459}