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 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 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 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 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 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 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 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 let ty = expr.ty.clone();
505 Expression::new(
506 working_set,
507 Expr::Collect(var_id, Box::new(expr)),
508 span,
509 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 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 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 let block = working_set.get_block(block_id);
611 let block_captures_empty = block.captures.is_empty();
612 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}