summaryrefslogtreecommitdiff
path: root/crates/nu-command/src/debug/view_ir.rs
blob: 676285844c662d5bf3609e71d45a7053a3d09042 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
use nu_engine::command_prelude::*;

#[derive(Clone)]
pub struct ViewIr;

impl Command for ViewIr {
    fn name(&self) -> &str {
        "view ir"
    }

    fn signature(&self) -> Signature {
        Signature::build(self.name())
            .required(
                "target",
                SyntaxShape::Any,
                "The name or block to view compiled code for.",
            )
            .switch(
                "json",
                "Dump the raw block data as JSON (unstable).",
                Some('j'),
            )
            .switch(
                "decl-id",
                "Integer is a declaration ID rather than a block ID.",
                Some('d'),
            )
            .input_output_type(Type::Nothing, Type::String)
            .category(Category::Debug)
    }

    fn description(&self) -> &str {
        "View the compiled IR code for a block of code."
    }

    fn extra_description(&self) -> &str {
        "
The target can be a closure, the name of a custom command, or an internal block
ID. Closure literals within IR dumps often reference the block by ID (e.g.
`closure(3231)`), so this provides an easy way to read the IR of any embedded
closures.

The --decl-id option is provided to use a declaration ID instead, which can be
found on `call` instructions. This is sometimes better than using the name, as
the declaration may not be in scope.
"
        .trim()
    }

    fn run(
        &self,
        engine_state: &EngineState,
        stack: &mut Stack,
        call: &Call,
        _input: PipelineData,
    ) -> Result<PipelineData, ShellError> {
        let target: Value = call.req(engine_state, stack, 0)?;
        let json = call.has_flag(engine_state, stack, "json")?;
        let is_decl_id = call.has_flag(engine_state, stack, "decl-id")?;

        let block_id = match target {
            Value::Closure { ref val, .. } => val.block_id,
            // Decl by name
            Value::String { ref val, .. } => {
                if let Some(decl_id) = engine_state.find_decl(val.as_bytes(), &[]) {
                    let decl = engine_state.get_decl(decl_id);
                    decl.block_id().ok_or_else(|| ShellError::GenericError {
                        error: format!("Can't view IR for `{val}`"),
                        msg: "not a custom command".into(),
                        span: Some(target.span()),
                        help: Some("internal commands don't have Nushell source code".into()),
                        inner: vec![],
                    })?
                } else {
                    return Err(ShellError::GenericError {
                        error: format!("Can't view IR for `{val}`"),
                        msg: "can't find a command with this name".into(),
                        span: Some(target.span()),
                        help: None,
                        inner: vec![],
                    });
                }
            }
            // Decl by ID - IR dump always shows name of decl, but sometimes it isn't in scope
            Value::Int { val, .. } if is_decl_id => {
                let decl_id = val
                    .try_into()
                    .ok()
                    .filter(|id| *id < engine_state.num_decls())
                    .ok_or_else(|| ShellError::IncorrectValue {
                        msg: "not a valid decl id".into(),
                        val_span: target.span(),
                        call_span: call.head,
                    })?;
                let decl = engine_state.get_decl(decl_id);
                decl.block_id().ok_or_else(|| ShellError::GenericError {
                    error: format!("Can't view IR for `{}`", decl.name()),
                    msg: "not a custom command".into(),
                    span: Some(target.span()),
                    help: Some("internal commands don't have Nushell source code".into()),
                    inner: vec![],
                })?
            }
            // Block by ID - often shows up in IR
            Value::Int { val, .. } => val.try_into().map_err(|_| ShellError::IncorrectValue {
                msg: "not a valid block id".into(),
                val_span: target.span(),
                call_span: call.head,
            })?,
            // Pass through errors
            Value::Error { error, .. } => return Err(*error),
            _ => {
                return Err(ShellError::TypeMismatch {
                    err_message: "expected closure, string, or int".into(),
                    span: call.head,
                })
            }
        };

        let Some(block) = engine_state.try_get_block(block_id) else {
            return Err(ShellError::GenericError {
                error: format!("Unknown block ID: {block_id}"),
                msg: "ensure the block ID is correct and try again".into(),
                span: Some(target.span()),
                help: None,
                inner: vec![],
            });
        };

        let ir_block = block
            .ir_block
            .as_ref()
            .ok_or_else(|| ShellError::GenericError {
                error: "Can't view IR for this block".into(),
                msg: "block is missing compiled representation".into(),
                span: block.span,
                help: Some("the IrBlock is probably missing due to a compilation error".into()),
                inner: vec![],
            })?;

        let formatted = if json {
            let formatted_instructions = ir_block
                .instructions
                .iter()
                .map(|instruction| {
                    instruction
                        .display(engine_state, &ir_block.data)
                        .to_string()
                })
                .collect::<Vec<_>>();

            serde_json::to_string_pretty(&serde_json::json!({
                "block_id": block_id,
                "span": block.span,
                "ir_block": ir_block,
                "formatted_instructions": formatted_instructions,
            }))
            .map_err(|err| ShellError::GenericError {
                error: "JSON serialization failed".into(),
                msg: err.to_string(),
                span: Some(call.head),
                help: None,
                inner: vec![],
            })?
        } else {
            format!("{}", ir_block.display(engine_state))
        };

        Ok(Value::string(formatted, call.head).into_pipeline_data())
    }
}