Skip to main content

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