nu_parser/
parser.rs

1#![allow(clippy::byte_char_slices)]
2
3use crate::{
4    Token, TokenContents,
5    lex::{LexState, is_assignment_operator, lex, lex_n_tokens, lex_signature},
6    lite_parser::{LiteCommand, LitePipeline, LiteRedirection, LiteRedirectionTarget, lite_parse},
7    parse_keywords::*,
8    parse_patterns::parse_pattern,
9    parse_shape_specs::{parse_completer, parse_shape_name, parse_type},
10    type_check::{self, check_range_types, math_result_type, type_compatible},
11};
12use itertools::Itertools;
13use log::trace;
14use nu_engine::DIR_VAR_PARSER_INFO;
15use nu_protocol::{
16    BlockId, DeclId, DidYouMean, ENV_VARIABLE_ID, FilesizeUnit, Flag, IN_VARIABLE_ID, ParseError,
17    PositionalArg, ShellError, Signature, Span, Spanned, SyntaxShape, Type, Value, VarId, ast::*,
18    casing::Casing, did_you_mean, engine::StateWorkingSet, eval_const::eval_constant,
19};
20use std::{
21    collections::{HashMap, HashSet},
22    str,
23    sync::Arc,
24};
25
26pub fn garbage(working_set: &mut StateWorkingSet, span: Span) -> Expression {
27    Expression::garbage(working_set, span)
28}
29
30pub fn garbage_pipeline(working_set: &mut StateWorkingSet, spans: &[Span]) -> Pipeline {
31    Pipeline::from_vec(vec![garbage(working_set, Span::concat(spans))])
32}
33
34fn is_identifier_byte(b: u8) -> bool {
35    b != b'.'
36        && b != b'['
37        && b != b'('
38        && b != b'{'
39        && b != b'+'
40        && b != b'-'
41        && b != b'*'
42        && b != b'^'
43        && b != b'/'
44        && b != b'='
45        && b != b'!'
46        && b != b'<'
47        && b != b'>'
48        && b != b'&'
49        && b != b'|'
50}
51
52pub fn is_math_expression_like(working_set: &mut StateWorkingSet, span: Span) -> bool {
53    let bytes = working_set.get_span_contents(span);
54    if bytes.is_empty() {
55        return false;
56    }
57
58    if bytes == b"true"
59        || bytes == b"false"
60        || bytes == b"null"
61        || bytes == b"not"
62        || bytes == b"if"
63        || bytes == b"match"
64    {
65        return true;
66    }
67
68    let b = bytes[0];
69
70    // check for raw string
71    if bytes.starts_with(b"r#") {
72        return true;
73    }
74
75    if b == b'(' || b == b'{' || b == b'[' || b == b'$' || b == b'"' || b == b'\'' || b == b'-' {
76        return true;
77    }
78
79    let starting_error_count = working_set.parse_errors.len();
80
81    // Number
82    parse_number(working_set, span);
83    if working_set.parse_errors.len() == starting_error_count {
84        return true;
85    }
86    working_set.parse_errors.truncate(starting_error_count);
87
88    // Filesize
89    parse_filesize(working_set, span);
90    if working_set.parse_errors.len() == starting_error_count {
91        return true;
92    }
93    working_set.parse_errors.truncate(starting_error_count);
94
95    parse_duration(working_set, span);
96    if working_set.parse_errors.len() == starting_error_count {
97        return true;
98    }
99    working_set.parse_errors.truncate(starting_error_count);
100
101    parse_datetime(working_set, span);
102    if working_set.parse_errors.len() == starting_error_count {
103        return true;
104    }
105    working_set.parse_errors.truncate(starting_error_count);
106
107    parse_binary(working_set, span);
108    // We need an additional negate match to check if the last error was unexpected
109    // or more specifically, if it was `ParseError::InvalidBinaryString`.
110    // If so, we suppress the error and stop parsing to the next (which is `parse_range()`).
111    if working_set.parse_errors.len() == starting_error_count {
112        return true;
113    } else if !matches!(
114        working_set.parse_errors.last(),
115        Some(ParseError::Expected(_, _))
116    ) {
117        working_set.parse_errors.truncate(starting_error_count);
118        return true;
119    }
120    working_set.parse_errors.truncate(starting_error_count);
121
122    let is_range = parse_range(working_set, span).is_some();
123    working_set.parse_errors.truncate(starting_error_count);
124    is_range
125}
126
127fn is_env_variable_name(bytes: &[u8]) -> bool {
128    if bytes.is_empty() {
129        return false;
130    }
131
132    let first = bytes[0];
133    if !first.is_ascii_alphabetic() && first != b'_' {
134        return false;
135    }
136
137    bytes
138        .iter()
139        .skip(1)
140        .all(|&b| b.is_ascii_alphanumeric() || b == b'_')
141}
142
143fn is_identifier(bytes: &[u8]) -> bool {
144    bytes.iter().all(|x| is_identifier_byte(*x))
145}
146
147pub fn is_variable(bytes: &[u8]) -> bool {
148    if bytes.len() > 1 && bytes[0] == b'$' {
149        is_identifier(&bytes[1..])
150    } else {
151        is_identifier(bytes)
152    }
153}
154
155pub fn trim_quotes(bytes: &[u8]) -> &[u8] {
156    if (bytes.starts_with(b"\"") && bytes.ends_with(b"\"") && bytes.len() > 1)
157        || (bytes.starts_with(b"\'") && bytes.ends_with(b"\'") && bytes.len() > 1)
158        || (bytes.starts_with(b"`") && bytes.ends_with(b"`") && bytes.len() > 1)
159    {
160        &bytes[1..(bytes.len() - 1)]
161    } else {
162        bytes
163    }
164}
165
166pub fn trim_quotes_str(s: &str) -> &str {
167    if (s.starts_with('"') && s.ends_with('"') && s.len() > 1)
168        || (s.starts_with('\'') && s.ends_with('\'') && s.len() > 1)
169        || (s.starts_with('`') && s.ends_with('`') && s.len() > 1)
170    {
171        &s[1..(s.len() - 1)]
172    } else {
173        s
174    }
175}
176
177/// Return type of `check_call`
178#[derive(Debug, PartialEq, Eq)]
179pub enum CallKind {
180    Help,
181    Valid,
182    Invalid,
183}
184
185pub(crate) fn check_call(
186    working_set: &mut StateWorkingSet,
187    command: Span,
188    sig: &Signature,
189    call: &Call,
190) -> CallKind {
191    // Allow the call to pass if they pass in the help flag
192    if call.named_iter().any(|(n, _, _)| n.item == "help") {
193        return CallKind::Help;
194    }
195
196    if call.positional_len() < sig.required_positional.len() {
197        let end_offset = call
198            .positional_iter()
199            .last()
200            .map(|last| last.span.end)
201            .unwrap_or(command.end);
202        // Comparing the types of all signature positional arguments against the parsed
203        // expressions found in the call. If one type is not found then it could be assumed
204        // that that positional argument is missing from the parsed call
205        for argument in &sig.required_positional {
206            let found = call.positional_iter().fold(false, |ac, expr| {
207                if argument.shape.to_type() == expr.ty || argument.shape == SyntaxShape::Any {
208                    true
209                } else {
210                    ac
211                }
212            });
213            if !found {
214                working_set.error(ParseError::MissingPositional(
215                    argument.name.clone(),
216                    Span::new(end_offset, end_offset),
217                    sig.call_signature(),
218                ));
219                return CallKind::Invalid;
220            }
221        }
222
223        let missing = &sig.required_positional[call.positional_len()];
224        working_set.error(ParseError::MissingPositional(
225            missing.name.clone(),
226            Span::new(end_offset, end_offset),
227            sig.call_signature(),
228        ));
229        return CallKind::Invalid;
230    } else {
231        for req_flag in sig.named.iter().filter(|x| x.required) {
232            if call.named_iter().all(|(n, _, _)| n.item != req_flag.long) {
233                working_set.error(ParseError::MissingRequiredFlag(
234                    req_flag.long.clone(),
235                    command,
236                ));
237                return CallKind::Invalid;
238            }
239        }
240    }
241    CallKind::Valid
242}
243
244/// Parses an unknown argument for the given signature. This handles the parsing as appropriate to
245/// the rest type of the command.
246fn parse_unknown_arg(
247    working_set: &mut StateWorkingSet,
248    span: Span,
249    signature: &Signature,
250) -> Expression {
251    let shape = signature
252        .rest_positional
253        .as_ref()
254        .map(|arg| arg.shape.clone())
255        .unwrap_or(SyntaxShape::Any);
256
257    parse_value(working_set, span, &shape)
258}
259
260/// Parses a string in the arg or head position of an external call.
261///
262/// If the string begins with `r#`, it is parsed as a raw string. If it doesn't contain any quotes
263/// or parentheses, it is parsed as a glob pattern so that tilde and glob expansion can be handled
264/// by `run-external`. Otherwise, we use a custom state machine to put together an interpolated
265/// string, where each balanced pair of quotes is parsed as a separate part of the string, and then
266/// concatenated together.
267///
268/// For example, `-foo="bar\nbaz"` becomes `$"-foo=bar\nbaz"`
269fn parse_external_string(working_set: &mut StateWorkingSet, span: Span) -> Expression {
270    let contents = working_set.get_span_contents(span);
271
272    if contents.starts_with(b"r#") {
273        parse_raw_string(working_set, span)
274    } else if contents
275        .iter()
276        .any(|b| matches!(b, b'"' | b'\'' | b'(' | b')' | b'`'))
277    {
278        enum State {
279            Bare {
280                from: usize,
281            },
282            BackTickQuote {
283                from: usize,
284            },
285            Quote {
286                from: usize,
287                quote_char: u8,
288                escaped: bool,
289            },
290            Parenthesized {
291                from: usize,
292                depth: usize,
293            },
294        }
295        // Find the spans of parts of the string that can be parsed as their own strings for
296        // concatenation.
297        //
298        // By passing each of these parts to `parse_string()`, we can eliminate the quotes and also
299        // handle string interpolation.
300        let make_span = |from: usize, index: usize| Span {
301            start: span.start + from,
302            end: span.start + index,
303        };
304        let mut spans = vec![];
305        let mut state = State::Bare { from: 0 };
306        let mut index = 0;
307        while index < contents.len() {
308            let ch = contents[index];
309            match &mut state {
310                State::Bare { from } => match ch {
311                    b'"' | b'\'' => {
312                        // Push bare string
313                        if index != *from {
314                            spans.push(make_span(*from, index));
315                        }
316                        // then transition to other state
317                        state = State::Quote {
318                            from: index,
319                            quote_char: ch,
320                            escaped: false,
321                        };
322                    }
323                    b'$' => {
324                        if let Some(&quote_char @ (b'"' | b'\'')) = contents.get(index + 1) {
325                            // Start a dollar quote (interpolated string)
326                            if index != *from {
327                                spans.push(make_span(*from, index));
328                            }
329                            state = State::Quote {
330                                from: index,
331                                quote_char,
332                                escaped: false,
333                            };
334                            // Skip over two chars (the dollar sign and the quote)
335                            index += 2;
336                            continue;
337                        }
338                    }
339                    b'`' => {
340                        if index != *from {
341                            spans.push(make_span(*from, index))
342                        }
343                        state = State::BackTickQuote { from: index }
344                    }
345                    b'(' => {
346                        if index != *from {
347                            spans.push(make_span(*from, index))
348                        }
349                        state = State::Parenthesized {
350                            from: index,
351                            depth: 1,
352                        }
353                    }
354                    // Continue to consume
355                    _ => (),
356                },
357                State::Quote {
358                    from,
359                    quote_char,
360                    escaped,
361                } => match ch {
362                    ch if ch == *quote_char && !*escaped => {
363                        // quoted string ended, just make a new span for it.
364                        spans.push(make_span(*from, index + 1));
365                        // go back to Bare state.
366                        state = State::Bare { from: index + 1 };
367                    }
368                    b'\\' if !*escaped && *quote_char == b'"' => {
369                        // The next token is escaped so it doesn't count (only for double quote)
370                        *escaped = true;
371                    }
372                    _ => {
373                        *escaped = false;
374                    }
375                },
376                State::BackTickQuote { from } => {
377                    if ch == b'`' {
378                        spans.push(make_span(*from, index + 1));
379                        state = State::Bare { from: index + 1 };
380                    }
381                }
382                State::Parenthesized { from, depth } => {
383                    if ch == b')' {
384                        if *depth == 1 {
385                            spans.push(make_span(*from, index + 1));
386                            state = State::Bare { from: index + 1 };
387                        } else {
388                            *depth -= 1;
389                        }
390                    } else if ch == b'(' {
391                        *depth += 1;
392                    }
393                }
394            }
395            index += 1;
396        }
397
398        // Add the final span
399        match state {
400            State::Bare { from }
401            | State::Quote { from, .. }
402            | State::Parenthesized { from, .. }
403            | State::BackTickQuote { from, .. } => {
404                if from < contents.len() {
405                    spans.push(make_span(from, contents.len()));
406                }
407            }
408        }
409
410        // Log the spans that will be parsed
411        if log::log_enabled!(log::Level::Trace) {
412            let contents = spans
413                .iter()
414                .map(|span| String::from_utf8_lossy(working_set.get_span_contents(*span)))
415                .collect::<Vec<_>>();
416
417            trace!("parsing: external string, parts: {contents:?}")
418        }
419
420        // Check if the whole thing is quoted. If not, it should be a glob
421        let quoted =
422            (contents.len() >= 3 && contents.starts_with(b"$\"") && contents.ends_with(b"\""))
423                || is_quoted(contents);
424
425        // Parse each as its own string
426        let exprs: Vec<Expression> = spans
427            .into_iter()
428            .map(|span| parse_string(working_set, span))
429            .collect();
430
431        if exprs
432            .iter()
433            .all(|expr| matches!(expr.expr, Expr::String(..)))
434        {
435            // If the exprs are all strings anyway, just collapse into a single string.
436            let string = exprs
437                .into_iter()
438                .map(|expr| {
439                    let Expr::String(contents) = expr.expr else {
440                        unreachable!("already checked that this was a String")
441                    };
442                    contents
443                })
444                .collect::<String>();
445            if quoted {
446                Expression::new(working_set, Expr::String(string), span, Type::String)
447            } else {
448                Expression::new(
449                    working_set,
450                    Expr::GlobPattern(string, false),
451                    span,
452                    Type::Glob,
453                )
454            }
455        } else {
456            // Flatten any string interpolations contained with the exprs.
457            let exprs = exprs
458                .into_iter()
459                .flat_map(|expr| match expr.expr {
460                    Expr::StringInterpolation(subexprs) => subexprs,
461                    _ => vec![expr],
462                })
463                .collect();
464            // Make an interpolation out of the expressions. Use `GlobInterpolation` if it's a bare
465            // word, so that the unquoted state can get passed through to `run-external`.
466            if quoted {
467                Expression::new(
468                    working_set,
469                    Expr::StringInterpolation(exprs),
470                    span,
471                    Type::String,
472                )
473            } else {
474                Expression::new(
475                    working_set,
476                    Expr::GlobInterpolation(exprs, false),
477                    span,
478                    Type::Glob,
479                )
480            }
481        }
482    } else {
483        parse_glob_pattern(working_set, span)
484    }
485}
486
487fn parse_external_arg(working_set: &mut StateWorkingSet, span: Span) -> ExternalArgument {
488    let contents = working_set.get_span_contents(span);
489
490    if contents.len() > 3
491        && contents.starts_with(b"...")
492        && (contents[3] == b'$' || contents[3] == b'[' || contents[3] == b'(')
493    {
494        ExternalArgument::Spread(parse_value(
495            working_set,
496            Span::new(span.start + 3, span.end),
497            &SyntaxShape::List(Box::new(SyntaxShape::Any)),
498        ))
499    } else {
500        ExternalArgument::Regular(parse_regular_external_arg(working_set, span))
501    }
502}
503
504fn parse_regular_external_arg(working_set: &mut StateWorkingSet, span: Span) -> Expression {
505    let contents = working_set.get_span_contents(span);
506
507    if contents.starts_with(b"$") {
508        parse_dollar_expr(working_set, span)
509    } else if contents.starts_with(b"(") {
510        parse_paren_expr(working_set, span, &SyntaxShape::Any)
511    } else if contents.starts_with(b"[") {
512        parse_list_expression(working_set, span, &SyntaxShape::Any)
513    } else {
514        parse_external_string(working_set, span)
515    }
516}
517
518pub fn parse_external_call(working_set: &mut StateWorkingSet, spans: &[Span]) -> Expression {
519    trace!("parse external");
520
521    let head_contents = working_set.get_span_contents(spans[0]);
522
523    let head_span = if head_contents.starts_with(b"^") {
524        Span::new(spans[0].start + 1, spans[0].end)
525    } else {
526        spans[0]
527    };
528
529    let head_contents = working_set.get_span_contents(head_span).to_vec();
530
531    let head = if head_contents.starts_with(b"$") || head_contents.starts_with(b"(") {
532        // the expression is inside external_call, so it's a subexpression
533        let arg = parse_expression(working_set, &[head_span]);
534        Box::new(arg)
535    } else {
536        Box::new(parse_external_string(working_set, head_span))
537    };
538
539    let args = spans[1..]
540        .iter()
541        .map(|&span| parse_external_arg(working_set, span))
542        .collect();
543
544    Expression::new(
545        working_set,
546        Expr::ExternalCall(head, args),
547        Span::concat(spans),
548        Type::Any,
549    )
550}
551
552fn ensure_flag_arg_type(
553    working_set: &mut StateWorkingSet,
554    arg_name: String,
555    arg: Expression,
556    arg_shape: &SyntaxShape,
557    long_name_span: Span,
558) -> (Spanned<String>, Expression) {
559    if !type_compatible(&arg.ty, &arg_shape.to_type()) {
560        working_set.error(ParseError::TypeMismatch(
561            arg_shape.to_type(),
562            arg.ty,
563            arg.span,
564        ));
565        (
566            Spanned {
567                item: arg_name,
568                span: long_name_span,
569            },
570            Expression::garbage(working_set, arg.span),
571        )
572    } else {
573        (
574            Spanned {
575                item: arg_name,
576                span: long_name_span,
577            },
578            arg,
579        )
580    }
581}
582
583fn parse_long_flag(
584    working_set: &mut StateWorkingSet,
585    spans: &[Span],
586    spans_idx: &mut usize,
587    sig: &Signature,
588) -> (Option<Spanned<String>>, Option<Expression>) {
589    let arg_span = spans[*spans_idx];
590    let arg_contents = working_set.get_span_contents(arg_span);
591
592    if arg_contents.starts_with(b"--") {
593        // FIXME: only use the first flag you find?
594        let split: Vec<_> = arg_contents.split(|x| *x == b'=').collect();
595        let long_name = String::from_utf8(split[0].into());
596        if let Ok(long_name) = long_name {
597            let long_name = long_name[2..].to_string();
598            if let Some(flag) = sig.get_long_flag(&long_name) {
599                if let Some(arg_shape) = &flag.arg {
600                    if split.len() > 1 {
601                        // and we also have the argument
602                        let long_name_len = long_name.len();
603                        let mut span = arg_span;
604                        span.start += long_name_len + 3; //offset by long flag and '='
605
606                        let arg = parse_value(working_set, span, arg_shape);
607                        let (arg_name, val_expression) = ensure_flag_arg_type(
608                            working_set,
609                            long_name,
610                            arg,
611                            arg_shape,
612                            Span::new(arg_span.start, arg_span.start + long_name_len + 2),
613                        );
614                        (Some(arg_name), Some(val_expression))
615                    } else if let Some(arg) = spans.get(*spans_idx + 1) {
616                        let arg = parse_value(working_set, *arg, arg_shape);
617
618                        *spans_idx += 1;
619                        let (arg_name, val_expression) =
620                            ensure_flag_arg_type(working_set, long_name, arg, arg_shape, arg_span);
621                        (Some(arg_name), Some(val_expression))
622                    } else {
623                        working_set.error(ParseError::MissingFlagParam(
624                            arg_shape.to_string(),
625                            arg_span,
626                        ));
627                        // NOTE: still need to cover this incomplete flag in the final expression
628                        // see https://github.com/nushell/nushell/issues/16375
629                        (
630                            Some(Spanned {
631                                item: long_name,
632                                span: arg_span,
633                            }),
634                            None,
635                        )
636                    }
637                } else {
638                    // A flag with no argument
639                    // It can also takes a boolean value like --x=true
640                    if split.len() > 1 {
641                        // and we also have the argument
642                        let long_name_len = long_name.len();
643                        let mut span = arg_span;
644                        span.start += long_name_len + 3; //offset by long flag and '='
645
646                        let arg = parse_value(working_set, span, &SyntaxShape::Boolean);
647
648                        let (arg_name, val_expression) = ensure_flag_arg_type(
649                            working_set,
650                            long_name,
651                            arg,
652                            &SyntaxShape::Boolean,
653                            Span::new(arg_span.start, arg_span.start + long_name_len + 2),
654                        );
655                        (Some(arg_name), Some(val_expression))
656                    } else {
657                        (
658                            Some(Spanned {
659                                item: long_name,
660                                span: arg_span,
661                            }),
662                            None,
663                        )
664                    }
665                }
666            } else {
667                let suggestion = did_you_mean(sig.get_names(), &long_name)
668                    .map(|name| format!("Did you mean: `--{name}`?"))
669                    .unwrap_or("Use `--help` to see available flags".to_owned());
670                working_set.error(ParseError::UnknownFlag(
671                    sig.name.clone(),
672                    long_name.clone(),
673                    arg_span,
674                    suggestion,
675                ));
676                (
677                    Some(Spanned {
678                        item: long_name.clone(),
679                        span: arg_span,
680                    }),
681                    None,
682                )
683            }
684        } else {
685            working_set.error(ParseError::NonUtf8(arg_span));
686            (
687                Some(Spanned {
688                    item: "--".into(),
689                    span: arg_span,
690                }),
691                None,
692            )
693        }
694    } else {
695        (None, None)
696    }
697}
698
699fn parse_short_flags(
700    working_set: &mut StateWorkingSet,
701    spans: &[Span],
702    spans_idx: &mut usize,
703    positional_idx: usize,
704    sig: &Signature,
705) -> Option<Vec<Flag>> {
706    let arg_span = spans[*spans_idx];
707
708    let arg_contents = working_set.get_span_contents(arg_span);
709
710    if let Ok(arg_contents_uft8_ref) = str::from_utf8(arg_contents) {
711        if arg_contents_uft8_ref.starts_with('-') && arg_contents_uft8_ref.len() > 1 {
712            let short_flags = &arg_contents_uft8_ref[1..];
713            let num_chars = short_flags.chars().count();
714            let mut found_short_flags = vec![];
715            let mut unmatched_short_flags = vec![];
716            for (offset, short_flag) in short_flags.char_indices() {
717                let short_flag_span = Span::new(
718                    arg_span.start + 1 + offset,
719                    arg_span.start + 1 + offset + short_flag.len_utf8(),
720                );
721                if let Some(flag) = sig.get_short_flag(short_flag) {
722                    // Allow args in short flag batches as long as it is the last flag.
723                    if flag.arg.is_some() && offset < num_chars - 1 {
724                        working_set
725                            .error(ParseError::OnlyLastFlagInBatchCanTakeArg(short_flag_span));
726                        break;
727                    }
728                    found_short_flags.push(flag);
729                } else {
730                    unmatched_short_flags.push(short_flag_span);
731                }
732            }
733
734            if found_short_flags.is_empty()
735                // check to see if we have a negative number
736                && matches!(
737                    sig.get_positional(positional_idx),
738                    Some(PositionalArg {
739                        shape: SyntaxShape::Int | SyntaxShape::Number | SyntaxShape::Float,
740                        ..
741                    })
742                )
743                && String::from_utf8_lossy(working_set.get_span_contents(arg_span))
744                    .parse::<f64>()
745                    .is_ok()
746            {
747                return None;
748            } else if let Some(first) = unmatched_short_flags.first() {
749                let contents = working_set.get_span_contents(*first);
750                working_set.error(ParseError::UnknownFlag(
751                    sig.name.clone(),
752                    format!("-{}", String::from_utf8_lossy(contents)),
753                    *first,
754                    "Use `--help` to see available flags".to_owned(),
755                ));
756            }
757
758            Some(found_short_flags)
759        } else {
760            None
761        }
762    } else {
763        working_set.error(ParseError::NonUtf8(arg_span));
764        None
765    }
766}
767
768fn first_kw_idx(
769    working_set: &StateWorkingSet,
770    signature: &Signature,
771    spans: &[Span],
772    spans_idx: usize,
773    positional_idx: usize,
774) -> (Option<usize>, usize) {
775    for idx in (positional_idx + 1)..signature.num_positionals() {
776        if let Some(PositionalArg {
777            shape: SyntaxShape::Keyword(kw, ..),
778            ..
779        }) = signature.get_positional(idx)
780        {
781            for (span_idx, &span) in spans.iter().enumerate().skip(spans_idx) {
782                let contents = working_set.get_span_contents(span);
783
784                if contents == kw {
785                    return (Some(idx), span_idx);
786                }
787            }
788        }
789    }
790    (None, spans.len())
791}
792
793fn calculate_end_span(
794    working_set: &StateWorkingSet,
795    signature: &Signature,
796    spans: &[Span],
797    spans_idx: usize,
798    positional_idx: usize,
799) -> usize {
800    if signature.rest_positional.is_some() {
801        spans.len()
802    } else {
803        let (kw_pos, kw_idx) =
804            first_kw_idx(working_set, signature, spans, spans_idx, positional_idx);
805
806        if let Some(kw_pos) = kw_pos {
807            // We found a keyword. Keywords, once found, create a guidepost to
808            // show us where the positionals will lay into the arguments. Because they're
809            // keywords, they get to set this by being present
810
811            let positionals_between = kw_pos - positional_idx - 1;
812            if positionals_between >= (kw_idx - spans_idx) {
813                kw_idx
814            } else {
815                kw_idx - positionals_between
816            }
817        } else {
818            // Make space for the remaining require positionals, if we can
819            // spans_idx < spans.len() is an invariant
820            let remaining_spans = spans.len() - (spans_idx + 1);
821            // positional_idx can be larger than required_positional.len() if we have optional args
822            let remaining_positional = signature
823                .required_positional
824                .len()
825                .saturating_sub(positional_idx + 1);
826            // Saturates to 0 when we have too few args
827            let extra_spans = remaining_spans.saturating_sub(remaining_positional);
828            spans_idx + 1 + extra_spans
829        }
830    }
831}
832
833fn parse_oneof(
834    working_set: &mut StateWorkingSet,
835    spans: &[Span],
836    spans_idx: &mut usize,
837    possible_shapes: &Vec<SyntaxShape>,
838    multispan: bool,
839) -> Expression {
840    let starting_spans_idx = *spans_idx;
841    let mut best_guess = None;
842    let mut best_guess_errors = Vec::new();
843    let mut max_first_error_offset = 0;
844    let mut propagate_error = false;
845    for shape in possible_shapes {
846        let starting_error_count = working_set.parse_errors.len();
847        *spans_idx = starting_spans_idx;
848        let value = match multispan {
849            true => parse_multispan_value(working_set, spans, spans_idx, shape),
850            false => parse_value(working_set, spans[*spans_idx], shape),
851        };
852
853        let new_errors = working_set.parse_errors[starting_error_count..].to_vec();
854        // no new errors found means success
855        let Some(first_error_offset) = new_errors.iter().map(|e| e.span().start).min() else {
856            return value;
857        };
858
859        if first_error_offset > max_first_error_offset {
860            // while trying the possible shapes, ignore Expected type errors
861            // unless they're inside a block, closure, or expression
862            propagate_error = match working_set.parse_errors.last() {
863                Some(ParseError::Expected(_, error_span))
864                | Some(ParseError::ExpectedWithStringMsg(_, error_span)) => {
865                    matches!(
866                        shape,
867                        SyntaxShape::Block | SyntaxShape::Closure(_) | SyntaxShape::Expression
868                    ) && *error_span != spans[*spans_idx]
869                }
870                _ => true,
871            };
872            max_first_error_offset = first_error_offset;
873            best_guess = Some(value);
874            best_guess_errors = new_errors;
875        }
876        working_set.parse_errors.truncate(starting_error_count);
877    }
878
879    // if best_guess results in new errors further than current span, then accept it
880    // or propagate_error is marked as true for it
881    if max_first_error_offset > spans[starting_spans_idx].start || propagate_error {
882        working_set.parse_errors.extend(best_guess_errors);
883        best_guess.expect("best_guess should not be None here!")
884    } else {
885        working_set.error(ParseError::ExpectedWithStringMsg(
886            format!("one of a list of accepted shapes: {possible_shapes:?}"),
887            spans[starting_spans_idx],
888        ));
889        Expression::garbage(working_set, spans[starting_spans_idx])
890    }
891}
892
893pub fn parse_multispan_value(
894    working_set: &mut StateWorkingSet,
895    spans: &[Span],
896    spans_idx: &mut usize,
897    shape: &SyntaxShape,
898) -> Expression {
899    trace!("parse multispan value");
900    match shape {
901        SyntaxShape::VarWithOptType => {
902            trace!("parsing: var with opt type");
903
904            parse_var_with_opt_type(working_set, spans, spans_idx, false).0
905        }
906        SyntaxShape::RowCondition => {
907            trace!("parsing: row condition");
908            let arg = parse_row_condition(working_set, &spans[*spans_idx..]);
909            *spans_idx = spans.len() - 1;
910
911            arg
912        }
913        SyntaxShape::MathExpression => {
914            trace!("parsing: math expression");
915
916            let arg = parse_math_expression(working_set, &spans[*spans_idx..], None);
917            *spans_idx = spans.len() - 1;
918
919            arg
920        }
921        SyntaxShape::OneOf(possible_shapes) => {
922            parse_oneof(working_set, spans, spans_idx, possible_shapes, true)
923        }
924
925        SyntaxShape::Expression => {
926            trace!("parsing: expression");
927
928            // is it subexpression?
929            // Not sure, but let's make it not, so the behavior is the same as previous version of nushell.
930            let arg = parse_expression(working_set, &spans[*spans_idx..]);
931            *spans_idx = spans.len() - 1;
932
933            arg
934        }
935        SyntaxShape::Signature => {
936            trace!("parsing: signature");
937
938            let sig = parse_full_signature(working_set, &spans[*spans_idx..]);
939            *spans_idx = spans.len() - 1;
940
941            sig
942        }
943        SyntaxShape::Keyword(keyword, arg) => {
944            trace!(
945                "parsing: keyword({}) {:?}",
946                String::from_utf8_lossy(keyword),
947                arg
948            );
949            let arg_span = spans[*spans_idx];
950
951            let arg_contents = working_set.get_span_contents(arg_span);
952
953            if arg_contents != keyword {
954                // When keywords mismatch, this is a strong indicator of something going wrong.
955                // We won't often override the current error, but as this is a strong indicator
956                // go ahead and override the current error and tell the user about the missing
957                // keyword/literal.
958                working_set.error(ParseError::ExpectedKeyword(
959                    String::from_utf8_lossy(keyword).into(),
960                    arg_span,
961                ))
962            }
963
964            *spans_idx += 1;
965            if *spans_idx >= spans.len() {
966                working_set.error(ParseError::KeywordMissingArgument(
967                    arg.to_string(),
968                    String::from_utf8_lossy(keyword).into(),
969                    Span::new(spans[*spans_idx - 1].end, spans[*spans_idx - 1].end),
970                ));
971                let keyword = Keyword {
972                    keyword: keyword.as_slice().into(),
973                    span: spans[*spans_idx - 1],
974                    expr: Expression::garbage(working_set, arg_span),
975                };
976                return Expression::new(
977                    working_set,
978                    Expr::Keyword(Box::new(keyword)),
979                    arg_span,
980                    Type::Any,
981                );
982            }
983
984            let keyword = Keyword {
985                keyword: keyword.as_slice().into(),
986                span: spans[*spans_idx - 1],
987                expr: parse_multispan_value(working_set, spans, spans_idx, arg),
988            };
989
990            Expression::new(
991                working_set,
992                Expr::Keyword(Box::new(keyword.clone())),
993                keyword.span.merge(keyword.expr.span),
994                keyword.expr.ty,
995            )
996        }
997        _ => {
998            // All other cases are single-span values
999            let arg_span = spans[*spans_idx];
1000
1001            parse_value(working_set, arg_span, shape)
1002        }
1003    }
1004}
1005
1006pub struct ParsedInternalCall {
1007    pub call: Box<Call>,
1008    pub output: Type,
1009    pub call_kind: CallKind,
1010}
1011
1012/// Sometimes the arguments of an internal command need to be parsed in dedicated functions, e.g. `parse_module`.
1013/// If so, `parse_internal_call` should be called with the appropriate parsing level to avoid repetition.
1014///
1015/// Defaults to `ArgumentParsingLevel::Full`
1016#[derive(Default)]
1017pub enum ArgumentParsingLevel {
1018    #[default]
1019    Full,
1020    /// Parse only the first `k` arguments
1021    FirstK { k: usize },
1022}
1023
1024pub fn parse_internal_call(
1025    working_set: &mut StateWorkingSet,
1026    command_span: Span,
1027    spans: &[Span],
1028    decl_id: DeclId,
1029    arg_parsing_level: ArgumentParsingLevel,
1030) -> ParsedInternalCall {
1031    trace!("parsing: internal call (decl id: {})", decl_id.get());
1032
1033    let mut call = Call::new(command_span);
1034    call.decl_id = decl_id;
1035    call.head = command_span;
1036    let _ = working_set.add_span(call.head);
1037
1038    let decl = working_set.get_decl(decl_id);
1039    let signature = working_set.get_signature(decl);
1040    let output = signature.get_output_type();
1041
1042    let deprecation = decl.deprecation_info();
1043
1044    // storing the var ID for later due to borrowing issues
1045    let lib_dirs_var_id = match decl.name() {
1046        "use" | "overlay use" | "source-env" if decl.is_keyword() => {
1047            find_dirs_var(working_set, LIB_DIRS_VAR)
1048        }
1049        "nu-check" if decl.is_builtin() => find_dirs_var(working_set, LIB_DIRS_VAR),
1050        _ => None,
1051    };
1052
1053    // The index into the positional parameter in the definition
1054    let mut positional_idx = 0;
1055
1056    // The index into the spans of argument data given to parse
1057    // Starting at the first argument
1058    let mut spans_idx = 0;
1059
1060    if let Some(alias) = decl.as_alias() {
1061        if let Expression {
1062            expr: Expr::Call(wrapped_call),
1063            ..
1064        } = &alias.wrapped_call
1065        {
1066            // Replace this command's call with the aliased call, but keep the alias name
1067            call = *wrapped_call.clone();
1068            call.head = command_span;
1069            // Skip positionals passed to aliased call
1070            positional_idx = call.positional_len();
1071        } else {
1072            working_set.error(ParseError::UnknownState(
1073                "Alias does not point to internal call.".to_string(),
1074                command_span,
1075            ));
1076            return ParsedInternalCall {
1077                call: Box::new(call),
1078                output: Type::Any,
1079                call_kind: CallKind::Invalid,
1080            };
1081        }
1082    }
1083
1084    if let Some(var_id) = lib_dirs_var_id {
1085        call.set_parser_info(
1086            DIR_VAR_PARSER_INFO.to_owned(),
1087            Expression::new(working_set, Expr::Var(var_id), call.head, Type::Any),
1088        );
1089    }
1090
1091    if signature.creates_scope {
1092        working_set.enter_scope();
1093    }
1094
1095    while spans_idx < spans.len() {
1096        let arg_span = spans[spans_idx];
1097
1098        let starting_error_count = working_set.parse_errors.len();
1099        // Check if we're on a long flag, if so, parse
1100        let (long_name, arg) = parse_long_flag(working_set, spans, &mut spans_idx, &signature);
1101
1102        if let Some(long_name) = long_name {
1103            // We found a long flag, like --bar
1104            if working_set.parse_errors[starting_error_count..]
1105                .iter()
1106                .any(|x| matches!(x, ParseError::UnknownFlag(_, _, _, _)))
1107                && signature.allows_unknown_args
1108            {
1109                working_set.parse_errors.truncate(starting_error_count);
1110                let arg = parse_unknown_arg(working_set, arg_span, &signature);
1111
1112                call.add_unknown(arg);
1113            } else {
1114                call.add_named((long_name, None, arg));
1115            }
1116
1117            spans_idx += 1;
1118            continue;
1119        }
1120
1121        let starting_error_count = working_set.parse_errors.len();
1122
1123        // Check if we're on a short flag or group of short flags, if so, parse
1124        let short_flags = parse_short_flags(
1125            working_set,
1126            spans,
1127            &mut spans_idx,
1128            positional_idx,
1129            &signature,
1130        );
1131
1132        if let Some(mut short_flags) = short_flags {
1133            if short_flags.is_empty() {
1134                // workaround for completions (PR #6067)
1135                short_flags.push(Flag {
1136                    long: "".to_string(),
1137                    short: Some('a'),
1138                    arg: None,
1139                    required: false,
1140                    desc: "".to_string(),
1141                    var_id: None,
1142                    default_value: None,
1143                    completion: None,
1144                })
1145            }
1146
1147            if working_set.parse_errors[starting_error_count..]
1148                .iter()
1149                .any(|x| matches!(x, ParseError::UnknownFlag(_, _, _, _)))
1150                && signature.allows_unknown_args
1151            {
1152                working_set.parse_errors.truncate(starting_error_count);
1153                let arg = parse_unknown_arg(working_set, arg_span, &signature);
1154
1155                call.add_unknown(arg);
1156            } else {
1157                for flag in short_flags {
1158                    let _ = working_set.add_span(spans[spans_idx]);
1159
1160                    if let Some(arg_shape) = flag.arg {
1161                        if let Some(arg) = spans.get(spans_idx + 1) {
1162                            let arg = parse_value(working_set, *arg, &arg_shape);
1163                            let (arg_name, val_expression) = ensure_flag_arg_type(
1164                                working_set,
1165                                flag.long.clone(),
1166                                arg.clone(),
1167                                &arg_shape,
1168                                spans[spans_idx],
1169                            );
1170
1171                            if flag.long.is_empty() {
1172                                if let Some(short) = flag.short {
1173                                    call.add_named((
1174                                        arg_name,
1175                                        Some(Spanned {
1176                                            item: short.to_string(),
1177                                            span: spans[spans_idx],
1178                                        }),
1179                                        Some(val_expression),
1180                                    ));
1181                                }
1182                            } else {
1183                                call.add_named((arg_name, None, Some(val_expression)));
1184                            }
1185                            spans_idx += 1;
1186                        } else {
1187                            working_set.error(ParseError::MissingFlagParam(
1188                                arg_shape.to_string(),
1189                                arg_span,
1190                            ));
1191                            // NOTE: still need to cover this incomplete flag in the final expression
1192                            // see https://github.com/nushell/nushell/issues/16375
1193                            call.add_named((
1194                                Spanned {
1195                                    item: String::new(),
1196                                    span: spans[spans_idx],
1197                                },
1198                                None,
1199                                None,
1200                            ));
1201                        }
1202                    } else if flag.long.is_empty() {
1203                        if let Some(short) = flag.short {
1204                            call.add_named((
1205                                Spanned {
1206                                    item: String::new(),
1207                                    span: spans[spans_idx],
1208                                },
1209                                Some(Spanned {
1210                                    item: short.to_string(),
1211                                    span: spans[spans_idx],
1212                                }),
1213                                None,
1214                            ));
1215                        }
1216                    } else {
1217                        call.add_named((
1218                            Spanned {
1219                                item: flag.long.clone(),
1220                                span: spans[spans_idx],
1221                            },
1222                            None,
1223                            None,
1224                        ));
1225                    }
1226                }
1227            }
1228
1229            spans_idx += 1;
1230            continue;
1231        }
1232
1233        {
1234            let contents = working_set.get_span_contents(spans[spans_idx]);
1235
1236            if contents.len() > 3
1237                && contents.starts_with(b"...")
1238                && (contents[3] == b'$' || contents[3] == b'[' || contents[3] == b'(')
1239            {
1240                if signature.rest_positional.is_none() && !signature.allows_unknown_args {
1241                    working_set.error(ParseError::UnexpectedSpreadArg(
1242                        signature.call_signature(),
1243                        arg_span,
1244                    ));
1245                    call.add_positional(Expression::garbage(working_set, arg_span));
1246                } else if positional_idx < signature.required_positional.len() {
1247                    working_set.error(ParseError::MissingPositional(
1248                        signature.required_positional[positional_idx].name.clone(),
1249                        Span::new(spans[spans_idx].start, spans[spans_idx].start),
1250                        signature.call_signature(),
1251                    ));
1252                    call.add_positional(Expression::garbage(working_set, arg_span));
1253                } else {
1254                    let rest_shape = match &signature.rest_positional {
1255                        Some(arg) if matches!(arg.shape, SyntaxShape::ExternalArgument) => {
1256                            // External args aren't parsed inside lists in spread position.
1257                            SyntaxShape::Any
1258                        }
1259                        Some(arg) => arg.shape.clone(),
1260                        None => SyntaxShape::Any,
1261                    };
1262                    // Parse list of arguments to be spread
1263                    let args = parse_value(
1264                        working_set,
1265                        Span::new(arg_span.start + 3, arg_span.end),
1266                        &SyntaxShape::List(Box::new(rest_shape)),
1267                    );
1268
1269                    call.add_spread(args);
1270                    // Let the parser know that it's parsing rest arguments now
1271                    positional_idx =
1272                        signature.required_positional.len() + signature.optional_positional.len();
1273                }
1274
1275                spans_idx += 1;
1276                continue;
1277            }
1278        }
1279
1280        // Parse a positional arg if there is one
1281        if let Some(positional) = signature.get_positional(positional_idx) {
1282            let end = calculate_end_span(working_set, &signature, spans, spans_idx, positional_idx);
1283
1284            // Missing arguments before next keyword
1285            if end == spans_idx {
1286                let prev_span = if spans_idx == 0 {
1287                    command_span
1288                } else {
1289                    spans[spans_idx - 1]
1290                };
1291                let whitespace_span = Span::new(prev_span.end, spans[spans_idx].start);
1292                working_set.error(ParseError::MissingPositional(
1293                    positional.name.clone(),
1294                    whitespace_span,
1295                    signature.call_signature(),
1296                ));
1297                call.add_positional(Expression::garbage(working_set, whitespace_span));
1298                positional_idx += 1;
1299                continue;
1300            }
1301            debug_assert!(end <= spans.len());
1302
1303            if spans[..end].is_empty() || spans_idx == end {
1304                working_set.error(ParseError::MissingPositional(
1305                    positional.name.clone(),
1306                    Span::new(spans[spans_idx].end, spans[spans_idx].end),
1307                    signature.call_signature(),
1308                ));
1309                positional_idx += 1;
1310                continue;
1311            }
1312
1313            let compile_error_count = working_set.compile_errors.len();
1314
1315            // HACK: avoid repeated parsing of argument values in special cases
1316            // see https://github.com/nushell/nushell/issues/16398
1317            let arg = match arg_parsing_level {
1318                ArgumentParsingLevel::FirstK { k } if k <= positional_idx => {
1319                    Expression::garbage(working_set, spans[spans_idx])
1320                }
1321                _ => parse_multispan_value(
1322                    working_set,
1323                    &spans[..end],
1324                    &mut spans_idx,
1325                    &positional.shape,
1326                ),
1327            };
1328
1329            // HACK: try-catch's signature defines the catch block as a Closure, even though it's
1330            // used like a Block. Because closures are compiled eagerly, this ends up making the
1331            // following code technically invalid:
1332            // ```nu
1333            // loop { try { } catch {|e| break } }
1334            // ```
1335            // Thus, we discard the compilation error here
1336            if let SyntaxShape::Keyword(ref keyword, ..) = positional.shape
1337                && keyword == b"catch"
1338                && let [nu_protocol::CompileError::NotInALoop { .. }] =
1339                    &working_set.compile_errors[compile_error_count..]
1340            {
1341                working_set.compile_errors.truncate(compile_error_count);
1342            }
1343
1344            let arg = if !type_compatible(&positional.shape.to_type(), &arg.ty) {
1345                working_set.error(ParseError::TypeMismatch(
1346                    positional.shape.to_type(),
1347                    arg.ty,
1348                    arg.span,
1349                ));
1350                Expression::garbage(working_set, arg.span)
1351            } else {
1352                arg
1353            };
1354
1355            call.add_positional(arg);
1356            positional_idx += 1;
1357        } else if signature.allows_unknown_args {
1358            let arg = parse_unknown_arg(working_set, arg_span, &signature);
1359
1360            call.add_unknown(arg);
1361        } else {
1362            call.add_positional(Expression::garbage(working_set, arg_span));
1363            working_set.error(ParseError::ExtraPositional(
1364                signature.call_signature(),
1365                arg_span,
1366            ))
1367        }
1368
1369        spans_idx += 1;
1370    }
1371
1372    // TODO: Inline `check_call`,
1373    // move missing positional checking into the while loop above with two pointers.
1374    // Maybe more `CallKind::Invalid` if errors found during argument parsing.
1375    let call_kind = check_call(working_set, command_span, &signature, &call);
1376
1377    deprecation
1378        .into_iter()
1379        .filter_map(|entry| entry.parse_warning(&signature.name, &call))
1380        .for_each(|warning| {
1381            // FIXME: if two flags are deprecated and both are used in one command,
1382            // the second flag's deprecation won't show until the first flag is removed
1383            // (but it won't be flagged as reported until it is actually reported)
1384            working_set.warning(warning);
1385        });
1386
1387    if signature.creates_scope {
1388        working_set.exit_scope();
1389    }
1390
1391    ParsedInternalCall {
1392        call: Box::new(call),
1393        output,
1394        call_kind,
1395    }
1396}
1397
1398pub fn parse_call(working_set: &mut StateWorkingSet, spans: &[Span], head: Span) -> Expression {
1399    trace!("parsing: call");
1400
1401    if spans.is_empty() {
1402        working_set.error(ParseError::UnknownState(
1403            "Encountered command with zero spans".into(),
1404            Span::concat(spans),
1405        ));
1406        return garbage(working_set, head);
1407    }
1408
1409    let (cmd_start, pos, _name, maybe_decl_id) = find_longest_decl(working_set, spans);
1410
1411    if let Some(decl_id) = maybe_decl_id {
1412        // Before the internal parsing we check if there is no let or alias declarations
1413        // that are missing their name, e.g.: let = 1 or alias = 2
1414        if spans.len() > 1 {
1415            let test_equal = working_set.get_span_contents(spans[1]);
1416
1417            if test_equal == [b'='] {
1418                trace!("incomplete statement");
1419
1420                working_set.error(ParseError::UnknownState(
1421                    "Incomplete statement".into(),
1422                    Span::concat(spans),
1423                ));
1424                return garbage(working_set, Span::concat(spans));
1425            }
1426        }
1427
1428        let decl = working_set.get_decl(decl_id);
1429
1430        let parsed_call = if let Some(alias) = decl.as_alias() {
1431            if let Expression {
1432                expr: Expr::ExternalCall(head, args),
1433                span: _,
1434                span_id: _,
1435                ty,
1436            } = &alias.clone().wrapped_call
1437            {
1438                trace!("parsing: alias of external call");
1439
1440                let mut head = head.clone();
1441                head.span = Span::concat(&spans[cmd_start..pos]); // replacing the spans preserves syntax highlighting
1442
1443                let mut final_args = args.clone().into_vec();
1444                for arg_span in &spans[pos..] {
1445                    let arg = parse_external_arg(working_set, *arg_span);
1446                    final_args.push(arg);
1447                }
1448
1449                let expression = Expression::new(
1450                    working_set,
1451                    Expr::ExternalCall(head, final_args.into()),
1452                    Span::concat(spans),
1453                    ty.clone(),
1454                );
1455
1456                return expression;
1457            } else {
1458                trace!("parsing: alias of internal call");
1459                parse_internal_call(
1460                    working_set,
1461                    Span::concat(&spans[cmd_start..pos]),
1462                    &spans[pos..],
1463                    decl_id,
1464                    ArgumentParsingLevel::Full,
1465                )
1466            }
1467        } else {
1468            trace!("parsing: internal call");
1469            parse_internal_call(
1470                working_set,
1471                Span::concat(&spans[cmd_start..pos]),
1472                &spans[pos..],
1473                decl_id,
1474                ArgumentParsingLevel::Full,
1475            )
1476        };
1477
1478        Expression::new(
1479            working_set,
1480            Expr::Call(parsed_call.call),
1481            Span::concat(spans),
1482            parsed_call.output,
1483        )
1484    } else {
1485        // We might be parsing left-unbounded range ("..10")
1486        let bytes = working_set.get_span_contents(spans[0]);
1487        trace!("parsing: range {bytes:?}");
1488        if let (Some(b'.'), Some(b'.')) = (bytes.first(), bytes.get(1)) {
1489            trace!("-- found leading range indicator");
1490            let starting_error_count = working_set.parse_errors.len();
1491
1492            if let Some(range_expr) = parse_range(working_set, spans[0]) {
1493                trace!("-- successfully parsed range");
1494                return range_expr;
1495            }
1496            working_set.parse_errors.truncate(starting_error_count);
1497        }
1498        trace!("parsing: external call");
1499
1500        // Otherwise, try external command
1501        parse_external_call(working_set, spans)
1502    }
1503}
1504
1505pub fn find_longest_decl(
1506    working_set: &mut StateWorkingSet<'_>,
1507    spans: &[Span],
1508) -> (
1509    usize,
1510    usize,
1511    Vec<u8>,
1512    Option<nu_protocol::Id<nu_protocol::marker::Decl>>,
1513) {
1514    find_longest_decl_with_prefix(working_set, spans, b"")
1515}
1516
1517pub fn find_longest_decl_with_prefix(
1518    working_set: &mut StateWorkingSet<'_>,
1519    spans: &[Span],
1520    prefix: &[u8],
1521) -> (
1522    usize,
1523    usize,
1524    Vec<u8>,
1525    Option<nu_protocol::Id<nu_protocol::marker::Decl>>,
1526) {
1527    let mut pos = 0;
1528    let cmd_start = pos;
1529    let mut name_spans = vec![];
1530    let mut name = vec![];
1531    name.extend(prefix);
1532
1533    for word_span in spans[cmd_start..].iter() {
1534        // Find the longest group of words that could form a command
1535
1536        name_spans.push(*word_span);
1537
1538        let name_part = working_set.get_span_contents(*word_span);
1539        if name.is_empty() {
1540            name.extend(name_part);
1541        } else {
1542            name.push(b' ');
1543            name.extend(name_part);
1544        }
1545
1546        pos += 1;
1547    }
1548
1549    let mut maybe_decl_id = working_set.find_decl(&name);
1550
1551    while maybe_decl_id.is_none() {
1552        // Find the longest command match
1553        if name_spans.len() <= 1 {
1554            // Keep the first word even if it does not match -- could be external command
1555            break;
1556        }
1557
1558        name_spans.pop();
1559        pos -= 1;
1560
1561        // TODO: Refactor to avoid recreating name with an inner loop.
1562        name.clear();
1563        name.extend(prefix);
1564        for name_span in &name_spans {
1565            let name_part = working_set.get_span_contents(*name_span);
1566            if name.is_empty() {
1567                name.extend(name_part);
1568            } else {
1569                name.push(b' ');
1570                name.extend(name_part);
1571            }
1572        }
1573        maybe_decl_id = working_set.find_decl(&name);
1574    }
1575    (cmd_start, pos, name, maybe_decl_id)
1576}
1577
1578pub fn parse_attribute(
1579    working_set: &mut StateWorkingSet,
1580    lite_command: &LiteCommand,
1581) -> (Attribute, Option<String>) {
1582    let _ = lite_command
1583        .parts
1584        .first()
1585        .filter(|s| working_set.get_span_contents(**s).starts_with(b"@"))
1586        .expect("Attributes always start with an `@`");
1587
1588    assert!(
1589        lite_command.attribute_idx.is_empty(),
1590        "attributes can't have attributes"
1591    );
1592
1593    let mut spans = lite_command.parts.clone();
1594    if let Some(first) = spans.first_mut() {
1595        first.start += 1;
1596    }
1597    let spans = spans.as_slice();
1598    let attr_span = Span::concat(spans);
1599
1600    let (cmd_start, cmd_end, mut name, decl_id) =
1601        find_longest_decl_with_prefix(working_set, spans, b"attr");
1602
1603    debug_assert!(name.starts_with(b"attr "));
1604    let _ = name.drain(..(b"attr ".len()));
1605
1606    let name_span = Span::concat(&spans[cmd_start..cmd_end]);
1607
1608    let Ok(name) = String::from_utf8(name) else {
1609        working_set.error(ParseError::NonUtf8(name_span));
1610        return (
1611            Attribute {
1612                expr: garbage(working_set, attr_span),
1613            },
1614            None,
1615        );
1616    };
1617
1618    let Some(decl_id) = decl_id else {
1619        working_set.error(ParseError::UnknownCommand(name_span));
1620        return (
1621            Attribute {
1622                expr: garbage(working_set, attr_span),
1623            },
1624            None,
1625        );
1626    };
1627
1628    let decl = working_set.get_decl(decl_id);
1629
1630    let parsed_call = match decl.as_alias() {
1631        // TODO: Once `const def` is available, we should either disallow aliases as attributes OR
1632        // allow them but rather than using the aliases' name, use the name of the aliased command
1633        Some(alias) => match &alias.clone().wrapped_call {
1634            Expression {
1635                expr: Expr::ExternalCall(..),
1636                ..
1637            } => {
1638                let shell_error = ShellError::NotAConstCommand { span: name_span };
1639                working_set.error(shell_error.wrap(working_set, attr_span));
1640                return (
1641                    Attribute {
1642                        expr: garbage(working_set, Span::concat(spans)),
1643                    },
1644                    None,
1645                );
1646            }
1647            _ => {
1648                trace!("parsing: alias of internal call");
1649                parse_internal_call(
1650                    working_set,
1651                    name_span,
1652                    &spans[cmd_end..],
1653                    decl_id,
1654                    ArgumentParsingLevel::Full,
1655                )
1656            }
1657        },
1658        None => {
1659            trace!("parsing: internal call");
1660            parse_internal_call(
1661                working_set,
1662                name_span,
1663                &spans[cmd_end..],
1664                decl_id,
1665                ArgumentParsingLevel::Full,
1666            )
1667        }
1668    };
1669
1670    (
1671        Attribute {
1672            expr: Expression::new(
1673                working_set,
1674                Expr::Call(parsed_call.call),
1675                Span::concat(spans),
1676                parsed_call.output,
1677            ),
1678        },
1679        Some(name),
1680    )
1681}
1682
1683pub fn parse_binary(working_set: &mut StateWorkingSet, span: Span) -> Expression {
1684    trace!("parsing: binary");
1685    let contents = working_set.get_span_contents(span);
1686    if contents.starts_with(b"0x[") {
1687        parse_binary_with_base(working_set, span, 16, 2, b"0x[", b"]")
1688    } else if contents.starts_with(b"0o[") {
1689        parse_binary_with_base(working_set, span, 8, 3, b"0o[", b"]")
1690    } else if contents.starts_with(b"0b[") {
1691        parse_binary_with_base(working_set, span, 2, 8, b"0b[", b"]")
1692    } else {
1693        working_set.error(ParseError::Expected("binary", span));
1694        garbage(working_set, span)
1695    }
1696}
1697
1698fn parse_binary_with_base(
1699    working_set: &mut StateWorkingSet,
1700    span: Span,
1701    base: u32,
1702    min_digits_per_byte: usize,
1703    prefix: &[u8],
1704    suffix: &[u8],
1705) -> Expression {
1706    let token = working_set.get_span_contents(span);
1707
1708    if let Some(token) = token.strip_prefix(prefix)
1709        && let Some(token) = token.strip_suffix(suffix)
1710    {
1711        let (lexed, err) = lex(
1712            token,
1713            span.start + prefix.len(),
1714            &[b',', b'\r', b'\n'],
1715            &[],
1716            true,
1717        );
1718        if let Some(err) = err {
1719            working_set.error(err);
1720        }
1721
1722        let mut binary_value = vec![];
1723        for token in lexed {
1724            match token.contents {
1725                TokenContents::Item => {
1726                    let contents = working_set.get_span_contents(token.span);
1727
1728                    binary_value.extend_from_slice(contents);
1729                }
1730                TokenContents::Pipe
1731                | TokenContents::PipePipe
1732                | TokenContents::ErrGreaterPipe
1733                | TokenContents::OutGreaterThan
1734                | TokenContents::OutErrGreaterPipe
1735                | TokenContents::OutGreaterGreaterThan
1736                | TokenContents::ErrGreaterThan
1737                | TokenContents::ErrGreaterGreaterThan
1738                | TokenContents::OutErrGreaterThan
1739                | TokenContents::OutErrGreaterGreaterThan
1740                | TokenContents::AssignmentOperator => {
1741                    working_set.error(ParseError::Expected("binary", span));
1742                    return garbage(working_set, span);
1743                }
1744                TokenContents::Comment | TokenContents::Semicolon | TokenContents::Eol => {}
1745            }
1746        }
1747
1748        let required_padding =
1749            (min_digits_per_byte - binary_value.len() % min_digits_per_byte) % min_digits_per_byte;
1750
1751        if required_padding != 0 {
1752            binary_value = {
1753                let mut tail = binary_value;
1754                let mut binary_value: Vec<u8> = vec![b'0'; required_padding];
1755                binary_value.append(&mut tail);
1756                binary_value
1757            };
1758        }
1759
1760        let str = String::from_utf8_lossy(&binary_value).to_string();
1761
1762        match decode_with_base(&str, base, min_digits_per_byte) {
1763            Ok(v) => return Expression::new(working_set, Expr::Binary(v), span, Type::Binary),
1764            Err(help) => {
1765                working_set.error(ParseError::InvalidBinaryString(span, help.to_string()));
1766                return garbage(working_set, span);
1767            }
1768        }
1769    }
1770
1771    working_set.error(ParseError::Expected("binary", span));
1772    garbage(working_set, span)
1773}
1774
1775fn decode_with_base(s: &str, base: u32, digits_per_byte: usize) -> Result<Vec<u8>, &str> {
1776    s.chars()
1777        .chunks(digits_per_byte)
1778        .into_iter()
1779        .map(|chunk| {
1780            let str: String = chunk.collect();
1781            u8::from_str_radix(&str, base).map_err(|_| match base {
1782                2 => "binary strings may contain only 0 or 1.",
1783                8 => "octal strings must have a length that is a multiple of three and contain values between 0o000 and 0o377.",
1784                16 => "hexadecimal strings may contain only the characters 0–9 and A–F.",
1785                _ => "internal error: radix other than 2, 8, or 16 is not allowed."
1786            })
1787        })
1788        .collect()
1789}
1790
1791fn strip_underscores(token: &[u8]) -> String {
1792    String::from_utf8_lossy(token)
1793        .chars()
1794        .filter(|c| *c != '_')
1795        .collect()
1796}
1797
1798pub fn parse_int(working_set: &mut StateWorkingSet, span: Span) -> Expression {
1799    let token = working_set.get_span_contents(span);
1800
1801    fn extract_int(
1802        working_set: &mut StateWorkingSet,
1803        token: &str,
1804        span: Span,
1805        radix: u32,
1806    ) -> Expression {
1807        // Parse as a u64, then cast to i64, otherwise, for numbers like "0xffffffffffffffef",
1808        // you'll get `Error parsing hex string: number too large to fit in target type`.
1809        if let Ok(num) = u64::from_str_radix(token, radix).map(|val| val as i64) {
1810            Expression::new(working_set, Expr::Int(num), span, Type::Int)
1811        } else {
1812            working_set.error(ParseError::InvalidLiteral(
1813                format!("invalid digits for radix {radix}"),
1814                "int".into(),
1815                span,
1816            ));
1817
1818            garbage(working_set, span)
1819        }
1820    }
1821
1822    let token = strip_underscores(token);
1823
1824    if token.is_empty() {
1825        working_set.error(ParseError::Expected("int", span));
1826        return garbage(working_set, span);
1827    }
1828
1829    if let Some(num) = token.strip_prefix("0b") {
1830        extract_int(working_set, num, span, 2)
1831    } else if let Some(num) = token.strip_prefix("0o") {
1832        extract_int(working_set, num, span, 8)
1833    } else if let Some(num) = token.strip_prefix("0x") {
1834        extract_int(working_set, num, span, 16)
1835    } else if let Ok(num) = token.parse::<i64>() {
1836        Expression::new(working_set, Expr::Int(num), span, Type::Int)
1837    } else {
1838        working_set.error(ParseError::Expected("int", span));
1839        garbage(working_set, span)
1840    }
1841}
1842
1843pub fn parse_float(working_set: &mut StateWorkingSet, span: Span) -> Expression {
1844    let token = working_set.get_span_contents(span);
1845    let token = strip_underscores(token);
1846
1847    if let Ok(x) = token.parse::<f64>() {
1848        Expression::new(working_set, Expr::Float(x), span, Type::Float)
1849    } else {
1850        working_set.error(ParseError::Expected("float", span));
1851
1852        garbage(working_set, span)
1853    }
1854}
1855
1856pub fn parse_number(working_set: &mut StateWorkingSet, span: Span) -> Expression {
1857    let starting_error_count = working_set.parse_errors.len();
1858
1859    let result = parse_int(working_set, span);
1860    if starting_error_count == working_set.parse_errors.len() {
1861        return result;
1862    } else if !matches!(
1863        working_set.parse_errors.last(),
1864        Some(ParseError::Expected(_, _))
1865    ) {
1866    } else {
1867        working_set.parse_errors.truncate(starting_error_count);
1868    }
1869
1870    let result = parse_float(working_set, span);
1871
1872    if starting_error_count == working_set.parse_errors.len() {
1873        return result;
1874    }
1875    working_set.parse_errors.truncate(starting_error_count);
1876
1877    working_set.error(ParseError::Expected("number", span));
1878    garbage(working_set, span)
1879}
1880
1881pub fn parse_range(working_set: &mut StateWorkingSet, span: Span) -> Option<Expression> {
1882    trace!("parsing: range");
1883    let starting_error_count = working_set.parse_errors.len();
1884
1885    // Range follows the following syntax: [<from>][<next_operator><next>]<range_operator>[<to>]
1886    //   where <next_operator> is ".."
1887    //   and  <range_operator> is "..", "..=" or "..<"
1888    //   and one of the <from> or <to> bounds must be present (just '..' is not allowed since it
1889    //     looks like parent directory)
1890    //bugbug range cannot be [..] because that looks like parent directory
1891
1892    let contents = working_set.get_span_contents(span);
1893
1894    let token = if let Ok(s) = String::from_utf8(contents.into()) {
1895        s
1896    } else {
1897        working_set.error(ParseError::NonUtf8(span));
1898        return None;
1899    };
1900
1901    if token.starts_with("...") {
1902        working_set.error(ParseError::Expected(
1903            "range operator ('..'), got spread ('...')",
1904            span,
1905        ));
1906        return None;
1907    }
1908
1909    if !token.contains("..") {
1910        working_set.error(ParseError::Expected("at least one range bound set", span));
1911        return None;
1912    }
1913
1914    // First, figure out what exact operators are used and determine their positions
1915    let dotdot_pos: Vec<_> = token.match_indices("..").map(|(pos, _)| pos).collect();
1916
1917    let (next_op_pos, range_op_pos) = match dotdot_pos.len() {
1918        1 => (None, dotdot_pos[0]),
1919        2 => (Some(dotdot_pos[0]), dotdot_pos[1]),
1920        _ => {
1921            working_set.error(ParseError::Expected(
1922                "one range operator ('..' or '..<') and optionally one next operator ('..')",
1923                span,
1924            ));
1925            return None;
1926        }
1927    };
1928    // Avoid calling sub-parsers on unmatched parens, to prevent quadratic time on things like ((((1..2))))
1929    // No need to call the expensive parse_value on "((((1"
1930    if dotdot_pos[0] > 0 {
1931        let (_tokens, err) = lex(
1932            &contents[..dotdot_pos[0]],
1933            span.start,
1934            &[],
1935            &[b'.', b'?', b'!'],
1936            true,
1937        );
1938        if let Some(_err) = err {
1939            working_set.error(ParseError::Expected("Valid expression before ..", span));
1940            return None;
1941        }
1942    }
1943
1944    let (inclusion, range_op_str, range_op_span) = if let Some(pos) = token.find("..<") {
1945        if pos == range_op_pos {
1946            let op_str = "..<";
1947            let op_span = Span::new(
1948                span.start + range_op_pos,
1949                span.start + range_op_pos + op_str.len(),
1950            );
1951            (RangeInclusion::RightExclusive, "..<", op_span)
1952        } else {
1953            working_set.error(ParseError::Expected(
1954                "inclusive operator preceding second range bound",
1955                span,
1956            ));
1957            return None;
1958        }
1959    } else {
1960        let op_str = if token[range_op_pos..].starts_with("..=") {
1961            "..="
1962        } else {
1963            ".."
1964        };
1965
1966        let op_span = Span::new(
1967            span.start + range_op_pos,
1968            span.start + range_op_pos + op_str.len(),
1969        );
1970        (RangeInclusion::Inclusive, op_str, op_span)
1971    };
1972
1973    // Now, based on the operator positions, figure out where the bounds & next are located and
1974    // parse them
1975    // TODO: Actually parse the next number in the range
1976    let from = if token.starts_with("..") {
1977        // token starts with either next operator, or range operator -- we don't care which one
1978        None
1979    } else {
1980        let from_span = Span::new(span.start, span.start + dotdot_pos[0]);
1981        Some(parse_value(working_set, from_span, &SyntaxShape::Number))
1982    };
1983
1984    let to = if token.ends_with(range_op_str) {
1985        None
1986    } else {
1987        let to_span = Span::new(range_op_span.end, span.end);
1988        Some(parse_value(working_set, to_span, &SyntaxShape::Number))
1989    };
1990
1991    trace!("-- from: {from:?} to: {to:?}");
1992
1993    if let (None, None) = (&from, &to) {
1994        working_set.error(ParseError::Expected("at least one range bound set", span));
1995        return None;
1996    }
1997
1998    let (next, next_op_span) = if let Some(pos) = next_op_pos {
1999        let next_op_span = Span::new(span.start + pos, span.start + pos + "..".len());
2000        let next_span = Span::new(next_op_span.end, range_op_span.start);
2001
2002        (
2003            Some(parse_value(working_set, next_span, &SyntaxShape::Number)),
2004            next_op_span,
2005        )
2006    } else {
2007        (None, span)
2008    };
2009
2010    if working_set.parse_errors.len() != starting_error_count {
2011        return None;
2012    }
2013
2014    let operator = RangeOperator {
2015        inclusion,
2016        span: range_op_span,
2017        next_op_span,
2018    };
2019
2020    let mut range = Range {
2021        from,
2022        next,
2023        to,
2024        operator,
2025    };
2026
2027    check_range_types(working_set, &mut range);
2028
2029    Some(Expression::new(
2030        working_set,
2031        Expr::Range(Box::new(range)),
2032        span,
2033        Type::Range,
2034    ))
2035}
2036
2037pub(crate) fn parse_dollar_expr(working_set: &mut StateWorkingSet, span: Span) -> Expression {
2038    trace!("parsing: dollar expression");
2039    let contents = working_set.get_span_contents(span);
2040
2041    if contents.starts_with(b"$\"") || contents.starts_with(b"$'") {
2042        parse_string_interpolation(working_set, span)
2043    } else if contents.starts_with(b"$.") {
2044        parse_simple_cell_path(working_set, Span::new(span.start + 2, span.end))
2045    } else {
2046        let starting_error_count = working_set.parse_errors.len();
2047
2048        if let Some(expr) = parse_range(working_set, span) {
2049            expr
2050        } else {
2051            working_set.parse_errors.truncate(starting_error_count);
2052            parse_full_cell_path(working_set, None, span)
2053        }
2054    }
2055}
2056
2057pub fn parse_raw_string(working_set: &mut StateWorkingSet, span: Span) -> Expression {
2058    trace!("parsing: raw-string, with required delimiters");
2059
2060    let bytes = working_set.get_span_contents(span);
2061
2062    let prefix_sharp_cnt = if bytes.starts_with(b"r#") {
2063        // actually `sharp_cnt` is always `index - 1`
2064        // but create a variable here to make it clearer.
2065        let mut sharp_cnt = 1;
2066        let mut index = 2;
2067        while index < bytes.len() && bytes[index] == b'#' {
2068            index += 1;
2069            sharp_cnt += 1;
2070        }
2071        sharp_cnt
2072    } else {
2073        working_set.error(ParseError::Expected("r#", span));
2074        return garbage(working_set, span);
2075    };
2076    let expect_postfix_sharp_cnt = prefix_sharp_cnt;
2077    // check the length of whole raw string.
2078    // the whole raw string should contains at least
2079    // 1(r) + prefix_sharp_cnt + 1(') + 1(') + postfix_sharp characters
2080    if bytes.len() < prefix_sharp_cnt + expect_postfix_sharp_cnt + 3 {
2081        working_set.error(ParseError::Unclosed('\''.into(), span));
2082        return garbage(working_set, span);
2083    }
2084
2085    // check for unbalanced # and single quotes.
2086    let postfix_bytes = &bytes[bytes.len() - expect_postfix_sharp_cnt..bytes.len()];
2087    if postfix_bytes.iter().any(|b| *b != b'#') {
2088        working_set.error(ParseError::Unbalanced(
2089            "prefix #".to_string(),
2090            "postfix #".to_string(),
2091            span,
2092        ));
2093        return garbage(working_set, span);
2094    }
2095    // check for unblanaced single quotes.
2096    if bytes[1 + prefix_sharp_cnt] != b'\''
2097        || bytes[bytes.len() - expect_postfix_sharp_cnt - 1] != b'\''
2098    {
2099        working_set.error(ParseError::Unclosed('\''.into(), span));
2100        return garbage(working_set, span);
2101    }
2102
2103    let bytes = &bytes[prefix_sharp_cnt + 1 + 1..bytes.len() - 1 - prefix_sharp_cnt];
2104    if let Ok(token) = String::from_utf8(bytes.into()) {
2105        Expression::new(working_set, Expr::RawString(token), span, Type::String)
2106    } else {
2107        working_set.error(ParseError::Expected("utf8 raw-string", span));
2108        garbage(working_set, span)
2109    }
2110}
2111
2112pub fn parse_paren_expr(
2113    working_set: &mut StateWorkingSet,
2114    span: Span,
2115    shape: &SyntaxShape,
2116) -> Expression {
2117    let starting_error_count = working_set.parse_errors.len();
2118
2119    if let Some(expr) = parse_range(working_set, span) {
2120        return expr;
2121    }
2122
2123    working_set.parse_errors.truncate(starting_error_count);
2124
2125    if let SyntaxShape::Signature = shape {
2126        return parse_signature(working_set, span);
2127    }
2128
2129    let fcp_expr = parse_full_cell_path(working_set, None, span);
2130    let fcp_error_count = working_set.parse_errors.len();
2131    if fcp_error_count > starting_error_count {
2132        let malformed_subexpr = working_set.parse_errors[starting_error_count..]
2133            .first()
2134            .is_some_and(|e| match e {
2135                ParseError::Unclosed(right, _) if (right == ")") => true,
2136                ParseError::Unbalanced(left, right, _) if left == "(" && right == ")" => true,
2137                _ => false,
2138            });
2139        if malformed_subexpr {
2140            working_set.parse_errors.truncate(starting_error_count);
2141            parse_string_interpolation(working_set, span)
2142        } else {
2143            fcp_expr
2144        }
2145    } else {
2146        fcp_expr
2147    }
2148}
2149
2150pub fn parse_brace_expr(
2151    working_set: &mut StateWorkingSet,
2152    span: Span,
2153    shape: &SyntaxShape,
2154) -> Expression {
2155    // Try to detect what kind of value we're about to parse
2156    // FIXME: In the future, we should work over the token stream so we only have to do this once
2157    // before parsing begins
2158
2159    // FIXME: we're still using the shape because we rely on it to know how to handle syntax where
2160    // the parse is ambiguous. We'll need to update the parts of the grammar where this is ambiguous
2161    // and then revisit the parsing.
2162
2163    if span.end <= (span.start + 1) {
2164        working_set.error(ParseError::ExpectedWithStringMsg(
2165            format!("non-block value: {shape}"),
2166            span,
2167        ));
2168        return Expression::garbage(working_set, span);
2169    }
2170
2171    let bytes = working_set.get_span_contents(Span::new(span.start + 1, span.end - 1));
2172    let (tokens, _) = lex(bytes, span.start + 1, &[b'\r', b'\n', b'\t'], &[b':'], true);
2173
2174    match tokens.as_slice() {
2175        // If we're empty, that means an empty record or closure
2176        [] => match shape {
2177            SyntaxShape::Closure(_) => parse_closure_expression(working_set, shape, span),
2178            SyntaxShape::Block => parse_block_expression(working_set, span),
2179            SyntaxShape::MatchBlock => parse_match_block_expression(working_set, span),
2180            _ => parse_record(working_set, span),
2181        },
2182        [
2183            Token {
2184                contents: TokenContents::Pipe | TokenContents::PipePipe,
2185                ..
2186            },
2187            ..,
2188        ] => {
2189            if let SyntaxShape::Block = shape {
2190                working_set.error(ParseError::Mismatch("block".into(), "closure".into(), span));
2191                return Expression::garbage(working_set, span);
2192            }
2193            parse_closure_expression(working_set, shape, span)
2194        }
2195        [_, third, ..] if working_set.get_span_contents(third.span) == b":" => {
2196            parse_full_cell_path(working_set, None, span)
2197        }
2198        [second, ..] => {
2199            let second_bytes = working_set.get_span_contents(second.span);
2200            match shape {
2201                SyntaxShape::Closure(_) => parse_closure_expression(working_set, shape, span),
2202                SyntaxShape::Block => parse_block_expression(working_set, span),
2203                SyntaxShape::MatchBlock => parse_match_block_expression(working_set, span),
2204                _ if second_bytes.starts_with(b"...")
2205                    && second_bytes.get(3).is_some_and(|c| b"${(".contains(c)) =>
2206                {
2207                    parse_record(working_set, span)
2208                }
2209                SyntaxShape::Any => parse_closure_expression(working_set, shape, span),
2210                _ => {
2211                    working_set.error(ParseError::ExpectedWithStringMsg(
2212                        format!("non-block value: {shape}"),
2213                        span,
2214                    ));
2215
2216                    Expression::garbage(working_set, span)
2217                }
2218            }
2219        }
2220    }
2221}
2222
2223pub fn parse_string_interpolation(working_set: &mut StateWorkingSet, span: Span) -> Expression {
2224    #[derive(PartialEq, Eq, Debug)]
2225    enum InterpolationMode {
2226        String,
2227        Expression,
2228    }
2229
2230    let contents = working_set.get_span_contents(span);
2231
2232    let mut double_quote = false;
2233
2234    let (start, end) = if contents.starts_with(b"$\"") {
2235        double_quote = true;
2236        let end = if contents.ends_with(b"\"") && contents.len() > 2 {
2237            span.end - 1
2238        } else {
2239            span.end
2240        };
2241        (span.start + 2, end)
2242    } else if contents.starts_with(b"$'") {
2243        let end = if contents.ends_with(b"'") && contents.len() > 2 {
2244            span.end - 1
2245        } else {
2246            span.end
2247        };
2248        (span.start + 2, end)
2249    } else {
2250        (span.start, span.end)
2251    };
2252
2253    let inner_span = Span::new(start, end);
2254    let contents = working_set.get_span_contents(inner_span).to_vec();
2255
2256    let mut output = vec![];
2257    let mut mode = InterpolationMode::String;
2258    let mut token_start = start;
2259    let mut delimiter_stack = vec![];
2260
2261    let mut consecutive_backslashes: usize = 0;
2262
2263    let mut b = start;
2264
2265    while b != end {
2266        let current_byte = contents[b - start];
2267
2268        if mode == InterpolationMode::String {
2269            let preceding_consecutive_backslashes = consecutive_backslashes;
2270
2271            let is_backslash = current_byte == b'\\';
2272            consecutive_backslashes = if is_backslash {
2273                preceding_consecutive_backslashes + 1
2274            } else {
2275                0
2276            };
2277
2278            if current_byte == b'('
2279                && (!double_quote || preceding_consecutive_backslashes.is_multiple_of(2))
2280            {
2281                mode = InterpolationMode::Expression;
2282                if token_start < b {
2283                    let span = Span::new(token_start, b);
2284                    let str_contents = working_set.get_span_contents(span);
2285
2286                    let (str_contents, err) = if double_quote {
2287                        unescape_string(str_contents, span)
2288                    } else {
2289                        (str_contents.to_vec(), None)
2290                    };
2291                    if let Some(err) = err {
2292                        working_set.error(err);
2293                    }
2294
2295                    output.push(Expression::new(
2296                        working_set,
2297                        Expr::String(String::from_utf8_lossy(&str_contents).to_string()),
2298                        span,
2299                        Type::String,
2300                    ));
2301                    token_start = b;
2302                }
2303            }
2304        }
2305
2306        if mode == InterpolationMode::Expression {
2307            let byte = current_byte;
2308            if let Some(b'\'') = delimiter_stack.last() {
2309                if byte == b'\'' {
2310                    delimiter_stack.pop();
2311                }
2312            } else if let Some(b'"') = delimiter_stack.last() {
2313                if byte == b'"' {
2314                    delimiter_stack.pop();
2315                }
2316            } else if let Some(b'`') = delimiter_stack.last() {
2317                if byte == b'`' {
2318                    delimiter_stack.pop();
2319                }
2320            } else if byte == b'\'' {
2321                delimiter_stack.push(b'\'')
2322            } else if byte == b'"' {
2323                delimiter_stack.push(b'"');
2324            } else if byte == b'`' {
2325                delimiter_stack.push(b'`')
2326            } else if byte == b'(' {
2327                delimiter_stack.push(b')');
2328            } else if byte == b')' {
2329                if let Some(b')') = delimiter_stack.last() {
2330                    delimiter_stack.pop();
2331                }
2332                if delimiter_stack.is_empty() {
2333                    mode = InterpolationMode::String;
2334
2335                    if token_start < b {
2336                        let span = Span::new(token_start, b + 1);
2337
2338                        let expr = parse_full_cell_path(working_set, None, span);
2339                        output.push(expr);
2340                    }
2341
2342                    token_start = b + 1;
2343                    continue;
2344                }
2345            }
2346        }
2347        b += 1;
2348    }
2349
2350    match mode {
2351        InterpolationMode::String => {
2352            if token_start < end {
2353                let span = Span::new(token_start, end);
2354                let str_contents = working_set.get_span_contents(span);
2355
2356                let (str_contents, err) = if double_quote {
2357                    unescape_string(str_contents, span)
2358                } else {
2359                    (str_contents.to_vec(), None)
2360                };
2361                if let Some(err) = err {
2362                    working_set.error(err);
2363                }
2364
2365                output.push(Expression::new(
2366                    working_set,
2367                    Expr::String(String::from_utf8_lossy(&str_contents).to_string()),
2368                    span,
2369                    Type::String,
2370                ));
2371            }
2372        }
2373        InterpolationMode::Expression => {
2374            if token_start < end {
2375                let span = Span::new(token_start, end);
2376
2377                if delimiter_stack.is_empty() {
2378                    let expr = parse_full_cell_path(working_set, None, span);
2379                    output.push(expr);
2380                }
2381            }
2382        }
2383    }
2384
2385    Expression::new(
2386        working_set,
2387        Expr::StringInterpolation(output),
2388        span,
2389        Type::String,
2390    )
2391}
2392
2393pub fn parse_variable_expr(working_set: &mut StateWorkingSet, span: Span) -> Expression {
2394    let contents = working_set.get_span_contents(span);
2395
2396    if contents == b"$nu" {
2397        return Expression::new(
2398            working_set,
2399            Expr::Var(nu_protocol::NU_VARIABLE_ID),
2400            span,
2401            Type::Any,
2402        );
2403    } else if contents == b"$in" {
2404        return Expression::new(
2405            working_set,
2406            Expr::Var(nu_protocol::IN_VARIABLE_ID),
2407            span,
2408            Type::Any,
2409        );
2410    } else if contents == b"$env" {
2411        return Expression::new(
2412            working_set,
2413            Expr::Var(nu_protocol::ENV_VARIABLE_ID),
2414            span,
2415            Type::Any,
2416        );
2417    }
2418
2419    let name = if contents.starts_with(b"$") {
2420        String::from_utf8_lossy(&contents[1..]).to_string()
2421    } else {
2422        String::from_utf8_lossy(contents).to_string()
2423    };
2424
2425    let bytes = working_set.get_span_contents(span);
2426    let suggestion = || {
2427        DidYouMean::new(
2428            &working_set.list_variables(),
2429            working_set.get_span_contents(span),
2430        )
2431    };
2432    if !is_variable(bytes) {
2433        working_set.error(ParseError::ExpectedWithDidYouMean(
2434            "valid variable name",
2435            suggestion(),
2436            span,
2437        ));
2438        garbage(working_set, span)
2439    } else if let Some(id) = working_set.find_variable(bytes) {
2440        Expression::new(
2441            working_set,
2442            Expr::Var(id),
2443            span,
2444            working_set.get_variable(id).ty.clone(),
2445        )
2446    } else if working_set.get_env_var(&name).is_some() {
2447        working_set.error(ParseError::EnvVarNotVar(name, span));
2448        garbage(working_set, span)
2449    } else {
2450        working_set.error(ParseError::VariableNotFound(suggestion(), span));
2451        garbage(working_set, span)
2452    }
2453}
2454
2455pub fn parse_cell_path(
2456    working_set: &mut StateWorkingSet,
2457    tokens: impl Iterator<Item = Token>,
2458    expect_dot: bool,
2459) -> Vec<PathMember> {
2460    enum TokenType {
2461        Dot,              // .
2462        DotOrSign,        // . or ? or !
2463        DotOrExclamation, // . or !
2464        DotOrQuestion,    // . or ?
2465        PathMember,       // an int or string, like `1` or `foo`
2466    }
2467
2468    enum ModifyMember {
2469        No,
2470        Optional,
2471        Insensitive,
2472    }
2473
2474    impl TokenType {
2475        fn expect(&mut self, byte: u8) -> Result<ModifyMember, &'static str> {
2476            match (&*self, byte) {
2477                (Self::PathMember, _) => {
2478                    *self = Self::DotOrSign;
2479                    Ok(ModifyMember::No)
2480                }
2481                (
2482                    Self::Dot | Self::DotOrSign | Self::DotOrExclamation | Self::DotOrQuestion,
2483                    b'.',
2484                ) => {
2485                    *self = Self::PathMember;
2486                    Ok(ModifyMember::No)
2487                }
2488                (Self::DotOrSign, b'!') => {
2489                    *self = Self::DotOrQuestion;
2490                    Ok(ModifyMember::Insensitive)
2491                }
2492                (Self::DotOrSign, b'?') => {
2493                    *self = Self::DotOrExclamation;
2494                    Ok(ModifyMember::Optional)
2495                }
2496                (Self::DotOrSign, _) => Err(". or ! or ?"),
2497                (Self::DotOrExclamation, b'!') => {
2498                    *self = Self::Dot;
2499                    Ok(ModifyMember::Insensitive)
2500                }
2501                (Self::DotOrExclamation, _) => Err(". or !"),
2502                (Self::DotOrQuestion, b'?') => {
2503                    *self = Self::Dot;
2504                    Ok(ModifyMember::Optional)
2505                }
2506                (Self::DotOrQuestion, _) => Err(". or ?"),
2507                (Self::Dot, _) => Err("."),
2508            }
2509        }
2510    }
2511
2512    // Parsing a cell path is essentially a state machine, and this is the state
2513    let mut expected_token = if expect_dot {
2514        TokenType::Dot
2515    } else {
2516        TokenType::PathMember
2517    };
2518
2519    let mut tail = vec![];
2520
2521    for path_element in tokens {
2522        let bytes = working_set.get_span_contents(path_element.span);
2523
2524        // both parse_int and parse_string require their source to be non-empty
2525        // all cases where `bytes` is empty is an error
2526        let Some((&first, rest)) = bytes.split_first() else {
2527            working_set.error(ParseError::Expected("string", path_element.span));
2528            return tail;
2529        };
2530        let single_char = rest.is_empty();
2531
2532        if let TokenType::PathMember = expected_token {
2533            let starting_error_count = working_set.parse_errors.len();
2534
2535            let expr = parse_int(working_set, path_element.span);
2536            working_set.parse_errors.truncate(starting_error_count);
2537
2538            match expr {
2539                Expression {
2540                    expr: Expr::Int(val),
2541                    span,
2542                    ..
2543                } => tail.push(PathMember::Int {
2544                    val: val as usize,
2545                    span,
2546                    optional: false,
2547                }),
2548                _ => {
2549                    let result = parse_string(working_set, path_element.span);
2550                    match result {
2551                        Expression {
2552                            expr: Expr::String(string),
2553                            span,
2554                            ..
2555                        } => {
2556                            tail.push(PathMember::String {
2557                                val: string,
2558                                span,
2559                                optional: false,
2560                                casing: Casing::Sensitive,
2561                            });
2562                        }
2563                        _ => {
2564                            working_set.error(ParseError::Expected("string", path_element.span));
2565                            return tail;
2566                        }
2567                    }
2568                }
2569            }
2570            expected_token = TokenType::DotOrSign;
2571        } else {
2572            match expected_token.expect(if single_char { first } else { b' ' }) {
2573                Ok(modify) => {
2574                    if let Some(last) = tail.last_mut() {
2575                        match modify {
2576                            ModifyMember::No => {}
2577                            ModifyMember::Optional => last.make_optional(),
2578                            ModifyMember::Insensitive => last.make_insensitive(),
2579                        }
2580                    };
2581                }
2582                Err(expected) => {
2583                    working_set.error(ParseError::Expected(expected, path_element.span));
2584                    return tail;
2585                }
2586            }
2587        }
2588    }
2589
2590    tail
2591}
2592
2593pub fn parse_simple_cell_path(working_set: &mut StateWorkingSet, span: Span) -> Expression {
2594    let source = working_set.get_span_contents(span);
2595
2596    let (tokens, err) = lex(
2597        source,
2598        span.start,
2599        &[b'\n', b'\r'],
2600        &[b'.', b'?', b'!'],
2601        true,
2602    );
2603    if let Some(err) = err {
2604        working_set.error(err)
2605    }
2606
2607    let tokens = tokens.into_iter().peekable();
2608
2609    let cell_path = parse_cell_path(working_set, tokens, false);
2610
2611    Expression::new(
2612        working_set,
2613        Expr::CellPath(CellPath { members: cell_path }),
2614        span,
2615        Type::CellPath,
2616    )
2617}
2618
2619pub fn parse_full_cell_path(
2620    working_set: &mut StateWorkingSet,
2621    implicit_head: Option<VarId>,
2622    span: Span,
2623) -> Expression {
2624    trace!("parsing: full cell path");
2625    let full_cell_span = span;
2626    let source = working_set.get_span_contents(span);
2627
2628    let (tokens, err) = lex(
2629        source,
2630        span.start,
2631        &[b'\n', b'\r'],
2632        &[b'.', b'?', b'!'],
2633        true,
2634    );
2635    if let Some(err) = err {
2636        working_set.error(err)
2637    }
2638
2639    let mut tokens = tokens.into_iter().peekable();
2640    if let Some(head) = tokens.peek() {
2641        let bytes = working_set.get_span_contents(head.span);
2642        let (head, expect_dot) = if bytes.starts_with(b"(") {
2643            trace!("parsing: paren-head of full cell path");
2644
2645            let head_span = head.span;
2646            let mut start = head.span.start;
2647            let mut end = head.span.end;
2648            let mut is_closed = true;
2649
2650            if bytes.starts_with(b"(") {
2651                start += 1;
2652            }
2653            if bytes.ends_with(b")") {
2654                end -= 1;
2655            } else {
2656                working_set.error(ParseError::Unclosed(")".into(), Span::new(end, end)));
2657                is_closed = false;
2658            }
2659
2660            let span = Span::new(start, end);
2661
2662            let source = working_set.get_span_contents(span);
2663
2664            let (output, err) = lex(source, span.start, &[b'\n', b'\r'], &[], true);
2665            if let Some(err) = err {
2666                working_set.error(err)
2667            }
2668
2669            // Creating a Type scope to parse the new block. This will keep track of
2670            // the previous input type found in that block
2671            let output = parse_block(working_set, &output, span, is_closed, true);
2672
2673            let ty = output.output_type();
2674
2675            let block_id = working_set.add_block(Arc::new(output));
2676            tokens.next();
2677
2678            (
2679                Expression::new(working_set, Expr::Subexpression(block_id), head_span, ty),
2680                true,
2681            )
2682        } else if bytes.starts_with(b"[") {
2683            trace!("parsing: table head of full cell path");
2684
2685            let output = parse_table_expression(working_set, head.span, &SyntaxShape::Any);
2686
2687            tokens.next();
2688
2689            (output, true)
2690        } else if bytes.starts_with(b"{") {
2691            trace!("parsing: record head of full cell path");
2692            let output = parse_record(working_set, head.span);
2693
2694            tokens.next();
2695
2696            (output, true)
2697        } else if bytes.starts_with(b"$") {
2698            trace!("parsing: $variable head of full cell path");
2699
2700            let out = parse_variable_expr(working_set, head.span);
2701
2702            tokens.next();
2703
2704            (out, true)
2705        } else if let Some(var_id) = implicit_head {
2706            trace!("parsing: implicit head of full cell path");
2707            (
2708                Expression::new(working_set, Expr::Var(var_id), head.span, Type::Any),
2709                false,
2710            )
2711        } else {
2712            working_set.error(ParseError::Mismatch(
2713                "variable or subexpression".into(),
2714                String::from_utf8_lossy(bytes).to_string(),
2715                span,
2716            ));
2717            return garbage(working_set, span);
2718        };
2719
2720        let tail = parse_cell_path(working_set, tokens, expect_dot);
2721        // FIXME: Get the type of the data at the tail using follow_cell_path() (or something)
2722        let ty = if !tail.is_empty() {
2723            // Until the aforementioned fix is implemented, this is necessary to allow mutable list upserts
2724            // such as $a.1 = 2 to work correctly.
2725            Type::Any
2726        } else {
2727            head.ty.clone()
2728        };
2729
2730        Expression::new(
2731            working_set,
2732            Expr::FullCellPath(Box::new(FullCellPath { head, tail })),
2733            full_cell_span,
2734            ty,
2735        )
2736    } else {
2737        garbage(working_set, span)
2738    }
2739}
2740
2741pub fn parse_directory(working_set: &mut StateWorkingSet, span: Span) -> Expression {
2742    let bytes = working_set.get_span_contents(span);
2743    trace!("parsing: directory");
2744
2745    // Check for bare word interpolation
2746    if !bytes.is_empty()
2747        && bytes[0] != b'\''
2748        && bytes[0] != b'"'
2749        && bytes[0] != b'`'
2750        && bytes.contains(&b'(')
2751    {
2752        return parse_string_interpolation(working_set, span);
2753    }
2754
2755    let quoted = is_quoted(bytes);
2756    let (token, err) = unescape_unquote_string(bytes, span);
2757
2758    if err.is_none() {
2759        trace!("-- found {token}");
2760
2761        Expression::new(
2762            working_set,
2763            Expr::Directory(token, quoted),
2764            span,
2765            Type::String,
2766        )
2767    } else {
2768        working_set.error(ParseError::Expected("directory", span));
2769
2770        garbage(working_set, span)
2771    }
2772}
2773
2774pub fn parse_filepath(working_set: &mut StateWorkingSet, span: Span) -> Expression {
2775    let bytes = working_set.get_span_contents(span);
2776    trace!("parsing: filepath");
2777
2778    // Check for bare word interpolation
2779    if !bytes.is_empty()
2780        && bytes[0] != b'\''
2781        && bytes[0] != b'"'
2782        && bytes[0] != b'`'
2783        && bytes.contains(&b'(')
2784    {
2785        return parse_string_interpolation(working_set, span);
2786    }
2787
2788    let quoted = is_quoted(bytes);
2789    let (token, err) = unescape_unquote_string(bytes, span);
2790
2791    if err.is_none() {
2792        trace!("-- found {token}");
2793
2794        Expression::new(
2795            working_set,
2796            Expr::Filepath(token, quoted),
2797            span,
2798            Type::String,
2799        )
2800    } else {
2801        working_set.error(ParseError::Expected("filepath", span));
2802
2803        garbage(working_set, span)
2804    }
2805}
2806
2807/// Parse a datetime type, eg '2022-02-02'
2808pub fn parse_datetime(working_set: &mut StateWorkingSet, span: Span) -> Expression {
2809    trace!("parsing: datetime");
2810
2811    let bytes = working_set.get_span_contents(span);
2812
2813    if bytes.len() < 6
2814        || !bytes[0].is_ascii_digit()
2815        || !bytes[1].is_ascii_digit()
2816        || !bytes[2].is_ascii_digit()
2817        || !bytes[3].is_ascii_digit()
2818        || bytes[4] != b'-'
2819    {
2820        working_set.error(ParseError::Expected("datetime", span));
2821        return garbage(working_set, span);
2822    }
2823
2824    let token = String::from_utf8_lossy(bytes).to_string();
2825
2826    if let Ok(datetime) = chrono::DateTime::parse_from_rfc3339(&token) {
2827        return Expression::new(working_set, Expr::DateTime(datetime), span, Type::Date);
2828    }
2829
2830    // Just the date
2831    let just_date = token.clone() + "T00:00:00+00:00";
2832    if let Ok(datetime) = chrono::DateTime::parse_from_rfc3339(&just_date) {
2833        return Expression::new(working_set, Expr::DateTime(datetime), span, Type::Date);
2834    }
2835
2836    // Date and time, assume UTC
2837    let datetime = token + "+00:00";
2838    if let Ok(datetime) = chrono::DateTime::parse_from_rfc3339(&datetime) {
2839        return Expression::new(working_set, Expr::DateTime(datetime), span, Type::Date);
2840    }
2841
2842    working_set.error(ParseError::Expected("datetime", span));
2843
2844    garbage(working_set, span)
2845}
2846
2847/// Parse a duration type, eg '10day'
2848pub fn parse_duration(working_set: &mut StateWorkingSet, span: Span) -> Expression {
2849    trace!("parsing: duration");
2850
2851    let bytes = working_set.get_span_contents(span);
2852
2853    match parse_unit_value(bytes, span, DURATION_UNIT_GROUPS, Type::Duration, |x| x) {
2854        Some(Ok(expr)) => {
2855            let span_id = working_set.add_span(span);
2856            expr.with_span_id(span_id)
2857        }
2858        Some(Err(mk_err_for)) => {
2859            working_set.error(mk_err_for("duration"));
2860            garbage(working_set, span)
2861        }
2862        None => {
2863            working_set.error(ParseError::Expected("duration with valid units", span));
2864            garbage(working_set, span)
2865        }
2866    }
2867}
2868
2869/// Parse a unit type, eg '10kb'
2870pub fn parse_filesize(working_set: &mut StateWorkingSet, span: Span) -> Expression {
2871    trace!("parsing: filesize");
2872
2873    let bytes = working_set.get_span_contents(span);
2874
2875    // the hex digit `b` might be mistaken for the unit `b`, so check that first
2876    if bytes.starts_with(b"0x") {
2877        working_set.error(ParseError::Expected("filesize with valid units", span));
2878        return garbage(working_set, span);
2879    }
2880
2881    match parse_unit_value(bytes, span, FILESIZE_UNIT_GROUPS, Type::Filesize, |x| {
2882        x.to_ascii_uppercase()
2883    }) {
2884        Some(Ok(expr)) => {
2885            let span_id = working_set.add_span(span);
2886            expr.with_span_id(span_id)
2887        }
2888        Some(Err(mk_err_for)) => {
2889            working_set.error(mk_err_for("filesize"));
2890            garbage(working_set, span)
2891        }
2892        None => {
2893            working_set.error(ParseError::Expected("filesize with valid units", span));
2894            garbage(working_set, span)
2895        }
2896    }
2897}
2898
2899type ParseUnitResult<'res> = Result<Expression, Box<dyn Fn(&'res str) -> ParseError>>;
2900type UnitGroup<'unit> = (Unit, &'unit str, Option<(Unit, i64)>);
2901
2902pub fn parse_unit_value<'res>(
2903    bytes: &[u8],
2904    span: Span,
2905    unit_groups: &[UnitGroup],
2906    ty: Type,
2907    transform: fn(String) -> String,
2908) -> Option<ParseUnitResult<'res>> {
2909    if bytes.len() < 2
2910        || !(bytes[0].is_ascii_digit() || (bytes[0] == b'-' && bytes[1].is_ascii_digit()))
2911    {
2912        return None;
2913    }
2914
2915    // Bail if not UTF-8
2916    let value = transform(str::from_utf8(bytes).ok()?.into());
2917
2918    if let Some((unit, name, convert)) = unit_groups.iter().find(|x| value.ends_with(x.1)) {
2919        let lhs_len = value.len() - name.len();
2920        let lhs = strip_underscores(&value.as_bytes()[..lhs_len]);
2921        let lhs_span = Span::new(span.start, span.start + lhs_len);
2922        let unit_span = Span::new(span.start + lhs_len, span.end);
2923        if lhs.ends_with('$') {
2924            // If `parse_unit_value` has higher precedence over `parse_range`,
2925            // a variable with the name of a unit could otherwise not be used as the end of a range.
2926            return None;
2927        }
2928
2929        let (decimal_part, number_part) = modf(match lhs.parse::<f64>() {
2930            Ok(it) => it,
2931            Err(_) => {
2932                let mk_err = move |name| {
2933                    ParseError::LabeledError(
2934                        format!("{name} value must be a number"),
2935                        "not a number".into(),
2936                        lhs_span,
2937                    )
2938                };
2939                return Some(Err(Box::new(mk_err)));
2940            }
2941        });
2942
2943        let mut unit = match convert {
2944            Some(convert_to) => convert_to.0,
2945            None => *unit,
2946        };
2947
2948        let num_float = match convert {
2949            Some(convert_to) => {
2950                (number_part * convert_to.1 as f64) + (decimal_part * convert_to.1 as f64)
2951            }
2952            None => number_part,
2953        };
2954
2955        // Convert all durations to nanoseconds, and filesizes to bytes,
2956        // to minimize loss of precision
2957        let factor = match ty {
2958            Type::Filesize => unit_to_byte_factor(&unit),
2959            Type::Duration => unit_to_ns_factor(&unit),
2960            _ => None,
2961        };
2962
2963        let num = match factor {
2964            Some(factor) => {
2965                let num_base = num_float * factor;
2966                if i64::MIN as f64 <= num_base && num_base <= i64::MAX as f64 {
2967                    unit = if ty == Type::Filesize {
2968                        Unit::Filesize(FilesizeUnit::B)
2969                    } else {
2970                        Unit::Nanosecond
2971                    };
2972                    num_base as i64
2973                } else {
2974                    // not safe to convert, because of the overflow
2975                    num_float as i64
2976                }
2977            }
2978            None => num_float as i64,
2979        };
2980
2981        trace!("-- found {num} {unit:?}");
2982        let value = ValueWithUnit {
2983            expr: Expression::new_unknown(Expr::Int(num), lhs_span, Type::Number),
2984            unit: Spanned {
2985                item: unit,
2986                span: unit_span,
2987            },
2988        };
2989        let expr = Expression::new_unknown(Expr::ValueWithUnit(Box::new(value)), span, ty);
2990
2991        Some(Ok(expr))
2992    } else {
2993        None
2994    }
2995}
2996
2997pub const FILESIZE_UNIT_GROUPS: &[UnitGroup] = &[
2998    (
2999        Unit::Filesize(FilesizeUnit::KB),
3000        "KB",
3001        Some((Unit::Filesize(FilesizeUnit::B), 1000)),
3002    ),
3003    (
3004        Unit::Filesize(FilesizeUnit::MB),
3005        "MB",
3006        Some((Unit::Filesize(FilesizeUnit::KB), 1000)),
3007    ),
3008    (
3009        Unit::Filesize(FilesizeUnit::GB),
3010        "GB",
3011        Some((Unit::Filesize(FilesizeUnit::MB), 1000)),
3012    ),
3013    (
3014        Unit::Filesize(FilesizeUnit::TB),
3015        "TB",
3016        Some((Unit::Filesize(FilesizeUnit::GB), 1000)),
3017    ),
3018    (
3019        Unit::Filesize(FilesizeUnit::PB),
3020        "PB",
3021        Some((Unit::Filesize(FilesizeUnit::TB), 1000)),
3022    ),
3023    (
3024        Unit::Filesize(FilesizeUnit::EB),
3025        "EB",
3026        Some((Unit::Filesize(FilesizeUnit::PB), 1000)),
3027    ),
3028    (
3029        Unit::Filesize(FilesizeUnit::KiB),
3030        "KIB",
3031        Some((Unit::Filesize(FilesizeUnit::B), 1024)),
3032    ),
3033    (
3034        Unit::Filesize(FilesizeUnit::MiB),
3035        "MIB",
3036        Some((Unit::Filesize(FilesizeUnit::KiB), 1024)),
3037    ),
3038    (
3039        Unit::Filesize(FilesizeUnit::GiB),
3040        "GIB",
3041        Some((Unit::Filesize(FilesizeUnit::MiB), 1024)),
3042    ),
3043    (
3044        Unit::Filesize(FilesizeUnit::TiB),
3045        "TIB",
3046        Some((Unit::Filesize(FilesizeUnit::GiB), 1024)),
3047    ),
3048    (
3049        Unit::Filesize(FilesizeUnit::PiB),
3050        "PIB",
3051        Some((Unit::Filesize(FilesizeUnit::TiB), 1024)),
3052    ),
3053    (
3054        Unit::Filesize(FilesizeUnit::EiB),
3055        "EIB",
3056        Some((Unit::Filesize(FilesizeUnit::PiB), 1024)),
3057    ),
3058    (Unit::Filesize(FilesizeUnit::B), "B", None),
3059];
3060
3061pub const DURATION_UNIT_GROUPS: &[UnitGroup] = &[
3062    (Unit::Nanosecond, "ns", None),
3063    // todo start adding aliases for duration units here
3064    (Unit::Microsecond, "us", Some((Unit::Nanosecond, 1000))),
3065    (
3066        // µ Micro Sign
3067        Unit::Microsecond,
3068        "\u{00B5}s",
3069        Some((Unit::Nanosecond, 1000)),
3070    ),
3071    (
3072        // μ Greek small letter Mu
3073        Unit::Microsecond,
3074        "\u{03BC}s",
3075        Some((Unit::Nanosecond, 1000)),
3076    ),
3077    (Unit::Millisecond, "ms", Some((Unit::Microsecond, 1000))),
3078    (Unit::Second, "sec", Some((Unit::Millisecond, 1000))),
3079    (Unit::Minute, "min", Some((Unit::Second, 60))),
3080    (Unit::Hour, "hr", Some((Unit::Minute, 60))),
3081    (Unit::Day, "day", Some((Unit::Minute, 1440))),
3082    (Unit::Week, "wk", Some((Unit::Day, 7))),
3083];
3084
3085fn unit_to_ns_factor(unit: &Unit) -> Option<f64> {
3086    match unit {
3087        Unit::Nanosecond => Some(1.0),
3088        Unit::Microsecond => Some(1_000.0),
3089        Unit::Millisecond => Some(1_000_000.0),
3090        Unit::Second => Some(1_000_000_000.0),
3091        Unit::Minute => Some(60.0 * 1_000_000_000.0),
3092        Unit::Hour => Some(60.0 * 60.0 * 1_000_000_000.0),
3093        Unit::Day => Some(24.0 * 60.0 * 60.0 * 1_000_000_000.0),
3094        Unit::Week => Some(7.0 * 24.0 * 60.0 * 60.0 * 1_000_000_000.0),
3095        _ => None,
3096    }
3097}
3098
3099fn unit_to_byte_factor(unit: &Unit) -> Option<f64> {
3100    match unit {
3101        Unit::Filesize(FilesizeUnit::B) => Some(1.0),
3102        Unit::Filesize(FilesizeUnit::KB) => Some(1_000.0),
3103        Unit::Filesize(FilesizeUnit::MB) => Some(1_000_000.0),
3104        Unit::Filesize(FilesizeUnit::GB) => Some(1_000_000_000.0),
3105        Unit::Filesize(FilesizeUnit::TB) => Some(1_000_000_000_000.0),
3106        Unit::Filesize(FilesizeUnit::PB) => Some(1_000_000_000_000_000.0),
3107        Unit::Filesize(FilesizeUnit::EB) => Some(1_000_000_000_000_000_000.0),
3108        Unit::Filesize(FilesizeUnit::KiB) => Some(1024.0),
3109        Unit::Filesize(FilesizeUnit::MiB) => Some(1024.0 * 1024.0),
3110        Unit::Filesize(FilesizeUnit::GiB) => Some(1024.0 * 1024.0 * 1024.0),
3111        Unit::Filesize(FilesizeUnit::TiB) => Some(1024.0 * 1024.0 * 1024.0 * 1024.0),
3112        Unit::Filesize(FilesizeUnit::PiB) => Some(1024.0 * 1024.0 * 1024.0 * 1024.0 * 1024.0),
3113        Unit::Filesize(FilesizeUnit::EiB) => {
3114            Some(1024.0 * 1024.0 * 1024.0 * 1024.0 * 1024.0 * 1024.0)
3115        }
3116        _ => None,
3117    }
3118}
3119
3120// Borrowed from libm at https://github.com/rust-lang/libm/blob/master/src/math/modf.rs
3121fn modf(x: f64) -> (f64, f64) {
3122    let rv2: f64;
3123    let mut u = x.to_bits();
3124    let e = (((u >> 52) & 0x7ff) as i32) - 0x3ff;
3125
3126    /* no fractional part */
3127    if e >= 52 {
3128        rv2 = x;
3129        if e == 0x400 && (u << 12) != 0 {
3130            /* nan */
3131            return (x, rv2);
3132        }
3133        u &= 1 << 63;
3134        return (f64::from_bits(u), rv2);
3135    }
3136
3137    /* no integral part*/
3138    if e < 0 {
3139        u &= 1 << 63;
3140        rv2 = f64::from_bits(u);
3141        return (x, rv2);
3142    }
3143
3144    let mask = ((!0) >> 12) >> e;
3145    if (u & mask) == 0 {
3146        rv2 = x;
3147        u &= 1 << 63;
3148        return (f64::from_bits(u), rv2);
3149    }
3150    u &= !mask;
3151    rv2 = f64::from_bits(u);
3152    (x - rv2, rv2)
3153}
3154
3155pub fn parse_glob_pattern(working_set: &mut StateWorkingSet, span: Span) -> Expression {
3156    let bytes = working_set.get_span_contents(span);
3157    let quoted = is_quoted(bytes);
3158    let (token, err) = unescape_unquote_string(bytes, span);
3159    trace!("parsing: glob pattern");
3160
3161    if err.is_none() {
3162        trace!("-- found {token}");
3163
3164        Expression::new(
3165            working_set,
3166            Expr::GlobPattern(token, quoted),
3167            span,
3168            Type::Glob,
3169        )
3170    } else {
3171        working_set.error(ParseError::Expected("glob pattern string", span));
3172
3173        garbage(working_set, span)
3174    }
3175}
3176
3177pub fn unescape_string(bytes: &[u8], span: Span) -> (Vec<u8>, Option<ParseError>) {
3178    let mut output = Vec::new();
3179    let mut error = None;
3180
3181    let mut idx = 0;
3182
3183    if !bytes.contains(&b'\\') {
3184        return (bytes.to_vec(), None);
3185    }
3186
3187    'us_loop: while idx < bytes.len() {
3188        if bytes[idx] == b'\\' {
3189            // We're in an escape
3190            idx += 1;
3191
3192            match bytes.get(idx) {
3193                Some(b'"') => {
3194                    output.push(b'"');
3195                    idx += 1;
3196                }
3197                Some(b'\'') => {
3198                    output.push(b'\'');
3199                    idx += 1;
3200                }
3201                Some(b'\\') => {
3202                    output.push(b'\\');
3203                    idx += 1;
3204                }
3205                Some(b'/') => {
3206                    output.push(b'/');
3207                    idx += 1;
3208                }
3209                Some(b'(') => {
3210                    output.push(b'(');
3211                    idx += 1;
3212                }
3213                Some(b')') => {
3214                    output.push(b')');
3215                    idx += 1;
3216                }
3217                Some(b'{') => {
3218                    output.push(b'{');
3219                    idx += 1;
3220                }
3221                Some(b'}') => {
3222                    output.push(b'}');
3223                    idx += 1;
3224                }
3225                Some(b'$') => {
3226                    output.push(b'$');
3227                    idx += 1;
3228                }
3229                Some(b'^') => {
3230                    output.push(b'^');
3231                    idx += 1;
3232                }
3233                Some(b'#') => {
3234                    output.push(b'#');
3235                    idx += 1;
3236                }
3237                Some(b'|') => {
3238                    output.push(b'|');
3239                    idx += 1;
3240                }
3241                Some(b'~') => {
3242                    output.push(b'~');
3243                    idx += 1;
3244                }
3245                Some(b'a') => {
3246                    output.push(0x7);
3247                    idx += 1;
3248                }
3249                Some(b'b') => {
3250                    output.push(0x8);
3251                    idx += 1;
3252                }
3253                Some(b'e') => {
3254                    output.push(0x1b);
3255                    idx += 1;
3256                }
3257                Some(b'f') => {
3258                    output.push(0xc);
3259                    idx += 1;
3260                }
3261                Some(b'n') => {
3262                    output.push(b'\n');
3263                    idx += 1;
3264                }
3265                Some(b'r') => {
3266                    output.push(b'\r');
3267                    idx += 1;
3268                }
3269                Some(b't') => {
3270                    output.push(b'\t');
3271                    idx += 1;
3272                }
3273                Some(b'u') => {
3274                    let mut digits = String::with_capacity(10);
3275                    let mut cur_idx = idx + 1; // index of first beyond current end of token
3276
3277                    if let Some(b'{') = bytes.get(idx + 1) {
3278                        cur_idx = idx + 2;
3279                        loop {
3280                            match bytes.get(cur_idx) {
3281                                Some(b'}') => {
3282                                    cur_idx += 1;
3283                                    break;
3284                                }
3285                                Some(c) => {
3286                                    digits.push(*c as char);
3287                                    cur_idx += 1;
3288                                }
3289                                _ => {
3290                                    error = error.or(Some(ParseError::InvalidLiteral(
3291                                        "missing '}' for unicode escape '\\u{X...}'".into(),
3292                                        "string".into(),
3293                                        Span::new(span.start + idx, span.end),
3294                                    )));
3295                                    break 'us_loop;
3296                                }
3297                            }
3298                        }
3299                    }
3300
3301                    if (1..=6).contains(&digits.len()) {
3302                        let int = u32::from_str_radix(&digits, 16);
3303
3304                        if let Ok(int) = int
3305                            && int <= 0x10ffff
3306                        {
3307                            let result = char::from_u32(int);
3308
3309                            if let Some(result) = result {
3310                                let mut buffer = [0; 4];
3311                                let result = result.encode_utf8(&mut buffer);
3312
3313                                for elem in result.bytes() {
3314                                    output.push(elem);
3315                                }
3316
3317                                idx = cur_idx;
3318                                continue 'us_loop;
3319                            }
3320                        }
3321                    }
3322                    // fall through -- escape not accepted above, must be error.
3323                    error = error.or(Some(ParseError::InvalidLiteral(
3324                            "invalid unicode escape '\\u{X...}', must be 1-6 hex digits, max value 10FFFF".into(),
3325                            "string".into(),
3326                            Span::new(span.start + idx, span.end),
3327                    )));
3328                    break 'us_loop;
3329                }
3330
3331                _ => {
3332                    error = error.or(Some(ParseError::InvalidLiteral(
3333                        "unrecognized escape after '\\'".into(),
3334                        "string".into(),
3335                        Span::new(span.start + idx, span.end),
3336                    )));
3337                    break 'us_loop;
3338                }
3339            }
3340        } else {
3341            output.push(bytes[idx]);
3342            idx += 1;
3343        }
3344    }
3345
3346    (output, error)
3347}
3348
3349pub fn unescape_unquote_string(bytes: &[u8], span: Span) -> (String, Option<ParseError>) {
3350    if bytes.starts_with(b"\"") {
3351        // Needs unescaping
3352        let bytes = trim_quotes(bytes);
3353
3354        let (bytes, err) = unescape_string(bytes, span);
3355
3356        if let Ok(token) = String::from_utf8(bytes) {
3357            (token, err)
3358        } else {
3359            (String::new(), Some(ParseError::Expected("string", span)))
3360        }
3361    } else {
3362        let bytes = trim_quotes(bytes);
3363
3364        if let Ok(token) = String::from_utf8(bytes.into()) {
3365            (token, None)
3366        } else {
3367            (String::new(), Some(ParseError::Expected("string", span)))
3368        }
3369    }
3370}
3371
3372pub fn parse_string(working_set: &mut StateWorkingSet, span: Span) -> Expression {
3373    trace!("parsing: string");
3374
3375    let bytes = working_set.get_span_contents(span);
3376
3377    if bytes.is_empty() {
3378        working_set.error(ParseError::Expected("String", span));
3379        return Expression::garbage(working_set, span);
3380    }
3381
3382    // Check for bare word interpolation
3383    if bytes[0] != b'\'' && bytes[0] != b'"' && bytes[0] != b'`' && bytes.contains(&b'(') {
3384        return parse_string_interpolation(working_set, span);
3385    }
3386    // Check for unbalanced quotes:
3387    {
3388        if bytes.starts_with(b"\"")
3389            && (bytes.iter().filter(|ch| **ch == b'"').count() > 1 && !bytes.ends_with(b"\""))
3390        {
3391            let close_delimiter_index = bytes
3392                .iter()
3393                .skip(1)
3394                .position(|ch| *ch == b'"')
3395                .expect("Already check input bytes contains at least two double quotes");
3396            // needs `+2` rather than `+1`, because we have skip 1 to find close_delimiter_index before.
3397            let span = Span::new(span.start + close_delimiter_index + 2, span.end);
3398            working_set.error(ParseError::ExtraTokensAfterClosingDelimiter(span));
3399            return garbage(working_set, span);
3400        }
3401
3402        if bytes.starts_with(b"\'")
3403            && (bytes.iter().filter(|ch| **ch == b'\'').count() > 1 && !bytes.ends_with(b"\'"))
3404        {
3405            let close_delimiter_index = bytes
3406                .iter()
3407                .skip(1)
3408                .position(|ch| *ch == b'\'')
3409                .expect("Already check input bytes contains at least two double quotes");
3410            // needs `+2` rather than `+1`, because we have skip 1 to find close_delimiter_index before.
3411            let span = Span::new(span.start + close_delimiter_index + 2, span.end);
3412            working_set.error(ParseError::ExtraTokensAfterClosingDelimiter(span));
3413            return garbage(working_set, span);
3414        }
3415    }
3416
3417    let (s, err) = unescape_unquote_string(bytes, span);
3418    if let Some(err) = err {
3419        working_set.error(err);
3420    }
3421
3422    Expression::new(working_set, Expr::String(s), span, Type::String)
3423}
3424
3425fn is_quoted(bytes: &[u8]) -> bool {
3426    (bytes.starts_with(b"\"") && bytes.ends_with(b"\"") && bytes.len() > 1)
3427        || (bytes.starts_with(b"\'") && bytes.ends_with(b"\'") && bytes.len() > 1)
3428}
3429
3430pub fn parse_string_strict(working_set: &mut StateWorkingSet, span: Span) -> Expression {
3431    trace!("parsing: string, with required delimiters");
3432
3433    let bytes = working_set.get_span_contents(span);
3434
3435    // Check for unbalanced quotes:
3436    {
3437        let bytes = if bytes.starts_with(b"$") {
3438            &bytes[1..]
3439        } else {
3440            bytes
3441        };
3442        if bytes.starts_with(b"\"") && (bytes.len() == 1 || !bytes.ends_with(b"\"")) {
3443            working_set.error(ParseError::Unclosed("\"".into(), span));
3444            return garbage(working_set, span);
3445        }
3446        if bytes.starts_with(b"\'") && (bytes.len() == 1 || !bytes.ends_with(b"\'")) {
3447            working_set.error(ParseError::Unclosed("\'".into(), span));
3448            return garbage(working_set, span);
3449        }
3450        if bytes.starts_with(b"r#") && (bytes.len() == 1 || !bytes.ends_with(b"#")) {
3451            working_set.error(ParseError::Unclosed("r#".into(), span));
3452            return garbage(working_set, span);
3453        }
3454    }
3455
3456    let (bytes, quoted) = if (bytes.starts_with(b"\"") && bytes.ends_with(b"\"") && bytes.len() > 1)
3457        || (bytes.starts_with(b"\'") && bytes.ends_with(b"\'") && bytes.len() > 1)
3458    {
3459        (&bytes[1..(bytes.len() - 1)], true)
3460    } else if (bytes.starts_with(b"$\"") && bytes.ends_with(b"\"") && bytes.len() > 2)
3461        || (bytes.starts_with(b"$\'") && bytes.ends_with(b"\'") && bytes.len() > 2)
3462    {
3463        (&bytes[2..(bytes.len() - 1)], true)
3464    } else {
3465        (bytes, false)
3466    };
3467
3468    if let Ok(token) = String::from_utf8(bytes.into()) {
3469        trace!("-- found {token}");
3470
3471        if quoted {
3472            Expression::new(working_set, Expr::String(token), span, Type::String)
3473        } else if token.contains(' ') {
3474            working_set.error(ParseError::Expected("string", span));
3475
3476            garbage(working_set, span)
3477        } else {
3478            Expression::new(working_set, Expr::String(token), span, Type::String)
3479        }
3480    } else {
3481        working_set.error(ParseError::Expected("string", span));
3482        garbage(working_set, span)
3483    }
3484}
3485
3486pub fn parse_import_pattern<'a>(
3487    working_set: &mut StateWorkingSet,
3488    mut arg_iter: impl Iterator<Item = &'a Expression>,
3489    spans: &[Span],
3490) -> Expression {
3491    let Some(head_expr) = arg_iter.next() else {
3492        working_set.error(ParseError::WrongImportPattern(
3493            "needs at least one component of import pattern".to_string(),
3494            Span::concat(spans),
3495        ));
3496        return garbage(working_set, Span::concat(spans));
3497    };
3498
3499    let (maybe_module_id, head_name) = match eval_constant(working_set, head_expr) {
3500        Ok(Value::Nothing { .. }) => {
3501            return Expression::new(
3502                working_set,
3503                Expr::Nothing,
3504                Span::concat(spans),
3505                Type::Nothing,
3506            );
3507        }
3508        Ok(val) => match val.coerce_into_string() {
3509            Ok(s) => (working_set.find_module(s.as_bytes()), s.into_bytes()),
3510            Err(err) => {
3511                working_set.error(err.wrap(working_set, Span::concat(spans)));
3512                return garbage(working_set, Span::concat(spans));
3513            }
3514        },
3515        Err(err) => {
3516            working_set.error(err.wrap(working_set, Span::concat(spans)));
3517            return garbage(working_set, Span::concat(spans));
3518        }
3519    };
3520
3521    let mut import_pattern = ImportPattern {
3522        head: ImportPatternHead {
3523            name: head_name,
3524            id: maybe_module_id,
3525            span: head_expr.span,
3526        },
3527        members: vec![],
3528        hidden: HashSet::new(),
3529        constants: vec![],
3530    };
3531
3532    let mut leaf_member_expr: Option<(&str, Span)> = None;
3533
3534    // TODO: box pattern syntax is experimental @rust v1.89.0
3535    let handle_list_items =
3536        |items: &Vec<ListItem>,
3537         span,
3538         working_set: &mut StateWorkingSet<'_>,
3539         import_pattern: &mut ImportPattern,
3540         leaf_member_expr: &mut Option<(&str, Span)>| {
3541            let mut output = vec![];
3542
3543            for item in items.iter() {
3544                match item {
3545                    ListItem::Item(expr) => {
3546                        if let Some(name) = expr.as_string() {
3547                            output.push((name.as_bytes().to_vec(), expr.span));
3548                        }
3549                    }
3550                    ListItem::Spread(_, spread) => {
3551                        working_set.error(ParseError::WrongImportPattern(
3552                            "cannot spread in an import pattern".into(),
3553                            spread.span,
3554                        ))
3555                    }
3556                }
3557            }
3558
3559            import_pattern
3560                .members
3561                .push(ImportPatternMember::List { names: output });
3562
3563            *leaf_member_expr = Some(("list", span));
3564        };
3565
3566    for tail_expr in arg_iter {
3567        if let Some((what, prev_span)) = leaf_member_expr {
3568            working_set.error(ParseError::WrongImportPattern(
3569                format!("{what} member can be only at the end of an import pattern"),
3570                prev_span,
3571            ));
3572            return Expression::new(
3573                working_set,
3574                Expr::ImportPattern(Box::new(import_pattern)),
3575                prev_span,
3576                Type::List(Box::new(Type::String)),
3577            );
3578        }
3579
3580        match &tail_expr.expr {
3581            Expr::String(name) => {
3582                let span = tail_expr.span;
3583                if name == "*" {
3584                    import_pattern
3585                        .members
3586                        .push(ImportPatternMember::Glob { span });
3587
3588                    leaf_member_expr = Some(("glob", span));
3589                } else {
3590                    import_pattern.members.push(ImportPatternMember::Name {
3591                        name: name.as_bytes().to_vec(),
3592                        span,
3593                    });
3594                }
3595            }
3596            Expr::FullCellPath(fcp) => {
3597                if let Expr::List(items) = &fcp.head.expr {
3598                    handle_list_items(
3599                        items,
3600                        fcp.head.span,
3601                        working_set,
3602                        &mut import_pattern,
3603                        &mut leaf_member_expr,
3604                    );
3605                }
3606            }
3607            Expr::List(items) => {
3608                handle_list_items(
3609                    items,
3610                    tail_expr.span,
3611                    working_set,
3612                    &mut import_pattern,
3613                    &mut leaf_member_expr,
3614                );
3615            }
3616            _ => {
3617                working_set.error(ParseError::WrongImportPattern(
3618                    "Wrong type of import pattern, only String and List<String> are allowed."
3619                        .into(),
3620                    tail_expr.span,
3621                ));
3622            }
3623        };
3624    }
3625
3626    Expression::new(
3627        working_set,
3628        Expr::ImportPattern(Box::new(import_pattern)),
3629        Span::concat(&spans[1..]),
3630        Type::List(Box::new(Type::String)),
3631    )
3632}
3633
3634/// Parse `spans[spans_idx..]` into a variable, with optional type annotation.
3635/// If the name of the variable ends with a colon (no space in-between allowed), then a type annotation
3636/// can appear after the variable, in which case the colon is stripped from the name of the variable.
3637/// `spans_idx` is updated to point to the last span that has been parsed.
3638pub fn parse_var_with_opt_type(
3639    working_set: &mut StateWorkingSet,
3640    spans: &[Span],
3641    spans_idx: &mut usize,
3642    mutable: bool,
3643) -> (Expression, Option<Type>) {
3644    let name_span = spans[*spans_idx];
3645    let bytes = working_set.get_span_contents(name_span).to_vec();
3646
3647    if bytes.contains(&b' ')
3648        || bytes.contains(&b'"')
3649        || bytes.contains(&b'\'')
3650        || bytes.contains(&b'`')
3651    {
3652        working_set.error(ParseError::VariableNotValid(spans[*spans_idx]));
3653        return (garbage(working_set, spans[*spans_idx]), None);
3654    }
3655
3656    if bytes.ends_with(b":") {
3657        let name_span = Span::new(name_span.start, name_span.end - 1);
3658        let var_name = bytes[0..(bytes.len() - 1)].to_vec();
3659
3660        // We end with colon, so the next span should be the type
3661        if *spans_idx + 1 < spans.len() {
3662            *spans_idx += 1;
3663            // signature like record<a: int b: int> is broken into multiple spans due to
3664            // whitespaces. Collect the rest into one span and work on it
3665            let full_span = Span::concat(&spans[*spans_idx..]);
3666            let type_bytes = working_set.get_span_contents(full_span).to_vec();
3667
3668            let (tokens, parse_error) =
3669                lex_signature(&type_bytes, full_span.start, &[], &[b','], true);
3670
3671            if let Some(parse_error) = parse_error {
3672                working_set.error(parse_error);
3673            }
3674
3675            let ty = parse_type(working_set, &type_bytes, tokens[0].span);
3676            *spans_idx = spans.len() - 1;
3677
3678            if !is_variable(&var_name) {
3679                working_set.error(ParseError::Expected(
3680                    "valid variable name",
3681                    spans[*spans_idx - 1],
3682                ));
3683                return (garbage(working_set, spans[*spans_idx - 1]), None);
3684            }
3685
3686            let id = working_set.add_variable(var_name, spans[*spans_idx - 1], ty.clone(), mutable);
3687
3688            (
3689                Expression::new(working_set, Expr::VarDecl(id), name_span, ty.clone()),
3690                Some(ty),
3691            )
3692        } else {
3693            if !is_variable(&var_name) {
3694                working_set.error(ParseError::Expected(
3695                    "valid variable name",
3696                    spans[*spans_idx],
3697                ));
3698                return (garbage(working_set, spans[*spans_idx]), None);
3699            }
3700
3701            let id = working_set.add_variable(var_name, spans[*spans_idx], Type::Any, mutable);
3702
3703            working_set.error(ParseError::MissingType(spans[*spans_idx]));
3704            (
3705                Expression::new(working_set, Expr::VarDecl(id), spans[*spans_idx], Type::Any),
3706                None,
3707            )
3708        }
3709    } else {
3710        let var_name = bytes;
3711
3712        if !is_variable(&var_name) {
3713            working_set.error(ParseError::Expected(
3714                "valid variable name",
3715                spans[*spans_idx],
3716            ));
3717            return (garbage(working_set, spans[*spans_idx]), None);
3718        }
3719
3720        let id = working_set.add_variable(
3721            var_name,
3722            Span::concat(&spans[*spans_idx..*spans_idx + 1]),
3723            Type::Any,
3724            mutable,
3725        );
3726
3727        (
3728            Expression::new(working_set, Expr::VarDecl(id), spans[*spans_idx], Type::Any),
3729            None,
3730        )
3731    }
3732}
3733
3734pub fn expand_to_cell_path(
3735    working_set: &mut StateWorkingSet,
3736    expression: &mut Expression,
3737    var_id: VarId,
3738) {
3739    trace!("parsing: expanding to cell path");
3740    if let Expression {
3741        expr: Expr::String(_),
3742        span,
3743        ..
3744    } = expression
3745    {
3746        // Re-parse the string as if it were a cell-path
3747        let new_expression = parse_full_cell_path(working_set, Some(var_id), *span);
3748
3749        *expression = new_expression;
3750    }
3751
3752    if let Expression {
3753        expr: Expr::UnaryNot(inner),
3754        ..
3755    } = expression
3756    {
3757        expand_to_cell_path(working_set, inner, var_id);
3758    }
3759}
3760
3761pub fn parse_input_output_types(
3762    working_set: &mut StateWorkingSet,
3763    spans: &[Span],
3764) -> Vec<(Type, Type)> {
3765    let mut full_span = Span::concat(spans);
3766
3767    let mut bytes = working_set.get_span_contents(full_span);
3768
3769    if bytes.starts_with(b"[") {
3770        bytes = &bytes[1..];
3771        full_span.start += 1;
3772    }
3773
3774    if bytes.ends_with(b"]") {
3775        bytes = &bytes[..(bytes.len() - 1)];
3776        full_span.end -= 1;
3777    }
3778
3779    let (tokens, parse_error) =
3780        lex_signature(bytes, full_span.start, &[b'\n', b'\r', b','], &[], true);
3781
3782    if let Some(parse_error) = parse_error {
3783        working_set.error(parse_error);
3784    }
3785
3786    let mut output = vec![];
3787
3788    let mut idx = 0;
3789    while idx < tokens.len() {
3790        let type_bytes = working_set.get_span_contents(tokens[idx].span).to_vec();
3791        let input_type = parse_type(working_set, &type_bytes, tokens[idx].span);
3792
3793        idx += 1;
3794        if idx >= tokens.len() {
3795            working_set.error(ParseError::Expected(
3796                "arrow (->)",
3797                Span::new(tokens[idx - 1].span.end, tokens[idx - 1].span.end),
3798            ));
3799            break;
3800        }
3801
3802        let arrow = working_set.get_span_contents(tokens[idx].span);
3803        if arrow != b"->" {
3804            working_set.error(ParseError::Expected("arrow (->)", tokens[idx].span));
3805        }
3806
3807        idx += 1;
3808        if idx >= tokens.len() {
3809            working_set.error(ParseError::MissingType(Span::new(
3810                tokens[idx - 1].span.end,
3811                tokens[idx - 1].span.end,
3812            )));
3813            break;
3814        }
3815
3816        let type_bytes = working_set.get_span_contents(tokens[idx].span).to_vec();
3817        let output_type = parse_type(working_set, &type_bytes, tokens[idx].span);
3818
3819        output.push((input_type, output_type));
3820
3821        idx += 1;
3822    }
3823
3824    output
3825}
3826
3827pub fn parse_full_signature(working_set: &mut StateWorkingSet, spans: &[Span]) -> Expression {
3828    match spans.len() {
3829        // This case should never happen. It corresponds to declarations like `def foo {}`,
3830        // which should throw a 'Missing required positional argument.' before getting to this point
3831        0 => {
3832            working_set.error(ParseError::InternalError(
3833                "failed to catch missing positional arguments".to_string(),
3834                Span::concat(spans),
3835            ));
3836            garbage(working_set, Span::concat(spans))
3837        }
3838
3839        // e.g. `[ b"[foo: string]" ]`
3840        1 => parse_signature(working_set, spans[0]),
3841
3842        // This case is needed to distinguish between e.g.
3843        // `[ b"[]", b"{ true }" ]` vs `[ b"[]:", b"int" ]`
3844        2 if working_set.get_span_contents(spans[1]).starts_with(b"{") => {
3845            parse_signature(working_set, spans[0])
3846        }
3847
3848        // This should handle every other case, e.g.
3849        // `[ b"[]:", b"int" ]`
3850        // `[ b"[]", b":", b"int" ]`
3851        // `[ b"[]", b":", b"int", b"->", b"bool" ]`
3852        _ => {
3853            let (mut arg_signature, input_output_types_pos) =
3854                if working_set.get_span_contents(spans[0]).ends_with(b":") {
3855                    (
3856                        parse_signature(working_set, Span::new(spans[0].start, spans[0].end - 1)),
3857                        1,
3858                    )
3859                } else if working_set.get_span_contents(spans[1]) == b":" {
3860                    (parse_signature(working_set, spans[0]), 2)
3861                } else {
3862                    // This should be an error case, but we call parse_signature anyway
3863                    // so it can handle the various possible errors
3864                    // e.g. `[ b"[]", b"int" ]` or `[
3865                    working_set.error(ParseError::Expected(
3866                        "colon (:) before type signature",
3867                        Span::concat(&spans[1..]),
3868                    ));
3869                    // (garbage(working_set, Span::concat(spans)), 1)
3870
3871                    (parse_signature(working_set, spans[0]), 1)
3872                };
3873
3874            let input_output_types =
3875                parse_input_output_types(working_set, &spans[input_output_types_pos..]);
3876
3877            if let Expression {
3878                expr: Expr::Signature(sig),
3879                span: expr_span,
3880                ..
3881            } = &mut arg_signature
3882            {
3883                sig.input_output_types = input_output_types;
3884                expr_span.end = Span::concat(&spans[input_output_types_pos..]).end;
3885            }
3886            arg_signature
3887        }
3888    }
3889}
3890
3891pub fn parse_row_condition(working_set: &mut StateWorkingSet, spans: &[Span]) -> Expression {
3892    let pos = spans.first().map(|s| s.start).unwrap_or(0);
3893    let var_id = working_set.add_variable(b"$it".to_vec(), Span::new(pos, pos), Type::Any, false);
3894    let expression = parse_math_expression(working_set, spans, Some(var_id));
3895    let span = Span::concat(spans);
3896
3897    let block_id = match expression.expr {
3898        Expr::Block(block_id) => block_id,
3899        Expr::Closure(block_id) => block_id,
3900        Expr::FullCellPath(ref box_fcp) if box_fcp.head.as_var().is_some_and(|id| id != var_id) => {
3901            let mut expression = expression;
3902            expression.ty = Type::Any;
3903            return expression;
3904        }
3905        Expr::Var(arg_var_id) if arg_var_id != var_id => {
3906            let mut expression = expression;
3907            expression.ty = Type::Any;
3908            return expression;
3909        }
3910        _ => {
3911            // We have an expression, check that it's compatible with bool
3912            if !type_compatible(&Type::Bool, &expression.ty) {
3913                working_set.error(ParseError::TypeMismatch(
3914                    Type::Bool,
3915                    expression.ty.clone(),
3916                    expression.span,
3917                ));
3918                return Expression::garbage(working_set, expression.span);
3919            }
3920
3921            // Convert this expression into a block.
3922            let mut block = Block::new();
3923            let mut pipeline = Pipeline::new();
3924            pipeline.elements.push(PipelineElement {
3925                pipe: None,
3926                expr: expression,
3927                redirection: None,
3928            });
3929
3930            block.pipelines.push(pipeline);
3931
3932            block.signature.required_positional.push(PositionalArg {
3933                name: "$it".into(),
3934                desc: "row condition".into(),
3935                shape: SyntaxShape::Any,
3936                var_id: Some(var_id),
3937                default_value: None,
3938                completion: None,
3939            });
3940
3941            compile_block(working_set, &mut block);
3942
3943            working_set.add_block(Arc::new(block))
3944        }
3945    };
3946
3947    Expression::new(working_set, Expr::RowCondition(block_id), span, Type::Bool)
3948}
3949
3950pub fn parse_signature(working_set: &mut StateWorkingSet, span: Span) -> Expression {
3951    let bytes = working_set.get_span_contents(span);
3952
3953    let mut start = span.start;
3954    let mut end = span.end;
3955
3956    let mut has_paren = false;
3957
3958    if bytes.starts_with(b"[") {
3959        start += 1;
3960    } else if bytes.starts_with(b"(") {
3961        has_paren = true;
3962        start += 1;
3963    } else {
3964        working_set.error(ParseError::Expected("[ or (", Span::new(start, start + 1)));
3965        return garbage(working_set, span);
3966    }
3967
3968    if (has_paren && bytes.ends_with(b")")) || (!has_paren && bytes.ends_with(b"]")) {
3969        end -= 1;
3970    } else {
3971        working_set.error(ParseError::Unclosed("] or )".into(), Span::new(end, end)));
3972    }
3973
3974    let sig = parse_signature_helper(working_set, Span::new(start, end));
3975
3976    Expression::new(working_set, Expr::Signature(sig), span, Type::Any)
3977}
3978
3979pub fn parse_signature_helper(working_set: &mut StateWorkingSet, span: Span) -> Box<Signature> {
3980    enum ParseMode {
3981        Arg,
3982        AfterCommaArg,
3983        Type,
3984        AfterType,
3985        DefaultValue,
3986    }
3987
3988    #[derive(Debug)]
3989    enum Arg {
3990        Positional {
3991            arg: PositionalArg,
3992            required: bool,
3993            type_annotated: bool,
3994        },
3995        RestPositional(PositionalArg),
3996        Flag {
3997            flag: Flag,
3998            type_annotated: bool,
3999        },
4000    }
4001
4002    let source = working_set.get_span_contents(span);
4003
4004    let (output, err) = lex_signature(
4005        source,
4006        span.start,
4007        &[b'\n', b'\r'],
4008        &[b':', b'=', b','],
4009        false,
4010    );
4011    if let Some(err) = err {
4012        working_set.error(err);
4013    }
4014
4015    let mut args: Vec<Arg> = vec![];
4016    let mut parse_mode = ParseMode::Arg;
4017
4018    for (index, token) in output.iter().enumerate() {
4019        let last_token = index == output.len() - 1;
4020
4021        match token {
4022            Token {
4023                contents: crate::TokenContents::Item | crate::TokenContents::AssignmentOperator,
4024                span,
4025            } => {
4026                let span = *span;
4027                let contents = working_set.get_span_contents(span).to_vec();
4028
4029                // The : symbol separates types
4030                if contents == b":" {
4031                    match parse_mode {
4032                        ParseMode::Arg if last_token => working_set
4033                            .error(ParseError::Expected("type", Span::new(span.end, span.end))),
4034                        ParseMode::Arg => {
4035                            parse_mode = ParseMode::Type;
4036                        }
4037                        ParseMode::AfterCommaArg | ParseMode::AfterType => {
4038                            working_set.error(ParseError::Expected("parameter or flag", span));
4039                        }
4040                        ParseMode::Type | ParseMode::DefaultValue => {
4041                            // We're seeing two types for the same thing for some reason, error
4042                            working_set.error(ParseError::Expected("type", span));
4043                        }
4044                    }
4045                }
4046                // The = symbol separates a variable from its default value
4047                else if contents == b"=" {
4048                    match parse_mode {
4049                        ParseMode::Arg | ParseMode::AfterType if last_token => working_set.error(
4050                            ParseError::Expected("default value", Span::new(span.end, span.end)),
4051                        ),
4052                        ParseMode::Arg | ParseMode::AfterType => {
4053                            parse_mode = ParseMode::DefaultValue;
4054                        }
4055                        ParseMode::Type => {
4056                            working_set.error(ParseError::Expected("type", span));
4057                        }
4058                        ParseMode::AfterCommaArg => {
4059                            working_set.error(ParseError::Expected("parameter or flag", span));
4060                        }
4061                        ParseMode::DefaultValue => {
4062                            // We're seeing two default values for some reason, error
4063                            working_set.error(ParseError::Expected("default value", span));
4064                        }
4065                    }
4066                }
4067                // The , symbol separates params only
4068                else if contents == b"," {
4069                    match parse_mode {
4070                        ParseMode::Arg | ParseMode::AfterType => {
4071                            parse_mode = ParseMode::AfterCommaArg
4072                        }
4073                        ParseMode::AfterCommaArg => {
4074                            working_set.error(ParseError::Expected("parameter or flag", span));
4075                        }
4076                        ParseMode::Type => {
4077                            working_set.error(ParseError::Expected("type", span));
4078                        }
4079                        ParseMode::DefaultValue => {
4080                            working_set.error(ParseError::Expected("default value", span));
4081                        }
4082                    }
4083                } else {
4084                    match parse_mode {
4085                        ParseMode::Arg | ParseMode::AfterCommaArg | ParseMode::AfterType => {
4086                            // Long flag with optional short form following with no whitespace, e.g. --output, --age(-a)
4087                            if contents.starts_with(b"--") && contents.len() > 2 {
4088                                // Split the long flag from the short flag with the ( character as delimiter.
4089                                // The trailing ) is removed further down.
4090                                let flags: Vec<_> = contents.split(|x| x == &b'(').collect();
4091
4092                                let long = String::from_utf8_lossy(&flags[0][2..]).to_string();
4093                                let mut variable_name = flags[0][2..].to_vec();
4094                                // Replace the '-' in a variable name with '_'
4095                                for byte in variable_name.iter_mut() {
4096                                    if *byte == b'-' {
4097                                        *byte = b'_';
4098                                    }
4099                                }
4100
4101                                if !is_variable(&variable_name) {
4102                                    working_set.error(ParseError::Expected(
4103                                        "valid variable name for this long flag",
4104                                        span,
4105                                    ))
4106                                }
4107
4108                                let var_id =
4109                                    working_set.add_variable(variable_name, span, Type::Any, false);
4110
4111                                // If there's no short flag, exit now. Otherwise, parse it.
4112                                if flags.len() == 1 {
4113                                    args.push(Arg::Flag {
4114                                        flag: Flag {
4115                                            arg: None,
4116                                            desc: String::new(),
4117                                            long,
4118                                            short: None,
4119                                            required: false,
4120                                            var_id: Some(var_id),
4121                                            default_value: None,
4122                                            completion: None,
4123                                        },
4124                                        type_annotated: false,
4125                                    });
4126                                } else if flags.len() >= 3 {
4127                                    working_set.error(ParseError::Expected(
4128                                        "only one short flag alternative",
4129                                        span,
4130                                    ));
4131                                } else {
4132                                    let short_flag = &flags[1];
4133                                    let short_flag = if !short_flag.starts_with(b"-")
4134                                        || !short_flag.ends_with(b")")
4135                                    {
4136                                        working_set.error(ParseError::Expected(
4137                                            "short flag alternative for the long flag",
4138                                            span,
4139                                        ));
4140                                        short_flag
4141                                    } else {
4142                                        // Obtain the flag's name by removing the starting - and trailing )
4143                                        &short_flag[1..(short_flag.len() - 1)]
4144                                    };
4145                                    // Note that it is currently possible to make a short flag with non-alphanumeric characters,
4146                                    // like -).
4147
4148                                    let short_flag =
4149                                        String::from_utf8_lossy(short_flag).to_string();
4150                                    let chars: Vec<char> = short_flag.chars().collect();
4151
4152                                    if chars.len() == 1 {
4153                                        args.push(Arg::Flag {
4154                                            flag: Flag {
4155                                                arg: None,
4156                                                desc: String::new(),
4157                                                long,
4158                                                short: Some(chars[0]),
4159                                                required: false,
4160                                                var_id: Some(var_id),
4161                                                default_value: None,
4162                                                completion: None,
4163                                            },
4164                                            type_annotated: false,
4165                                        });
4166                                    } else {
4167                                        working_set.error(ParseError::Expected("short flag", span));
4168                                    }
4169                                }
4170                                parse_mode = ParseMode::Arg;
4171                            }
4172                            // Mandatory short flag, e.g. -e (must be one character)
4173                            else if contents.starts_with(b"-") && contents.len() > 1 {
4174                                let short_flag = &contents[1..];
4175                                let short_flag = String::from_utf8_lossy(short_flag).to_string();
4176                                let chars: Vec<char> = short_flag.chars().collect();
4177
4178                                if chars.len() > 1 {
4179                                    working_set.error(ParseError::Expected("short flag", span));
4180                                }
4181
4182                                let mut encoded_var_name = [0u8; 4];
4183                                let len = chars[0].encode_utf8(&mut encoded_var_name).len();
4184                                let variable_name = encoded_var_name[0..len].to_vec();
4185
4186                                if !is_variable(&variable_name) {
4187                                    working_set.error(ParseError::Expected(
4188                                        "valid variable name for this short flag",
4189                                        span,
4190                                    ))
4191                                }
4192
4193                                let var_id =
4194                                    working_set.add_variable(variable_name, span, Type::Any, false);
4195
4196                                args.push(Arg::Flag {
4197                                    flag: Flag {
4198                                        arg: None,
4199                                        desc: String::new(),
4200                                        long: String::new(),
4201                                        short: Some(chars[0]),
4202                                        required: false,
4203                                        var_id: Some(var_id),
4204                                        default_value: None,
4205                                        completion: None,
4206                                    },
4207                                    type_annotated: false,
4208                                });
4209                                parse_mode = ParseMode::Arg;
4210                            }
4211                            // Short flag alias for long flag, e.g. --b (-a)
4212                            // This is the same as the short flag in --b(-a)
4213                            else if let Some(short_flag) = contents.strip_prefix(b"(-") {
4214                                if let ParseMode::AfterCommaArg = parse_mode {
4215                                    working_set
4216                                        .error(ParseError::Expected("parameter or flag", span));
4217                                }
4218
4219                                let short_flag = if !short_flag.ends_with(b")") {
4220                                    working_set.error(ParseError::Expected("short flag", span));
4221                                    short_flag
4222                                } else {
4223                                    &short_flag[..(short_flag.len() - 1)]
4224                                };
4225
4226                                let short_flag = String::from_utf8_lossy(short_flag).to_string();
4227                                let chars: Vec<char> = short_flag.chars().collect();
4228
4229                                if chars.len() == 1 {
4230                                    match args.last_mut() {
4231                                        Some(Arg::Flag { flag, .. }) => {
4232                                            if flag.short.is_some() {
4233                                                working_set.error(ParseError::Expected(
4234                                                    "one short flag",
4235                                                    span,
4236                                                ));
4237                                            } else {
4238                                                flag.short = Some(chars[0]);
4239                                            }
4240                                        }
4241                                        _ => {
4242                                            working_set
4243                                                .error(ParseError::Expected("unknown flag", span));
4244                                        }
4245                                    }
4246                                } else {
4247                                    working_set.error(ParseError::Expected("short flag", span));
4248                                }
4249                            }
4250                            // Positional arg, optional
4251                            else if let Some(optional_param) = contents.strip_suffix(b"?") {
4252                                let name = String::from_utf8_lossy(optional_param).to_string();
4253
4254                                if !is_variable(optional_param) {
4255                                    working_set.error(ParseError::Expected(
4256                                        "valid variable name for this optional parameter",
4257                                        span,
4258                                    ))
4259                                }
4260
4261                                let var_id = working_set.add_variable(
4262                                    optional_param.to_vec(),
4263                                    span,
4264                                    Type::Any,
4265                                    false,
4266                                );
4267
4268                                args.push(Arg::Positional {
4269                                    arg: PositionalArg {
4270                                        desc: String::new(),
4271                                        name,
4272                                        shape: SyntaxShape::Any,
4273                                        var_id: Some(var_id),
4274                                        default_value: None,
4275                                        completion: None,
4276                                    },
4277                                    required: false,
4278                                    type_annotated: false,
4279                                });
4280                                parse_mode = ParseMode::Arg;
4281                            }
4282                            // Rest param
4283                            else if let Some(contents) = contents.strip_prefix(b"...") {
4284                                let name = String::from_utf8_lossy(contents).to_string();
4285                                let contents_vec: Vec<u8> = contents.to_vec();
4286
4287                                if !is_variable(&contents_vec) {
4288                                    working_set.error(ParseError::Expected(
4289                                        "valid variable name for this rest parameter",
4290                                        span,
4291                                    ))
4292                                }
4293
4294                                let var_id =
4295                                    working_set.add_variable(contents_vec, span, Type::Any, false);
4296
4297                                args.push(Arg::RestPositional(PositionalArg {
4298                                    desc: String::new(),
4299                                    name,
4300                                    shape: SyntaxShape::Any,
4301                                    var_id: Some(var_id),
4302                                    default_value: None,
4303                                    completion: None,
4304                                }));
4305                                parse_mode = ParseMode::Arg;
4306                            }
4307                            // Normal param
4308                            else {
4309                                let name = String::from_utf8_lossy(&contents).to_string();
4310                                let contents_vec = contents.to_vec();
4311
4312                                if !is_variable(&contents_vec) {
4313                                    working_set.error(ParseError::Expected(
4314                                        "valid variable name for this parameter",
4315                                        span,
4316                                    ))
4317                                }
4318
4319                                let var_id =
4320                                    working_set.add_variable(contents_vec, span, Type::Any, false);
4321
4322                                // Positional arg, required
4323                                args.push(Arg::Positional {
4324                                    arg: PositionalArg {
4325                                        desc: String::new(),
4326                                        name,
4327                                        shape: SyntaxShape::Any,
4328                                        var_id: Some(var_id),
4329                                        default_value: None,
4330                                        completion: None,
4331                                    },
4332                                    required: true,
4333                                    type_annotated: false,
4334                                });
4335                                parse_mode = ParseMode::Arg;
4336                            }
4337                        }
4338                        ParseMode::Type => {
4339                            if let Some(last) = args.last_mut() {
4340                                let (syntax_shape, completer) = if contents.contains(&b'@') {
4341                                    let mut split = contents.splitn(2, |b| b == &b'@');
4342
4343                                    let shape_name = split
4344                                        .next()
4345                                        .expect("If `bytes` contains `@` splitn returns 2 slices");
4346                                    let shape_span =
4347                                        Span::new(span.start, span.start + shape_name.len());
4348                                    let cmd_span =
4349                                        Span::new(span.start + shape_name.len() + 1, span.end);
4350                                    let cmd_name = split
4351                                        .next()
4352                                        .expect("If `bytes` contains `@` splitn returns 2 slices");
4353                                    (
4354                                        parse_shape_name(working_set, shape_name, shape_span),
4355                                        parse_completer(working_set, cmd_name, cmd_span),
4356                                    )
4357                                } else {
4358                                    (parse_shape_name(working_set, &contents, span), None)
4359                                };
4360                                //TODO check if we're replacing a custom parameter already
4361                                match last {
4362                                    Arg::Positional {
4363                                        arg:
4364                                            PositionalArg {
4365                                                shape,
4366                                                var_id,
4367                                                completion,
4368                                                ..
4369                                            },
4370                                        required: _,
4371                                        type_annotated,
4372                                    } => {
4373                                        working_set.set_variable_type(
4374                                            var_id.expect(
4375                                                "internal error: all custom parameters must have \
4376                                                 var_ids",
4377                                            ),
4378                                            syntax_shape.to_type(),
4379                                        );
4380                                        *completion = completer;
4381                                        *shape = syntax_shape;
4382                                        *type_annotated = true;
4383                                    }
4384                                    Arg::RestPositional(PositionalArg {
4385                                        shape,
4386                                        var_id,
4387                                        completion,
4388                                        ..
4389                                    }) => {
4390                                        working_set.set_variable_type(
4391                                            var_id.expect(
4392                                                "internal error: all custom parameters must have \
4393                                                 var_ids",
4394                                            ),
4395                                            Type::List(Box::new(syntax_shape.to_type())),
4396                                        );
4397                                        *completion = completer;
4398                                        *shape = syntax_shape;
4399                                    }
4400                                    Arg::Flag {
4401                                        flag:
4402                                            Flag {
4403                                                arg,
4404                                                var_id,
4405                                                completion,
4406                                                ..
4407                                            },
4408                                        type_annotated,
4409                                    } => {
4410                                        working_set.set_variable_type(var_id.expect("internal error: all custom parameters must have var_ids"), syntax_shape.to_type());
4411                                        if syntax_shape == SyntaxShape::Boolean {
4412                                            working_set.error(ParseError::LabeledError(
4413                                                "Type annotations are not allowed for boolean switches.".to_string(),
4414                                                "Remove the `: bool` type annotation.".to_string(),
4415                                                span,
4416                                            ));
4417                                        }
4418                                        *completion = completer;
4419                                        *arg = Some(syntax_shape);
4420                                        *type_annotated = true;
4421                                    }
4422                                }
4423                            }
4424                            parse_mode = ParseMode::AfterType;
4425                        }
4426                        ParseMode::DefaultValue => {
4427                            if let Some(last) = args.last_mut() {
4428                                let expression = parse_value(working_set, span, &SyntaxShape::Any);
4429
4430                                //TODO check if we're replacing a custom parameter already
4431                                match last {
4432                                    Arg::Positional {
4433                                        arg:
4434                                            PositionalArg {
4435                                                shape,
4436                                                var_id,
4437                                                default_value,
4438                                                ..
4439                                            },
4440                                        required,
4441                                        type_annotated,
4442                                    } => {
4443                                        let var_id = var_id.expect("internal error: all custom parameters must have var_ids");
4444                                        let var_type = &working_set.get_variable(var_id).ty;
4445                                        match var_type {
4446                                            Type::Any => {
4447                                                if !*type_annotated {
4448                                                    working_set.set_variable_type(
4449                                                        var_id,
4450                                                        expression.ty.clone(),
4451                                                    );
4452                                                }
4453                                            }
4454                                            _ => {
4455                                                if !type_compatible(var_type, &expression.ty) {
4456                                                    working_set.error(
4457                                                        ParseError::AssignmentMismatch(
4458                                                            "Default value wrong type".into(),
4459                                                            format!(
4460                                                            "expected default value to be `{var_type}`"
4461                                                        ),
4462                                                            expression.span,
4463                                                        ),
4464                                                    )
4465                                                }
4466                                            }
4467                                        }
4468
4469                                        *default_value = if let Ok(constant) =
4470                                            eval_constant(working_set, &expression)
4471                                        {
4472                                            Some(constant)
4473                                        } else {
4474                                            working_set.error(ParseError::NonConstantDefaultValue(
4475                                                expression.span,
4476                                            ));
4477                                            None
4478                                        };
4479
4480                                        if !*type_annotated {
4481                                            *shape = expression.ty.to_shape();
4482                                        }
4483                                        *required = false;
4484                                    }
4485                                    Arg::RestPositional(..) => {
4486                                        working_set.error(ParseError::AssignmentMismatch(
4487                                            "Rest parameter was given a default value".into(),
4488                                            "can't have default value".into(),
4489                                            expression.span,
4490                                        ))
4491                                    }
4492                                    Arg::Flag {
4493                                        flag:
4494                                            Flag {
4495                                                arg,
4496                                                var_id,
4497                                                default_value,
4498                                                ..
4499                                            },
4500                                        type_annotated,
4501                                    } => {
4502                                        let expression_span = expression.span;
4503
4504                                        *default_value = if let Ok(value) =
4505                                            eval_constant(working_set, &expression)
4506                                        {
4507                                            Some(value)
4508                                        } else {
4509                                            working_set.error(ParseError::NonConstantDefaultValue(
4510                                                expression_span,
4511                                            ));
4512                                            None
4513                                        };
4514
4515                                        let var_id = var_id.expect("internal error: all custom parameters must have var_ids");
4516                                        let var_type = &working_set.get_variable(var_id).ty;
4517                                        let expression_ty = expression.ty.clone();
4518
4519                                        // Flags with no TypeMode are just present/not-present switches
4520                                        // in the case, `var_type` is any.
4521                                        match var_type {
4522                                            Type::Any => {
4523                                                if !*type_annotated {
4524                                                    *arg = Some(expression_ty.to_shape());
4525                                                    working_set
4526                                                        .set_variable_type(var_id, expression_ty);
4527                                                }
4528                                            }
4529                                            t => {
4530                                                if !type_compatible(t, &expression_ty) {
4531                                                    working_set.error(
4532                                                        ParseError::AssignmentMismatch(
4533                                                            "Default value is the wrong type"
4534                                                                .into(),
4535                                                            format!(
4536                                                                "expected default value to be `{t}`"
4537                                                            ),
4538                                                            expression_span,
4539                                                        ),
4540                                                    )
4541                                                }
4542                                            }
4543                                        }
4544                                    }
4545                                }
4546                            }
4547                            parse_mode = ParseMode::Arg;
4548                        }
4549                    }
4550                }
4551            }
4552            Token {
4553                contents: crate::TokenContents::Comment,
4554                span,
4555            } => {
4556                let contents = working_set.get_span_contents(Span::new(span.start + 1, span.end));
4557
4558                let mut contents = String::from_utf8_lossy(contents).to_string();
4559                contents = contents.trim().into();
4560
4561                if let Some(last) = args.last_mut() {
4562                    match last {
4563                        Arg::Flag { flag, .. } => {
4564                            if !flag.desc.is_empty() {
4565                                flag.desc.push('\n');
4566                            }
4567                            flag.desc.push_str(&contents);
4568                        }
4569                        Arg::Positional {
4570                            arg: positional, ..
4571                        } => {
4572                            if !positional.desc.is_empty() {
4573                                positional.desc.push('\n');
4574                            }
4575                            positional.desc.push_str(&contents);
4576                        }
4577                        Arg::RestPositional(positional) => {
4578                            if !positional.desc.is_empty() {
4579                                positional.desc.push('\n');
4580                            }
4581                            positional.desc.push_str(&contents);
4582                        }
4583                    }
4584                }
4585            }
4586            _ => {}
4587        }
4588    }
4589
4590    let mut sig = Signature::new(String::new());
4591
4592    for arg in args {
4593        match arg {
4594            Arg::Positional {
4595                arg: positional,
4596                required,
4597                ..
4598            } => {
4599                if required {
4600                    if !sig.optional_positional.is_empty() {
4601                        working_set.error(ParseError::RequiredAfterOptional(
4602                            positional.name.clone(),
4603                            span,
4604                        ))
4605                    }
4606                    sig.required_positional.push(positional)
4607                } else {
4608                    sig.optional_positional.push(positional)
4609                }
4610            }
4611            Arg::Flag { flag, .. } => sig.named.push(flag),
4612            Arg::RestPositional(positional) => {
4613                if positional.name.is_empty() {
4614                    working_set.error(ParseError::RestNeedsName(span))
4615                } else if sig.rest_positional.is_none() {
4616                    sig.rest_positional = Some(PositionalArg {
4617                        name: positional.name,
4618                        ..positional
4619                    })
4620                } else {
4621                    // Too many rest params
4622                    working_set.error(ParseError::MultipleRestParams(span))
4623                }
4624            }
4625        }
4626    }
4627
4628    Box::new(sig)
4629}
4630
4631pub fn parse_list_expression(
4632    working_set: &mut StateWorkingSet,
4633    span: Span,
4634    element_shape: &SyntaxShape,
4635) -> Expression {
4636    let bytes = working_set.get_span_contents(span);
4637
4638    let mut start = span.start;
4639    let mut end = span.end;
4640
4641    if bytes.starts_with(b"[") {
4642        start += 1;
4643    }
4644    if bytes.ends_with(b"]") {
4645        end -= 1;
4646    } else {
4647        working_set.error(ParseError::Unclosed("]".into(), Span::new(end, end)));
4648    }
4649
4650    let inner_span = Span::new(start, end);
4651    let source = working_set.get_span_contents(inner_span);
4652
4653    let (output, err) = lex(source, inner_span.start, &[b'\n', b'\r', b','], &[], true);
4654    if let Some(err) = err {
4655        working_set.error(err)
4656    }
4657
4658    let (mut output, err) = lite_parse(&output, working_set);
4659    if let Some(err) = err {
4660        working_set.error(err)
4661    }
4662
4663    let mut args = vec![];
4664
4665    let mut contained_type: Option<Type> = None;
4666
4667    if !output.block.is_empty() {
4668        for mut command in output.block.remove(0).commands {
4669            let mut spans_idx = 0;
4670
4671            while spans_idx < command.parts.len() {
4672                let curr_span = command.parts[spans_idx];
4673                let curr_tok = working_set.get_span_contents(curr_span);
4674                let (arg, ty) = if curr_tok.starts_with(b"...")
4675                    && curr_tok.len() > 3
4676                    && (curr_tok[3] == b'$' || curr_tok[3] == b'[' || curr_tok[3] == b'(')
4677                {
4678                    // Parse the spread operator
4679                    // Remove "..." before parsing argument to spread operator
4680                    command.parts[spans_idx] = Span::new(curr_span.start + 3, curr_span.end);
4681                    let spread_arg = parse_multispan_value(
4682                        working_set,
4683                        &command.parts,
4684                        &mut spans_idx,
4685                        &SyntaxShape::List(Box::new(element_shape.clone())),
4686                    );
4687                    let elem_ty = match &spread_arg.ty {
4688                        Type::List(elem_ty) => *elem_ty.clone(),
4689                        _ => Type::Any,
4690                    };
4691                    let span = Span::new(curr_span.start, curr_span.start + 3);
4692                    (ListItem::Spread(span, spread_arg), elem_ty)
4693                } else {
4694                    let arg = parse_multispan_value(
4695                        working_set,
4696                        &command.parts,
4697                        &mut spans_idx,
4698                        element_shape,
4699                    );
4700                    let ty = arg.ty.clone();
4701                    (ListItem::Item(arg), ty)
4702                };
4703
4704                contained_type = match contained_type {
4705                    Some(ctype) => Some(ctype.widen(ty)),
4706                    None => Some(ty),
4707                };
4708
4709                args.push(arg);
4710
4711                spans_idx += 1;
4712            }
4713        }
4714    }
4715
4716    Expression::new(
4717        working_set,
4718        Expr::List(args),
4719        span,
4720        Type::List(Box::new(if let Some(ty) = contained_type {
4721            ty
4722        } else {
4723            Type::Any
4724        })),
4725    )
4726}
4727
4728fn parse_table_row(
4729    working_set: &mut StateWorkingSet,
4730    span: Span,
4731) -> Result<(Vec<Expression>, Span), Span> {
4732    let list = parse_list_expression(working_set, span, &SyntaxShape::Any);
4733    let Expression {
4734        expr: Expr::List(list),
4735        span,
4736        ..
4737    } = list
4738    else {
4739        unreachable!("the item must be a list")
4740    };
4741
4742    list.into_iter()
4743        .map(|item| match item {
4744            ListItem::Item(expr) => Ok(expr),
4745            ListItem::Spread(_, spread) => Err(spread.span),
4746        })
4747        .collect::<Result<_, _>>()
4748        .map(|exprs| (exprs, span))
4749}
4750
4751fn parse_table_expression(
4752    working_set: &mut StateWorkingSet,
4753    span: Span,
4754    list_element_shape: &SyntaxShape,
4755) -> Expression {
4756    let bytes = working_set.get_span_contents(span);
4757    let inner_span = {
4758        let start = if bytes.starts_with(b"[") {
4759            span.start + 1
4760        } else {
4761            span.start
4762        };
4763
4764        let end = if bytes.ends_with(b"]") {
4765            span.end - 1
4766        } else {
4767            let end = span.end;
4768            working_set.error(ParseError::Unclosed("]".into(), Span::new(end, end)));
4769            span.end
4770        };
4771
4772        Span::new(start, end)
4773    };
4774
4775    let source = working_set.get_span_contents(inner_span);
4776    let (tokens, err) = lex(source, inner_span.start, &[b'\n', b'\r', b','], &[], true);
4777    if let Some(err) = err {
4778        working_set.error(err);
4779    }
4780
4781    // Check that we have all arguments first, before trying to parse the first
4782    // in order to avoid exponential parsing time
4783    let [first, second, rest @ ..] = &tokens[..] else {
4784        return parse_list_expression(working_set, span, list_element_shape);
4785    };
4786    if !working_set.get_span_contents(first.span).starts_with(b"[")
4787        || second.contents != TokenContents::Semicolon
4788        || rest.is_empty()
4789    {
4790        return parse_list_expression(working_set, span, list_element_shape);
4791    };
4792    let head = parse_table_row(working_set, first.span);
4793
4794    let errors = working_set.parse_errors.len();
4795
4796    let (head, rows) = match head {
4797        Ok((head, _)) => {
4798            let rows = rest
4799                .iter()
4800                .filter_map(|it| {
4801                    use std::cmp::Ordering;
4802
4803                    match working_set.get_span_contents(it.span) {
4804                        b"," => None,
4805                        text if !text.starts_with(b"[") => {
4806                            let err = ParseError::LabeledErrorWithHelp {
4807                                error: String::from("Table item not list"),
4808                                label: String::from("not a list"),
4809                                span: it.span,
4810                                help: String::from("All table items must be lists"),
4811                            };
4812                            working_set.error(err);
4813                            None
4814                        }
4815                        _ => match parse_table_row(working_set, it.span) {
4816                            Ok((list, span)) => {
4817                                match list.len().cmp(&head.len()) {
4818                                    Ordering::Less => {
4819                                        let err = ParseError::MissingColumns(head.len(), span);
4820                                        working_set.error(err);
4821                                    }
4822                                    Ordering::Greater => {
4823                                        let span = {
4824                                            let start = list[head.len()].span.start;
4825                                            let end = span.end;
4826                                            Span::new(start, end)
4827                                        };
4828                                        let err = ParseError::ExtraColumns(head.len(), span);
4829                                        working_set.error(err);
4830                                    }
4831                                    Ordering::Equal => {}
4832                                }
4833                                Some(list)
4834                            }
4835                            Err(span) => {
4836                                let err = ParseError::LabeledError(
4837                                    String::from("Cannot spread in a table row"),
4838                                    String::from("invalid spread here"),
4839                                    span,
4840                                );
4841                                working_set.error(err);
4842                                None
4843                            }
4844                        },
4845                    }
4846                })
4847                .collect();
4848
4849            (head, rows)
4850        }
4851        Err(span) => {
4852            let err = ParseError::LabeledError(
4853                String::from("Cannot spread in a table row"),
4854                String::from("invalid spread here"),
4855                span,
4856            );
4857            working_set.error(err);
4858            (Vec::new(), Vec::new())
4859        }
4860    };
4861
4862    let ty = if working_set.parse_errors.len() == errors {
4863        let (ty, errs) = table_type(&head, &rows);
4864        working_set.parse_errors.extend(errs);
4865        ty
4866    } else {
4867        Type::table()
4868    };
4869
4870    let table = Table {
4871        columns: head.into(),
4872        rows: rows.into_iter().map(Into::into).collect(),
4873    };
4874
4875    Expression::new(working_set, Expr::Table(table), span, ty)
4876}
4877
4878fn table_type(head: &[Expression], rows: &[Vec<Expression>]) -> (Type, Vec<ParseError>) {
4879    let mut errors = vec![];
4880    let mut rows = rows.to_vec();
4881    let mut mk_ty = || -> Type {
4882        let types = rows
4883            .iter_mut()
4884            .map(|row| row.pop().map(|x| x.ty).unwrap_or_default());
4885        Type::supertype_of(types).unwrap_or_default()
4886    };
4887
4888    let mk_error = |span| ParseError::LabeledErrorWithHelp {
4889        error: "Table column name not string".into(),
4890        label: "must be a string".into(),
4891        help: "Table column names should be able to be converted into strings".into(),
4892        span,
4893    };
4894
4895    let mut ty = head
4896        .iter()
4897        .rev()
4898        // Include only known column names in type
4899        .filter_map(|expr| {
4900            if !Type::String.is_subtype_of(&expr.ty) {
4901                errors.push(mk_error(expr.span));
4902                None
4903            } else {
4904                expr.as_string()
4905            }
4906        })
4907        .map(|title| (title, mk_ty()))
4908        .collect_vec();
4909
4910    ty.reverse();
4911
4912    (Type::Table(ty.into()), errors)
4913}
4914
4915pub fn parse_block_expression(working_set: &mut StateWorkingSet, span: Span) -> Expression {
4916    trace!("parsing: block expression");
4917
4918    let bytes = working_set.get_span_contents(span);
4919
4920    let mut start = span.start;
4921    let mut end = span.end;
4922    let mut is_closed = true;
4923
4924    if bytes.starts_with(b"{") {
4925        start += 1;
4926    } else {
4927        working_set.error(ParseError::Expected("block", span));
4928        return garbage(working_set, span);
4929    }
4930    if bytes.ends_with(b"}") {
4931        end -= 1;
4932    } else {
4933        working_set.error(ParseError::Unclosed("}".into(), Span::new(end, end)));
4934        is_closed = false;
4935    }
4936
4937    let inner_span = Span::new(start, end);
4938
4939    let source = working_set.get_span_contents(inner_span);
4940
4941    let (output, err) = lex(source, start, &[], &[], false);
4942    if let Some(err) = err {
4943        working_set.error(err);
4944    }
4945
4946    working_set.enter_scope();
4947
4948    // Check to see if we have parameters
4949    let (signature, amt_to_skip): (Option<(Box<Signature>, Span)>, usize) = match output.first() {
4950        Some(Token {
4951            contents: TokenContents::Pipe,
4952            span,
4953        }) => {
4954            working_set.error(ParseError::Expected("block but found closure", *span));
4955            (None, 0)
4956        }
4957        _ => (None, 0),
4958    };
4959
4960    let mut output = parse_block(working_set, &output[amt_to_skip..], span, false, false);
4961
4962    if let Some(signature) = signature {
4963        output.signature = signature.0;
4964    }
4965
4966    output.span = Some(span);
4967
4968    if is_closed {
4969        working_set.exit_scope();
4970    }
4971
4972    let block_id = working_set.add_block(Arc::new(output));
4973
4974    Expression::new(working_set, Expr::Block(block_id), span, Type::Block)
4975}
4976
4977pub fn parse_match_block_expression(working_set: &mut StateWorkingSet, span: Span) -> Expression {
4978    let bytes = working_set.get_span_contents(span);
4979
4980    let mut start = span.start;
4981    let mut end = span.end;
4982    let mut is_closed = true;
4983
4984    if bytes.starts_with(b"{") {
4985        start += 1;
4986    } else {
4987        working_set.error(ParseError::Expected("closure", span));
4988        return garbage(working_set, span);
4989    }
4990    if bytes.ends_with(b"}") {
4991        end -= 1;
4992    } else {
4993        working_set.error(ParseError::Unclosed("}".into(), Span::new(end, end)));
4994        is_closed = false;
4995    }
4996
4997    let inner_span = Span::new(start, end);
4998
4999    let source = working_set.get_span_contents(inner_span);
5000
5001    let (output, err) = lex(source, start, &[b' ', b'\r', b'\n', b',', b'|'], &[], true);
5002    if let Some(err) = err {
5003        working_set.error(err);
5004    }
5005
5006    let mut position = 0;
5007
5008    let mut output_matches = vec![];
5009
5010    while position < output.len() {
5011        // Each match gets its own scope
5012
5013        working_set.enter_scope();
5014
5015        // First parse the pattern
5016        let mut pattern = parse_pattern(working_set, output[position].span);
5017
5018        position += 1;
5019
5020        if position >= output.len() {
5021            working_set.error(ParseError::Mismatch(
5022                "=>".into(),
5023                "end of input".into(),
5024                Span::new(output[position - 1].span.end, output[position - 1].span.end),
5025            ));
5026
5027            working_set.exit_scope();
5028            break;
5029        }
5030
5031        let mut connector = working_set.get_span_contents(output[position].span);
5032
5033        // Multiple patterns connected by '|'
5034        if connector == b"|" && position < output.len() {
5035            let mut or_pattern = vec![pattern];
5036
5037            while connector == b"|" && position < output.len() {
5038                connector = b"";
5039
5040                position += 1;
5041
5042                if position >= output.len() {
5043                    working_set.error(ParseError::Mismatch(
5044                        "pattern".into(),
5045                        "end of input".into(),
5046                        Span::new(output[position - 1].span.end, output[position - 1].span.end),
5047                    ));
5048                    break;
5049                }
5050
5051                let pattern = parse_pattern(working_set, output[position].span);
5052                or_pattern.push(pattern);
5053
5054                position += 1;
5055                if position >= output.len() {
5056                    working_set.error(ParseError::Mismatch(
5057                        "=>".into(),
5058                        "end of input".into(),
5059                        Span::new(output[position - 1].span.end, output[position - 1].span.end),
5060                    ));
5061                    break;
5062                } else {
5063                    connector = working_set.get_span_contents(output[position].span);
5064                }
5065            }
5066
5067            let start = or_pattern
5068                .first()
5069                .expect("internal error: unexpected state of or-pattern")
5070                .span
5071                .start;
5072            let end = or_pattern
5073                .last()
5074                .expect("internal error: unexpected state of or-pattern")
5075                .span
5076                .end;
5077
5078            pattern = MatchPattern {
5079                pattern: Pattern::Or(or_pattern),
5080                guard: None,
5081                span: Span::new(start, end),
5082            }
5083        // A match guard
5084        } else if connector == b"if" {
5085            let if_end = {
5086                let end = output[position].span.end;
5087                Span::new(end, end)
5088            };
5089
5090            position += 1;
5091
5092            let mk_err = || ParseError::LabeledErrorWithHelp {
5093                error: "Match guard without an expression".into(),
5094                label: "expected an expression".into(),
5095                help: "The `if` keyword must be followed with an expression".into(),
5096                span: if_end,
5097            };
5098
5099            if output.get(position).is_none() {
5100                working_set.error(mk_err());
5101                return garbage(working_set, span);
5102            };
5103
5104            let (tokens, found) = if let Some((pos, _)) = output[position..]
5105                .iter()
5106                .find_position(|t| working_set.get_span_contents(t.span) == b"=>")
5107            {
5108                if position + pos == position {
5109                    working_set.error(mk_err());
5110                    return garbage(working_set, span);
5111                }
5112
5113                (&output[position..position + pos], true)
5114            } else {
5115                (&output[position..], false)
5116            };
5117
5118            let mut start = 0;
5119            let guard = parse_multispan_value(
5120                working_set,
5121                &tokens.iter().map(|tok| tok.span).collect_vec(),
5122                &mut start,
5123                &SyntaxShape::MathExpression,
5124            );
5125
5126            pattern.guard = Some(Box::new(guard));
5127            position += if found { start + 1 } else { start };
5128            connector = working_set.get_span_contents(output[position].span);
5129        }
5130        // Then the `=>` arrow
5131        if connector != b"=>" {
5132            working_set.error(ParseError::Mismatch(
5133                "=>".into(),
5134                "end of input".into(),
5135                Span::new(output[position - 1].span.end, output[position - 1].span.end),
5136            ));
5137        } else {
5138            position += 1;
5139        }
5140
5141        // Finally, the value/expression/block that we will run to produce the result
5142        if position >= output.len() {
5143            working_set.error(ParseError::Mismatch(
5144                "match result".into(),
5145                "end of input".into(),
5146                Span::new(output[position - 1].span.end, output[position - 1].span.end),
5147            ));
5148
5149            working_set.exit_scope();
5150            break;
5151        }
5152
5153        let result = parse_multispan_value(
5154            working_set,
5155            &[output[position].span],
5156            &mut 0,
5157            &SyntaxShape::OneOf(vec![SyntaxShape::Block, SyntaxShape::Expression]),
5158        );
5159        position += 1;
5160        if is_closed {
5161            working_set.exit_scope();
5162        }
5163
5164        output_matches.push((pattern, result));
5165    }
5166
5167    Expression::new(
5168        working_set,
5169        Expr::MatchBlock(output_matches),
5170        span,
5171        Type::Any,
5172    )
5173}
5174
5175pub fn parse_closure_expression(
5176    working_set: &mut StateWorkingSet,
5177    shape: &SyntaxShape,
5178    span: Span,
5179) -> Expression {
5180    trace!("parsing: closure expression");
5181
5182    let bytes = working_set.get_span_contents(span);
5183
5184    let mut start = span.start;
5185    let mut end = span.end;
5186    let mut is_closed = true;
5187
5188    if bytes.starts_with(b"{") {
5189        start += 1;
5190    } else {
5191        working_set.error(ParseError::Expected("closure", span));
5192        return garbage(working_set, span);
5193    }
5194    if bytes.ends_with(b"}") {
5195        end -= 1;
5196    } else {
5197        working_set.error(ParseError::Unclosed("}".into(), Span::new(end, end)));
5198        is_closed = false;
5199    }
5200
5201    let inner_span = Span::new(start, end);
5202
5203    let source = working_set.get_span_contents(inner_span);
5204
5205    let (output, err) = lex(source, start, &[], &[], false);
5206    if let Some(err) = err {
5207        working_set.error(err);
5208    }
5209
5210    working_set.enter_scope();
5211
5212    // Check to see if we have parameters
5213    let (signature, amt_to_skip): (Option<(Box<Signature>, Span)>, usize) = match output.first() {
5214        Some(Token {
5215            contents: TokenContents::Pipe,
5216            span,
5217        }) => {
5218            // We've found a parameter list
5219            let start_point = span.start;
5220            let mut token_iter = output.iter().enumerate().skip(1);
5221            let mut end_span = None;
5222            let mut amt_to_skip = 1;
5223
5224            for token in &mut token_iter {
5225                if let Token {
5226                    contents: TokenContents::Pipe,
5227                    span,
5228                } = token.1
5229                {
5230                    end_span = Some(span);
5231                    amt_to_skip += token.0;
5232                    break;
5233                }
5234            }
5235
5236            let end_point = if let Some(span) = end_span {
5237                span.end
5238            } else {
5239                working_set.error(ParseError::Unclosed("|".into(), Span::new(end, end)));
5240                end
5241            };
5242
5243            let signature_span = Span::new(start_point, end_point);
5244            let signature = parse_signature_helper(working_set, signature_span);
5245
5246            (Some((signature, signature_span)), amt_to_skip)
5247        }
5248        Some(Token {
5249            contents: TokenContents::PipePipe,
5250            span,
5251        }) => (
5252            Some((Box::new(Signature::new("closure".to_string())), *span)),
5253            1,
5254        ),
5255        _ => (None, 0),
5256    };
5257
5258    // TODO: Finish this
5259    if let SyntaxShape::Closure(Some(v)) = shape
5260        && let Some((sig, sig_span)) = &signature
5261    {
5262        if sig.num_positionals() > v.len() {
5263            working_set.error(ParseError::ExpectedWithStringMsg(
5264                format!(
5265                    "{} closure parameter{}",
5266                    v.len(),
5267                    if v.len() > 1 { "s" } else { "" }
5268                ),
5269                *sig_span,
5270            ));
5271        }
5272
5273        for (expected, PositionalArg { name, shape, .. }) in
5274            v.iter().zip(sig.required_positional.iter())
5275        {
5276            if expected != shape && *shape != SyntaxShape::Any {
5277                working_set.error(ParseError::ParameterMismatchType(
5278                    name.to_owned(),
5279                    expected.to_string(),
5280                    shape.to_string(),
5281                    *sig_span,
5282                ));
5283            }
5284        }
5285    }
5286
5287    let mut output = parse_block(working_set, &output[amt_to_skip..], span, false, false);
5288
5289    // NOTE: closures need to be compiled eagerly due to these reasons:
5290    //  - their `Block`s (which contains their `IrBlock`) are stored in the working_set
5291    //  - Ir compiler does not have mutable access to the working_set and can't attach `IrBlock`s
5292    //  to existing `Block`s
5293    // so they can't be compiled as part of their parent `Block`'s compilation
5294    //
5295    // If the compiler used a mechanism similar to the `EngineState`/`StateWorkingSet` divide, we
5296    // could defer all compilation and apply the generated delta to `StateWorkingSet` afterwards.
5297    if working_set.parse_errors.is_empty() {
5298        compile_block(working_set, &mut output);
5299    }
5300
5301    if let Some(signature) = signature {
5302        output.signature = signature.0;
5303    }
5304
5305    output.span = Some(span);
5306
5307    if is_closed {
5308        working_set.exit_scope();
5309    }
5310
5311    let block_id = working_set.add_block(Arc::new(output));
5312
5313    Expression::new(working_set, Expr::Closure(block_id), span, Type::Closure)
5314}
5315
5316pub fn parse_value(
5317    working_set: &mut StateWorkingSet,
5318    span: Span,
5319    shape: &SyntaxShape,
5320) -> Expression {
5321    trace!("parsing: value: {shape}");
5322
5323    let bytes = working_set.get_span_contents(span);
5324
5325    if bytes.is_empty() {
5326        working_set.error(ParseError::IncompleteParser(span));
5327        return garbage(working_set, span);
5328    }
5329
5330    match bytes[0] {
5331        b'$' => return parse_dollar_expr(working_set, span),
5332        b'(' => return parse_paren_expr(working_set, span, shape),
5333        b'{' => return parse_brace_expr(working_set, span, shape),
5334        b'[' => match shape {
5335            SyntaxShape::Any
5336            | SyntaxShape::List(_)
5337            | SyntaxShape::Table(_)
5338            | SyntaxShape::Signature
5339            | SyntaxShape::Filepath
5340            | SyntaxShape::String
5341            | SyntaxShape::GlobPattern
5342            | SyntaxShape::ExternalArgument => {}
5343            SyntaxShape::OneOf(possible_shapes) => {
5344                if !possible_shapes
5345                    .iter()
5346                    .any(|s| matches!(s, SyntaxShape::List(_)))
5347                {
5348                    working_set.error(ParseError::ExpectedWithStringMsg(shape.to_string(), span));
5349                    return Expression::garbage(working_set, span);
5350                }
5351            }
5352            _ => {
5353                working_set.error(ParseError::ExpectedWithStringMsg(shape.to_string(), span));
5354                return Expression::garbage(working_set, span);
5355            }
5356        },
5357        b'r' if bytes.len() > 1 && bytes[1] == b'#' => {
5358            return parse_raw_string(working_set, span);
5359        }
5360        _ => {}
5361    }
5362
5363    match shape {
5364        SyntaxShape::Number => parse_number(working_set, span),
5365        SyntaxShape::Float => parse_float(working_set, span),
5366        SyntaxShape::Int => parse_int(working_set, span),
5367        SyntaxShape::Duration => parse_duration(working_set, span),
5368        SyntaxShape::DateTime => parse_datetime(working_set, span),
5369        SyntaxShape::Filesize => parse_filesize(working_set, span),
5370        SyntaxShape::Range => {
5371            parse_range(working_set, span).unwrap_or_else(|| garbage(working_set, span))
5372        }
5373        // Check for reserved keyword values
5374        SyntaxShape::Nothing | SyntaxShape::Any if bytes == b"null" => {
5375            Expression::new(working_set, Expr::Nothing, span, Type::Nothing)
5376        }
5377        SyntaxShape::Boolean | SyntaxShape::Any if bytes == b"true" => {
5378            Expression::new(working_set, Expr::Bool(true), span, Type::Bool)
5379        }
5380        SyntaxShape::Boolean | SyntaxShape::Any if bytes == b"false" => {
5381            Expression::new(working_set, Expr::Bool(false), span, Type::Bool)
5382        }
5383        SyntaxShape::Filepath
5384        | SyntaxShape::Directory
5385        | SyntaxShape::GlobPattern
5386        // TODO: this serves for backward compatibility.
5387        // As a consequence, for commands like `def foo [foo: string] {}`,
5388        // it forbids usage like `foo true`, have to call it explicitly with `foo "true"`.
5389        // On the other hand, given current `SyntaxShape` based `parse_value`, `foo 10.0` doesn't raise any error.
5390        // We want to fix this discrepancy in the future.
5391        | SyntaxShape::String
5392            if matches!(bytes, b"true" | b"false" | b"null") =>
5393        {
5394            working_set.error(ParseError::ExpectedWithStringMsg(shape.to_string(), span));
5395            garbage(working_set, span)
5396        }
5397        SyntaxShape::Filepath => parse_filepath(working_set, span),
5398        SyntaxShape::Directory => parse_directory(working_set, span),
5399        SyntaxShape::GlobPattern => parse_glob_pattern(working_set, span),
5400        SyntaxShape::String => parse_string(working_set, span),
5401        SyntaxShape::Binary => parse_binary(working_set, span),
5402        SyntaxShape::Signature if bytes.starts_with(b"[") => parse_signature(working_set, span),
5403        SyntaxShape::List(elem) if bytes.starts_with(b"[") => {
5404            parse_table_expression(working_set, span, elem)
5405        }
5406        SyntaxShape::Table(_) if bytes.starts_with(b"[") => {
5407            parse_table_expression(working_set, span, &SyntaxShape::Any)
5408        }
5409        SyntaxShape::CellPath => parse_simple_cell_path(working_set, span),
5410
5411        // Be sure to return ParseError::Expected(..) if invoked for one of these shapes, but lex
5412        // stream doesn't start with '{'} -- parsing in SyntaxShape::Any arm depends on this error variant.
5413        SyntaxShape::Block | SyntaxShape::Closure(..) | SyntaxShape::Record(_) => {
5414            working_set.error(ParseError::Expected("block, closure or record", span));
5415
5416            Expression::garbage(working_set, span)
5417        }
5418
5419        SyntaxShape::ExternalArgument => parse_regular_external_arg(working_set, span),
5420        SyntaxShape::OneOf(possible_shapes) => {
5421            parse_oneof(working_set, &[span], &mut 0, possible_shapes, false)
5422        }
5423
5424        SyntaxShape::Any => {
5425            if bytes.starts_with(b"[") {
5426                //parse_value(working_set, span, &SyntaxShape::Table)
5427                parse_full_cell_path(working_set, None, span)
5428            } else {
5429                let shapes = [
5430                    SyntaxShape::Binary,
5431                    SyntaxShape::Range,
5432                    SyntaxShape::Filesize,
5433                    SyntaxShape::Duration,
5434                    SyntaxShape::DateTime,
5435                    SyntaxShape::Int,
5436                    SyntaxShape::Number,
5437                    SyntaxShape::String,
5438                ];
5439                for shape in shapes.iter() {
5440                    let starting_error_count = working_set.parse_errors.len();
5441
5442                    let s = parse_value(working_set, span, shape);
5443
5444                    if starting_error_count == working_set.parse_errors.len() {
5445                        return s;
5446                    } else {
5447                        match working_set.parse_errors.get(starting_error_count) {
5448                            Some(
5449                                ParseError::Expected(_, _)
5450                                | ParseError::ExpectedWithStringMsg(_, _),
5451                            ) => {
5452                                working_set.parse_errors.truncate(starting_error_count);
5453                                continue;
5454                            }
5455                            _ => {
5456                                return s;
5457                            }
5458                        }
5459                    }
5460                }
5461                working_set.error(ParseError::Expected("any shape", span));
5462                garbage(working_set, span)
5463            }
5464        }
5465        _ => {
5466            working_set.error(ParseError::ExpectedWithStringMsg(shape.to_string(), span));
5467            garbage(working_set, span)
5468        }
5469    }
5470}
5471
5472pub fn parse_assignment_operator(working_set: &mut StateWorkingSet, span: Span) -> Expression {
5473    let contents = working_set.get_span_contents(span);
5474
5475    let operator = match contents {
5476        b"=" => Operator::Assignment(Assignment::Assign),
5477        b"+=" => Operator::Assignment(Assignment::AddAssign),
5478        b"-=" => Operator::Assignment(Assignment::SubtractAssign),
5479        b"*=" => Operator::Assignment(Assignment::MultiplyAssign),
5480        b"/=" => Operator::Assignment(Assignment::DivideAssign),
5481        b"++=" => Operator::Assignment(Assignment::ConcatenateAssign),
5482        _ => {
5483            working_set.error(ParseError::Expected("assignment operator", span));
5484            return garbage(working_set, span);
5485        }
5486    };
5487
5488    Expression::new(working_set, Expr::Operator(operator), span, Type::Any)
5489}
5490
5491pub fn parse_assignment_expression(
5492    working_set: &mut StateWorkingSet,
5493    spans: &[Span],
5494) -> Expression {
5495    trace!("parsing: assignment expression");
5496    let expr_span = Span::concat(spans);
5497
5498    // Assignment always has the most precedence, and its right-hand side can be a pipeline
5499    let Some(op_index) = spans
5500        .iter()
5501        .position(|span| is_assignment_operator(working_set.get_span_contents(*span)))
5502    else {
5503        working_set.error(ParseError::Expected("assignment expression", expr_span));
5504        return garbage(working_set, expr_span);
5505    };
5506
5507    let lhs_spans = &spans[0..op_index];
5508    let op_span = spans[op_index];
5509    let rhs_spans = &spans[(op_index + 1)..];
5510
5511    if lhs_spans.is_empty() {
5512        working_set.error(ParseError::Expected(
5513            "left hand side of assignment",
5514            op_span,
5515        ));
5516        return garbage(working_set, expr_span);
5517    }
5518
5519    if rhs_spans.is_empty() {
5520        working_set.error(ParseError::Expected(
5521            "right hand side of assignment",
5522            op_span,
5523        ));
5524        return garbage(working_set, expr_span);
5525    }
5526
5527    // Parse the lhs and operator as usual for a math expression
5528    let mut lhs = parse_expression(working_set, lhs_spans);
5529    // make sure that lhs is a mutable variable.
5530    match &lhs.expr {
5531        Expr::FullCellPath(p) => {
5532            if let Expr::Var(var_id) = p.head.expr
5533                && var_id != nu_protocol::ENV_VARIABLE_ID
5534                && !working_set.get_variable(var_id).mutable
5535            {
5536                working_set.error(ParseError::AssignmentRequiresMutableVar(lhs.span))
5537            }
5538        }
5539        _ => working_set.error(ParseError::AssignmentRequiresVar(lhs.span)),
5540    }
5541
5542    let mut operator = parse_assignment_operator(working_set, op_span);
5543
5544    // Re-parse the right-hand side as a subexpression
5545    let rhs_span = Span::concat(rhs_spans);
5546
5547    let (rhs_tokens, rhs_error) = lex(
5548        working_set.get_span_contents(rhs_span),
5549        rhs_span.start,
5550        &[],
5551        &[],
5552        false,
5553    );
5554    working_set.parse_errors.extend(rhs_error);
5555
5556    trace!("parsing: assignment right-hand side subexpression");
5557    let rhs_block = parse_block(working_set, &rhs_tokens, rhs_span, false, true);
5558    let rhs_ty = rhs_block.output_type();
5559
5560    // TEMP: double-check that if the RHS block starts with an external call, it must start with a
5561    // caret. This is to mitigate the change in assignment parsing introduced in 0.97.0 which could
5562    // result in unintentional execution of commands.
5563    if let Some(Expr::ExternalCall(head, ..)) = rhs_block
5564        .pipelines
5565        .first()
5566        .and_then(|pipeline| pipeline.elements.first())
5567        .map(|element| &element.expr.expr)
5568    {
5569        let contents = working_set.get_span_contents(Span {
5570            start: head.span.start - 1,
5571            end: head.span.end,
5572        });
5573        if !contents.starts_with(b"^") {
5574            working_set.parse_errors.push(ParseError::LabeledErrorWithHelp {
5575                error: "External command calls must be explicit in assignments".into(),
5576                label: "add a caret (^) before the command name if you intended to run and capture its output".into(),
5577                help: "the parsing of assignments was changed in 0.97.0, and this would have previously been treated as a string. Alternatively, quote the string with single or double quotes to avoid it being interpreted as a command name. This restriction may be removed in a future release.".into(),
5578                span: head.span,
5579            });
5580        }
5581    }
5582
5583    let rhs_block_id = working_set.add_block(Arc::new(rhs_block));
5584    let mut rhs = Expression::new(
5585        working_set,
5586        Expr::Subexpression(rhs_block_id),
5587        rhs_span,
5588        rhs_ty,
5589    );
5590
5591    let (result_ty, err) = math_result_type(working_set, &mut lhs, &mut operator, &mut rhs);
5592    if let Some(err) = err {
5593        working_set.parse_errors.push(err);
5594    }
5595
5596    Expression::new(
5597        working_set,
5598        Expr::BinaryOp(Box::new(lhs), Box::new(operator), Box::new(rhs)),
5599        expr_span,
5600        result_ty,
5601    )
5602}
5603
5604pub fn parse_operator(working_set: &mut StateWorkingSet, span: Span) -> Expression {
5605    let contents = working_set.get_span_contents(span);
5606
5607    let operator = match contents {
5608        b"==" => Operator::Comparison(Comparison::Equal),
5609        b"!=" => Operator::Comparison(Comparison::NotEqual),
5610        b"<" => Operator::Comparison(Comparison::LessThan),
5611        b"<=" => Operator::Comparison(Comparison::LessThanOrEqual),
5612        b">" => Operator::Comparison(Comparison::GreaterThan),
5613        b">=" => Operator::Comparison(Comparison::GreaterThanOrEqual),
5614        b"=~" | b"like" => Operator::Comparison(Comparison::RegexMatch),
5615        b"!~" | b"not-like" => Operator::Comparison(Comparison::NotRegexMatch),
5616        b"in" => Operator::Comparison(Comparison::In),
5617        b"not-in" => Operator::Comparison(Comparison::NotIn),
5618        b"has" => Operator::Comparison(Comparison::Has),
5619        b"not-has" => Operator::Comparison(Comparison::NotHas),
5620        b"starts-with" => Operator::Comparison(Comparison::StartsWith),
5621        b"not-starts-with" => Operator::Comparison(Comparison::NotStartsWith),
5622        b"ends-with" => Operator::Comparison(Comparison::EndsWith),
5623        b"not-ends-with" => Operator::Comparison(Comparison::NotEndsWith),
5624        b"+" => Operator::Math(Math::Add),
5625        b"-" => Operator::Math(Math::Subtract),
5626        b"*" => Operator::Math(Math::Multiply),
5627        b"/" => Operator::Math(Math::Divide),
5628        b"//" => Operator::Math(Math::FloorDivide),
5629        b"mod" => Operator::Math(Math::Modulo),
5630        b"**" => Operator::Math(Math::Pow),
5631        b"++" => Operator::Math(Math::Concatenate),
5632        b"bit-or" => Operator::Bits(Bits::BitOr),
5633        b"bit-xor" => Operator::Bits(Bits::BitXor),
5634        b"bit-and" => Operator::Bits(Bits::BitAnd),
5635        b"bit-shl" => Operator::Bits(Bits::ShiftLeft),
5636        b"bit-shr" => Operator::Bits(Bits::ShiftRight),
5637        b"or" => Operator::Boolean(Boolean::Or),
5638        b"xor" => Operator::Boolean(Boolean::Xor),
5639        b"and" => Operator::Boolean(Boolean::And),
5640        // WARNING: not actual operators below! Error handling only
5641        pow @ (b"^" | b"pow") => {
5642            working_set.error(ParseError::UnknownOperator(
5643                match pow {
5644                    b"^" => "^",
5645                    b"pow" => "pow",
5646                    _ => unreachable!(),
5647                },
5648                "Use '**' for exponentiation or 'bit-xor' for bitwise XOR.",
5649                span,
5650            ));
5651            return garbage(working_set, span);
5652        }
5653        equality @ (b"is" | b"===") => {
5654            working_set.error(ParseError::UnknownOperator(
5655                match equality {
5656                    b"is" => "is",
5657                    b"===" => "===",
5658                    _ => unreachable!(),
5659                },
5660                "Did you mean '=='?",
5661                span,
5662            ));
5663            return garbage(working_set, span);
5664        }
5665        b"contains" => {
5666            working_set.error(ParseError::UnknownOperator(
5667                "contains",
5668                "Did you mean 'has'?",
5669                span,
5670            ));
5671            return garbage(working_set, span);
5672        }
5673        b"%" => {
5674            working_set.error(ParseError::UnknownOperator(
5675                "%",
5676                "Did you mean 'mod'?",
5677                span,
5678            ));
5679            return garbage(working_set, span);
5680        }
5681        b"&" => {
5682            working_set.error(ParseError::UnknownOperator(
5683                "&",
5684                "Did you mean 'bit-and'?",
5685                span,
5686            ));
5687            return garbage(working_set, span);
5688        }
5689        b"<<" => {
5690            working_set.error(ParseError::UnknownOperator(
5691                "<<",
5692                "Did you mean 'bit-shl'?",
5693                span,
5694            ));
5695            return garbage(working_set, span);
5696        }
5697        b">>" => {
5698            working_set.error(ParseError::UnknownOperator(
5699                ">>",
5700                "Did you mean 'bit-shr'?",
5701                span,
5702            ));
5703            return garbage(working_set, span);
5704        }
5705        bits @ (b"bits-and" | b"bits-xor" | b"bits-or" | b"bits-shl" | b"bits-shr") => {
5706            working_set.error(ParseError::UnknownOperator(
5707                match bits {
5708                    b"bits-and" => "bits-and",
5709                    b"bits-xor" => "bits-xor",
5710                    b"bits-or" => "bits-or",
5711                    b"bits-shl" => "bits-shl",
5712                    b"bits-shr" => "bits-shr",
5713                    _ => unreachable!(),
5714                },
5715                match bits {
5716                    b"bits-and" => "Did you mean 'bit-and'?",
5717                    b"bits-xor" => "Did you mean 'bit-xor'?",
5718                    b"bits-or" => "Did you mean 'bit-or'?",
5719                    b"bits-shl" => "Did you mean 'bit-shl'?",
5720                    b"bits-shr" => "Did you mean 'bit-shr'?",
5721                    _ => unreachable!(),
5722                },
5723                span,
5724            ));
5725            return garbage(working_set, span);
5726        }
5727        op if is_assignment_operator(op) => {
5728            working_set.error(ParseError::Expected("a non-assignment operator", span));
5729            return garbage(working_set, span);
5730        }
5731        _ => {
5732            working_set.error(ParseError::Expected("operator", span));
5733            return garbage(working_set, span);
5734        }
5735    };
5736
5737    Expression::new(working_set, Expr::Operator(operator), span, Type::Any)
5738}
5739
5740pub fn parse_math_expression(
5741    working_set: &mut StateWorkingSet,
5742    spans: &[Span],
5743    lhs_row_var_id: Option<VarId>,
5744) -> Expression {
5745    trace!("parsing: math expression");
5746
5747    // As the expr_stack grows, we increase the required precedence to grow larger
5748    // If, at any time, the operator we're looking at is the same or lower precedence
5749    // of what is in the expression stack, we collapse the expression stack.
5750    //
5751    // This leads to an expression stack that grows under increasing precedence and collapses
5752    // under decreasing/sustained precedence
5753    //
5754    // The end result is a stack that we can fold into binary operations as right associations
5755    // safely.
5756
5757    let mut expr_stack: Vec<Expression> = vec![];
5758
5759    let mut idx = 0;
5760    let mut last_prec = u8::MAX;
5761
5762    let first_span = working_set.get_span_contents(spans[0]);
5763
5764    let mut not_start_spans = vec![];
5765
5766    if first_span == b"if" || first_span == b"match" {
5767        // If expression
5768        if spans.len() > 1 {
5769            return parse_call(working_set, spans, spans[0]);
5770        } else {
5771            working_set.error(ParseError::Expected(
5772                "expression",
5773                Span::new(spans[0].end, spans[0].end),
5774            ));
5775            return garbage(working_set, spans[0]);
5776        }
5777    } else if first_span == b"not" {
5778        not_start_spans.push(spans[idx].start);
5779        idx += 1;
5780        while idx < spans.len() {
5781            let next_value = working_set.get_span_contents(spans[idx]);
5782
5783            if next_value == b"not" {
5784                not_start_spans.push(spans[idx].start);
5785                idx += 1;
5786            } else {
5787                break;
5788            }
5789        }
5790
5791        if idx == spans.len() {
5792            working_set.error(ParseError::Expected(
5793                "expression",
5794                Span::new(spans[idx - 1].end, spans[idx - 1].end),
5795            ));
5796            return garbage(working_set, spans[idx - 1]);
5797        }
5798    }
5799
5800    let mut lhs = parse_value(working_set, spans[idx], &SyntaxShape::Any);
5801
5802    for not_start_span in not_start_spans.iter().rev() {
5803        lhs = Expression::new(
5804            working_set,
5805            Expr::UnaryNot(Box::new(lhs)),
5806            Span::new(*not_start_span, spans[idx].end),
5807            Type::Bool,
5808        );
5809    }
5810    not_start_spans.clear();
5811
5812    idx += 1;
5813
5814    if idx >= spans.len() {
5815        // We already found the one part of our expression, so let's expand
5816        if let Some(row_var_id) = lhs_row_var_id {
5817            expand_to_cell_path(working_set, &mut lhs, row_var_id);
5818        }
5819    }
5820
5821    expr_stack.push(lhs);
5822
5823    while idx < spans.len() {
5824        let op = parse_operator(working_set, spans[idx]);
5825
5826        let op_prec = op.precedence();
5827
5828        idx += 1;
5829
5830        if idx == spans.len() {
5831            // Handle broken math expr `1 +` etc
5832            working_set.error(ParseError::IncompleteMathExpression(spans[idx - 1]));
5833
5834            expr_stack.push(Expression::garbage(working_set, spans[idx - 1]));
5835            let missing_span = Span::new(spans[idx - 1].end, spans[idx - 1].end);
5836            expr_stack.push(Expression::garbage(working_set, missing_span));
5837
5838            break;
5839        }
5840
5841        let content = working_set.get_span_contents(spans[idx]);
5842        // allow `if` to be a special value for assignment.
5843
5844        if content == b"if" || content == b"match" {
5845            let rhs = parse_call(working_set, &spans[idx..], spans[0]);
5846            expr_stack.push(op);
5847            expr_stack.push(rhs);
5848            break;
5849        } else if content == b"not" {
5850            not_start_spans.push(spans[idx].start);
5851            idx += 1;
5852            while idx < spans.len() {
5853                let next_value = working_set.get_span_contents(spans[idx]);
5854
5855                if next_value == b"not" {
5856                    not_start_spans.push(spans[idx].start);
5857                    idx += 1;
5858                } else {
5859                    break;
5860                }
5861            }
5862
5863            if idx == spans.len() {
5864                working_set.error(ParseError::Expected(
5865                    "expression",
5866                    Span::new(spans[idx - 1].end, spans[idx - 1].end),
5867                ));
5868                return garbage(working_set, spans[idx - 1]);
5869            }
5870        }
5871        let mut rhs = parse_value(working_set, spans[idx], &SyntaxShape::Any);
5872
5873        for not_start_span in not_start_spans.iter().rev() {
5874            rhs = Expression::new(
5875                working_set,
5876                Expr::UnaryNot(Box::new(rhs)),
5877                Span::new(*not_start_span, spans[idx].end),
5878                Type::Bool,
5879            );
5880        }
5881        not_start_spans.clear();
5882
5883        // Parsing power must be right-associative unlike most operations which are left
5884        // Hence, we should not collapse if the last and current operations are both power
5885        let is_left_associative =
5886            op.expr != Expr::Operator(Operator::Math(Math::Pow)) && op_prec <= last_prec;
5887
5888        while is_left_associative && expr_stack.len() > 1 {
5889            // Collapse the right associated operations first
5890            // so that we can get back to a stack with a lower precedence
5891            let mut rhs = expr_stack
5892                .pop()
5893                .expect("internal error: expression stack empty");
5894            let mut op = expr_stack
5895                .pop()
5896                .expect("internal error: expression stack empty");
5897
5898            last_prec = op.precedence();
5899
5900            if last_prec < op_prec {
5901                expr_stack.push(op);
5902                expr_stack.push(rhs);
5903                break;
5904            }
5905
5906            let mut lhs = expr_stack
5907                .pop()
5908                .expect("internal error: expression stack empty");
5909
5910            if let Some(row_var_id) = lhs_row_var_id {
5911                expand_to_cell_path(working_set, &mut lhs, row_var_id);
5912            }
5913
5914            let (result_ty, err) = math_result_type(working_set, &mut lhs, &mut op, &mut rhs);
5915            if let Some(err) = err {
5916                working_set.error(err);
5917            }
5918
5919            let op_span = Span::append(lhs.span, rhs.span);
5920            expr_stack.push(Expression::new(
5921                working_set,
5922                Expr::BinaryOp(Box::new(lhs), Box::new(op), Box::new(rhs)),
5923                op_span,
5924                result_ty,
5925            ));
5926        }
5927        expr_stack.push(op);
5928        expr_stack.push(rhs);
5929
5930        last_prec = op_prec;
5931
5932        idx += 1;
5933    }
5934
5935    while expr_stack.len() != 1 {
5936        let mut rhs = expr_stack
5937            .pop()
5938            .expect("internal error: expression stack empty");
5939        let mut op = expr_stack
5940            .pop()
5941            .expect("internal error: expression stack empty");
5942        let mut lhs = expr_stack
5943            .pop()
5944            .expect("internal error: expression stack empty");
5945
5946        if let Some(row_var_id) = lhs_row_var_id {
5947            expand_to_cell_path(working_set, &mut lhs, row_var_id);
5948        }
5949
5950        let (result_ty, err) = math_result_type(working_set, &mut lhs, &mut op, &mut rhs);
5951        if let Some(err) = err {
5952            working_set.error(err)
5953        }
5954
5955        let binary_op_span = Span::append(lhs.span, rhs.span);
5956        expr_stack.push(Expression::new(
5957            working_set,
5958            Expr::BinaryOp(Box::new(lhs), Box::new(op), Box::new(rhs)),
5959            binary_op_span,
5960            result_ty,
5961        ));
5962    }
5963
5964    expr_stack
5965        .pop()
5966        .expect("internal error: expression stack empty")
5967}
5968
5969pub fn parse_expression(working_set: &mut StateWorkingSet, spans: &[Span]) -> Expression {
5970    trace!("parsing: expression");
5971
5972    let mut pos = 0;
5973    let mut shorthand = vec![];
5974
5975    while pos < spans.len() {
5976        // Check if there is any environment shorthand
5977        let name = working_set.get_span_contents(spans[pos]);
5978
5979        let split: Vec<_> = name.splitn(2, |x| *x == b'=').collect();
5980        if split.len() != 2 || !is_env_variable_name(split[0]) {
5981            break;
5982        }
5983
5984        let point = split[0].len() + 1;
5985        let starting_error_count = working_set.parse_errors.len();
5986
5987        let rhs = if spans[pos].start + point < spans[pos].end {
5988            let rhs_span = Span::new(spans[pos].start + point, spans[pos].end);
5989            if split[1].starts_with(b"$") {
5990                parse_dollar_expr(working_set, rhs_span)
5991            } else {
5992                parse_string_strict(working_set, rhs_span)
5993            }
5994        } else {
5995            Expression::new(
5996                working_set,
5997                Expr::String(String::new()),
5998                Span::unknown(),
5999                Type::Nothing,
6000            )
6001        };
6002
6003        let lhs_span = Span::new(spans[pos].start, spans[pos].start + point - 1);
6004        let lhs = parse_string_strict(working_set, lhs_span);
6005
6006        if starting_error_count == working_set.parse_errors.len() {
6007            shorthand.push((lhs, rhs));
6008            pos += 1;
6009        } else {
6010            working_set.parse_errors.truncate(starting_error_count);
6011            break;
6012        }
6013    }
6014
6015    if pos == spans.len() {
6016        working_set.error(ParseError::UnknownCommand(spans[0]));
6017        return garbage(working_set, Span::concat(spans));
6018    }
6019
6020    let output = if spans[pos..]
6021        .iter()
6022        .any(|span| is_assignment_operator(working_set.get_span_contents(*span)))
6023    {
6024        parse_assignment_expression(working_set, &spans[pos..])
6025    } else if is_math_expression_like(working_set, spans[pos]) {
6026        parse_math_expression(working_set, &spans[pos..], None)
6027    } else {
6028        let bytes = working_set.get_span_contents(spans[pos]).to_vec();
6029
6030        // For now, check for special parses of certain keywords
6031        match bytes.as_slice() {
6032            b"def" | b"extern" | b"for" | b"module" | b"use" | b"source" | b"alias" | b"export"
6033            | b"export-env" | b"hide" => {
6034                working_set.error(ParseError::BuiltinCommandInPipeline(
6035                    String::from_utf8(bytes)
6036                        .expect("builtin commands bytes should be able to convert to string"),
6037                    spans[0],
6038                ));
6039
6040                parse_call(working_set, &spans[pos..], spans[0])
6041            }
6042            b"let" | b"const" | b"mut" => {
6043                working_set.error(ParseError::AssignInPipeline(
6044                    String::from_utf8(bytes)
6045                        .expect("builtin commands bytes should be able to convert to string"),
6046                    String::from_utf8_lossy(match spans.len() {
6047                        1..=3 => b"value",
6048                        _ => working_set.get_span_contents(spans[3]),
6049                    })
6050                    .to_string(),
6051                    String::from_utf8_lossy(match spans.len() {
6052                        1 => b"variable",
6053                        _ => working_set.get_span_contents(spans[1]),
6054                    })
6055                    .to_string(),
6056                    spans[0],
6057                ));
6058                parse_call(working_set, &spans[pos..], spans[0])
6059            }
6060            b"overlay" => {
6061                if spans.len() > 1 && working_set.get_span_contents(spans[1]) == b"list" {
6062                    // whitelist 'overlay list'
6063                    parse_call(working_set, &spans[pos..], spans[0])
6064                } else {
6065                    working_set.error(ParseError::BuiltinCommandInPipeline(
6066                        "overlay".into(),
6067                        spans[0],
6068                    ));
6069
6070                    parse_call(working_set, &spans[pos..], spans[0])
6071                }
6072            }
6073            b"where" => parse_where_expr(working_set, &spans[pos..]),
6074            #[cfg(feature = "plugin")]
6075            b"plugin" => {
6076                if spans.len() > 1 && working_set.get_span_contents(spans[1]) == b"use" {
6077                    // only 'plugin use' is banned
6078                    working_set.error(ParseError::BuiltinCommandInPipeline(
6079                        "plugin use".into(),
6080                        spans[0],
6081                    ));
6082                }
6083
6084                parse_call(working_set, &spans[pos..], spans[0])
6085            }
6086
6087            _ => parse_call(working_set, &spans[pos..], spans[0]),
6088        }
6089    };
6090
6091    if !shorthand.is_empty() {
6092        let with_env = working_set.find_decl(b"with-env");
6093        if let Some(decl_id) = with_env {
6094            let mut block = Block::default();
6095            let ty = output.ty.clone();
6096            block.pipelines = vec![Pipeline::from_vec(vec![output])];
6097            block.span = Some(Span::concat(spans));
6098
6099            compile_block(working_set, &mut block);
6100
6101            let block_id = working_set.add_block(Arc::new(block));
6102
6103            let mut env_vars = vec![];
6104            for sh in shorthand {
6105                env_vars.push(RecordItem::Pair(sh.0, sh.1));
6106            }
6107
6108            let arguments = vec![
6109                Argument::Positional(Expression::new(
6110                    working_set,
6111                    Expr::Record(env_vars),
6112                    Span::concat(&spans[..pos]),
6113                    Type::Any,
6114                )),
6115                Argument::Positional(Expression::new(
6116                    working_set,
6117                    Expr::Closure(block_id),
6118                    Span::concat(&spans[pos..]),
6119                    Type::Closure,
6120                )),
6121            ];
6122
6123            let expr = Expr::Call(Box::new(Call {
6124                head: Span::unknown(),
6125                decl_id,
6126                arguments,
6127                parser_info: HashMap::new(),
6128            }));
6129
6130            Expression::new(working_set, expr, Span::concat(spans), ty)
6131        } else {
6132            output
6133        }
6134    } else {
6135        output
6136    }
6137}
6138
6139pub fn parse_builtin_commands(
6140    working_set: &mut StateWorkingSet,
6141    lite_command: &LiteCommand,
6142) -> Pipeline {
6143    trace!("parsing: builtin commands");
6144    if !is_math_expression_like(working_set, lite_command.parts[0])
6145        && !is_unaliasable_parser_keyword(working_set, &lite_command.parts)
6146    {
6147        trace!("parsing: not math expression or unaliasable parser keyword");
6148        let name = working_set.get_span_contents(lite_command.parts[0]);
6149        if let Some(decl_id) = working_set.find_decl(name) {
6150            let cmd = working_set.get_decl(decl_id);
6151            if cmd.is_alias() {
6152                // Parse keywords that can be aliased. Note that we check for "unaliasable" keywords
6153                // because alias can have any name, therefore, we can't check for "aliasable" keywords.
6154                let call_expr = parse_call(working_set, &lite_command.parts, lite_command.parts[0]);
6155
6156                if let Expression {
6157                    expr: Expr::Call(call),
6158                    ..
6159                } = call_expr
6160                {
6161                    // Apply parse keyword side effects
6162                    let cmd = working_set.get_decl(call.decl_id);
6163                    match cmd.name() {
6164                        "overlay hide" => return parse_overlay_hide(working_set, call),
6165                        "overlay new" => return parse_overlay_new(working_set, call),
6166                        "overlay use" => return parse_overlay_use(working_set, call),
6167                        _ => { /* this alias is not a parser keyword */ }
6168                    }
6169                }
6170            }
6171        }
6172    }
6173
6174    trace!("parsing: checking for keywords");
6175    let name = lite_command
6176        .command_parts()
6177        .first()
6178        .map(|s| working_set.get_span_contents(*s))
6179        .unwrap_or(b"");
6180
6181    match name {
6182        // `parse_def` and `parse_extern` work both with and without attributes
6183        b"def" => parse_def(working_set, lite_command, None).0,
6184        b"extern" => parse_extern(working_set, lite_command, None),
6185        // `parse_export_in_block` also handles attributes by itself
6186        b"export" => parse_export_in_block(working_set, lite_command),
6187        b"export-env" => parse_export_env(working_set, &lite_command.parts).0,
6188        // Other definitions can't have attributes, so we handle attributes here with parse_attribute_block
6189        _ if lite_command.has_attributes() => parse_attribute_block(working_set, lite_command),
6190        b"let" => parse_let(
6191            working_set,
6192            &lite_command
6193                .parts_including_redirection()
6194                .collect::<Vec<Span>>(),
6195        ),
6196        b"const" => parse_const(working_set, &lite_command.parts).0,
6197        b"mut" => parse_mut(
6198            working_set,
6199            &lite_command
6200                .parts_including_redirection()
6201                .collect::<Vec<Span>>(),
6202        ),
6203        b"for" => {
6204            let expr = parse_for(working_set, lite_command);
6205            Pipeline::from_vec(vec![expr])
6206        }
6207        b"alias" => parse_alias(working_set, lite_command, None),
6208        b"module" => parse_module(working_set, lite_command, None).0,
6209        b"use" => parse_use(working_set, lite_command, None).0,
6210        b"overlay" => {
6211            if let Some(redirection) = lite_command.redirection.as_ref() {
6212                working_set.error(redirecting_builtin_error("overlay", redirection));
6213                return garbage_pipeline(working_set, &lite_command.parts);
6214            }
6215            parse_keyword(working_set, lite_command)
6216        }
6217        b"source" | b"source-env" => parse_source(working_set, lite_command),
6218        b"hide" => parse_hide(working_set, lite_command),
6219        b"where" => parse_where(working_set, lite_command),
6220        // Only "plugin use" is a keyword
6221        #[cfg(feature = "plugin")]
6222        b"plugin"
6223            if lite_command
6224                .parts
6225                .get(1)
6226                .is_some_and(|span| working_set.get_span_contents(*span) == b"use") =>
6227        {
6228            if let Some(redirection) = lite_command.redirection.as_ref() {
6229                working_set.error(redirecting_builtin_error("plugin use", redirection));
6230                return garbage_pipeline(working_set, &lite_command.parts);
6231            }
6232            parse_keyword(working_set, lite_command)
6233        }
6234        _ => {
6235            let element = parse_pipeline_element(working_set, lite_command);
6236
6237            // There is still a chance to make `parse_pipeline_element` parse into
6238            // some keyword that should apply side effects first, Example:
6239            //
6240            // module a { export alias b = overlay use first.nu };
6241            // use a
6242            // a b
6243            //
6244            // In this case, `a b` will be parsed as a pipeline element, which leads
6245            // to the `overlay use` command.
6246            // In this case, we need to ensure that the side effects of these keywords
6247            // are applied.
6248            if let Expression {
6249                expr: Expr::Call(call),
6250                ..
6251            } = &element.expr
6252            {
6253                // Apply parse keyword side effects
6254                let cmd = working_set.get_decl(call.decl_id);
6255                match cmd.name() {
6256                    "overlay hide" => return parse_overlay_hide(working_set, call.clone()),
6257                    "overlay new" => return parse_overlay_new(working_set, call.clone()),
6258                    "overlay use" => return parse_overlay_use(working_set, call.clone()),
6259                    _ => { /* this alias is not a parser keyword */ }
6260                }
6261            }
6262            Pipeline {
6263                elements: vec![element],
6264            }
6265        }
6266    }
6267}
6268
6269fn check_record_key_or_value(
6270    working_set: &StateWorkingSet,
6271    expr: &Expression,
6272    position: &str,
6273) -> Option<ParseError> {
6274    let bareword_error = |string_value: &Expression| {
6275        working_set
6276            .get_span_contents(string_value.span)
6277            .iter()
6278            .find_position(|b| **b == b':')
6279            .map(|(i, _)| {
6280                let colon_position = i + string_value.span.start;
6281                ParseError::InvalidLiteral(
6282                    "colon".to_string(),
6283                    format!("bare word specifying record {position}"),
6284                    Span::new(colon_position, colon_position + 1),
6285                )
6286            })
6287    };
6288    let value_span = working_set.get_span_contents(expr.span);
6289    match expr.expr {
6290        Expr::String(_) => {
6291            if ![b'"', b'\'', b'`'].contains(&value_span[0]) {
6292                bareword_error(expr)
6293            } else {
6294                None
6295            }
6296        }
6297        Expr::StringInterpolation(ref expressions) => {
6298            if value_span[0] != b'$' {
6299                expressions
6300                    .iter()
6301                    .filter(|expr| matches!(expr.expr, Expr::String(_)))
6302                    .filter_map(bareword_error)
6303                    .next()
6304            } else {
6305                None
6306            }
6307        }
6308        _ => None,
6309    }
6310}
6311
6312pub fn parse_record(working_set: &mut StateWorkingSet, span: Span) -> Expression {
6313    let bytes = working_set.get_span_contents(span);
6314
6315    let mut start = span.start;
6316    let mut end = span.end;
6317
6318    if bytes.starts_with(b"{") {
6319        start += 1;
6320    } else {
6321        working_set.error(ParseError::Expected("{", Span::new(start, start + 1)));
6322        return garbage(working_set, span);
6323    }
6324
6325    let mut unclosed = false;
6326    let mut extra_tokens = false;
6327    if bytes.ends_with(b"}") {
6328        end -= 1;
6329    } else {
6330        unclosed = true;
6331    }
6332
6333    let inner_span = Span::new(start, end);
6334
6335    let mut lex_state = LexState {
6336        input: working_set.get_span_contents(inner_span),
6337        output: Vec::new(),
6338        error: None,
6339        span_offset: start,
6340    };
6341    while !lex_state.input.is_empty() {
6342        if let Some(ParseError::Unbalanced(left, right, _)) = lex_state.error.as_ref()
6343            && left == "{"
6344            && right == "}"
6345        {
6346            extra_tokens = true;
6347            unclosed = false;
6348            break;
6349        }
6350        let additional_whitespace = &[b'\n', b'\r', b','];
6351        if lex_n_tokens(&mut lex_state, additional_whitespace, &[b':'], true, 1) < 1 {
6352            break;
6353        };
6354        let span = lex_state
6355            .output
6356            .last()
6357            .expect("should have gotten 1 token")
6358            .span;
6359        let contents = working_set.get_span_contents(span);
6360        if contents.len() > 3
6361            && contents.starts_with(b"...")
6362            && (contents[3] == b'$' || contents[3] == b'{' || contents[3] == b'(')
6363        {
6364            // This was a spread operator, so there's no value
6365            continue;
6366        }
6367        // Get token for colon
6368        if lex_n_tokens(&mut lex_state, additional_whitespace, &[b':'], true, 1) < 1 {
6369            break;
6370        };
6371        // Get token for value
6372        if lex_n_tokens(&mut lex_state, additional_whitespace, &[], true, 1) < 1 {
6373            break;
6374        };
6375    }
6376    let (tokens, err) = (lex_state.output, lex_state.error);
6377
6378    if unclosed {
6379        working_set.error(ParseError::Unclosed("}".into(), Span::new(end, end)));
6380    } else if extra_tokens {
6381        working_set.error(ParseError::ExtraTokensAfterClosingDelimiter(Span::new(
6382            lex_state.span_offset,
6383            end,
6384        )));
6385    }
6386
6387    if let Some(err) = err {
6388        working_set.error(err);
6389    }
6390
6391    let mut output = vec![];
6392    let mut idx = 0;
6393
6394    let mut field_types = Some(vec![]);
6395    while idx < tokens.len() {
6396        let curr_span = tokens[idx].span;
6397        let curr_tok = working_set.get_span_contents(curr_span);
6398        if curr_tok.starts_with(b"...")
6399            && curr_tok.len() > 3
6400            && (curr_tok[3] == b'$' || curr_tok[3] == b'{' || curr_tok[3] == b'(')
6401        {
6402            // Parse spread operator
6403            let inner = parse_value(
6404                working_set,
6405                Span::new(curr_span.start + 3, curr_span.end),
6406                &SyntaxShape::Record(vec![]),
6407            );
6408            idx += 1;
6409
6410            match &inner.ty {
6411                Type::Record(inner_fields) => {
6412                    if let Some(fields) = &mut field_types {
6413                        for (field, ty) in inner_fields.as_ref() {
6414                            fields.push((field.clone(), ty.clone()));
6415                        }
6416                    }
6417                }
6418                _ => {
6419                    // We can't properly see all the field types
6420                    // so fall back to the Any type later
6421                    field_types = None;
6422                }
6423            }
6424            output.push(RecordItem::Spread(
6425                Span::new(curr_span.start, curr_span.start + 3),
6426                inner,
6427            ));
6428        } else {
6429            // Normal key-value pair
6430            let field_token = &tokens[idx];
6431            let field = if field_token.contents != TokenContents::Item {
6432                working_set.error(ParseError::Expected(
6433                    "item in record key position",
6434                    Span::new(field_token.span.start, field_token.span.end),
6435                ));
6436                garbage(working_set, curr_span)
6437            } else {
6438                let field = parse_value(working_set, curr_span, &SyntaxShape::Any);
6439                if let Some(error) = check_record_key_or_value(working_set, &field, "key") {
6440                    working_set.error(error);
6441                    garbage(working_set, field.span)
6442                } else {
6443                    field
6444                }
6445            };
6446
6447            idx += 1;
6448            if idx == tokens.len() {
6449                working_set.error(ParseError::Expected(
6450                    "':'",
6451                    Span::new(curr_span.end, curr_span.end),
6452                ));
6453                output.push(RecordItem::Pair(
6454                    garbage(working_set, curr_span),
6455                    garbage(working_set, Span::new(curr_span.end, curr_span.end)),
6456                ));
6457                break;
6458            }
6459            let colon_span = tokens[idx].span;
6460            let colon = working_set.get_span_contents(colon_span);
6461            idx += 1;
6462            if colon != b":" {
6463                working_set.error(ParseError::Expected(
6464                    "':'",
6465                    Span::new(colon_span.start, colon_span.start),
6466                ));
6467                output.push(RecordItem::Pair(
6468                    field,
6469                    garbage(
6470                        working_set,
6471                        Span::new(colon_span.start, tokens[tokens.len() - 1].span.end),
6472                    ),
6473                ));
6474                break;
6475            }
6476            if idx == tokens.len() {
6477                working_set.error(ParseError::Expected(
6478                    "value for record field",
6479                    Span::new(colon_span.end, colon_span.end),
6480                ));
6481                output.push(RecordItem::Pair(
6482                    garbage(working_set, Span::new(curr_span.start, colon_span.end)),
6483                    garbage(
6484                        working_set,
6485                        Span::new(colon_span.end, tokens[tokens.len() - 1].span.end),
6486                    ),
6487                ));
6488                break;
6489            }
6490
6491            let value_token = &tokens[idx];
6492            let value = if value_token.contents != TokenContents::Item {
6493                working_set.error(ParseError::Expected(
6494                    "item in record value position",
6495                    Span::new(value_token.span.start, value_token.span.end),
6496                ));
6497                garbage(
6498                    working_set,
6499                    Span::new(value_token.span.start, value_token.span.end),
6500                )
6501            } else {
6502                let value = parse_value(working_set, tokens[idx].span, &SyntaxShape::Any);
6503                if let Some(parse_error) = check_record_key_or_value(working_set, &value, "value") {
6504                    working_set.error(parse_error);
6505                    garbage(working_set, value.span)
6506                } else {
6507                    value
6508                }
6509            };
6510            idx += 1;
6511
6512            if let Some(field) = field.as_string() {
6513                if let Some(fields) = &mut field_types {
6514                    fields.push((field, value.ty.clone()));
6515                }
6516            } else {
6517                // We can't properly see all the field types
6518                // so fall back to the Any type later
6519                field_types = None;
6520            }
6521            output.push(RecordItem::Pair(field, value));
6522        }
6523    }
6524
6525    Expression::new(
6526        working_set,
6527        Expr::Record(output),
6528        span,
6529        if let Some(fields) = field_types {
6530            Type::Record(fields.into())
6531        } else {
6532            Type::Any
6533        },
6534    )
6535}
6536
6537fn parse_redirection_target(
6538    working_set: &mut StateWorkingSet,
6539    target: &LiteRedirectionTarget,
6540) -> RedirectionTarget {
6541    match target {
6542        LiteRedirectionTarget::File {
6543            connector,
6544            file,
6545            append,
6546        } => RedirectionTarget::File {
6547            expr: parse_value(working_set, *file, &SyntaxShape::Any),
6548            append: *append,
6549            span: *connector,
6550        },
6551        LiteRedirectionTarget::Pipe { connector } => RedirectionTarget::Pipe { span: *connector },
6552    }
6553}
6554
6555pub(crate) fn parse_redirection(
6556    working_set: &mut StateWorkingSet,
6557    target: &LiteRedirection,
6558) -> PipelineRedirection {
6559    match target {
6560        LiteRedirection::Single { source, target } => PipelineRedirection::Single {
6561            source: *source,
6562            target: parse_redirection_target(working_set, target),
6563        },
6564        LiteRedirection::Separate { out, err } => PipelineRedirection::Separate {
6565            out: parse_redirection_target(working_set, out),
6566            err: parse_redirection_target(working_set, err),
6567        },
6568    }
6569}
6570
6571fn parse_pipeline_element(
6572    working_set: &mut StateWorkingSet,
6573    command: &LiteCommand,
6574) -> PipelineElement {
6575    trace!("parsing: pipeline element");
6576
6577    let expr = parse_expression(working_set, &command.parts);
6578
6579    let redirection = command
6580        .redirection
6581        .as_ref()
6582        .map(|r| parse_redirection(working_set, r));
6583
6584    PipelineElement {
6585        pipe: command.pipe,
6586        expr,
6587        redirection,
6588    }
6589}
6590
6591pub(crate) fn redirecting_builtin_error(
6592    name: &'static str,
6593    redirection: &LiteRedirection,
6594) -> ParseError {
6595    match redirection {
6596        LiteRedirection::Single { target, .. } => {
6597            ParseError::RedirectingBuiltinCommand(name, target.connector(), None)
6598        }
6599        LiteRedirection::Separate { out, err } => ParseError::RedirectingBuiltinCommand(
6600            name,
6601            out.connector().min(err.connector()),
6602            Some(out.connector().max(err.connector())),
6603        ),
6604    }
6605}
6606
6607pub fn parse_pipeline(working_set: &mut StateWorkingSet, pipeline: &LitePipeline) -> Pipeline {
6608    if pipeline.commands.len() > 1 {
6609        // Parse a normal multi command pipeline
6610        let elements: Vec<_> = pipeline
6611            .commands
6612            .iter()
6613            .enumerate()
6614            .map(|(index, element)| {
6615                let element = parse_pipeline_element(working_set, element);
6616                // Handle $in for pipeline elements beyond the first one
6617                if index > 0 && element.has_in_variable(working_set) {
6618                    wrap_element_with_collect(working_set, element.clone())
6619                } else {
6620                    element
6621                }
6622            })
6623            .collect();
6624
6625        Pipeline { elements }
6626    } else {
6627        // If there's only one command in the pipeline, this could be a builtin command
6628        parse_builtin_commands(working_set, &pipeline.commands[0])
6629    }
6630}
6631
6632pub fn parse_block(
6633    working_set: &mut StateWorkingSet,
6634    tokens: &[Token],
6635    span: Span,
6636    scoped: bool,
6637    is_subexpression: bool,
6638) -> Block {
6639    let (lite_block, err) = lite_parse(tokens, working_set);
6640    if let Some(err) = err {
6641        working_set.error(err);
6642    }
6643
6644    trace!("parsing block: {lite_block:?}");
6645
6646    if scoped {
6647        working_set.enter_scope();
6648    }
6649
6650    // Pre-declare any definition so that definitions
6651    // that share the same block can see each other
6652    for pipeline in &lite_block.block {
6653        if pipeline.commands.len() == 1 {
6654            parse_def_predecl(working_set, pipeline.commands[0].command_parts())
6655        }
6656    }
6657
6658    let mut block = Block::new_with_capacity(lite_block.block.len());
6659    block.span = Some(span);
6660
6661    for lite_pipeline in &lite_block.block {
6662        let pipeline = parse_pipeline(working_set, lite_pipeline);
6663        block.pipelines.push(pipeline);
6664    }
6665
6666    // If this is not a subexpression and there are any pipelines where the first element has $in,
6667    // we can wrap the whole block in collect so that they all reference the same $in
6668    if !is_subexpression
6669        && block
6670            .pipelines
6671            .iter()
6672            .flat_map(|pipeline| pipeline.elements.first())
6673            .any(|element| element.has_in_variable(working_set))
6674    {
6675        // Move the block out to prepare it to become a subexpression
6676        let inner_block = std::mem::take(&mut block);
6677        block.span = inner_block.span;
6678        let ty = inner_block.output_type();
6679        let block_id = working_set.add_block(Arc::new(inner_block));
6680
6681        // Now wrap it in a Collect expression, and put it in the block as the only pipeline
6682        let subexpression = Expression::new(working_set, Expr::Subexpression(block_id), span, ty);
6683        let collect = wrap_expr_with_collect(working_set, subexpression);
6684
6685        block.pipelines.push(Pipeline {
6686            elements: vec![PipelineElement {
6687                pipe: None,
6688                expr: collect,
6689                redirection: None,
6690            }],
6691        });
6692    }
6693
6694    if scoped {
6695        working_set.exit_scope();
6696    }
6697
6698    let errors = type_check::check_block_input_output(working_set, &block);
6699    if !errors.is_empty() {
6700        working_set.parse_errors.extend_from_slice(&errors);
6701    }
6702
6703    block
6704}
6705
6706/// Compile an [IrBlock][nu_protocol::ir::IrBlock] for the [Block], adding a compile error on
6707/// failure.
6708///
6709/// To compile a block that's already in the [StateWorkingSet] use [compile_block_with_id]
6710pub fn compile_block(working_set: &mut StateWorkingSet<'_>, block: &mut Block) {
6711    if !working_set.parse_errors.is_empty() {
6712        // This means there might be a bug in the parser, since calling this function while parse
6713        // errors are present is a logic error. However, it's not fatal and it's best to continue
6714        // without doing anything.
6715        log::error!("compile_block called with parse errors");
6716        return;
6717    }
6718
6719    match nu_engine::compile(working_set, block) {
6720        Ok(ir_block) => {
6721            block.ir_block = Some(ir_block);
6722        }
6723        Err(err) => working_set.compile_errors.push(err),
6724    }
6725}
6726
6727/// Compile an [IrBlock][nu_protocol::ir::IrBlock] for a [Block] that's already in the
6728/// [StateWorkingSet] using its id, adding a compile error on failure.
6729pub fn compile_block_with_id(working_set: &mut StateWorkingSet<'_>, block_id: BlockId) {
6730    if !working_set.parse_errors.is_empty() {
6731        // This means there might be a bug in the parser, since calling this function while parse
6732        // errors are present is a logic error. However, it's not fatal and it's best to continue
6733        // without doing anything.
6734        log::error!("compile_block_with_id called with parse errors");
6735        return;
6736    }
6737
6738    match nu_engine::compile(working_set, working_set.get_block(block_id)) {
6739        Ok(ir_block) => {
6740            working_set.get_block_mut(block_id).ir_block = Some(ir_block);
6741        }
6742        Err(err) => {
6743            working_set.compile_errors.push(err);
6744        }
6745    };
6746}
6747
6748pub fn discover_captures_in_closure(
6749    working_set: &StateWorkingSet,
6750    block: &Block,
6751    seen: &mut Vec<VarId>,
6752    seen_blocks: &mut HashMap<BlockId, Vec<(VarId, Span)>>,
6753    output: &mut Vec<(VarId, Span)>,
6754) -> Result<(), ParseError> {
6755    for flag in &block.signature.named {
6756        if let Some(var_id) = flag.var_id {
6757            seen.push(var_id);
6758        }
6759    }
6760
6761    for positional in &block.signature.required_positional {
6762        if let Some(var_id) = positional.var_id {
6763            seen.push(var_id);
6764        }
6765    }
6766    for positional in &block.signature.optional_positional {
6767        if let Some(var_id) = positional.var_id {
6768            seen.push(var_id);
6769        }
6770    }
6771    if let Some(positional) = &block.signature.rest_positional
6772        && let Some(var_id) = positional.var_id
6773    {
6774        seen.push(var_id);
6775    }
6776
6777    for pipeline in &block.pipelines {
6778        discover_captures_in_pipeline(working_set, pipeline, seen, seen_blocks, output)?;
6779    }
6780
6781    Ok(())
6782}
6783
6784fn discover_captures_in_pipeline(
6785    working_set: &StateWorkingSet,
6786    pipeline: &Pipeline,
6787    seen: &mut Vec<VarId>,
6788    seen_blocks: &mut HashMap<BlockId, Vec<(VarId, Span)>>,
6789    output: &mut Vec<(VarId, Span)>,
6790) -> Result<(), ParseError> {
6791    for element in &pipeline.elements {
6792        discover_captures_in_pipeline_element(working_set, element, seen, seen_blocks, output)?;
6793    }
6794
6795    Ok(())
6796}
6797
6798// Closes over captured variables
6799pub fn discover_captures_in_pipeline_element(
6800    working_set: &StateWorkingSet,
6801    element: &PipelineElement,
6802    seen: &mut Vec<VarId>,
6803    seen_blocks: &mut HashMap<BlockId, Vec<(VarId, Span)>>,
6804    output: &mut Vec<(VarId, Span)>,
6805) -> Result<(), ParseError> {
6806    discover_captures_in_expr(working_set, &element.expr, seen, seen_blocks, output)?;
6807
6808    if let Some(redirection) = element.redirection.as_ref() {
6809        match redirection {
6810            PipelineRedirection::Single { target, .. } => {
6811                if let Some(expr) = target.expr() {
6812                    discover_captures_in_expr(working_set, expr, seen, seen_blocks, output)?;
6813                }
6814            }
6815            PipelineRedirection::Separate { out, err } => {
6816                if let Some(expr) = out.expr() {
6817                    discover_captures_in_expr(working_set, expr, seen, seen_blocks, output)?;
6818                }
6819                if let Some(expr) = err.expr() {
6820                    discover_captures_in_expr(working_set, expr, seen, seen_blocks, output)?;
6821                }
6822            }
6823        }
6824    }
6825
6826    Ok(())
6827}
6828
6829pub fn discover_captures_in_pattern(pattern: &MatchPattern, seen: &mut Vec<VarId>) {
6830    match &pattern.pattern {
6831        Pattern::Variable(var_id) => seen.push(*var_id),
6832        Pattern::List(items) => {
6833            for item in items {
6834                discover_captures_in_pattern(item, seen)
6835            }
6836        }
6837        Pattern::Record(items) => {
6838            for item in items {
6839                discover_captures_in_pattern(&item.1, seen)
6840            }
6841        }
6842        Pattern::Or(patterns) => {
6843            for pattern in patterns {
6844                discover_captures_in_pattern(pattern, seen)
6845            }
6846        }
6847        Pattern::Rest(var_id) => seen.push(*var_id),
6848        Pattern::Expression(_)
6849        | Pattern::Value(_)
6850        | Pattern::IgnoreValue
6851        | Pattern::IgnoreRest
6852        | Pattern::Garbage => {}
6853    }
6854}
6855
6856// Closes over captured variables
6857pub fn discover_captures_in_expr(
6858    working_set: &StateWorkingSet,
6859    expr: &Expression,
6860    seen: &mut Vec<VarId>,
6861    seen_blocks: &mut HashMap<BlockId, Vec<(VarId, Span)>>,
6862    output: &mut Vec<(VarId, Span)>,
6863) -> Result<(), ParseError> {
6864    match &expr.expr {
6865        Expr::AttributeBlock(ab) => {
6866            discover_captures_in_expr(working_set, &ab.item, seen, seen_blocks, output)?;
6867        }
6868        Expr::BinaryOp(lhs, _, rhs) => {
6869            discover_captures_in_expr(working_set, lhs, seen, seen_blocks, output)?;
6870            discover_captures_in_expr(working_set, rhs, seen, seen_blocks, output)?;
6871        }
6872        Expr::UnaryNot(expr) => {
6873            discover_captures_in_expr(working_set, expr, seen, seen_blocks, output)?;
6874        }
6875        Expr::Closure(block_id) => {
6876            let block = working_set.get_block(*block_id);
6877            let results = {
6878                let mut seen = vec![];
6879                let mut results = vec![];
6880
6881                discover_captures_in_closure(
6882                    working_set,
6883                    block,
6884                    &mut seen,
6885                    seen_blocks,
6886                    &mut results,
6887                )?;
6888
6889                for (var_id, span) in results.iter() {
6890                    if !seen.contains(var_id)
6891                        && let Some(variable) = working_set.get_variable_if_possible(*var_id)
6892                        && variable.mutable
6893                    {
6894                        return Err(ParseError::CaptureOfMutableVar(*span));
6895                    }
6896                }
6897
6898                results
6899            };
6900            seen_blocks.insert(*block_id, results.clone());
6901            for (var_id, span) in results.into_iter() {
6902                if !seen.contains(&var_id) {
6903                    output.push((var_id, span))
6904                }
6905            }
6906        }
6907        Expr::Block(block_id) => {
6908            let block = working_set.get_block(*block_id);
6909            // FIXME: is this correct?
6910            let results = {
6911                let mut seen = vec![];
6912                let mut results = vec![];
6913                discover_captures_in_closure(
6914                    working_set,
6915                    block,
6916                    &mut seen,
6917                    seen_blocks,
6918                    &mut results,
6919                )?;
6920                results
6921            };
6922
6923            seen_blocks.insert(*block_id, results.clone());
6924            for (var_id, span) in results.into_iter() {
6925                if !seen.contains(&var_id) {
6926                    output.push((var_id, span))
6927                }
6928            }
6929        }
6930        Expr::Binary(_) => {}
6931        Expr::Bool(_) => {}
6932        Expr::Call(call) => {
6933            let decl = working_set.get_decl(call.decl_id);
6934            if let Some(block_id) = decl.block_id() {
6935                match seen_blocks.get(&block_id) {
6936                    Some(capture_list) => {
6937                        // Push captures onto the outer closure that aren't created by that outer closure
6938                        for capture in capture_list {
6939                            if !seen.contains(&capture.0) {
6940                                output.push(*capture);
6941                            }
6942                        }
6943                    }
6944                    None => {
6945                        let block = working_set.get_block(block_id);
6946                        if !block.captures.is_empty() {
6947                            for (capture, span) in &block.captures {
6948                                if !seen.contains(capture) {
6949                                    output.push((*capture, *span));
6950                                }
6951                            }
6952                        } else {
6953                            let result = {
6954                                let mut seen = vec![];
6955                                seen_blocks.insert(block_id, output.clone());
6956
6957                                let mut result = vec![];
6958                                discover_captures_in_closure(
6959                                    working_set,
6960                                    block,
6961                                    &mut seen,
6962                                    seen_blocks,
6963                                    &mut result,
6964                                )?;
6965
6966                                result
6967                            };
6968                            // Push captures onto the outer closure that aren't created by that outer closure
6969                            for capture in &result {
6970                                if !seen.contains(&capture.0) {
6971                                    output.push(*capture);
6972                                }
6973                            }
6974
6975                            seen_blocks.insert(block_id, result);
6976                        }
6977                    }
6978                }
6979            }
6980
6981            for arg in &call.arguments {
6982                match arg {
6983                    Argument::Named(named) => {
6984                        if let Some(arg) = &named.2 {
6985                            discover_captures_in_expr(working_set, arg, seen, seen_blocks, output)?;
6986                        }
6987                    }
6988                    Argument::Positional(expr)
6989                    | Argument::Unknown(expr)
6990                    | Argument::Spread(expr) => {
6991                        discover_captures_in_expr(working_set, expr, seen, seen_blocks, output)?;
6992                    }
6993                }
6994            }
6995        }
6996        Expr::CellPath(_) => {}
6997        Expr::DateTime(_) => {}
6998        Expr::ExternalCall(head, args) => {
6999            discover_captures_in_expr(working_set, head, seen, seen_blocks, output)?;
7000
7001            for ExternalArgument::Regular(expr) | ExternalArgument::Spread(expr) in args.as_ref() {
7002                discover_captures_in_expr(working_set, expr, seen, seen_blocks, output)?;
7003            }
7004        }
7005        Expr::Filepath(_, _) => {}
7006        Expr::Directory(_, _) => {}
7007        Expr::Float(_) => {}
7008        Expr::FullCellPath(cell_path) => {
7009            discover_captures_in_expr(working_set, &cell_path.head, seen, seen_blocks, output)?;
7010        }
7011        Expr::ImportPattern(_) => {}
7012        Expr::Overlay(_) => {}
7013        Expr::Garbage => {}
7014        Expr::Nothing => {}
7015        Expr::GlobPattern(_, _) => {}
7016        Expr::Int(_) => {}
7017        Expr::Keyword(kw) => {
7018            discover_captures_in_expr(working_set, &kw.expr, seen, seen_blocks, output)?;
7019        }
7020        Expr::List(list) => {
7021            for item in list {
7022                discover_captures_in_expr(working_set, item.expr(), seen, seen_blocks, output)?;
7023            }
7024        }
7025        Expr::Operator(_) => {}
7026        Expr::Range(range) => {
7027            if let Some(from) = &range.from {
7028                discover_captures_in_expr(working_set, from, seen, seen_blocks, output)?;
7029            }
7030            if let Some(next) = &range.next {
7031                discover_captures_in_expr(working_set, next, seen, seen_blocks, output)?;
7032            }
7033            if let Some(to) = &range.to {
7034                discover_captures_in_expr(working_set, to, seen, seen_blocks, output)?;
7035            }
7036        }
7037        Expr::Record(items) => {
7038            for item in items {
7039                match item {
7040                    RecordItem::Pair(field_name, field_value) => {
7041                        discover_captures_in_expr(
7042                            working_set,
7043                            field_name,
7044                            seen,
7045                            seen_blocks,
7046                            output,
7047                        )?;
7048                        discover_captures_in_expr(
7049                            working_set,
7050                            field_value,
7051                            seen,
7052                            seen_blocks,
7053                            output,
7054                        )?;
7055                    }
7056                    RecordItem::Spread(_, record) => {
7057                        discover_captures_in_expr(working_set, record, seen, seen_blocks, output)?;
7058                    }
7059                }
7060            }
7061        }
7062        Expr::Signature(sig) => {
7063            // Something with a declaration, similar to a var decl, will introduce more VarIds into the stack at eval
7064            for pos in &sig.required_positional {
7065                if let Some(var_id) = pos.var_id {
7066                    seen.push(var_id);
7067                }
7068            }
7069            for pos in &sig.optional_positional {
7070                if let Some(var_id) = pos.var_id {
7071                    seen.push(var_id);
7072                }
7073            }
7074            if let Some(rest) = &sig.rest_positional
7075                && let Some(var_id) = rest.var_id
7076            {
7077                seen.push(var_id);
7078            }
7079            for named in &sig.named {
7080                if let Some(var_id) = named.var_id {
7081                    seen.push(var_id);
7082                }
7083            }
7084        }
7085        Expr::String(_) => {}
7086        Expr::RawString(_) => {}
7087        Expr::StringInterpolation(exprs) | Expr::GlobInterpolation(exprs, _) => {
7088            for expr in exprs {
7089                discover_captures_in_expr(working_set, expr, seen, seen_blocks, output)?;
7090            }
7091        }
7092        Expr::MatchBlock(match_block) => {
7093            for match_ in match_block {
7094                discover_captures_in_pattern(&match_.0, seen);
7095                discover_captures_in_expr(working_set, &match_.1, seen, seen_blocks, output)?;
7096            }
7097        }
7098        Expr::Collect(var_id, expr) => {
7099            seen.push(*var_id);
7100            discover_captures_in_expr(working_set, expr, seen, seen_blocks, output)?
7101        }
7102        Expr::RowCondition(block_id) | Expr::Subexpression(block_id) => {
7103            let block = working_set.get_block(*block_id);
7104
7105            let results = {
7106                let mut results = vec![];
7107                let mut seen = vec![];
7108                discover_captures_in_closure(
7109                    working_set,
7110                    block,
7111                    &mut seen,
7112                    seen_blocks,
7113                    &mut results,
7114                )?;
7115                results
7116            };
7117
7118            seen_blocks.insert(*block_id, results.clone());
7119            for (var_id, span) in results.into_iter() {
7120                if !seen.contains(&var_id) {
7121                    output.push((var_id, span))
7122                }
7123            }
7124        }
7125        Expr::Table(table) => {
7126            for header in table.columns.as_ref() {
7127                discover_captures_in_expr(working_set, header, seen, seen_blocks, output)?;
7128            }
7129            for row in table.rows.as_ref() {
7130                for cell in row.as_ref() {
7131                    discover_captures_in_expr(working_set, cell, seen, seen_blocks, output)?;
7132                }
7133            }
7134        }
7135        Expr::ValueWithUnit(value) => {
7136            discover_captures_in_expr(working_set, &value.expr, seen, seen_blocks, output)?;
7137        }
7138        Expr::Var(var_id) => {
7139            if (*var_id > ENV_VARIABLE_ID || *var_id == IN_VARIABLE_ID) && !seen.contains(var_id) {
7140                output.push((*var_id, expr.span));
7141            }
7142        }
7143        Expr::VarDecl(var_id) => {
7144            seen.push(*var_id);
7145        }
7146    }
7147    Ok(())
7148}
7149
7150fn wrap_redirection_with_collect(
7151    working_set: &mut StateWorkingSet,
7152    target: RedirectionTarget,
7153) -> RedirectionTarget {
7154    match target {
7155        RedirectionTarget::File { expr, append, span } => RedirectionTarget::File {
7156            expr: wrap_expr_with_collect(working_set, expr),
7157            span,
7158            append,
7159        },
7160        RedirectionTarget::Pipe { span } => RedirectionTarget::Pipe { span },
7161    }
7162}
7163
7164fn wrap_element_with_collect(
7165    working_set: &mut StateWorkingSet,
7166    element: PipelineElement,
7167) -> PipelineElement {
7168    PipelineElement {
7169        pipe: element.pipe,
7170        expr: wrap_expr_with_collect(working_set, element.expr),
7171        redirection: element.redirection.map(|r| match r {
7172            PipelineRedirection::Single { source, target } => PipelineRedirection::Single {
7173                source,
7174                target: wrap_redirection_with_collect(working_set, target),
7175            },
7176            PipelineRedirection::Separate { out, err } => PipelineRedirection::Separate {
7177                out: wrap_redirection_with_collect(working_set, out),
7178                err: wrap_redirection_with_collect(working_set, err),
7179            },
7180        }),
7181    }
7182}
7183
7184fn wrap_expr_with_collect(working_set: &mut StateWorkingSet, expr: Expression) -> Expression {
7185    let span = expr.span;
7186
7187    // IN_VARIABLE_ID should get replaced with a unique variable, so that we don't have to
7188    // execute as a closure
7189    let var_id = working_set.add_variable(
7190        b"$in".into(),
7191        Span::new(span.start, span.start),
7192        Type::Any,
7193        false,
7194    );
7195    let mut expr = expr.clone();
7196    expr.replace_in_variable(working_set, var_id);
7197
7198    // Bind the custom `$in` variable for that particular expression
7199    let ty = expr.ty.clone();
7200    Expression::new(
7201        working_set,
7202        Expr::Collect(var_id, Box::new(expr)),
7203        span,
7204        // We can expect it to have the same result type
7205        ty,
7206    )
7207}
7208
7209// Parses a vector of u8 to create an AST Block. If a file name is given, then
7210// the name is stored in the working set. When parsing a source without a file
7211// name, the source of bytes is stored as "source"
7212pub fn parse(
7213    working_set: &mut StateWorkingSet,
7214    fname: Option<&str>,
7215    contents: &[u8],
7216    scoped: bool,
7217) -> Arc<Block> {
7218    trace!("parse");
7219    let name = match fname {
7220        Some(fname) => {
7221            // use the canonical name for this filename
7222            nu_path::expand_to_real_path(fname)
7223                .to_string_lossy()
7224                .to_string()
7225        }
7226        None => "source".to_string(),
7227    };
7228
7229    let file_id = working_set.add_file(name, contents);
7230    let new_span = working_set.get_span_for_file(file_id);
7231
7232    let previously_parsed_block = working_set.find_block_by_span(new_span);
7233
7234    let mut output = {
7235        if let Some(block) = previously_parsed_block {
7236            return block;
7237        } else {
7238            let (output, err) = lex(contents, new_span.start, &[], &[], false);
7239            if let Some(err) = err {
7240                working_set.error(err)
7241            }
7242
7243            Arc::new(parse_block(working_set, &output, new_span, scoped, false))
7244        }
7245    };
7246
7247    // Top level `Block`s are compiled eagerly, as they don't have a parent which would cause them
7248    // to be compiled later.
7249    if working_set.parse_errors.is_empty() {
7250        compile_block(working_set, Arc::make_mut(&mut output));
7251    }
7252
7253    let mut seen = vec![];
7254    let mut seen_blocks = HashMap::new();
7255
7256    let mut captures = vec![];
7257    match discover_captures_in_closure(
7258        working_set,
7259        &output,
7260        &mut seen,
7261        &mut seen_blocks,
7262        &mut captures,
7263    ) {
7264        Ok(_) => {
7265            Arc::make_mut(&mut output).captures = captures;
7266        }
7267        Err(err) => working_set.error(err),
7268    }
7269
7270    // Also check other blocks that might have been imported
7271    let mut errors = vec![];
7272    for (block_idx, block) in working_set.delta.blocks.iter().enumerate() {
7273        let block_id = block_idx + working_set.permanent_state.num_blocks();
7274        let block_id = BlockId::new(block_id);
7275
7276        if !seen_blocks.contains_key(&block_id) {
7277            let mut captures = vec![];
7278
7279            match discover_captures_in_closure(
7280                working_set,
7281                block,
7282                &mut seen,
7283                &mut seen_blocks,
7284                &mut captures,
7285            ) {
7286                Ok(_) => {
7287                    seen_blocks.insert(block_id, captures);
7288                }
7289                Err(err) => {
7290                    errors.push(err);
7291                }
7292            }
7293        }
7294    }
7295    for err in errors {
7296        working_set.error(err)
7297    }
7298
7299    for (block_id, captures) in seen_blocks.into_iter() {
7300        // In theory, we should only be updating captures where we have new information
7301        // the only place where this is possible would be blocks that are newly created
7302        // by our working set delta. If we ever tried to modify the permanent state, we'd
7303        // panic (again, in theory, this shouldn't be possible)
7304        let block = working_set.get_block(block_id);
7305        let block_captures_empty = block.captures.is_empty();
7306        // need to check block_id >= working_set.permanent_state.num_blocks()
7307        // to avoid mutate a block that is in the permanent state.
7308        // this can happened if user defines a function with recursive call
7309        // and pipe a variable to the command, e.g:
7310        // def px [] { if true { 42 } else { px } };    # the block px is saved in permanent state.
7311        // let x = 3
7312        // $x | px
7313        // If we don't guard for `block_id`, it will change captures of `px`, which is
7314        // already saved in permanent state
7315        if !captures.is_empty()
7316            && block_captures_empty
7317            && block_id.get() >= working_set.permanent_state.num_blocks()
7318        {
7319            let block = working_set.get_block_mut(block_id);
7320            block.captures = captures;
7321        }
7322    }
7323
7324    output
7325}