Skip to main content

nu_parser/
parse_pipelines.rs

1use log::trace;
2use nu_protocol::{ParseError, Span, SyntaxShape, Type, ast::*, engine::StateWorkingSet};
3use std::sync::Arc;
4
5use crate::{
6    Token,
7    lite_parser::{LiteCommand, LitePipeline, LiteRedirection, LiteRedirectionTarget, lite_parse},
8    parse_keywords::parse_def_predecl,
9    parser::{
10        parse_builtin_commands, parse_expression, parse_value, wrap_element_with_collect,
11        wrap_expr_with_collect,
12    },
13    type_check,
14};
15
16fn parse_redirection_target(
17    working_set: &mut StateWorkingSet,
18    target: &LiteRedirectionTarget,
19) -> RedirectionTarget {
20    match target {
21        LiteRedirectionTarget::File {
22            connector,
23            file,
24            append,
25        } => RedirectionTarget::File {
26            expr: parse_value(working_set, *file, &SyntaxShape::Any, None),
27            append: *append,
28            span: *connector,
29        },
30        LiteRedirectionTarget::Pipe { connector } => RedirectionTarget::Pipe { span: *connector },
31    }
32}
33
34pub(crate) fn parse_redirection(
35    working_set: &mut StateWorkingSet,
36    target: &LiteRedirection,
37) -> PipelineRedirection {
38    match target {
39        LiteRedirection::Single { source, target } => PipelineRedirection::Single {
40            source: *source,
41            target: parse_redirection_target(working_set, target),
42        },
43        LiteRedirection::Separate { out, err } => PipelineRedirection::Separate {
44            out: parse_redirection_target(working_set, out),
45            err: parse_redirection_target(working_set, err),
46        },
47    }
48}
49
50pub(crate) fn parse_pipeline_element(
51    working_set: &mut StateWorkingSet,
52    command: &LiteCommand,
53    input_type: &Type,
54) -> PipelineElement {
55    trace!("parsing: pipeline element");
56
57    let expr = parse_expression(working_set, &command.parts, Some(input_type));
58
59    let redirection = command
60        .redirection
61        .as_ref()
62        .map(|r| parse_redirection(working_set, r));
63
64    PipelineElement {
65        pipe: command.pipe,
66        expr,
67        redirection,
68    }
69}
70
71pub(crate) fn redirecting_builtin_error(
72    name: &'static str,
73    redirection: &LiteRedirection,
74) -> ParseError {
75    match redirection {
76        LiteRedirection::Single { target, .. } => {
77            ParseError::RedirectingBuiltinCommand(name, target.connector(), None)
78        }
79        LiteRedirection::Separate { out, err } => ParseError::RedirectingBuiltinCommand(
80            name,
81            out.connector().min(err.connector()),
82            Some(out.connector().max(err.connector())),
83        ),
84    }
85}
86
87pub fn parse_pipeline(
88    working_set: &mut StateWorkingSet,
89    pipeline: &LitePipeline,
90    input_type: Option<&Type>,
91) -> Pipeline {
92    match pipeline.commands.as_slice() {
93        [] => unreachable!("at this point the pipeline must have at least one element"),
94        [single] => parse_builtin_commands(working_set, single, input_type),
95        [first, rest @ ..] => {
96            let mut current_pipeline_type = input_type.cloned().unwrap_or(Type::Any);
97
98            let mut elements = Vec::new();
99            elements.push({
100                let element = parse_pipeline_element(working_set, first, &current_pipeline_type);
101                // the output becomes the input for the next pipeline element
102                current_pipeline_type = element.expr.ty.clone();
103
104                element
105            });
106
107            // Parse a normal multi command pipeline
108            let rest_elements = rest.iter().map(|element| {
109                let input_clone = current_pipeline_type.clone();
110                let element = parse_pipeline_element(working_set, element, &current_pipeline_type);
111                // the output becomes the input for the next pipeline element
112                current_pipeline_type = element.expr.ty.clone();
113
114                // Handle $in for pipeline elements beyond the first one
115                if element.has_in_variable(working_set) {
116                    wrap_element_with_collect(working_set, element, Some(&input_clone))
117                } else {
118                    element
119                }
120            });
121
122            elements.extend(rest_elements);
123
124            Pipeline { elements }
125        }
126    }
127}
128
129pub fn parse_block(
130    working_set: &mut StateWorkingSet,
131    tokens: &[Token],
132    span: Span,
133    scoped: bool,
134    is_subexpression: bool,
135    input_type: Option<&Type>,
136) -> Block {
137    let (lite_block, err) = lite_parse(tokens, working_set);
138    if let Some(err) = err {
139        working_set.error(err);
140    }
141
142    trace!("parsing block: {lite_block:?}");
143
144    if scoped {
145        working_set.enter_scope();
146    }
147
148    // Pre-declare any definition so that definitions
149    // that share the same block can see each other
150    for pipeline in &lite_block.block {
151        if let [lite_command] = pipeline.commands.as_slice() {
152            parse_def_predecl(working_set, lite_command.command_parts())
153        }
154    }
155
156    let mut block = Block::new_with_capacity(lite_block.block.len());
157    block.span = Some(span);
158
159    if let [first, rest @ ..] = lite_block.block.as_slice() {
160        // only the first pipeline receives the block's pipeline input
161        let pipeline = parse_pipeline(working_set, first, input_type);
162        block.pipelines.push(pipeline);
163
164        for lite_pipeline in rest {
165            let pipeline = parse_pipeline(working_set, lite_pipeline, None);
166            block.pipelines.push(pipeline);
167        }
168    }
169
170    // If this is not a subexpression and there are any pipelines where the first element has $in,
171    // we can wrap the whole block in collect so that they all reference the same $in
172    if !is_subexpression
173        && block
174            .pipelines
175            .iter()
176            .flat_map(|pipeline| pipeline.elements.first())
177            .any(|element| element.has_in_variable(working_set))
178    {
179        // Move the block out to prepare it to become a subexpression
180        let inner_block = std::mem::take(&mut block);
181        block.span = inner_block.span;
182        let ty = inner_block.output_type();
183        let block_id = working_set.add_block(Arc::new(inner_block));
184
185        // Now wrap it in a Collect expression, and put it in the block as the only pipeline
186        let subexpression = Expression::new(working_set, Expr::Subexpression(block_id), span, ty);
187        let collect = wrap_expr_with_collect(working_set, subexpression, input_type);
188
189        block.pipelines.push(Pipeline {
190            elements: vec![PipelineElement {
191                pipe: None,
192                expr: collect,
193                redirection: None,
194            }],
195        });
196    }
197
198    if scoped {
199        working_set.exit_scope();
200    }
201
202    let errors = type_check::check_block_input_output(working_set, &block);
203    working_set.parse_errors.extend(errors);
204
205    block
206}