Skip to main content

nu_parser/
parse_captures_compile.rs

1use crate::{
2    lex::lex, parse_helpers::PERCENT_FORCED_BUILTIN_PARSER_INFO, parse_pipelines::parse_block,
3};
4use log::trace;
5use nu_protocol::{
6    BlockId, ENV_VARIABLE_ID, IN_VARIABLE_ID, ParseError, Span, Type, VarId, ast::*,
7    engine::StateWorkingSet,
8};
9use std::{collections::HashMap, sync::Arc};
10
11pub fn compile_block(working_set: &mut StateWorkingSet<'_>, block: &mut Block) {
12    if !working_set.parse_errors.is_empty() {
13        // This means there might be a bug in the parser, since calling this function while parse
14        // errors are present is a logic error. However, it's not fatal and it's best to continue
15        // without doing anything.
16        log::error!("compile_block called with parse errors");
17        return;
18    }
19
20    match nu_engine::compile(working_set, block) {
21        Ok(ir_block) => {
22            block.ir_block = Some(ir_block);
23        }
24        Err(err) => working_set.compile_errors.push(err),
25    }
26}
27
28pub fn compile_block_with_id(working_set: &mut StateWorkingSet<'_>, block_id: BlockId) {
29    if !working_set.parse_errors.is_empty() {
30        // This means there might be a bug in the parser, since calling this function while parse
31        // errors are present is a logic error. However, it's not fatal and it's best to continue
32        // without doing anything.
33        log::error!("compile_block_with_id called with parse errors");
34        return;
35    }
36
37    match nu_engine::compile(working_set, working_set.get_block(block_id)) {
38        Ok(ir_block) => {
39            working_set.get_block_mut(block_id).ir_block = Some(ir_block);
40        }
41        Err(err) => {
42            working_set.compile_errors.push(err);
43        }
44    };
45}
46
47pub fn discover_captures_in_closure(
48    working_set: &StateWorkingSet,
49    block: &Block,
50    seen: &mut Vec<VarId>,
51    seen_blocks: &mut HashMap<BlockId, Vec<(VarId, Span)>>,
52    output: &mut Vec<(VarId, Span)>,
53) -> Result<(), ParseError> {
54    for flag in &block.signature.named {
55        if let Some(var_id) = flag.var_id {
56            seen.push(var_id);
57        }
58    }
59
60    for positional in &block.signature.required_positional {
61        if let Some(var_id) = positional.var_id {
62            seen.push(var_id);
63        }
64    }
65    for positional in &block.signature.optional_positional {
66        if let Some(var_id) = positional.var_id {
67            seen.push(var_id);
68        }
69    }
70    if let Some(positional) = &block.signature.rest_positional
71        && let Some(var_id) = positional.var_id
72    {
73        seen.push(var_id);
74    }
75
76    for pipeline in &block.pipelines {
77        discover_captures_in_pipeline(working_set, pipeline, seen, seen_blocks, output)?;
78    }
79
80    Ok(())
81}
82
83fn discover_captures_in_pipeline(
84    working_set: &StateWorkingSet,
85    pipeline: &Pipeline,
86    seen: &mut Vec<VarId>,
87    seen_blocks: &mut HashMap<BlockId, Vec<(VarId, Span)>>,
88    output: &mut Vec<(VarId, Span)>,
89) -> Result<(), ParseError> {
90    for element in &pipeline.elements {
91        discover_captures_in_pipeline_element(working_set, element, seen, seen_blocks, output)?;
92    }
93
94    Ok(())
95}
96
97pub fn discover_captures_in_pipeline_element(
98    working_set: &StateWorkingSet,
99    element: &PipelineElement,
100    seen: &mut Vec<VarId>,
101    seen_blocks: &mut HashMap<BlockId, Vec<(VarId, Span)>>,
102    output: &mut Vec<(VarId, Span)>,
103) -> Result<(), ParseError> {
104    discover_captures_in_expr(working_set, &element.expr, seen, seen_blocks, output)?;
105
106    if let Some(redirection) = element.redirection.as_ref() {
107        match redirection {
108            PipelineRedirection::Single { target, .. } => {
109                if let Some(expr) = target.expr() {
110                    discover_captures_in_expr(working_set, expr, seen, seen_blocks, output)?;
111                }
112            }
113            PipelineRedirection::Separate { out, err } => {
114                if let Some(expr) = out.expr() {
115                    discover_captures_in_expr(working_set, expr, seen, seen_blocks, output)?;
116                }
117                if let Some(expr) = err.expr() {
118                    discover_captures_in_expr(working_set, expr, seen, seen_blocks, output)?;
119                }
120            }
121        }
122    }
123
124    Ok(())
125}
126
127pub fn discover_captures_in_pattern(pattern: &MatchPattern, seen: &mut Vec<VarId>) {
128    match &pattern.pattern {
129        Pattern::Variable(var_id) => seen.push(*var_id),
130        Pattern::List(items) => {
131            for item in items {
132                discover_captures_in_pattern(item, seen)
133            }
134        }
135        Pattern::Record(items) => {
136            for item in items {
137                discover_captures_in_pattern(&item.1, seen)
138            }
139        }
140        Pattern::Or(patterns) => {
141            for pattern in patterns {
142                discover_captures_in_pattern(pattern, seen)
143            }
144        }
145        Pattern::Rest(var_id) => seen.push(*var_id),
146        Pattern::Expression(_)
147        | Pattern::Value(_)
148        | Pattern::IgnoreValue
149        | Pattern::IgnoreRest
150        | Pattern::Garbage => {}
151    }
152}
153
154pub fn discover_captures_in_expr(
155    working_set: &StateWorkingSet,
156    expr: &Expression,
157    seen: &mut Vec<VarId>,
158    seen_blocks: &mut HashMap<BlockId, Vec<(VarId, Span)>>,
159    output: &mut Vec<(VarId, Span)>,
160) -> Result<(), ParseError> {
161    match &expr.expr {
162        Expr::AttributeBlock(ab) => {
163            discover_captures_in_expr(working_set, &ab.item, seen, seen_blocks, output)?;
164        }
165        Expr::BinaryOp(lhs, _, rhs) => {
166            discover_captures_in_expr(working_set, lhs, seen, seen_blocks, output)?;
167            discover_captures_in_expr(working_set, rhs, seen, seen_blocks, output)?;
168        }
169        Expr::UnaryNot(expr) => {
170            discover_captures_in_expr(working_set, expr, seen, seen_blocks, output)?;
171        }
172        Expr::Closure(block_id) => {
173            let block = working_set.get_block(*block_id);
174            let results = {
175                let mut seen = vec![];
176                let mut results = vec![];
177
178                discover_captures_in_closure(
179                    working_set,
180                    block,
181                    &mut seen,
182                    seen_blocks,
183                    &mut results,
184                )?;
185
186                for (var_id, span) in results.iter() {
187                    if !seen.contains(var_id)
188                        && let Some(variable) = working_set.get_variable_if_possible(*var_id)
189                        && variable.mutable
190                    {
191                        return Err(ParseError::CaptureOfMutableVar(*span));
192                    }
193                }
194
195                results
196            };
197            seen_blocks.insert(*block_id, results.clone());
198            for (var_id, span) in results.into_iter() {
199                if !seen.contains(&var_id) {
200                    output.push((var_id, span))
201                }
202            }
203        }
204        Expr::Block(block_id) => {
205            let block = working_set.get_block(*block_id);
206            // FIXME: is this correct?
207            let results = {
208                let mut seen = vec![];
209                let mut results = vec![];
210                discover_captures_in_closure(
211                    working_set,
212                    block,
213                    &mut seen,
214                    seen_blocks,
215                    &mut results,
216                )?;
217                results
218            };
219
220            seen_blocks.insert(*block_id, results.clone());
221            for (var_id, span) in results.into_iter() {
222                if !seen.contains(&var_id) {
223                    output.push((var_id, span))
224                }
225            }
226        }
227        Expr::Binary(_) => {}
228        Expr::Bool(_) => {}
229        Expr::Call(call) => {
230            if let Some(head_expr) = call.parser_info.get(PERCENT_FORCED_BUILTIN_PARSER_INFO) {
231                discover_captures_in_expr(working_set, head_expr, seen, seen_blocks, output)?;
232            } else {
233                let decl = working_set.get_decl(call.decl_id);
234                if let Some(block_id) = decl.block_id() {
235                    match seen_blocks.get(&block_id) {
236                        Some(capture_list) => {
237                            // Push captures onto the outer closure that aren't created by that outer closure
238                            for capture in capture_list {
239                                if !seen.contains(&capture.0) {
240                                    output.push(*capture);
241                                }
242                            }
243                        }
244                        None => {
245                            let block = working_set.get_block(block_id);
246                            if !block.captures.is_empty() {
247                                for (capture, span) in &block.captures {
248                                    if !seen.contains(capture) {
249                                        output.push((*capture, *span));
250                                    }
251                                }
252                            } else {
253                                let result = {
254                                    let mut seen = vec![];
255                                    seen_blocks.insert(block_id, vec![]);
256
257                                    let mut result = vec![];
258                                    discover_captures_in_closure(
259                                        working_set,
260                                        block,
261                                        &mut seen,
262                                        seen_blocks,
263                                        &mut result,
264                                    )?;
265
266                                    result
267                                };
268                                // Push captures onto the outer closure that aren't created by that outer closure
269                                for capture in &result {
270                                    if !seen.contains(&capture.0) {
271                                        output.push(*capture);
272                                    }
273                                }
274
275                                seen_blocks.insert(block_id, result);
276                            }
277                        }
278                    }
279                }
280            }
281
282            for arg in &call.arguments {
283                match arg {
284                    Argument::Named(named) => {
285                        if let Some(arg) = &named.2 {
286                            discover_captures_in_expr(working_set, arg, seen, seen_blocks, output)?;
287                        }
288                    }
289                    Argument::Positional(expr)
290                    | Argument::Unknown(expr)
291                    | Argument::Spread(expr) => {
292                        discover_captures_in_expr(working_set, expr, seen, seen_blocks, output)?;
293                    }
294                }
295            }
296        }
297        Expr::CellPath(_) => {}
298        Expr::DateTime(_) => {}
299        Expr::ExternalCall(head, args) => {
300            discover_captures_in_expr(working_set, head, seen, seen_blocks, output)?;
301
302            for ExternalArgument::Regular(expr) | ExternalArgument::Spread(expr) in args.as_ref() {
303                discover_captures_in_expr(working_set, expr, seen, seen_blocks, output)?;
304            }
305        }
306        Expr::Filepath(_, _) => {}
307        Expr::Directory(_, _) => {}
308        Expr::Float(_) => {}
309        Expr::FullCellPath(cell_path) => {
310            discover_captures_in_expr(working_set, &cell_path.head, seen, seen_blocks, output)?;
311        }
312        Expr::ImportPattern(_) => {}
313        Expr::Overlay(_) => {}
314        Expr::Garbage => {}
315        Expr::Nothing => {}
316        Expr::GlobPattern(_, _) => {}
317        Expr::Int(_) => {}
318        Expr::Keyword(kw) => {
319            discover_captures_in_expr(working_set, &kw.expr, seen, seen_blocks, output)?;
320        }
321        Expr::List(list) => {
322            for item in list {
323                discover_captures_in_expr(working_set, item.expr(), seen, seen_blocks, output)?;
324            }
325        }
326        Expr::Operator(_) => {}
327        Expr::Range(range) => {
328            if let Some(from) = &range.from {
329                discover_captures_in_expr(working_set, from, seen, seen_blocks, output)?;
330            }
331            if let Some(next) = &range.next {
332                discover_captures_in_expr(working_set, next, seen, seen_blocks, output)?;
333            }
334            if let Some(to) = &range.to {
335                discover_captures_in_expr(working_set, to, seen, seen_blocks, output)?;
336            }
337        }
338        Expr::Record(items) => {
339            for item in items {
340                match item {
341                    RecordItem::Pair(field_name, field_value) => {
342                        discover_captures_in_expr(
343                            working_set,
344                            field_name,
345                            seen,
346                            seen_blocks,
347                            output,
348                        )?;
349                        discover_captures_in_expr(
350                            working_set,
351                            field_value,
352                            seen,
353                            seen_blocks,
354                            output,
355                        )?;
356                    }
357                    RecordItem::Spread(_, record) => {
358                        discover_captures_in_expr(working_set, record, seen, seen_blocks, output)?;
359                    }
360                }
361            }
362        }
363        Expr::Signature(sig) => {
364            // Something with a declaration, similar to a var decl, will introduce more VarIds into the stack at eval
365            for pos in &sig.required_positional {
366                if let Some(var_id) = pos.var_id {
367                    seen.push(var_id);
368                }
369            }
370            for pos in &sig.optional_positional {
371                if let Some(var_id) = pos.var_id {
372                    seen.push(var_id);
373                }
374            }
375            if let Some(rest) = &sig.rest_positional
376                && let Some(var_id) = rest.var_id
377            {
378                seen.push(var_id);
379            }
380            for named in &sig.named {
381                if let Some(var_id) = named.var_id {
382                    seen.push(var_id);
383                }
384            }
385        }
386        Expr::String(_) => {}
387        Expr::RawString(_) => {}
388        Expr::StringInterpolation(exprs) | Expr::GlobInterpolation(exprs, _) => {
389            for expr in exprs {
390                discover_captures_in_expr(working_set, expr, seen, seen_blocks, output)?;
391            }
392        }
393        Expr::MatchBlock(match_block) => {
394            for match_ in match_block {
395                discover_captures_in_pattern(&match_.0, seen);
396                discover_captures_in_expr(working_set, &match_.1, seen, seen_blocks, output)?;
397            }
398        }
399        Expr::Collect(var_id, expr) => {
400            seen.push(*var_id);
401            discover_captures_in_expr(working_set, expr, seen, seen_blocks, output)?
402        }
403        Expr::RowCondition(block_id) | Expr::Subexpression(block_id) => {
404            let block = working_set.get_block(*block_id);
405
406            let results = {
407                let mut results = vec![];
408                let mut seen = vec![];
409                discover_captures_in_closure(
410                    working_set,
411                    block,
412                    &mut seen,
413                    seen_blocks,
414                    &mut results,
415                )?;
416                results
417            };
418
419            seen_blocks.insert(*block_id, results.clone());
420            for (var_id, span) in results.into_iter() {
421                if !seen.contains(&var_id) {
422                    output.push((var_id, span))
423                }
424            }
425        }
426        Expr::Table(table) => {
427            for header in table.columns.as_ref() {
428                discover_captures_in_expr(working_set, header, seen, seen_blocks, output)?;
429            }
430            for row in table.rows.as_ref() {
431                for cell in row.as_ref() {
432                    discover_captures_in_expr(working_set, cell, seen, seen_blocks, output)?;
433                }
434            }
435        }
436        Expr::ValueWithUnit(value) => {
437            discover_captures_in_expr(working_set, &value.expr, seen, seen_blocks, output)?;
438        }
439        Expr::Var(var_id) => {
440            if (*var_id > ENV_VARIABLE_ID || *var_id == IN_VARIABLE_ID) && !seen.contains(var_id) {
441                output.push((*var_id, expr.span));
442            }
443        }
444        Expr::VarDecl(var_id) => {
445            seen.push(*var_id);
446        }
447    }
448    Ok(())
449}
450
451pub(crate) fn wrap_redirection_with_collect(
452    working_set: &mut StateWorkingSet,
453    target: RedirectionTarget,
454) -> RedirectionTarget {
455    match target {
456        RedirectionTarget::File { expr, append, span } => RedirectionTarget::File {
457            expr: wrap_expr_with_collect(working_set, expr, None),
458            span,
459            append,
460        },
461        RedirectionTarget::Pipe { span } => RedirectionTarget::Pipe { span },
462    }
463}
464
465pub(crate) fn wrap_element_with_collect(
466    working_set: &mut StateWorkingSet,
467    element: PipelineElement,
468    input_type: Option<&Type>,
469) -> PipelineElement {
470    PipelineElement {
471        pipe: element.pipe,
472        expr: wrap_expr_with_collect(working_set, element.expr, input_type),
473        redirection: element.redirection.map(|r| match r {
474            PipelineRedirection::Single { source, target } => PipelineRedirection::Single {
475                source,
476                target: wrap_redirection_with_collect(working_set, target),
477            },
478            PipelineRedirection::Separate { out, err } => PipelineRedirection::Separate {
479                out: wrap_redirection_with_collect(working_set, out),
480                err: wrap_redirection_with_collect(working_set, err),
481            },
482        }),
483    }
484}
485
486pub(crate) fn wrap_expr_with_collect(
487    working_set: &mut StateWorkingSet,
488    mut expr: Expression,
489    input_type: Option<&Type>,
490) -> Expression {
491    let span = expr.span;
492
493    // IN_VARIABLE_ID should get replaced with a unique variable, so that we don't have to
494    // execute as a closure
495    let var_id = working_set.add_variable(
496        b"$in".into(),
497        Span::new(span.start, span.start),
498        input_type.cloned().unwrap_or(Type::Any),
499        false,
500    );
501    expr.replace_in_variable(working_set, var_id);
502
503    // Bind the custom `$in` variable for that particular expression
504    let ty = expr.ty.clone();
505    Expression::new(
506        working_set,
507        Expr::Collect(var_id, Box::new(expr)),
508        span,
509        // We can expect it to have the same result type
510        ty,
511    )
512}
513
514pub fn parse(
515    working_set: &mut StateWorkingSet,
516    fname: Option<&str>,
517    contents: &[u8],
518    scoped: bool,
519) -> Arc<Block> {
520    trace!("parse");
521
522    let file_id = {
523        let fname = fname.map(nu_path::expand_to_real_path);
524        let fname = fname.as_deref().map(|p| p.to_string_lossy());
525        let name = fname.as_deref().unwrap_or("source");
526        working_set.add_file(name, contents)
527    };
528
529    let new_span = working_set.get_span_for_file(file_id);
530
531    let previously_parsed_block = working_set.find_block_by_span(new_span);
532
533    let mut output = {
534        if let Some(block) = previously_parsed_block {
535            return block;
536        } else {
537            let (output, err) = lex(contents, new_span.start, &[], &[], false);
538            if let Some(err) = err {
539                working_set.error(err)
540            }
541
542            Arc::new(parse_block(
543                working_set,
544                &output,
545                new_span,
546                scoped,
547                false,
548                None,
549            ))
550        }
551    };
552
553    // Top level `Block`s are compiled eagerly, as they don't have a parent which would cause them
554    // to be compiled later.
555    if working_set.parse_errors.is_empty() {
556        compile_block(working_set, Arc::make_mut(&mut output));
557    }
558
559    let mut seen = vec![];
560    let mut seen_blocks = HashMap::new();
561
562    let mut captures = vec![];
563    match discover_captures_in_closure(
564        working_set,
565        &output,
566        &mut seen,
567        &mut seen_blocks,
568        &mut captures,
569    ) {
570        Ok(_) => {
571            Arc::make_mut(&mut output).captures = captures;
572        }
573        Err(err) => working_set.error(err),
574    }
575
576    // Also check other blocks that might have been imported
577    let mut errors = vec![];
578    for (block_idx, block) in working_set.delta.blocks.iter().enumerate() {
579        let block_id = block_idx + working_set.permanent_state.num_blocks();
580        let block_id = BlockId::new(block_id);
581
582        if !seen_blocks.contains_key(&block_id) {
583            let mut captures = vec![];
584
585            match discover_captures_in_closure(
586                working_set,
587                block,
588                &mut seen,
589                &mut seen_blocks,
590                &mut captures,
591            ) {
592                Ok(_) => {
593                    seen_blocks.insert(block_id, captures);
594                }
595                Err(err) => {
596                    errors.push(err);
597                }
598            }
599        }
600    }
601    for err in errors {
602        working_set.error(err)
603    }
604
605    for (block_id, captures) in seen_blocks.into_iter() {
606        // In theory, we should only be updating captures where we have new information
607        // the only place where this is possible would be blocks that are newly created
608        // by our working set delta. If we ever tried to modify the permanent state, we'd
609        // panic (again, in theory, this shouldn't be possible)
610        let block = working_set.get_block(block_id);
611        let block_captures_empty = block.captures.is_empty();
612        // need to check block_id >= working_set.permanent_state.num_blocks()
613        // to avoid mutate a block that is in the permanent state.
614        // this can happened if user defines a function with recursive call
615        // and pipe a variable to the command, e.g:
616        // def px [] { if true { 42 } else { px } };    # the block px is saved in permanent state.
617        // let x = 3
618        // $x | px
619        // If we don't guard for `block_id`, it will change captures of `px`, which is
620        // already saved in permanent state
621        if !captures.is_empty()
622            && block_captures_empty
623            && block_id.get() >= working_set.permanent_state.num_blocks()
624        {
625            let block = working_set.get_block_mut(block_id);
626            block.captures = captures;
627        }
628    }
629
630    output
631}