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
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(
31 working_set,
32 &mut builder,
33 block,
34 RedirectModes::caller(span),
35 Some(BLOCK_INPUT),
36 BLOCK_INPUT,
37 )?;
38
39 builder.push(Instruction::Return { src: BLOCK_INPUT }.into_spanned(span))?;
41
42 builder.finish()
43}
44
45fn 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 if index == last_index {
68 redirect_modes
69 .take()
70 .expect("should only take redirect_modes once")
71 } else {
72 RedirectModes::default()
73 },
74 if index == 0 { in_reg } else { None },
76 out_reg,
77 )?;
78
79 if index != last_index {
80 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 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 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 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 if !has_nested_eval_expr(&element.expr.expr) {
200 finish_redirection(builder, redirect_modes, out_reg)?;
202 }
203
204 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}