use nu_protocol::{
ast::{Block, Pipeline, PipelineRedirection, RedirectionSource, RedirectionTarget},
engine::StateWorkingSet,
ir::{Instruction, IrBlock, RedirectMode},
CompileError, IntoSpanned, RegId, Span,
};
mod builder;
mod call;
mod expression;
mod keyword;
mod operator;
mod redirect;
use builder::BlockBuilder;
use call::*;
use expression::compile_expression;
use operator::*;
use redirect::*;
const BLOCK_INPUT: RegId = RegId::new(0);
pub fn compile(working_set: &StateWorkingSet, block: &Block) -> Result<IrBlock, CompileError> {
let mut builder = BlockBuilder::new(block.span);
let span = block.span.unwrap_or(Span::unknown());
compile_block(
working_set,
&mut builder,
block,
RedirectModes::caller(span),
Some(BLOCK_INPUT),
BLOCK_INPUT,
)?;
builder.push(Instruction::Return { src: BLOCK_INPUT }.into_spanned(span))?;
builder.finish()
}
fn compile_block(
working_set: &StateWorkingSet,
builder: &mut BlockBuilder,
block: &Block,
redirect_modes: RedirectModes,
in_reg: Option<RegId>,
out_reg: RegId,
) -> Result<(), CompileError> {
let span = block.span.unwrap_or(Span::unknown());
let mut redirect_modes = Some(redirect_modes);
if !block.pipelines.is_empty() {
let last_index = block.pipelines.len() - 1;
for (index, pipeline) in block.pipelines.iter().enumerate() {
compile_pipeline(
working_set,
builder,
pipeline,
span,
if index == last_index {
redirect_modes
.take()
.expect("should only take redirect_modes once")
} else {
RedirectModes::default()
},
if index == 0 { in_reg } else { None },
out_reg,
)?;
if index != last_index {
if builder.is_allocated(out_reg) {
builder.push(Instruction::Drain { src: out_reg }.into_spanned(span))?;
}
builder.load_empty(out_reg)?;
}
}
Ok(())
} else if in_reg.is_none() {
builder.load_empty(out_reg)
} else {
Ok(())
}
}
fn compile_pipeline(
working_set: &StateWorkingSet,
builder: &mut BlockBuilder,
pipeline: &Pipeline,
fallback_span: Span,
redirect_modes: RedirectModes,
in_reg: Option<RegId>,
out_reg: RegId,
) -> Result<(), CompileError> {
let mut iter = pipeline.elements.iter().peekable();
let mut in_reg = in_reg;
let mut redirect_modes = Some(redirect_modes);
while let Some(element) = iter.next() {
let span = element.pipe.unwrap_or(fallback_span);
let next_redirect_modes = if let Some(next_element) = iter.peek() {
let mut modes = redirect_modes_of_expression(working_set, &next_element.expr, span)?;
if modes.out.is_none()
&& !matches!(
element.redirection,
Some(PipelineRedirection::Single {
source: RedirectionSource::Stderr,
target: RedirectionTarget::Pipe { .. }
})
)
{
let pipe_span = next_element.pipe.unwrap_or(next_element.expr.span);
modes.out = Some(RedirectMode::Pipe.into_spanned(pipe_span));
}
modes
} else {
redirect_modes
.take()
.expect("should only take redirect_modes once")
};
let spec_redirect_modes = match &element.redirection {
Some(PipelineRedirection::Single { source, target }) => {
let mode = redirection_target_to_mode(working_set, builder, target)?;
match source {
RedirectionSource::Stdout => RedirectModes {
out: Some(mode),
err: None,
},
RedirectionSource::Stderr => RedirectModes {
out: None,
err: Some(mode),
},
RedirectionSource::StdoutAndStderr => RedirectModes {
out: Some(mode),
err: Some(mode),
},
}
}
Some(PipelineRedirection::Separate { out, err }) => {
assert!(
!matches!(
(out, err),
(
RedirectionTarget::Pipe { .. },
RedirectionTarget::Pipe { .. }
)
),
"for Separate redirection, out and err targets must not both be Pipe"
);
let out = redirection_target_to_mode(working_set, builder, out)?;
let err = redirection_target_to_mode(working_set, builder, err)?;
RedirectModes {
out: Some(out),
err: Some(err),
}
}
None => RedirectModes {
out: None,
err: None,
},
};
let redirect_modes = RedirectModes {
out: spec_redirect_modes.out.or(next_redirect_modes.out),
err: spec_redirect_modes.err.or(next_redirect_modes.err),
};
compile_expression(
working_set,
builder,
&element.expr,
redirect_modes.clone(),
in_reg,
out_reg,
)?;
finish_redirection(builder, redirect_modes, out_reg)?;
in_reg = Some(out_reg);
}
Ok(())
}