1use nu_protocol::{
2 DeclId, GetSpan, Span, SyntaxShape, VarId,
3 ast::{
4 Argument, Block, Expr, Expression, ExternalArgument, ImportPatternMember, ListItem,
5 MatchPattern, PathMember, Pattern, Pipeline, PipelineElement, PipelineRedirection,
6 RecordItem, RedirectionTarget,
7 },
8 engine::StateWorkingSet,
9};
10use std::fmt::{Display, Formatter, Result};
11
12#[derive(Debug, Eq, PartialEq, Ord, Clone, PartialOrd)]
13pub enum FlatShape {
14 Binary,
15 Block,
16 Bool,
17 Closure,
18 Custom(DeclId),
19 DateTime,
20 Directory,
21 External(Box<Span>),
25 ExternalArg,
26 ExternalResolved,
27 Filepath,
28 Flag,
29 Float,
30 Garbage,
31 GlobInterpolation,
32 GlobPattern,
33 Int,
34 InternalCall(DeclId),
35 Keyword,
36 List,
37 Literal,
38 MatchPattern,
39 Nothing,
40 Operator,
41 Pipe,
42 Range,
43 RawString,
44 Record,
45 Redirection,
46 Signature,
47 String,
48 StringInterpolation,
49 Table,
50 Variable(VarId),
51 VarDecl(VarId),
52}
53
54impl FlatShape {
55 pub fn as_str(&self) -> &str {
56 match self {
57 FlatShape::Binary => "shape_binary",
58 FlatShape::Block => "shape_block",
59 FlatShape::Bool => "shape_bool",
60 FlatShape::Closure => "shape_closure",
61 FlatShape::Custom(_) => "shape_custom",
62 FlatShape::DateTime => "shape_datetime",
63 FlatShape::Directory => "shape_directory",
64 FlatShape::External(_) => "shape_external",
65 FlatShape::ExternalArg => "shape_externalarg",
66 FlatShape::ExternalResolved => "shape_external_resolved",
67 FlatShape::Filepath => "shape_filepath",
68 FlatShape::Flag => "shape_flag",
69 FlatShape::Float => "shape_float",
70 FlatShape::Garbage => "shape_garbage",
71 FlatShape::GlobInterpolation => "shape_glob_interpolation",
72 FlatShape::GlobPattern => "shape_globpattern",
73 FlatShape::Int => "shape_int",
74 FlatShape::InternalCall(_) => "shape_internalcall",
75 FlatShape::Keyword => "shape_keyword",
76 FlatShape::List => "shape_list",
77 FlatShape::Literal => "shape_literal",
78 FlatShape::MatchPattern => "shape_match_pattern",
79 FlatShape::Nothing => "shape_nothing",
80 FlatShape::Operator => "shape_operator",
81 FlatShape::Pipe => "shape_pipe",
82 FlatShape::Range => "shape_range",
83 FlatShape::RawString => "shape_raw_string",
84 FlatShape::Record => "shape_record",
85 FlatShape::Redirection => "shape_redirection",
86 FlatShape::Signature => "shape_signature",
87 FlatShape::String => "shape_string",
88 FlatShape::StringInterpolation => "shape_string_interpolation",
89 FlatShape::Table => "shape_table",
90 FlatShape::Variable(_) => "shape_variable",
91 FlatShape::VarDecl(_) => "shape_vardecl",
92 }
93 }
94}
95
96impl Display for FlatShape {
97 fn fmt(&self, f: &mut Formatter) -> Result {
98 f.write_str(self.as_str())
99 }
100}
101
102fn flatten_block_into(
110 working_set: &StateWorkingSet,
111 block: &Block,
112 output: &mut Vec<(Span, FlatShape)>,
113) {
114 for pipeline in &block.pipelines {
115 flatten_pipeline_into(working_set, pipeline, output);
116 }
117}
118
119fn flatten_pipeline_into(
120 working_set: &StateWorkingSet,
121 pipeline: &Pipeline,
122 output: &mut Vec<(Span, FlatShape)>,
123) {
124 for expr in &pipeline.elements {
125 flatten_pipeline_element_into(working_set, expr, output)
126 }
127}
128
129fn flatten_pipeline_element_into(
130 working_set: &StateWorkingSet,
131 pipeline_element: &PipelineElement,
132 output: &mut Vec<(Span, FlatShape)>,
133) {
134 if let Some(span) = pipeline_element.pipe
137 && span.end <= pipeline_element.expr.span.start
138 {
139 output.push((span, FlatShape::Garbage));
140 }
141
142 flatten_expression_into(working_set, &pipeline_element.expr, output);
143
144 if let Some(redirection) = pipeline_element.redirection.as_ref() {
145 let flatten_redirection_target = |target: &RedirectionTarget| {
146 let span = target.span();
147 if working_set.get_span_contents(span) == b"2>" {
150 (span, FlatShape::Garbage)
151 } else {
152 (span, FlatShape::Redirection)
153 }
154 };
155 match redirection {
156 PipelineRedirection::Single { target, .. } => {
157 output.push(flatten_redirection_target(target));
158 if let Some(expr) = target.expr() {
159 flatten_expression_into(working_set, expr, output);
160 }
161 }
162 PipelineRedirection::Separate { out, err } => {
163 let (out, err) = if out.span() <= err.span() {
164 (out, err)
165 } else {
166 (err, out)
167 };
168
169 output.push(flatten_redirection_target(out));
170 if let Some(expr) = out.expr() {
171 flatten_expression_into(working_set, expr, output);
172 }
173 output.push(flatten_redirection_target(err));
174 if let Some(expr) = err.expr() {
175 flatten_expression_into(working_set, expr, output);
176 }
177 }
178 }
179 }
180
181 if let Some(span) = pipeline_element.pipe
183 && span.end > pipeline_element.expr.span.start
184 {
185 if let Some((last_span, _)) = output.last_mut()
188 && span == *last_span
189 {
190 last_span.end = last_span.end.saturating_sub(1);
191 output.push((
192 Span::new(span.end.saturating_sub(1), span.end),
193 FlatShape::Pipe,
194 ));
195 } else {
196 let shape = if span.len() > 1 {
199 FlatShape::Garbage
200 } else {
201 FlatShape::Pipe
202 };
203 output.push((span, shape));
204 }
205 }
206}
207
208fn flatten_positional_arg_into(
209 working_set: &StateWorkingSet,
210 positional: &Expression,
211 shape: &SyntaxShape,
212 output: &mut Vec<(Span, FlatShape)>,
213) {
214 if matches!(shape, SyntaxShape::ExternalArgument)
215 && matches!(positional.expr, Expr::String(..) | Expr::GlobPattern(..))
216 {
217 output.push((positional.span, FlatShape::ExternalArg));
219 } else {
220 flatten_expression_into(working_set, positional, output)
221 }
222}
223
224fn flatten_expression_into(
225 working_set: &StateWorkingSet,
226 expr: &Expression,
227 output: &mut Vec<(Span, FlatShape)>,
228) {
229 match &expr.expr {
230 Expr::AttributeBlock(ab) => {
231 for attr in &ab.attributes {
232 flatten_expression_into(working_set, &attr.expr, output);
233 }
234 flatten_expression_into(working_set, &ab.item, output);
235 }
236 Expr::BinaryOp(lhs, op, rhs) => {
237 flatten_expression_into(working_set, lhs, output);
238 flatten_expression_into(working_set, op, output);
239 flatten_expression_into(working_set, rhs, output);
240 }
241 Expr::UnaryNot(not) => {
242 output.push((
243 Span::new(expr.span.start, expr.span.start + 3),
244 FlatShape::Operator,
245 ));
246 flatten_expression_into(working_set, not, output);
247 }
248 Expr::Collect(_, expr) => {
249 flatten_expression_into(working_set, expr, output);
250 }
251 Expr::Closure(block_id) => {
252 let outer_span = expr.span;
253
254 let block = working_set.get_block(*block_id);
255 let flattened = flatten_block(working_set, block);
256
257 if let Some(first) = flattened.first()
258 && first.0.start > outer_span.start
259 {
260 output.push((
261 Span::new(outer_span.start, first.0.start),
262 FlatShape::Closure,
263 ));
264 }
265
266 let last = if let Some(last) = flattened.last() {
267 if last.0.end < outer_span.end {
268 Some((Span::new(last.0.end, outer_span.end), FlatShape::Closure))
269 } else {
270 None
271 }
272 } else {
273 Some((outer_span, FlatShape::Closure))
275 };
276
277 output.extend(flattened);
278 if let Some(last) = last {
279 output.push(last);
280 }
281 }
282 Expr::Block(block_id) | Expr::RowCondition(block_id) | Expr::Subexpression(block_id) => {
283 let outer_span = expr.span;
284
285 let flattened = flatten_block(working_set, working_set.get_block(*block_id));
286
287 if let Some(first) = flattened.first()
288 && first.0.start > outer_span.start
289 {
290 output.push((Span::new(outer_span.start, first.0.start), FlatShape::Block));
291 }
292
293 let last = if let Some(last) = flattened.last() {
294 if last.0.end < outer_span.end {
295 Some((Span::new(last.0.end, outer_span.end), FlatShape::Block))
296 } else {
297 None
298 }
299 } else {
300 None
301 };
302
303 output.extend(flattened);
304 if let Some(last) = last {
305 output.push(last);
306 }
307 }
308 Expr::Call(call) => {
309 let decl = working_set.get_decl(call.decl_id);
310
311 if call.head.end != 0 {
312 output.push((call.head, FlatShape::InternalCall(call.decl_id)));
314 }
315
316 let signature = decl.signature();
318 let mut positional_args = signature
319 .required_positional
320 .iter()
321 .chain(&signature.optional_positional);
322
323 let arg_start = output.len();
324 for arg in &call.arguments {
325 match arg {
326 Argument::Positional(positional) => {
327 let positional_arg = positional_args.next();
328 let shape = positional_arg
329 .or(signature.rest_positional.as_ref())
330 .map(|arg| &arg.shape)
331 .unwrap_or(&SyntaxShape::Any);
332
333 flatten_positional_arg_into(working_set, positional, shape, output)
334 }
335 Argument::Unknown(positional) => {
336 let shape = signature
337 .rest_positional
338 .as_ref()
339 .map(|arg| &arg.shape)
340 .unwrap_or(&SyntaxShape::Any);
341
342 flatten_positional_arg_into(working_set, positional, shape, output)
343 }
344 Argument::Named(named) => {
345 if named.0.span.end != 0 {
346 output.push((named.0.span, FlatShape::Flag));
348 }
349 if let Some(expr) = &named.2 {
350 flatten_expression_into(working_set, expr, output);
351 }
352 }
353 Argument::Spread(expr) => {
354 output.push((
355 Span::new(expr.span.start - 3, expr.span.start),
356 FlatShape::Operator,
357 ));
358 flatten_expression_into(working_set, expr, output);
359 }
360 }
361 }
362 output[arg_start..].sort();
364 }
365 Expr::ExternalCall(head, args) => {
366 if let Expr::String(..) | Expr::GlobPattern(..) = &head.expr {
367 let span = working_set.get_span(head.span_id);
374 output.push((span, FlatShape::External(Box::new(head.span))));
375 } else {
376 flatten_expression_into(working_set, head, output);
377 }
378
379 for arg in args.as_ref() {
380 match arg {
381 ExternalArgument::Regular(expr) => {
382 if let Expr::String(..) | Expr::GlobPattern(..) = &expr.expr {
383 output.push((expr.span, FlatShape::ExternalArg));
384 } else {
385 flatten_expression_into(working_set, expr, output);
386 }
387 }
388 ExternalArgument::Spread(expr) => {
389 output.push((
390 Span::new(expr.span.start - 3, expr.span.start),
391 FlatShape::Operator,
392 ));
393 flatten_expression_into(working_set, expr, output);
394 }
395 }
396 }
397 }
398 Expr::Garbage => output.push((expr.span, FlatShape::Garbage)),
399 Expr::Nothing => output.push((expr.span, FlatShape::Nothing)),
400 Expr::DateTime(_) => output.push((expr.span, FlatShape::DateTime)),
401 Expr::Binary(_) => output.push((expr.span, FlatShape::Binary)),
402 Expr::Int(_) => output.push((expr.span, FlatShape::Int)),
403 Expr::Float(_) => output.push((expr.span, FlatShape::Float)),
404 Expr::MatchBlock(matches) => {
405 for (pattern, expr) in matches {
406 flatten_pattern_into(pattern, output);
407 flatten_expression_into(working_set, expr, output);
408 }
409 }
410 Expr::ValueWithUnit(value) => {
411 flatten_expression_into(working_set, &value.expr, output);
412 output.push((value.unit.span, FlatShape::String));
413 }
414 Expr::CellPath(cell_path) => {
415 output.extend(cell_path.members.iter().map(|member| match *member {
416 PathMember::String { span, .. } => (span, FlatShape::String),
417 PathMember::Int { span, .. } => (span, FlatShape::Int),
418 }));
419 }
420 Expr::FullCellPath(cell_path) => {
421 flatten_expression_into(working_set, &cell_path.head, output);
422 output.extend(cell_path.tail.iter().map(|member| match *member {
423 PathMember::String { span, .. } => (span, FlatShape::String),
424 PathMember::Int { span, .. } => (span, FlatShape::Int),
425 }));
426 }
427 Expr::ImportPattern(import_pattern) => {
428 output.push((import_pattern.head.span, FlatShape::String));
429
430 for member in &import_pattern.members {
431 match member {
432 ImportPatternMember::Glob { span } => output.push((*span, FlatShape::String)),
433 ImportPatternMember::Name { span, .. } => {
434 output.push((*span, FlatShape::String))
435 }
436 ImportPatternMember::List { names } => {
437 output.extend(names.iter().map(|&(_, span)| (span, FlatShape::String)))
438 }
439 }
440 }
441 }
442 Expr::Overlay(_) => output.push((expr.span, FlatShape::String)),
443 Expr::Range(range) => {
444 if let Some(f) = &range.from {
445 flatten_expression_into(working_set, f, output);
446 }
447 if let Some(s) = &range.next {
448 output.push((range.operator.next_op_span, FlatShape::Operator));
449 flatten_expression_into(working_set, s, output);
450 }
451 output.push((range.operator.span, FlatShape::Operator));
452 if let Some(t) = &range.to {
453 flatten_expression_into(working_set, t, output);
454 }
455 }
456 Expr::Bool(_) => output.push((expr.span, FlatShape::Bool)),
457 Expr::Filepath(_, _) => output.push((expr.span, FlatShape::Filepath)),
458 Expr::Directory(_, _) => output.push((expr.span, FlatShape::Directory)),
459 Expr::GlobPattern(_, _) => output.push((expr.span, FlatShape::GlobPattern)),
460 Expr::List(list) => {
461 let outer_span = expr.span;
462 let mut last_end = outer_span.start;
463
464 for item in list {
465 match item {
466 ListItem::Item(expr) => {
467 let flattened = flatten_expression(working_set, expr);
468
469 if let Some(first) = flattened.first()
470 && first.0.start > last_end
471 {
472 output.push((Span::new(last_end, first.0.start), FlatShape::List));
473 }
474
475 if let Some(last) = flattened.last() {
476 last_end = last.0.end;
477 }
478
479 output.extend(flattened);
480 }
481 ListItem::Spread(op_span, expr) => {
482 if op_span.start > last_end {
483 output.push((Span::new(last_end, op_span.start), FlatShape::List));
484 }
485 output.push((*op_span, FlatShape::Operator));
486 last_end = op_span.end;
487
488 let flattened_inner = flatten_expression(working_set, expr);
489 if let Some(first) = flattened_inner.first()
490 && first.0.start > last_end
491 {
492 output.push((Span::new(last_end, first.0.start), FlatShape::List));
493 }
494 if let Some(last) = flattened_inner.last() {
495 last_end = last.0.end;
496 }
497 output.extend(flattened_inner);
498 }
499 }
500 }
501
502 if last_end < outer_span.end {
503 output.push((Span::new(last_end, outer_span.end), FlatShape::List));
504 }
505 }
506 Expr::StringInterpolation(exprs) => {
507 let mut flattened = vec![];
508 for expr in exprs {
509 flatten_expression_into(working_set, expr, &mut flattened);
510 }
511
512 if let Some(first) = flattened.first()
513 && first.0.start != expr.span.start
514 {
515 output.push((
517 Span::new(expr.span.start, expr.span.start + 2),
518 FlatShape::StringInterpolation,
519 ));
520 flattened.push((
521 Span::new(expr.span.end - 1, expr.span.end),
522 FlatShape::StringInterpolation,
523 ));
524 }
525 output.extend(flattened);
526 }
527 Expr::GlobInterpolation(exprs, quoted) => {
528 let mut flattened = vec![];
529 for expr in exprs {
530 flatten_expression_into(working_set, expr, &mut flattened);
531 }
532
533 if *quoted {
534 output.push((
536 Span::new(expr.span.start, expr.span.start + 2),
537 FlatShape::GlobInterpolation,
538 ));
539 flattened.push((
540 Span::new(expr.span.end - 1, expr.span.end),
541 FlatShape::GlobInterpolation,
542 ));
543 }
544 output.extend(flattened);
545 }
546 Expr::Record(list) => {
547 let outer_span = expr.span;
548 let mut last_end = outer_span.start;
549
550 for l in list {
551 match l {
552 RecordItem::Pair(key, val) => {
553 let flattened_lhs = flatten_expression(working_set, key);
554 let flattened_rhs = flatten_expression(working_set, val);
555
556 if let Some(first) = flattened_lhs.first()
557 && first.0.start > last_end
558 {
559 output.push((Span::new(last_end, first.0.start), FlatShape::Record));
560 }
561 if let Some(last) = flattened_lhs.last() {
562 last_end = last.0.end;
563 }
564 output.extend(flattened_lhs);
565
566 if let Some(first) = flattened_rhs.first()
567 && first.0.start > last_end
568 {
569 output.push((Span::new(last_end, first.0.start), FlatShape::Record));
570 }
571 if let Some(last) = flattened_rhs.last() {
572 last_end = last.0.end;
573 }
574
575 output.extend(flattened_rhs);
576 }
577 RecordItem::Spread(op_span, record) => {
578 if op_span.start > last_end {
579 output.push((Span::new(last_end, op_span.start), FlatShape::Record));
580 }
581 output.push((*op_span, FlatShape::Operator));
582 last_end = op_span.end;
583
584 let flattened = flatten_expression(working_set, record);
585 if let Some(first) = flattened.first()
586 && first.0.start > last_end
587 {
588 output.push((Span::new(last_end, first.0.start), FlatShape::Record));
589 }
590 if let Some(last) = flattened.last() {
591 last_end = last.0.end;
592 }
593 output.extend(flattened);
594 }
595 }
596 }
597 if last_end < outer_span.end {
598 output.push((Span::new(last_end, outer_span.end), FlatShape::Record));
599 }
600 }
601 Expr::Keyword(kw) => {
602 output.push((kw.span, FlatShape::Keyword));
603 flatten_expression_into(working_set, &kw.expr, output);
604 }
605 Expr::Operator(_) => output.push((expr.span, FlatShape::Operator)),
606 Expr::Signature(_) => output.push((expr.span, FlatShape::Signature)),
607 Expr::String(_) => output.push((expr.span, FlatShape::String)),
608 Expr::RawString(_) => output.push((expr.span, FlatShape::RawString)),
609 Expr::Table(table) => {
610 let outer_span = expr.span;
611 let mut last_end = outer_span.start;
612
613 for col in table.columns.as_ref() {
614 let flattened = flatten_expression(working_set, col);
615 if let Some(first) = flattened.first()
616 && first.0.start > last_end
617 {
618 output.push((Span::new(last_end, first.0.start), FlatShape::Table));
619 }
620
621 if let Some(last) = flattened.last() {
622 last_end = last.0.end;
623 }
624
625 output.extend(flattened);
626 }
627 for row in table.rows.as_ref() {
628 for expr in row.as_ref() {
629 let flattened = flatten_expression(working_set, expr);
630 if let Some(first) = flattened.first()
631 && first.0.start > last_end
632 {
633 output.push((Span::new(last_end, first.0.start), FlatShape::Table));
634 }
635
636 if let Some(last) = flattened.last() {
637 last_end = last.0.end;
638 }
639
640 output.extend(flattened);
641 }
642 }
643
644 if last_end < outer_span.end {
645 output.push((Span::new(last_end, outer_span.end), FlatShape::Table));
646 }
647 }
648 Expr::Var(var_id) => output.push((expr.span, FlatShape::Variable(*var_id))),
649 Expr::VarDecl(var_id) => output.push((expr.span, FlatShape::VarDecl(*var_id))),
650 }
651}
652
653fn flatten_pattern_into(match_pattern: &MatchPattern, output: &mut Vec<(Span, FlatShape)>) {
654 match &match_pattern.pattern {
655 Pattern::Garbage => output.push((match_pattern.span, FlatShape::Garbage)),
656 Pattern::IgnoreValue => output.push((match_pattern.span, FlatShape::Nothing)),
657 Pattern::IgnoreRest => output.push((match_pattern.span, FlatShape::Nothing)),
658 Pattern::List(items) => {
659 if let Some(first) = items.first() {
660 if let Some(last) = items.last() {
661 output.push((
662 Span::new(match_pattern.span.start, first.span.start),
663 FlatShape::MatchPattern,
664 ));
665 for item in items {
666 flatten_pattern_into(item, output);
667 }
668 output.push((
669 Span::new(last.span.end, match_pattern.span.end),
670 FlatShape::MatchPattern,
671 ))
672 }
673 } else {
674 output.push((match_pattern.span, FlatShape::MatchPattern));
675 }
676 }
677 Pattern::Record(items) => {
678 if let Some(first) = items.first() {
679 if let Some(last) = items.last() {
680 output.push((
681 Span::new(match_pattern.span.start, first.1.span.start),
682 FlatShape::MatchPattern,
683 ));
684 for (_, pattern) in items {
685 flatten_pattern_into(pattern, output);
686 }
687 output.push((
688 Span::new(last.1.span.end, match_pattern.span.end),
689 FlatShape::MatchPattern,
690 ))
691 }
692 } else {
693 output.push((match_pattern.span, FlatShape::MatchPattern));
694 }
695 }
696 Pattern::Expression(_) | Pattern::Value(_) => {
697 output.push((match_pattern.span, FlatShape::MatchPattern))
698 }
699 Pattern::Variable(var_id) => output.push((match_pattern.span, FlatShape::VarDecl(*var_id))),
700 Pattern::Rest(var_id) => output.push((match_pattern.span, FlatShape::VarDecl(*var_id))),
701 Pattern::Or(patterns) => {
702 for pattern in patterns {
703 flatten_pattern_into(pattern, output);
704 }
705 }
706 }
707}
708
709pub fn flatten_block(working_set: &StateWorkingSet, block: &Block) -> Vec<(Span, FlatShape)> {
710 let mut output = Vec::new();
711 flatten_block_into(working_set, block, &mut output);
712 output
713}
714
715pub fn flatten_pipeline(
716 working_set: &StateWorkingSet,
717 pipeline: &Pipeline,
718) -> Vec<(Span, FlatShape)> {
719 let mut output = Vec::new();
720 flatten_pipeline_into(working_set, pipeline, &mut output);
721 output
722}
723
724pub fn flatten_pipeline_element(
725 working_set: &StateWorkingSet,
726 pipeline_element: &PipelineElement,
727) -> Vec<(Span, FlatShape)> {
728 let mut output = Vec::new();
729 flatten_pipeline_element_into(working_set, pipeline_element, &mut output);
730 output
731}
732
733pub fn flatten_expression(
734 working_set: &StateWorkingSet,
735 expr: &Expression,
736) -> Vec<(Span, FlatShape)> {
737 let mut output = Vec::new();
738 flatten_expression_into(working_set, expr, &mut output);
739 output
740}