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