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