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(0); /// Compile Nushell pipeline abstract syntax tree (AST) to internal representation (IR) instructions /// for evaluation. pub fn compile(working_set: &StateWorkingSet, block: &Block) -> Result { 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, )?; // A complete block has to end with a `return` builder.push(Instruction::Return { src: BLOCK_INPUT }.into_spanned(span))?; builder.finish() } /// Compiles a [`Block`] in-place into an IR block. This can be used in a nested manner, for example /// by [`compile_if()`][keyword::compile_if], where the instructions for the blocks for the if/else /// are inlined into the top-level IR block. fn compile_block( working_set: &StateWorkingSet, builder: &mut BlockBuilder, block: &Block, redirect_modes: RedirectModes, in_reg: Option, 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, // the redirect mode only applies to the last pipeline. if index == last_index { redirect_modes .take() .expect("should only take redirect_modes once") } else { RedirectModes::default() }, // input is only passed to the first pipeline. if index == 0 { in_reg } else { None }, out_reg, )?; if index != last_index { // Explicitly drain the out reg after each non-final pipeline, because that's how // the semicolon functions. 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, 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); // We have to get the redirection mode from either the explicit redirection in the pipeline // element, or from the next expression if it's specified there. If this is the last // element, then it's from whatever is passed in as the mode to use. 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 there's a next element with no inherent redirection we always pipe out *unless* // this is a single redirection of stderr to pipe (e>|) 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 }) => { // In this case, out and err must not both be Pipe 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, )?; // Clean up the redirection finish_redirection(builder, redirect_modes, out_reg)?; // The next pipeline element takes input from this output in_reg = Some(out_reg); } Ok(()) }