nu_parser/
flatten.rs

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