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 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!(f, "{s:?}")
364 } else {
365 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}