1use nu_protocol::{
2 CompileError, IntoSpanned, RegId, Span,
3 ast::{Block, Expr, Pipeline, PipelineRedirection, RedirectionSource, RedirectionTarget},
4 engine::StateWorkingSet,
5 ir::{Instruction, IrBlock, RedirectMode, ScopeRegion},
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
23pub 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(
32 working_set,
33 &mut builder,
34 block,
35 false,
36 RedirectModes::caller(span),
37 Some(BLOCK_INPUT),
38 BLOCK_INPUT,
39 )?;
40
41 builder.push(Instruction::Return { src: BLOCK_INPUT }.into_spanned(span))?;
43
44 builder.finish()
45}
46
47fn compile_block(
56 working_set: &StateWorkingSet,
57 builder: &mut BlockBuilder,
58 block: &Block,
59 record_scope_region: bool,
60 redirect_modes: RedirectModes,
61 in_reg: Option<RegId>,
62 out_reg: RegId,
63) -> Result<(), CompileError> {
64 let span = block.span.unwrap_or(Span::unknown());
65 let region_start = builder.instructions.len();
66
67 let mut redirect_modes = Some(redirect_modes);
68 if !block.pipelines.is_empty() {
69 let last_index = block.pipelines.len() - 1;
70 for (index, pipeline) in block.pipelines.iter().enumerate() {
71 compile_pipeline(
72 working_set,
73 builder,
74 pipeline,
75 span,
76 if index == last_index {
78 redirect_modes
79 .take()
80 .expect("should only take redirect_modes once")
81 } else {
82 RedirectModes::default()
83 },
84 if index == 0 { in_reg } else { None },
86 out_reg,
87 )?;
88
89 if index != last_index {
90 if builder.is_allocated(out_reg) {
93 builder.push(Instruction::Drain { src: out_reg }.into_spanned(span))?;
94 }
95 builder.load_empty(out_reg)?;
96 }
97 }
98 } else if in_reg.is_none() {
99 builder.load_empty(out_reg)?;
100 }
101
102 if record_scope_region && let Some(bindings) = &block.scope_bindings {
103 let region_end = builder.instructions.len();
104 if region_start < region_end {
107 builder.scope_regions.push(ScopeRegion {
108 start: region_start,
109 end: region_end,
110 bindings: bindings.clone(),
111 });
112 }
113 }
114
115 Ok(())
116}
117
118fn compile_pipeline(
119 working_set: &StateWorkingSet,
120 builder: &mut BlockBuilder,
121 pipeline: &Pipeline,
122 fallback_span: Span,
123 redirect_modes: RedirectModes,
124 in_reg: Option<RegId>,
125 out_reg: RegId,
126) -> Result<(), CompileError> {
127 let mut iter = pipeline.elements.iter().peekable();
128 let mut in_reg = in_reg;
129 let mut redirect_modes = Some(redirect_modes);
130 while let Some(element) = iter.next() {
131 let span = element.pipe.unwrap_or(fallback_span);
132
133 let next_redirect_modes = if let Some(next_element) = iter.peek() {
138 let mut modes = redirect_modes_of_expression(working_set, &next_element.expr, span)?;
139
140 if modes.out.is_none()
143 && !matches!(
144 element.redirection,
145 Some(PipelineRedirection::Single {
146 source: RedirectionSource::Stderr,
147 target: RedirectionTarget::Pipe { .. }
148 })
149 )
150 {
151 let pipe_span = next_element.pipe.unwrap_or(next_element.expr.span);
152 modes.out = Some(RedirectMode::Pipe.into_spanned(pipe_span));
153 }
154
155 modes
156 } else {
157 redirect_modes
158 .take()
159 .expect("should only take redirect_modes once")
160 };
161
162 let spec_redirect_modes = match &element.redirection {
163 Some(PipelineRedirection::Single { source, target }) => {
164 let mode = redirection_target_to_mode(working_set, builder, target)?;
165 match source {
166 RedirectionSource::Stdout => RedirectModes {
167 out: Some(mode),
168 err: None,
169 },
170 RedirectionSource::Stderr => RedirectModes {
171 out: None,
172 err: Some(mode),
173 },
174 RedirectionSource::StdoutAndStderr => RedirectModes {
175 out: Some(mode),
176 err: Some(mode),
177 },
178 }
179 }
180 Some(PipelineRedirection::Separate { out, err }) => {
181 assert!(
183 !matches!(
184 (out, err),
185 (
186 RedirectionTarget::Pipe { .. },
187 RedirectionTarget::Pipe { .. }
188 )
189 ),
190 "for Separate redirection, out and err targets must not both be Pipe"
191 );
192 let out = redirection_target_to_mode(working_set, builder, out)?;
193 let err = redirection_target_to_mode(working_set, builder, err)?;
194 RedirectModes {
195 out: Some(out),
196 err: Some(err),
197 }
198 }
199 None => RedirectModes {
200 out: None,
201 err: None,
202 },
203 };
204
205 let redirect_modes = RedirectModes {
206 out: spec_redirect_modes.out.or(next_redirect_modes.out),
207 err: spec_redirect_modes.err.or(next_redirect_modes.err),
208 };
209
210 compile_expression(
211 working_set,
212 builder,
213 &element.expr,
214 redirect_modes.clone(),
215 in_reg,
216 out_reg,
217 )?;
218
219 if !has_nested_eval_expr(&element.expr.expr) {
222 finish_redirection(builder, redirect_modes, out_reg)?;
224 }
225
226 in_reg = Some(out_reg);
228 }
229 Ok(())
230}
231
232fn has_nested_eval_expr(expr: &Expr) -> bool {
233 is_subexpression(expr) || is_block_call(expr)
234}
235
236fn is_block_call(expr: &Expr) -> bool {
237 match expr {
238 Expr::Call(inner) => inner
239 .arguments
240 .iter()
241 .any(|arg| matches!(arg.expr().map(|e| &e.expr), Some(Expr::Block(..)))),
242 _ => false,
243 }
244}
245
246fn is_subexpression(expr: &Expr) -> bool {
247 match expr {
248 Expr::FullCellPath(inner) => {
249 matches!(&inner.head.expr, &Expr::Subexpression(..))
250 }
251 Expr::Subexpression(..) => true,
252 _ => false,
253 }
254}