Skip to main content

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