nu_engine/compile/
mod.rs

1use nu_protocol::{
2    CompileError, IntoSpanned, RegId, Span,
3    ast::{Block, Expr, Pipeline, PipelineRedirection, RedirectionSource, RedirectionTarget},
4    engine::StateWorkingSet,
5    ir::{Instruction, IrBlock, RedirectMode},
6};
7
8mod builder;
9mod call;
10mod expression;
11mod keyword;
12mod operator;
13mod redirect;
14
15use builder::BlockBuilder;
16use call::*;
17use expression::compile_expression;
18use operator::*;
19use redirect::*;
20
21const BLOCK_INPUT: RegId = RegId::new(0);
22
23/// Compile Nushell pipeline abstract syntax tree (AST) to internal representation (IR) instructions
24/// for evaluation.
25pub fn compile(working_set: &StateWorkingSet, block: &Block) -> Result<IrBlock, CompileError> {
26    let mut builder = BlockBuilder::new(block.span);
27
28    let span = block.span.unwrap_or(Span::unknown());
29
30    compile_block(
31        working_set,
32        &mut builder,
33        block,
34        RedirectModes::caller(span),
35        Some(BLOCK_INPUT),
36        BLOCK_INPUT,
37    )?;
38
39    // A complete block has to end with a `return`
40    builder.push(Instruction::Return { src: BLOCK_INPUT }.into_spanned(span))?;
41
42    builder.finish()
43}
44
45/// Compiles a [`Block`] in-place into an IR block. This can be used in a nested manner, for example
46/// by [`compile_if()`][keyword::compile_if], where the instructions for the blocks for the if/else
47/// are inlined into the top-level IR block.
48fn compile_block(
49    working_set: &StateWorkingSet,
50    builder: &mut BlockBuilder,
51    block: &Block,
52    redirect_modes: RedirectModes,
53    in_reg: Option<RegId>,
54    out_reg: RegId,
55) -> Result<(), CompileError> {
56    let span = block.span.unwrap_or(Span::unknown());
57    let mut redirect_modes = Some(redirect_modes);
58    if !block.pipelines.is_empty() {
59        let last_index = block.pipelines.len() - 1;
60        for (index, pipeline) in block.pipelines.iter().enumerate() {
61            compile_pipeline(
62                working_set,
63                builder,
64                pipeline,
65                span,
66                // the redirect mode only applies to the last pipeline.
67                if index == last_index {
68                    redirect_modes
69                        .take()
70                        .expect("should only take redirect_modes once")
71                } else {
72                    RedirectModes::default()
73                },
74                // input is only passed to the first pipeline.
75                if index == 0 { in_reg } else { None },
76                out_reg,
77            )?;
78
79            if index != last_index {
80                // Explicitly drain the out reg after each non-final pipeline, because that's how
81                // the semicolon functions.
82                if builder.is_allocated(out_reg) {
83                    builder.push(Instruction::Drain { src: out_reg }.into_spanned(span))?;
84                }
85                builder.load_empty(out_reg)?;
86            }
87        }
88        Ok(())
89    } else if in_reg.is_none() {
90        builder.load_empty(out_reg)
91    } else {
92        Ok(())
93    }
94}
95
96fn compile_pipeline(
97    working_set: &StateWorkingSet,
98    builder: &mut BlockBuilder,
99    pipeline: &Pipeline,
100    fallback_span: Span,
101    redirect_modes: RedirectModes,
102    in_reg: Option<RegId>,
103    out_reg: RegId,
104) -> Result<(), CompileError> {
105    let mut iter = pipeline.elements.iter().peekable();
106    let mut in_reg = in_reg;
107    let mut redirect_modes = Some(redirect_modes);
108    while let Some(element) = iter.next() {
109        let span = element.pipe.unwrap_or(fallback_span);
110
111        // We have to get the redirection mode from either the explicit redirection in the pipeline
112        // element, or from the next expression if it's specified there. If this is the last
113        // element, then it's from whatever is passed in as the mode to use.
114
115        let next_redirect_modes = if let Some(next_element) = iter.peek() {
116            let mut modes = redirect_modes_of_expression(working_set, &next_element.expr, span)?;
117
118            // If there's a next element with no inherent redirection we always pipe out *unless*
119            // this is a single redirection of stderr to pipe (e>|)
120            if modes.out.is_none()
121                && !matches!(
122                    element.redirection,
123                    Some(PipelineRedirection::Single {
124                        source: RedirectionSource::Stderr,
125                        target: RedirectionTarget::Pipe { .. }
126                    })
127                )
128            {
129                let pipe_span = next_element.pipe.unwrap_or(next_element.expr.span);
130                modes.out = Some(RedirectMode::Pipe.into_spanned(pipe_span));
131            }
132
133            modes
134        } else {
135            redirect_modes
136                .take()
137                .expect("should only take redirect_modes once")
138        };
139
140        let spec_redirect_modes = match &element.redirection {
141            Some(PipelineRedirection::Single { source, target }) => {
142                let mode = redirection_target_to_mode(working_set, builder, target)?;
143                match source {
144                    RedirectionSource::Stdout => RedirectModes {
145                        out: Some(mode),
146                        err: None,
147                    },
148                    RedirectionSource::Stderr => RedirectModes {
149                        out: None,
150                        err: Some(mode),
151                    },
152                    RedirectionSource::StdoutAndStderr => RedirectModes {
153                        out: Some(mode),
154                        err: Some(mode),
155                    },
156                }
157            }
158            Some(PipelineRedirection::Separate { out, err }) => {
159                // In this case, out and err must not both be Pipe
160                assert!(
161                    !matches!(
162                        (out, err),
163                        (
164                            RedirectionTarget::Pipe { .. },
165                            RedirectionTarget::Pipe { .. }
166                        )
167                    ),
168                    "for Separate redirection, out and err targets must not both be Pipe"
169                );
170                let out = redirection_target_to_mode(working_set, builder, out)?;
171                let err = redirection_target_to_mode(working_set, builder, err)?;
172                RedirectModes {
173                    out: Some(out),
174                    err: Some(err),
175                }
176            }
177            None => RedirectModes {
178                out: None,
179                err: None,
180            },
181        };
182
183        let redirect_modes = RedirectModes {
184            out: spec_redirect_modes.out.or(next_redirect_modes.out),
185            err: spec_redirect_modes.err.or(next_redirect_modes.err),
186        };
187
188        compile_expression(
189            working_set,
190            builder,
191            &element.expr,
192            redirect_modes.clone(),
193            in_reg,
194            out_reg,
195        )?;
196
197        // Only clean up the redirection if current element is NOT
198        // a nested eval expression, since this already cleans it.
199        if !has_nested_eval_expr(&element.expr.expr) {
200            // Clean up the redirection
201            finish_redirection(builder, redirect_modes, out_reg)?;
202        }
203
204        // The next pipeline element takes input from this output
205        in_reg = Some(out_reg);
206    }
207    Ok(())
208}
209
210fn has_nested_eval_expr(expr: &Expr) -> bool {
211    is_subexpression(expr) || is_block_call(expr)
212}
213
214fn is_block_call(expr: &Expr) -> bool {
215    match expr {
216        Expr::Call(inner) => inner
217            .arguments
218            .iter()
219            .any(|arg| matches!(arg.expr().map(|e| &e.expr), Some(Expr::Block(..)))),
220        _ => false,
221    }
222}
223
224fn is_subexpression(expr: &Expr) -> bool {
225    match expr {
226        Expr::FullCellPath(inner) => {
227            matches!(&inner.head.expr, &Expr::Subexpression(..))
228        }
229        Expr::Subexpression(..) => true,
230        _ => false,
231    }
232}