Skip to main content

nu_parser/
parse_literals.rs

1#![allow(clippy::byte_char_slices)]
2
3use crate::{
4    Token, TokenContents,
5    lex::lex,
6    parse_helpers::{
7        SPREAD_OPERATOR_STR, extract_spread_record, garbage, is_variable, trim_quotes,
8    },
9    parse_pipelines::parse_block,
10    type_check::check_range_types,
11};
12use itertools::Itertools;
13use log::trace;
14use nu_protocol::{
15    DidYouMean, FilesizeUnit, IntoSpanned, ParseError, Span, Spanned, SyntaxShape, Type, Unit,
16    VarId, ast::*, casing::Casing, engine::StateWorkingSet,
17};
18use std::sync::Arc;
19
20use crate::parse_expressions::{
21    parse_block_expression, parse_closure_expression, parse_match_block_expression, parse_record,
22    parse_table_expression,
23};
24use crate::parse_signatures::parse_signature;
25
26pub fn parse_binary(working_set: &mut StateWorkingSet, span: Span) -> Expression {
27    trace!("parsing: binary");
28    let contents = working_set.get_span_contents(span);
29    if contents.starts_with(b"0x[") {
30        parse_binary_with_base(working_set, span, 16, 2, b"0x[", b"]")
31    } else if contents.starts_with(b"0o[") {
32        parse_binary_with_base(working_set, span, 8, 3, b"0o[", b"]")
33    } else if contents.starts_with(b"0b[") {
34        parse_binary_with_base(working_set, span, 2, 8, b"0b[", b"]")
35    } else {
36        working_set.error(ParseError::Expected("binary", span));
37        garbage(working_set, span)
38    }
39}
40
41fn parse_binary_with_base(
42    working_set: &mut StateWorkingSet,
43    span: Span,
44    base: u32,
45    min_digits_per_byte: usize,
46    prefix: &[u8],
47    suffix: &[u8],
48) -> Expression {
49    let token = working_set.get_span_contents(span);
50
51    if let Some(token) = token.strip_prefix(prefix)
52        && let Some(token) = token.strip_suffix(suffix)
53    {
54        let (lexed, err) = lex(
55            token,
56            span.start + prefix.len(),
57            &[b',', b'\r', b'\n'],
58            &[],
59            true,
60        );
61        if let Some(err) = err {
62            working_set.error(err);
63        }
64
65        let mut binary_value = vec![];
66        for token in lexed {
67            match token.contents {
68                TokenContents::Item => {
69                    let contents = working_set.get_span_contents(token.span);
70
71                    binary_value.extend_from_slice(contents);
72                }
73                TokenContents::Pipe
74                | TokenContents::PipePipe
75                | TokenContents::ErrGreaterPipe
76                | TokenContents::OutGreaterThan
77                | TokenContents::OutErrGreaterPipe
78                | TokenContents::OutGreaterGreaterThan
79                | TokenContents::ErrGreaterThan
80                | TokenContents::ErrGreaterGreaterThan
81                | TokenContents::OutErrGreaterThan
82                | TokenContents::OutErrGreaterGreaterThan
83                | TokenContents::AssignmentOperator => {
84                    working_set.error(ParseError::Expected("binary", span));
85                    return garbage(working_set, span);
86                }
87                TokenContents::Comment | TokenContents::Semicolon | TokenContents::Eol => {}
88            }
89        }
90
91        let required_padding =
92            (min_digits_per_byte - binary_value.len() % min_digits_per_byte) % min_digits_per_byte;
93
94        if required_padding != 0 {
95            binary_value = {
96                let mut tail = binary_value;
97                let mut binary_value: Vec<u8> = vec![b'0'; required_padding];
98                binary_value.append(&mut tail);
99                binary_value
100            };
101        }
102
103        let str = String::from_utf8_lossy(&binary_value).to_string();
104
105        match decode_with_base(&str, base, min_digits_per_byte) {
106            Ok(v) => return Expression::new(working_set, Expr::Binary(v), span, Type::Binary),
107            Err(help) => {
108                working_set.error(ParseError::InvalidBinaryString(span, help.to_string()));
109                return garbage(working_set, span);
110            }
111        }
112    }
113
114    working_set.error(ParseError::Expected("binary", span));
115    garbage(working_set, span)
116}
117
118fn decode_with_base(s: &str, base: u32, digits_per_byte: usize) -> Result<Vec<u8>, &str> {
119    s.chars()
120        .chunks(digits_per_byte)
121        .into_iter()
122        .map(|chunk| {
123            let str: String = chunk.collect();
124            u8::from_str_radix(&str, base).map_err(|_| match base {
125                2 => "binary strings may contain only 0 or 1.",
126                8 => "octal strings must have a length that is a multiple of three and contain values between 0o000 and 0o377.",
127                16 => "hexadecimal strings may contain only the characters 0–9 and A–F.",
128                _ => "internal error: radix other than 2, 8, or 16 is not allowed."
129            })
130        })
131        .collect()
132}
133
134fn strip_underscores(token: &[u8]) -> String {
135    String::from_utf8_lossy(token)
136        .chars()
137        .filter(|c| *c != '_')
138        .collect()
139}
140
141pub fn parse_int(working_set: &mut StateWorkingSet, span: Span) -> Expression {
142    let token = working_set.get_span_contents(span);
143
144    fn extract_int(
145        working_set: &mut StateWorkingSet,
146        token: &str,
147        span: Span,
148        radix: u32,
149    ) -> Expression {
150        // Parse as a u64, then cast to i64, otherwise, for numbers like "0xffffffffffffffef",
151        // you'll get `Error parsing hex string: number too large to fit in target type`.
152        if let Ok(num) = u64::from_str_radix(token, radix).map(|val| val as i64) {
153            Expression::new(working_set, Expr::Int(num), span, Type::Int)
154        } else {
155            working_set.error(ParseError::InvalidLiteral(
156                format!("invalid digits for radix {radix}"),
157                "int".into(),
158                span,
159            ));
160
161            garbage(working_set, span)
162        }
163    }
164
165    let token = strip_underscores(token);
166
167    if token.is_empty() {
168        working_set.error(ParseError::Expected("int", span));
169        return garbage(working_set, span);
170    }
171
172    if let Some(num) = token.strip_prefix("0b") {
173        extract_int(working_set, num, span, 2)
174    } else if let Some(num) = token.strip_prefix("0o") {
175        extract_int(working_set, num, span, 8)
176    } else if let Some(num) = token.strip_prefix("0x") {
177        extract_int(working_set, num, span, 16)
178    } else if let Ok(num) = token.parse::<i64>() {
179        Expression::new(working_set, Expr::Int(num), span, Type::Int)
180    } else {
181        working_set.error(ParseError::Expected("int", span));
182        garbage(working_set, span)
183    }
184}
185
186pub fn parse_float(working_set: &mut StateWorkingSet, span: Span) -> Expression {
187    let token = working_set.get_span_contents(span);
188    let token = strip_underscores(token);
189
190    if let Ok(x) = token.parse::<f64>() {
191        Expression::new(working_set, Expr::Float(x), span, Type::Float)
192    } else {
193        working_set.error(ParseError::Expected("float", span));
194
195        garbage(working_set, span)
196    }
197}
198
199pub fn parse_number(working_set: &mut StateWorkingSet, span: Span) -> Expression {
200    let starting_error_count = working_set.parse_errors.len();
201
202    let result = parse_int(working_set, span);
203    if starting_error_count == working_set.parse_errors.len() {
204        return result;
205    } else if let Some(ParseError::Expected(_, _)) = working_set.parse_errors.last() {
206        working_set.parse_errors.truncate(starting_error_count);
207    }
208
209    let result = parse_float(working_set, span);
210
211    if starting_error_count == working_set.parse_errors.len() {
212        return result;
213    }
214    working_set.parse_errors.truncate(starting_error_count);
215
216    working_set.error(ParseError::Expected("number", span));
217    garbage(working_set, span)
218}
219
220pub fn parse_range(working_set: &mut StateWorkingSet, span: Span) -> Option<Expression> {
221    trace!("parsing: range");
222    let starting_error_count = working_set.parse_errors.len();
223
224    // Range follows the following syntax: [<from>][<next_operator><next>]<range_operator>[<to>]
225    //   where <next_operator> is ".."
226    //   and  <range_operator> is "..", "..=" or "..<"
227    //   and one of the <from> or <to> bounds must be present (just '..' is not allowed since it
228    //     looks like parent directory)
229    //bugbug range cannot be [..] because that looks like parent directory
230
231    let contents = working_set.get_span_contents(span);
232
233    let Ok(token) = String::from_utf8(contents.into()) else {
234        working_set.error(ParseError::NonUtf8(span));
235        return None;
236    };
237
238    if token.starts_with(SPREAD_OPERATOR_STR) {
239        working_set.error(ParseError::Expected(
240            "range operator ('..'), got spread ('...')",
241            span,
242        ));
243        return None;
244    }
245
246    if !token.contains("..") {
247        working_set.error(ParseError::Expected("at least one range bound set", span));
248        return None;
249    }
250
251    let dotdot_pos: Vec<_> = token
252        .match_indices("..")
253        .filter_map(|(pos, _)| {
254            // paren_depth = count of unclosed parens prior to pos
255            let before = &token[..pos];
256            let paren_opened = before.chars().filter(|&c| c == '(').count();
257            let paren_closed = before.chars().filter(|&c| c == ')').count();
258            let paren_depth = paren_opened.checked_sub(paren_closed)?;
259            (paren_depth == 0).then_some(pos)
260        })
261        .collect();
262
263    let (next_op_pos, range_op_pos) = match dotdot_pos.len() {
264        1 => (None, dotdot_pos[0]),
265        2 => (Some(dotdot_pos[0]), dotdot_pos[1]),
266        _ => {
267            working_set.error(ParseError::Expected(
268                "one range operator ('..' or '..<') and optionally one next operator ('..')",
269                span,
270            ));
271            return None;
272        }
273    };
274    // Avoid calling sub-parsers on unmatched parens, to prevent quadratic time on things like ((((1..2))))
275    // No need to call the expensive parse_value on "((((1"
276    if dotdot_pos[0] > 0 {
277        let (_tokens, err) = lex(
278            &contents[..dotdot_pos[0]],
279            span.start,
280            &[],
281            &[b'.', b'?', b'!'],
282            true,
283        );
284        if let Some(_err) = err {
285            working_set.error(ParseError::Expected("Valid expression before ..", span));
286            return None;
287        }
288    }
289
290    let (inclusion, range_op_str, range_op_span) = if let Some(pos) = token.find("..<") {
291        if pos == range_op_pos {
292            let op_str = "..<";
293            let op_span = Span::new(
294                span.start + range_op_pos,
295                span.start + range_op_pos + op_str.len(),
296            );
297            (RangeInclusion::RightExclusive, "..<", op_span)
298        } else {
299            working_set.error(ParseError::Expected(
300                "inclusive operator preceding second range bound",
301                span,
302            ));
303            return None;
304        }
305    } else {
306        let op_str = if token[range_op_pos..].starts_with("..=") {
307            "..="
308        } else {
309            ".."
310        };
311
312        let op_span = Span::new(
313            span.start + range_op_pos,
314            span.start + range_op_pos + op_str.len(),
315        );
316        (RangeInclusion::Inclusive, op_str, op_span)
317    };
318
319    // Now, based on the operator positions, figure out where the bounds & next are located and
320    // parse them
321    // TODO: Actually parse the next number in the range
322    let from = if token.starts_with("..") {
323        // token starts with either next operator, or range operator -- we don't care which one
324        None
325    } else {
326        let from_span = Span::new(span.start, span.start + dotdot_pos[0]);
327        Some(crate::parser::parse_value(
328            working_set,
329            from_span,
330            &SyntaxShape::Number,
331            None,
332        ))
333    };
334
335    let to = if token.ends_with(range_op_str) {
336        None
337    } else {
338        let to_span = Span::new(range_op_span.end, span.end);
339        Some(crate::parser::parse_value(
340            working_set,
341            to_span,
342            &SyntaxShape::Number,
343            None,
344        ))
345    };
346
347    trace!("-- from: {from:?} to: {to:?}");
348
349    if let (None, None) = (&from, &to) {
350        working_set.error(ParseError::Expected("at least one range bound set", span));
351        return None;
352    }
353
354    let (next, next_op_span) = if let Some(pos) = next_op_pos {
355        let next_op_span = Span::new(span.start + pos, span.start + pos + "..".len());
356        let next_span = Span::new(next_op_span.end, range_op_span.start);
357
358        (
359            Some(crate::parser::parse_value(
360                working_set,
361                next_span,
362                &SyntaxShape::Number,
363                None,
364            )),
365            next_op_span,
366        )
367    } else {
368        (None, span)
369    };
370
371    if working_set.parse_errors.len() != starting_error_count {
372        return None;
373    }
374
375    let operator = RangeOperator {
376        inclusion,
377        span: range_op_span,
378        next_op_span,
379    };
380
381    let mut range = Range {
382        from,
383        next,
384        to,
385        operator,
386    };
387
388    check_range_types(working_set, &mut range);
389
390    Some(Expression::new(
391        working_set,
392        Expr::Range(Box::new(range)),
393        span,
394        Type::Range,
395    ))
396}
397
398pub(crate) fn parse_dollar_expr(
399    working_set: &mut StateWorkingSet,
400    span: Span,
401    shape: &SyntaxShape,
402    input_type: Option<&Type>,
403) -> Expression {
404    trace!("parsing: dollar expression");
405    let contents = working_set.get_span_contents(span);
406
407    if contents.starts_with(b"$\"") || contents.starts_with(b"$'") {
408        if matches!(shape, SyntaxShape::GlobPattern) && is_bare_string_interpolation(contents) {
409            parse_glob_pattern(working_set, span)
410        } else {
411            parse_string_interpolation(working_set, span)
412        }
413    } else if contents.starts_with(b"$.") {
414        parse_simple_cell_path(working_set, Span::new(span.start + 2, span.end))
415    } else {
416        let starting_error_count = working_set.parse_errors.len();
417
418        if let Some(expr) = parse_range(working_set, span) {
419            expr
420        } else {
421            working_set.parse_errors.truncate(starting_error_count);
422            parse_full_cell_path(working_set, None, span, input_type)
423        }
424    }
425}
426
427pub fn parse_raw_string(working_set: &mut StateWorkingSet, span: Span) -> Expression {
428    trace!("parsing: raw-string, with required delimiters");
429
430    let bytes = working_set.get_span_contents(span);
431
432    let prefix_sharp_cnt = if bytes.starts_with(b"r#") {
433        // actually `sharp_cnt` is always `index - 1`
434        // but create a variable here to make it clearer.
435        let mut sharp_cnt = 1;
436        let mut index = 2;
437        while index < bytes.len() && bytes[index] == b'#' {
438            index += 1;
439            sharp_cnt += 1;
440        }
441        sharp_cnt
442    } else {
443        working_set.error(ParseError::Expected("r#", span));
444        return garbage(working_set, span);
445    };
446    let expect_postfix_sharp_cnt = prefix_sharp_cnt;
447    // check the length of whole raw string.
448    // the whole raw string should contains at least
449    // 1(r) + prefix_sharp_cnt + 1(') + 1(') + postfix_sharp characters
450    if bytes.len() < prefix_sharp_cnt + expect_postfix_sharp_cnt + 3 {
451        working_set.error(ParseError::Unclosed("'", span));
452        return garbage(working_set, span);
453    }
454
455    // check for unbalanced # and single quotes.
456    let postfix_bytes = &bytes[bytes.len() - expect_postfix_sharp_cnt..bytes.len()];
457    if postfix_bytes.iter().any(|b| *b != b'#') {
458        working_set.error(ParseError::Unbalanced("prefix #", "postfix #", span));
459        return garbage(working_set, span);
460    }
461    // check for unblanaced single quotes.
462    if bytes[1 + prefix_sharp_cnt] != b'\''
463        || bytes[bytes.len() - expect_postfix_sharp_cnt - 1] != b'\''
464    {
465        working_set.error(ParseError::Unclosed("'", span));
466        return garbage(working_set, span);
467    }
468
469    let bytes = &bytes[prefix_sharp_cnt + 1 + 1..bytes.len() - 1 - prefix_sharp_cnt];
470    if let Ok(token) = String::from_utf8(bytes.into()) {
471        Expression::new(working_set, Expr::RawString(token), span, Type::String)
472    } else {
473        working_set.error(ParseError::Expected("utf8 raw-string", span));
474        garbage(working_set, span)
475    }
476}
477
478pub fn parse_paren_expr(
479    working_set: &mut StateWorkingSet,
480    span: Span,
481    shape: &SyntaxShape,
482) -> Expression {
483    let starting_error_count = working_set.parse_errors.len();
484
485    if let Some(expr) = parse_range(working_set, span) {
486        return expr;
487    }
488
489    working_set.parse_errors.truncate(starting_error_count);
490
491    if let SyntaxShape::Signature = shape {
492        return parse_signature(working_set, span, false);
493    }
494
495    if let SyntaxShape::ExternalSignature = shape {
496        return parse_signature(working_set, span, true);
497    }
498
499    let fcp_expr = parse_full_cell_path(working_set, None, span, None);
500    let fcp_error_count = working_set.parse_errors.len();
501    if fcp_error_count > starting_error_count {
502        let malformed_subexpr = working_set.parse_errors[starting_error_count..]
503            .first()
504            .is_some_and(|e| match e {
505                ParseError::Unclosed(right, _) if (*right == ")") => true,
506                ParseError::Unbalanced(left, right, _) if *left == "(" && *right == ")" => true,
507                _ => false,
508            });
509        if malformed_subexpr {
510            working_set.parse_errors.truncate(starting_error_count);
511            if matches!(shape, SyntaxShape::GlobPattern) {
512                parse_glob_pattern(working_set, span)
513            } else {
514                parse_string_interpolation(working_set, span)
515            }
516        } else {
517            fcp_expr
518        }
519    } else {
520        fcp_expr
521    }
522}
523
524pub fn parse_brace_expr(
525    working_set: &mut StateWorkingSet,
526    span: Span,
527    shape: &SyntaxShape,
528    input_type: Option<&Type>,
529) -> Expression {
530    // Try to detect what kind of value we're about to parse
531    // FIXME: In the future, we should work over the token stream so we only have to do this once
532    // before parsing begins
533
534    // FIXME: we're still using the shape because we rely on it to know how to handle syntax where
535    // the parse is ambiguous. We'll need to update the parts of the grammar where this is ambiguous
536    // and then revisit the parsing.
537
538    if span.end <= (span.start + 1) {
539        working_set.error(ParseError::ExpectedWithStringMsg(
540            format!("non-block value: {shape}"),
541            span,
542        ));
543        return Expression::garbage(working_set, span);
544    }
545    let bytes = working_set.get_span_contents(Span::new(span.start + 1, span.end - 1));
546    let (tokens, _) = lex(bytes, span.start + 1, &[b'\r', b'\n', b'\t'], &[b':'], true);
547
548    match tokens.as_slice() {
549        // If we're empty, that means an empty record or closure
550        [] => match shape {
551            SyntaxShape::Closure(_) => parse_closure_expression(working_set, shape, span, None),
552            SyntaxShape::Block => parse_block_expression(working_set, span, input_type),
553            SyntaxShape::MatchBlock => parse_match_block_expression(working_set, span, input_type),
554            _ => parse_record(working_set, span),
555        },
556        [
557            Token {
558                contents: TokenContents::Pipe | TokenContents::PipePipe,
559                ..
560            },
561            ..,
562        ] => {
563            if let SyntaxShape::Block = shape {
564                working_set.error(ParseError::Mismatch("block".into(), "closure".into(), span));
565                return Expression::garbage(working_set, span);
566            }
567            parse_closure_expression(working_set, shape, span, None)
568        }
569        [_, third, ..] if working_set.get_span_contents(third.span) == b":" => {
570            parse_full_cell_path(working_set, None, span, None)
571        }
572        [second, ..] => {
573            let second_bytes = working_set.get_span_contents(second.span);
574            match shape {
575                SyntaxShape::Closure(_) => parse_closure_expression(working_set, shape, span, None),
576                SyntaxShape::Block => parse_block_expression(working_set, span, input_type),
577                SyntaxShape::MatchBlock => {
578                    parse_match_block_expression(working_set, span, input_type)
579                }
580                // For edge case of `{}.foo?`, #17896
581                _ if second_bytes == b"}" => parse_full_cell_path(working_set, None, span, None),
582                _ if extract_spread_record(second_bytes.into_spanned(second.span)).is_some() => {
583                    parse_record(working_set, span)
584                }
585                SyntaxShape::Any => parse_closure_expression(working_set, shape, span, None),
586                _ => {
587                    working_set.error(ParseError::ExpectedWithStringMsg(
588                        format!("non-block value: {shape}"),
589                        span,
590                    ));
591
592                    Expression::garbage(working_set, span)
593                }
594            }
595        }
596    }
597}
598
599pub fn parse_string_interpolation(working_set: &mut StateWorkingSet, span: Span) -> Expression {
600    #[derive(PartialEq, Eq, Debug)]
601    enum InterpolationMode {
602        String,
603        Expression,
604    }
605
606    let contents = working_set.get_span_contents(span);
607
608    let mut double_quote = false;
609
610    let (start, end) = if contents.starts_with(b"$\"") {
611        double_quote = true;
612
613        if let Err(err) = check_string_no_trailing_tokens(contents, span, 1, b'\"') {
614            working_set.error(err);
615            return garbage(working_set, span);
616        }
617
618        let end = if contents.ends_with(b"\"") && contents.len() > 2 {
619            span.end - 1
620        } else {
621            span.end
622        };
623        (span.start + 2, end)
624    } else if contents.starts_with(b"$'") {
625        if let Err(err) = check_string_no_trailing_tokens(contents, span, 1, b'\'') {
626            working_set.error(err);
627            return garbage(working_set, span);
628        }
629
630        let end = if contents.ends_with(b"'") && contents.len() > 2 {
631            span.end - 1
632        } else {
633            span.end
634        };
635        (span.start + 2, end)
636    } else {
637        (span.start, span.end)
638    };
639
640    let inner_span = Span::new(start, end);
641    let contents = working_set.get_span_contents(inner_span).to_vec();
642
643    let mut output = vec![];
644    let mut mode = InterpolationMode::String;
645    let mut token_start = start;
646
647    #[repr(u8)]
648    #[derive(Clone, Copy, PartialEq, Eq)]
649    enum Delimiter {
650        SingleQuote = b'\'',
651        DoubleQuote = b'"',
652        Backtick = b'`',
653        ParenLeft = b'(',
654        ParenRight = b')',
655    }
656
657    impl Delimiter {
658        const fn from_u8(b: u8) -> Option<Self> {
659            Some(match b {
660                b'\'' => Self::SingleQuote,
661                b'"' => Self::DoubleQuote,
662                b'`' => Self::Backtick,
663                b'(' => Self::ParenLeft,
664                b')' => Self::ParenRight,
665                _ => return None,
666            })
667        }
668        const fn is_paren(self) -> bool {
669            matches!(self, Self::ParenLeft | Self::ParenRight)
670        }
671        const fn pair(self) -> Self {
672            match self {
673                Self::ParenLeft => Self::ParenRight,
674                Self::ParenRight => Self::ParenLeft,
675                _ => self,
676            }
677        }
678    }
679    let mut delimiter_stack: Vec<Delimiter> = vec![];
680
681    let mut consecutive_backslashes: usize = 0;
682
683    let mut b = start;
684
685    while b != end {
686        let current_byte = contents[b - start];
687
688        if mode == InterpolationMode::String {
689            let preceding_consecutive_backslashes = consecutive_backslashes;
690
691            let is_backslash = current_byte == b'\\';
692            consecutive_backslashes = if is_backslash {
693                preceding_consecutive_backslashes + 1
694            } else {
695                0
696            };
697
698            if current_byte == b'('
699                && (!double_quote || preceding_consecutive_backslashes.is_multiple_of(2))
700            {
701                mode = InterpolationMode::Expression;
702                if token_start < b {
703                    let span = Span::new(token_start, b);
704                    let str_contents = working_set.get_span_contents(span);
705
706                    let (str_contents, err) = if double_quote {
707                        unescape_string(str_contents, span)
708                    } else {
709                        (str_contents.to_vec(), None)
710                    };
711                    if let Some(err) = err {
712                        working_set.error(err);
713                    }
714
715                    output.push(Expression::new(
716                        working_set,
717                        Expr::String(String::from_utf8_lossy(&str_contents).to_string()),
718                        span,
719                        Type::String,
720                    ));
721                    token_start = b;
722                }
723            }
724        }
725
726        if mode == InterpolationMode::Expression {
727            let byte = Delimiter::from_u8(current_byte);
728            match (delimiter_stack.last().copied(), byte) {
729                (Some(d), Some(byte)) if !d.is_paren() => {
730                    if byte == d {
731                        delimiter_stack.pop();
732                    }
733                }
734                (_, Some(byte)) if byte != Delimiter::ParenRight => {
735                    delimiter_stack.push(byte.pair())
736                }
737                (d, Some(Delimiter::ParenRight)) => {
738                    if let Some(Delimiter::ParenRight) = d {
739                        delimiter_stack.pop();
740                    }
741                    if delimiter_stack.is_empty() {
742                        mode = InterpolationMode::String;
743
744                        if token_start < b {
745                            let span = Span::new(token_start, b + 1);
746
747                            let expr = parse_full_cell_path(working_set, None, span, None);
748                            output.push(expr);
749                        }
750
751                        token_start = b + 1;
752                        continue;
753                    }
754                }
755                _ => (),
756            }
757        }
758        b += 1;
759    }
760
761    match mode {
762        InterpolationMode::String => {
763            if token_start < end {
764                let span = Span::new(token_start, end);
765                let str_contents = working_set.get_span_contents(span);
766
767                let (str_contents, err) = if double_quote {
768                    unescape_string(str_contents, span)
769                } else {
770                    (str_contents.to_vec(), None)
771                };
772                if let Some(err) = err {
773                    working_set.error(err);
774                }
775
776                output.push(Expression::new(
777                    working_set,
778                    Expr::String(String::from_utf8_lossy(&str_contents).to_string()),
779                    span,
780                    Type::String,
781                ));
782            }
783        }
784        InterpolationMode::Expression => {
785            if token_start < end {
786                let span = Span::new(token_start, end);
787                let expr = parse_full_cell_path(working_set, None, span, None);
788                output.push(expr);
789            }
790        }
791    }
792
793    Expression::new(
794        working_set,
795        Expr::StringInterpolation(output),
796        span,
797        Type::String,
798    )
799}
800
801pub fn parse_variable_expr(
802    working_set: &mut StateWorkingSet,
803    span: Span,
804    input_type: Option<&Type>,
805) -> Expression {
806    let contents = working_set.get_span_contents(span);
807
808    if contents == b"$nu" {
809        return Expression::new(
810            working_set,
811            Expr::Var(nu_protocol::NU_VARIABLE_ID),
812            span,
813            Type::Any,
814        );
815    } else if contents == b"$in" {
816        return Expression::new(
817            working_set,
818            Expr::Var(nu_protocol::IN_VARIABLE_ID),
819            span,
820            input_type.cloned().unwrap_or(Type::Any),
821        );
822    } else if contents == b"$env" {
823        return Expression::new(
824            working_set,
825            Expr::Var(nu_protocol::ENV_VARIABLE_ID),
826            span,
827            Type::Any,
828        );
829    }
830
831    let name = if contents.starts_with(b"$") {
832        String::from_utf8_lossy(&contents[1..]).to_string()
833    } else {
834        String::from_utf8_lossy(contents).to_string()
835    };
836
837    let bytes = working_set.get_span_contents(span);
838    let suggestion = || {
839        DidYouMean::new(
840            &working_set.list_variables(),
841            working_set.get_span_contents(span),
842        )
843    };
844    if !is_variable(bytes) {
845        working_set.error(ParseError::ExpectedWithDidYouMean(
846            "valid variable name",
847            suggestion(),
848            span,
849        ));
850        garbage(working_set, span)
851    } else if let Some(id) = working_set.find_variable(bytes) {
852        Expression::new(
853            working_set,
854            Expr::Var(id),
855            span,
856            working_set.get_variable(id).ty.clone(),
857        )
858    } else if working_set.get_env_var(&name).is_some() {
859        working_set.error(ParseError::EnvVarNotVar(name, span));
860        garbage(working_set, span)
861    } else {
862        working_set.error(ParseError::VariableNotFound(suggestion(), span));
863        garbage(working_set, span)
864    }
865}
866
867pub fn parse_cell_path(
868    working_set: &mut StateWorkingSet,
869    tokens: impl Iterator<Item = Token>,
870    expect_dot: bool,
871) -> Vec<PathMember> {
872    enum TokenType {
873        Dot,              // .
874        DotOrSign,        // . or ? or !
875        DotOrExclamation, // . or !
876        DotOrQuestion,    // . or ?
877        PathMember,       // an int or string, like `1` or `foo`
878    }
879
880    enum ModifyMember {
881        No,
882        Optional,
883        Insensitive,
884    }
885
886    impl TokenType {
887        fn expect(&mut self, byte: u8) -> Result<ModifyMember, &'static str> {
888            match (&*self, byte) {
889                (Self::PathMember, _) => {
890                    *self = Self::DotOrSign;
891                    Ok(ModifyMember::No)
892                }
893                (
894                    Self::Dot | Self::DotOrSign | Self::DotOrExclamation | Self::DotOrQuestion,
895                    b'.',
896                ) => {
897                    *self = Self::PathMember;
898                    Ok(ModifyMember::No)
899                }
900                (Self::DotOrSign, b'!') => {
901                    *self = Self::DotOrQuestion;
902                    Ok(ModifyMember::Insensitive)
903                }
904                (Self::DotOrSign, b'?') => {
905                    *self = Self::DotOrExclamation;
906                    Ok(ModifyMember::Optional)
907                }
908                (Self::DotOrSign, _) => Err(". or ! or ?"),
909                (Self::DotOrExclamation, b'!') => {
910                    *self = Self::Dot;
911                    Ok(ModifyMember::Insensitive)
912                }
913                (Self::DotOrExclamation, _) => Err(". or !"),
914                (Self::DotOrQuestion, b'?') => {
915                    *self = Self::Dot;
916                    Ok(ModifyMember::Optional)
917                }
918                (Self::DotOrQuestion, _) => Err(". or ?"),
919                (Self::Dot, _) => Err("."),
920            }
921        }
922    }
923
924    // Parsing a cell path is essentially a state machine, and this is the state
925    let mut expected_token = if expect_dot {
926        TokenType::Dot
927    } else {
928        TokenType::PathMember
929    };
930
931    let mut tail = vec![];
932
933    for path_element in tokens {
934        let bytes = working_set.get_span_contents(path_element.span);
935
936        // both parse_int and parse_string require their source to be non-empty
937        // all cases where `bytes` is empty is an error
938        let Some((&first, rest)) = bytes.split_first() else {
939            working_set.error(ParseError::Expected("string", path_element.span));
940            return tail;
941        };
942        let single_char = rest.is_empty();
943
944        if let TokenType::PathMember = expected_token {
945            let starting_error_count = working_set.parse_errors.len();
946
947            let expr = parse_int(working_set, path_element.span);
948            working_set.parse_errors.truncate(starting_error_count);
949
950            match expr {
951                Expression {
952                    expr: Expr::Int(val),
953                    span,
954                    ..
955                } => {
956                    if val < 0 {
957                        working_set.error(ParseError::InvalidLiteral(
958                            "negative index is not supported".into(),
959                            "cell path".into(),
960                            span,
961                        ));
962                        return tail;
963                    }
964                    tail.push(PathMember::Int {
965                        val: val as usize,
966                        span,
967                        optional: false,
968                    })
969                }
970                _ => {
971                    let result = parse_string(working_set, path_element.span);
972                    match result {
973                        Expression {
974                            expr: Expr::String(string),
975                            span,
976                            ..
977                        } => {
978                            tail.push(PathMember::String {
979                                val: string,
980                                span,
981                                optional: false,
982                                casing: Casing::Sensitive,
983                            });
984                        }
985                        _ => {
986                            working_set.error(ParseError::Expected("string", path_element.span));
987                            return tail;
988                        }
989                    }
990                }
991            }
992            expected_token = TokenType::DotOrSign;
993        } else {
994            match expected_token.expect(if single_char { first } else { b' ' }) {
995                Ok(modify) => {
996                    if let Some(last) = tail.last_mut() {
997                        match modify {
998                            ModifyMember::No => {}
999                            ModifyMember::Optional => last.make_optional(),
1000                            ModifyMember::Insensitive => last.make_insensitive(),
1001                        }
1002                    };
1003                }
1004                Err(expected) => {
1005                    working_set.error(ParseError::Expected(expected, path_element.span));
1006                    return tail;
1007                }
1008            }
1009        }
1010    }
1011
1012    tail
1013}
1014
1015pub fn parse_simple_cell_path(working_set: &mut StateWorkingSet, span: Span) -> Expression {
1016    let source = working_set.get_span_contents(span);
1017
1018    let (tokens, err) = lex(
1019        source,
1020        span.start,
1021        &[b'\n', b'\r'],
1022        &[b'.', b'?', b'!'],
1023        true,
1024    );
1025    if let Some(err) = err {
1026        working_set.error(err)
1027    }
1028
1029    let tokens = tokens.into_iter().peekable();
1030
1031    let cell_path = parse_cell_path(working_set, tokens, false);
1032
1033    Expression::new(
1034        working_set,
1035        Expr::CellPath(CellPath { members: cell_path }),
1036        span,
1037        Type::CellPath,
1038    )
1039}
1040
1041pub fn parse_full_cell_path(
1042    working_set: &mut StateWorkingSet,
1043    implicit_head: Option<VarId>,
1044    span: Span,
1045    input_type: Option<&Type>,
1046) -> Expression {
1047    trace!("parsing: full cell path");
1048    let full_cell_span = span;
1049    let source = working_set.get_span_contents(span);
1050
1051    let (tokens, err) = lex(
1052        source,
1053        span.start,
1054        &[b'\n', b'\r'],
1055        &[b'.', b'?', b'!'],
1056        true,
1057    );
1058    if let Some(err) = err {
1059        working_set.error(err)
1060    }
1061
1062    let mut tokens = tokens.into_iter().peekable();
1063    if let Some(head) = tokens.peek() {
1064        let bytes = working_set.get_span_contents(head.span);
1065        let (head, expect_dot) = if bytes.starts_with(b"(") {
1066            trace!("parsing: paren-head of full cell path");
1067
1068            let head_span = head.span;
1069            let mut start = head.span.start;
1070            let mut end = head.span.end;
1071            let mut is_closed = true;
1072
1073            if bytes.starts_with(b"(") {
1074                start += 1;
1075            }
1076            if bytes.ends_with(b")") {
1077                end -= 1;
1078            } else {
1079                working_set.error(ParseError::Unclosed(")", Span::new(end, end)));
1080                is_closed = false;
1081            }
1082
1083            let span = Span::new(start, end);
1084
1085            let source = working_set.get_span_contents(span);
1086
1087            let (output, err) = lex(source, span.start, &[b'\n', b'\r'], &[], true);
1088            if let Some(err) = err {
1089                working_set.error(err)
1090            }
1091
1092            // Creating a Type scope to parse the new block. This will keep track of
1093            // the previous input type found in that block
1094            let output = parse_block(working_set, &output, span, is_closed, true, None);
1095
1096            let ty = output.output_type();
1097
1098            let block_id = working_set.add_block(Arc::new(output));
1099            tokens.next();
1100
1101            (
1102                Expression::new(working_set, Expr::Subexpression(block_id), head_span, ty),
1103                true,
1104            )
1105        } else if bytes.starts_with(b"[") {
1106            trace!("parsing: table head of full cell path");
1107
1108            let output = parse_table_expression(working_set, head.span, &SyntaxShape::Any);
1109
1110            tokens.next();
1111
1112            (output, true)
1113        } else if bytes.starts_with(b"{") {
1114            trace!("parsing: record head of full cell path");
1115            let output = parse_record(working_set, head.span);
1116
1117            tokens.next();
1118
1119            (output, true)
1120        } else if bytes.starts_with(b"$") {
1121            trace!("parsing: $variable head of full cell path");
1122
1123            let out = parse_variable_expr(working_set, head.span, input_type);
1124
1125            tokens.next();
1126
1127            (out, true)
1128        } else if let Some(var_id) = implicit_head {
1129            trace!("parsing: implicit head of full cell path");
1130            (
1131                Expression::new(working_set, Expr::Var(var_id), head.span, Type::Any),
1132                false,
1133            )
1134        } else {
1135            working_set.error(ParseError::Mismatch(
1136                "variable or subexpression".into(),
1137                String::from_utf8_lossy(bytes).to_string(),
1138                span,
1139            ));
1140            return garbage(working_set, span);
1141        };
1142
1143        let tail = parse_cell_path(working_set, tokens, expect_dot);
1144        let ty = if !tail.is_empty() {
1145            if nu_experimental::CELL_PATH_TYPES.get() {
1146                head.ty
1147                    .follow_cell_path(&tail)
1148                    .map(|ty| ty.into_owned())
1149                    .unwrap_or(Type::Any)
1150            } else {
1151                Type::Any
1152            }
1153        } else {
1154            head.ty.clone()
1155        };
1156
1157        Expression::new(
1158            working_set,
1159            Expr::FullCellPath(Box::new(FullCellPath { head, tail })),
1160            full_cell_span,
1161            ty,
1162        )
1163    } else {
1164        garbage(working_set, span)
1165    }
1166}
1167
1168enum PathLikeKind {
1169    Directory,
1170    Filepath,
1171    Glob,
1172}
1173
1174impl PathLikeKind {
1175    /// Returns the name used for trace logging during parsing.
1176    fn trace_name(&self) -> &'static str {
1177        match self {
1178            PathLikeKind::Directory => "directory",
1179            PathLikeKind::Filepath => "filepath",
1180            PathLikeKind::Glob => "glob pattern",
1181        }
1182    }
1183
1184    /// Returns the error message displayed when parsing fails.
1185    fn error_msg(&self) -> &'static str {
1186        match self {
1187            PathLikeKind::Directory => "directory",
1188            PathLikeKind::Filepath => "filepath",
1189            PathLikeKind::Glob => "glob pattern string",
1190        }
1191    }
1192
1193    /// Constructs the appropriate `Expr` and its corresponding `Type` for a simple (non-interpolated) path.
1194    fn to_expr(&self, token: String, quoted: bool) -> (Expr, Type) {
1195        match self {
1196            PathLikeKind::Directory => (Expr::Directory(token, quoted), Type::String),
1197            PathLikeKind::Filepath => (Expr::Filepath(token, quoted), Type::String),
1198            PathLikeKind::Glob => (Expr::GlobPattern(token, quoted), Type::Glob),
1199        }
1200    }
1201
1202    /// Constructs the appropriate interpolation `Expr` for a path containing subexpressions.
1203    fn to_interpolation_expr(&self, exprs: Vec<Expression>, quoted: bool) -> Expr {
1204        match self {
1205            PathLikeKind::Directory | PathLikeKind::Filepath => Expr::StringInterpolation(exprs),
1206            PathLikeKind::Glob => Expr::GlobInterpolation(exprs, quoted),
1207        }
1208    }
1209}
1210
1211/// Common helper for parsing path-like expressions (filepath, directory, glob pattern).
1212///
1213/// This function consolidates the repetitive logic for parsing path types, including:
1214/// - Bare word interpolation detection
1215/// - Escape sequence processing
1216/// - Quote state tracking
1217/// - Error handling
1218///
1219/// # Arguments
1220///
1221/// * `working_set` - The current parser state
1222/// * `span` - The source span of the expression
1223/// * `kind` - The kind of path-like expression to parse
1224fn parse_path_like(
1225    working_set: &mut StateWorkingSet,
1226    span: Span,
1227    kind: PathLikeKind,
1228) -> Expression {
1229    let bytes = working_set.get_span_contents(span);
1230    let quoted = is_quoted(bytes);
1231    trace!("parsing: {}", kind.trace_name());
1232
1233    // Check for bare word interpolation
1234    if is_bare_string_interpolation(bytes) {
1235        let interpolation_expr = parse_string_interpolation(working_set, span);
1236
1237        // Convert StringInterpolation to the appropriate interpolation type
1238        if let Expr::StringInterpolation(exprs) = interpolation_expr.expr {
1239            return Expression::new(
1240                working_set,
1241                kind.to_interpolation_expr(exprs, quoted),
1242                span,
1243                interpolation_expr.ty.clone(),
1244            );
1245        }
1246
1247        return interpolation_expr;
1248    }
1249
1250    let (token, err) = unescape_unquote_string(bytes, span);
1251    let is_quoted_internal = is_quoted(bytes);
1252
1253    if err.is_none() {
1254        trace!("-- found {token}");
1255
1256        let (expr, ty) = kind.to_expr(token, is_quoted_internal);
1257
1258        Expression::new(working_set, expr, span, ty)
1259    } else {
1260        working_set.error(ParseError::Expected(kind.error_msg(), span));
1261
1262        garbage(working_set, span)
1263    }
1264}
1265
1266fn is_bare_string_interpolation(bytes: &[u8]) -> bool {
1267    match bytes {
1268        [] => false,
1269        [b'\'' | b'"' | b'`', ..] => false,
1270        _ => bytes.contains(&b'('),
1271    }
1272}
1273
1274pub fn parse_directory(working_set: &mut StateWorkingSet, span: Span) -> Expression {
1275    parse_path_like(working_set, span, PathLikeKind::Directory)
1276}
1277
1278pub fn parse_filepath(working_set: &mut StateWorkingSet, span: Span) -> Expression {
1279    parse_path_like(working_set, span, PathLikeKind::Filepath)
1280}
1281
1282pub fn parse_datetime(working_set: &mut StateWorkingSet, span: Span) -> Expression {
1283    trace!("parsing: datetime");
1284
1285    let bytes = working_set.get_span_contents(span);
1286
1287    if bytes.len() < 6
1288        || !bytes[0].is_ascii_digit()
1289        || !bytes[1].is_ascii_digit()
1290        || !bytes[2].is_ascii_digit()
1291        || !bytes[3].is_ascii_digit()
1292        || bytes[4] != b'-'
1293    {
1294        working_set.error(ParseError::Expected("datetime", span));
1295        return garbage(working_set, span);
1296    }
1297
1298    let token = String::from_utf8_lossy(bytes).to_string();
1299
1300    if let Ok(datetime) = chrono::DateTime::parse_from_rfc3339(&token) {
1301        return Expression::new(working_set, Expr::DateTime(datetime), span, Type::Date);
1302    }
1303
1304    // Just the date
1305    let just_date = token.clone() + "T00:00:00+00:00";
1306    if let Ok(datetime) = chrono::DateTime::parse_from_rfc3339(&just_date) {
1307        return Expression::new(working_set, Expr::DateTime(datetime), span, Type::Date);
1308    }
1309
1310    // Date and time, assume UTC
1311    let datetime = token + "+00:00";
1312    if let Ok(datetime) = chrono::DateTime::parse_from_rfc3339(&datetime) {
1313        return Expression::new(working_set, Expr::DateTime(datetime), span, Type::Date);
1314    }
1315
1316    working_set.error(ParseError::Expected("datetime", span));
1317
1318    garbage(working_set, span)
1319}
1320
1321/// Parse a duration type, eg '10day'
1322pub fn parse_duration(working_set: &mut StateWorkingSet, span: Span) -> Expression {
1323    trace!("parsing: duration");
1324
1325    let bytes = working_set.get_span_contents(span);
1326
1327    match parse_unit_value(bytes, span, DURATION_UNIT_GROUPS, Type::Duration, |x| x) {
1328        Some(Ok(expr)) => {
1329            let span_id = working_set.add_span(span);
1330            expr.with_span_id(span_id)
1331        }
1332        Some(Err(mk_err_for)) => {
1333            working_set.error(mk_err_for("duration"));
1334            garbage(working_set, span)
1335        }
1336        None => {
1337            working_set.error(ParseError::Expected("duration with valid units", span));
1338            garbage(working_set, span)
1339        }
1340    }
1341}
1342
1343/// Parse a unit type, eg '10kb'
1344pub fn parse_filesize(working_set: &mut StateWorkingSet, span: Span) -> Expression {
1345    trace!("parsing: filesize");
1346
1347    let bytes = working_set.get_span_contents(span);
1348
1349    // the hex digit `b` might be mistaken for the unit `b`, so check that first
1350    if bytes.starts_with(b"0x") {
1351        working_set.error(ParseError::Expected("filesize with valid units", span));
1352        return garbage(working_set, span);
1353    }
1354
1355    match parse_unit_value(bytes, span, FILESIZE_UNIT_GROUPS, Type::Filesize, |x| {
1356        x.to_ascii_uppercase()
1357    }) {
1358        Some(Ok(expr)) => {
1359            let span_id = working_set.add_span(span);
1360            expr.with_span_id(span_id)
1361        }
1362        Some(Err(mk_err_for)) => {
1363            working_set.error(mk_err_for("filesize"));
1364            garbage(working_set, span)
1365        }
1366        None => {
1367            working_set.error(ParseError::Expected("filesize with valid units", span));
1368            garbage(working_set, span)
1369        }
1370    }
1371}
1372
1373type ParseUnitResult<'res> = Result<Expression, Box<dyn Fn(&'res str) -> ParseError>>;
1374type UnitGroup<'unit> = (Unit, &'unit str, Option<(Unit, i64)>);
1375
1376pub fn parse_unit_value<'res>(
1377    bytes: &[u8],
1378    span: Span,
1379    unit_groups: &[UnitGroup],
1380    ty: Type,
1381    transform: fn(String) -> String,
1382) -> Option<ParseUnitResult<'res>> {
1383    if bytes.len() < 2
1384        || !(bytes[0].is_ascii_digit()
1385            || (bytes[0] == b'.' && bytes[1].is_ascii_digit())
1386            || (bytes[0] == b'-' && bytes[1].is_ascii_digit()))
1387    {
1388        return None;
1389    }
1390
1391    // Bail if not UTF-8
1392    let value = transform(str::from_utf8(bytes).ok()?.into());
1393
1394    if let Some((unit, name, convert)) = unit_groups.iter().find(|x| value.ends_with(x.1)) {
1395        let lhs_len = value.len() - name.len();
1396        let lhs = strip_underscores(&value.as_bytes()[..lhs_len]);
1397        let lhs_span = Span::new(span.start, span.start + lhs_len);
1398        let unit_span = Span::new(span.start + lhs_len, span.end);
1399        if lhs.ends_with('$') {
1400            // If `parse_unit_value` has higher precedence over `parse_range`,
1401            // a variable with the name of a unit could otherwise not be used as the end of a range.
1402            return None;
1403        }
1404
1405        let (decimal_part, number_part) = modf(match lhs.parse::<f64>() {
1406            Ok(it) => it,
1407            Err(_) => {
1408                let mk_err = move |name| {
1409                    ParseError::LabeledError(
1410                        format!("{name} value must be a number"),
1411                        "not a number".into(),
1412                        lhs_span,
1413                    )
1414                };
1415                return Some(Err(Box::new(mk_err)));
1416            }
1417        });
1418
1419        let mut unit = match convert {
1420            Some(convert_to) => convert_to.0,
1421            None => *unit,
1422        };
1423
1424        let num_float = match convert {
1425            Some(convert_to) => {
1426                (number_part * convert_to.1 as f64) + (decimal_part * convert_to.1 as f64)
1427            }
1428            None => number_part,
1429        };
1430
1431        // Convert all durations to nanoseconds, and filesizes to bytes,
1432        // to minimize loss of precision
1433        let factor = match ty {
1434            Type::Filesize => unit_to_byte_factor(&unit),
1435            Type::Duration => unit_to_ns_factor(&unit),
1436            _ => None,
1437        };
1438
1439        let num = match factor {
1440            Some(factor) => {
1441                let num_base = num_float * factor;
1442                if i64::MIN as f64 <= num_base && num_base <= i64::MAX as f64 {
1443                    unit = if ty == Type::Filesize {
1444                        Unit::Filesize(FilesizeUnit::B)
1445                    } else {
1446                        Unit::Nanosecond
1447                    };
1448                    num_base as i64
1449                } else {
1450                    // not safe to convert, because of the overflow
1451                    num_float as i64
1452                }
1453            }
1454            None => num_float as i64,
1455        };
1456
1457        trace!("-- found {num} {unit:?}");
1458        let value = ValueWithUnit {
1459            expr: Expression::new_unknown(Expr::Int(num), lhs_span, Type::Number),
1460            unit: Spanned {
1461                item: unit,
1462                span: unit_span,
1463            },
1464        };
1465        let expr = Expression::new_unknown(Expr::ValueWithUnit(Box::new(value)), span, ty);
1466
1467        Some(Ok(expr))
1468    } else {
1469        None
1470    }
1471}
1472
1473pub const FILESIZE_UNIT_GROUPS: &[UnitGroup] = &[
1474    (
1475        Unit::Filesize(FilesizeUnit::KB),
1476        "KB",
1477        Some((Unit::Filesize(FilesizeUnit::B), 1000)),
1478    ),
1479    (
1480        Unit::Filesize(FilesizeUnit::MB),
1481        "MB",
1482        Some((Unit::Filesize(FilesizeUnit::KB), 1000)),
1483    ),
1484    (
1485        Unit::Filesize(FilesizeUnit::GB),
1486        "GB",
1487        Some((Unit::Filesize(FilesizeUnit::MB), 1000)),
1488    ),
1489    (
1490        Unit::Filesize(FilesizeUnit::TB),
1491        "TB",
1492        Some((Unit::Filesize(FilesizeUnit::GB), 1000)),
1493    ),
1494    (
1495        Unit::Filesize(FilesizeUnit::PB),
1496        "PB",
1497        Some((Unit::Filesize(FilesizeUnit::TB), 1000)),
1498    ),
1499    (
1500        Unit::Filesize(FilesizeUnit::EB),
1501        "EB",
1502        Some((Unit::Filesize(FilesizeUnit::PB), 1000)),
1503    ),
1504    (
1505        Unit::Filesize(FilesizeUnit::KiB),
1506        "KIB",
1507        Some((Unit::Filesize(FilesizeUnit::B), 1024)),
1508    ),
1509    (
1510        Unit::Filesize(FilesizeUnit::MiB),
1511        "MIB",
1512        Some((Unit::Filesize(FilesizeUnit::KiB), 1024)),
1513    ),
1514    (
1515        Unit::Filesize(FilesizeUnit::GiB),
1516        "GIB",
1517        Some((Unit::Filesize(FilesizeUnit::MiB), 1024)),
1518    ),
1519    (
1520        Unit::Filesize(FilesizeUnit::TiB),
1521        "TIB",
1522        Some((Unit::Filesize(FilesizeUnit::GiB), 1024)),
1523    ),
1524    (
1525        Unit::Filesize(FilesizeUnit::PiB),
1526        "PIB",
1527        Some((Unit::Filesize(FilesizeUnit::TiB), 1024)),
1528    ),
1529    (
1530        Unit::Filesize(FilesizeUnit::EiB),
1531        "EIB",
1532        Some((Unit::Filesize(FilesizeUnit::PiB), 1024)),
1533    ),
1534    (Unit::Filesize(FilesizeUnit::B), "B", None),
1535];
1536
1537pub const DURATION_UNIT_GROUPS: &[UnitGroup] = &[
1538    (Unit::Nanosecond, "ns", None),
1539    // todo start adding aliases for duration units here
1540    (Unit::Microsecond, "us", Some((Unit::Nanosecond, 1000))),
1541    (
1542        // µ Micro Sign
1543        Unit::Microsecond,
1544        "\u{00B5}s",
1545        Some((Unit::Nanosecond, 1000)),
1546    ),
1547    (
1548        // μ Greek small letter Mu
1549        Unit::Microsecond,
1550        "\u{03BC}s",
1551        Some((Unit::Nanosecond, 1000)),
1552    ),
1553    (Unit::Millisecond, "ms", Some((Unit::Microsecond, 1000))),
1554    (Unit::Second, "sec", Some((Unit::Millisecond, 1000))),
1555    (Unit::Minute, "min", Some((Unit::Second, 60))),
1556    (Unit::Hour, "hr", Some((Unit::Minute, 60))),
1557    (Unit::Day, "day", Some((Unit::Minute, 1440))),
1558    (Unit::Week, "wk", Some((Unit::Day, 7))),
1559];
1560
1561fn unit_to_ns_factor(unit: &Unit) -> Option<f64> {
1562    match unit {
1563        Unit::Nanosecond => Some(1.0),
1564        Unit::Microsecond => Some(1_000.0),
1565        Unit::Millisecond => Some(1_000_000.0),
1566        Unit::Second => Some(1_000_000_000.0),
1567        Unit::Minute => Some(60.0 * 1_000_000_000.0),
1568        Unit::Hour => Some(60.0 * 60.0 * 1_000_000_000.0),
1569        Unit::Day => Some(24.0 * 60.0 * 60.0 * 1_000_000_000.0),
1570        Unit::Week => Some(7.0 * 24.0 * 60.0 * 60.0 * 1_000_000_000.0),
1571        _ => None,
1572    }
1573}
1574
1575fn unit_to_byte_factor(unit: &Unit) -> Option<f64> {
1576    match unit {
1577        Unit::Filesize(FilesizeUnit::B) => Some(1.0),
1578        Unit::Filesize(FilesizeUnit::KB) => Some(1_000.0),
1579        Unit::Filesize(FilesizeUnit::MB) => Some(1_000_000.0),
1580        Unit::Filesize(FilesizeUnit::GB) => Some(1_000_000_000.0),
1581        Unit::Filesize(FilesizeUnit::TB) => Some(1_000_000_000_000.0),
1582        Unit::Filesize(FilesizeUnit::PB) => Some(1_000_000_000_000_000.0),
1583        Unit::Filesize(FilesizeUnit::EB) => Some(1_000_000_000_000_000_000.0),
1584        Unit::Filesize(FilesizeUnit::KiB) => Some(1024.0),
1585        Unit::Filesize(FilesizeUnit::MiB) => Some(1024.0 * 1024.0),
1586        Unit::Filesize(FilesizeUnit::GiB) => Some(1024.0 * 1024.0 * 1024.0),
1587        Unit::Filesize(FilesizeUnit::TiB) => Some(1024.0 * 1024.0 * 1024.0 * 1024.0),
1588        Unit::Filesize(FilesizeUnit::PiB) => Some(1024.0 * 1024.0 * 1024.0 * 1024.0 * 1024.0),
1589        Unit::Filesize(FilesizeUnit::EiB) => {
1590            Some(1024.0 * 1024.0 * 1024.0 * 1024.0 * 1024.0 * 1024.0)
1591        }
1592        _ => None,
1593    }
1594}
1595
1596// Borrowed from libm at https://github.com/rust-lang/libm/blob/master/src/math/modf.rs
1597fn modf(x: f64) -> (f64, f64) {
1598    let rv2: f64;
1599    let mut u = x.to_bits();
1600    let e = (((u >> 52) & 0x7ff) as i32) - 0x3ff;
1601
1602    /* no fractional part */
1603    if e >= 52 {
1604        rv2 = x;
1605        if e == 0x400 && (u << 12) != 0 {
1606            /* nan */
1607            return (x, rv2);
1608        }
1609        u &= 1 << 63;
1610        return (f64::from_bits(u), rv2);
1611    }
1612
1613    /* no integral part*/
1614    if e < 0 {
1615        u &= 1 << 63;
1616        rv2 = f64::from_bits(u);
1617        return (x, rv2);
1618    }
1619
1620    let mask = ((!0) >> 12) >> e;
1621    if (u & mask) == 0 {
1622        rv2 = x;
1623        u &= 1 << 63;
1624        return (f64::from_bits(u), rv2);
1625    }
1626    u &= !mask;
1627    rv2 = f64::from_bits(u);
1628    (x - rv2, rv2)
1629}
1630
1631pub fn parse_glob_pattern(working_set: &mut StateWorkingSet, span: Span) -> Expression {
1632    parse_path_like(working_set, span, PathLikeKind::Glob)
1633}
1634
1635fn parse_hex_escape(bytes: &[u8], start_idx: usize, span: Span) -> Result<(u8, usize), ParseError> {
1636    let hex_digits = bytes.get(start_idx + 1..start_idx + 3).ok_or_else(|| {
1637        ParseError::InvalidLiteral(
1638            "incomplete hex escape '\\xHH', expected 2 hex digits".into(),
1639            "string".into(),
1640            Span::new(span.start + start_idx, span.end),
1641        )
1642    })?;
1643    if !hex_digits.iter().all(u8::is_ascii_hexdigit) {
1644        return Err(ParseError::InvalidLiteral(
1645            "invalid hex escape '\\xHH', expected exactly 2 hex digits".into(),
1646            "string".into(),
1647            Span::new(span.start + start_idx, span.end),
1648        ));
1649    }
1650    str::from_utf8(hex_digits)
1651        .ok()
1652        .and_then(|s| u8::from_str_radix(s, 0x10).ok())
1653        .map(|byte_val| (byte_val, start_idx + 3))
1654        .ok_or_else(|| {
1655            ParseError::InvalidLiteral(
1656                "invalid hex escape '\\xHH'".into(),
1657                "string".into(),
1658                Span::new(span.start + start_idx, span.end),
1659            )
1660        })
1661}
1662
1663fn parse_unicode_escape(
1664    bytes: &[u8],
1665    start_idx: usize,
1666    span: Span,
1667) -> Result<(char, usize), ParseError> {
1668    let mut slice = &bytes[(start_idx + 1)..];
1669    let mut current_idx = start_idx + 1;
1670
1671    // NOTE: this is a more defensive approach meant to avoid reading too much, but requires
1672    //       changing error messages
1673    // read no more than 8 bytes "{xxxxxx}"
1674    // slice = &slice[..(8.min(slice.len()))];
1675
1676    slice = slice.strip_prefix(b"{").ok_or_else(|| {
1677        ParseError::InvalidLiteral(
1678            "invalid unicode escape '\\u{...}', must be 1-6 hex digits, max codepoint 0x10FFFF"
1679                .into(),
1680            "string".into(),
1681            Span::new(span.start + start_idx, span.end),
1682        )
1683    })?;
1684    current_idx += 1;
1685
1686    let end = slice.iter().position(|b| *b == b'}').ok_or_else(|| {
1687        ParseError::InvalidLiteral(
1688            "incomplete unicode escape '\\u{...}', missing closing '}'".into(),
1689            "string".into(),
1690            Span::new(span.start + start_idx, span.end),
1691        )
1692    })?;
1693    let digits = &slice[..end];
1694    current_idx += end; // the digits
1695    current_idx += 1; // closing brace
1696    let current_idx = current_idx;
1697
1698    let ch = Some(digits)
1699        .filter(|b| (1..=6).contains(&b.len()))
1700        .and_then(|b| str::from_utf8(b).ok())
1701        .and_then(|s| u32::from_str_radix(s, 0x10).ok())
1702        .and_then(char::from_u32)
1703        .ok_or_else(|| {
1704            ParseError::InvalidLiteral(
1705                "invalid unicode escape '\\u{...}', must be 1-6 hex digits, max codepoint 0x10FFFF"
1706                    .into(),
1707                "string".into(),
1708                Span::new(span.start + start_idx, span.end),
1709            )
1710        })?;
1711
1712    Ok((ch, current_idx))
1713}
1714
1715pub fn unescape_string(bytes: &[u8], span: Span) -> (Vec<u8>, Option<ParseError>) {
1716    let mut output = Vec::new();
1717    let mut error = None;
1718
1719    let mut idx = 0;
1720
1721    if !bytes.contains(&b'\\') {
1722        return (bytes.to_vec(), None);
1723    }
1724
1725    'us_loop: while idx < bytes.len() {
1726        if bytes[idx] == b'\\' {
1727            // We're in an escape
1728            idx += 1;
1729
1730            match bytes.get(idx) {
1731                Some(b'"') => {
1732                    output.push(b'"');
1733                    idx += 1;
1734                }
1735                Some(b'\'') => {
1736                    output.push(b'\'');
1737                    idx += 1;
1738                }
1739                Some(b'\\') => {
1740                    output.push(b'\\');
1741                    idx += 1;
1742                }
1743                Some(b'/') => {
1744                    output.push(b'/');
1745                    idx += 1;
1746                }
1747                Some(b'(') => {
1748                    output.push(b'(');
1749                    idx += 1;
1750                }
1751                Some(b')') => {
1752                    output.push(b')');
1753                    idx += 1;
1754                }
1755                Some(b'{') => {
1756                    output.push(b'{');
1757                    idx += 1;
1758                }
1759                Some(b'}') => {
1760                    output.push(b'}');
1761                    idx += 1;
1762                }
1763                Some(b'$') => {
1764                    output.push(b'$');
1765                    idx += 1;
1766                }
1767                Some(b'^') => {
1768                    output.push(b'^');
1769                    idx += 1;
1770                }
1771                Some(b'#') => {
1772                    output.push(b'#');
1773                    idx += 1;
1774                }
1775                Some(b'|') => {
1776                    output.push(b'|');
1777                    idx += 1;
1778                }
1779                Some(b'~') => {
1780                    output.push(b'~');
1781                    idx += 1;
1782                }
1783                Some(b'a') => {
1784                    output.push(0x7);
1785                    idx += 1;
1786                }
1787                Some(b'b') => {
1788                    output.push(0x8);
1789                    idx += 1;
1790                }
1791                Some(b'e') => {
1792                    output.push(0x1b);
1793                    idx += 1;
1794                }
1795                Some(b'f') => {
1796                    output.push(0xc);
1797                    idx += 1;
1798                }
1799                Some(b'n') => {
1800                    output.push(b'\n');
1801                    idx += 1;
1802                }
1803                Some(b'r') => {
1804                    output.push(b'\r');
1805                    idx += 1;
1806                }
1807                Some(b't') => {
1808                    output.push(b'\t');
1809                    idx += 1;
1810                }
1811                Some(b'0') => {
1812                    output.push(b'\0');
1813                    idx += 1;
1814                }
1815                Some(b'x') => {
1816                    // Hex escape: \xHH (exactly 2 hex digits)
1817                    match parse_hex_escape(bytes, idx, span) {
1818                        Ok((byte_val, new_idx)) => {
1819                            output.push(byte_val);
1820                            idx = new_idx;
1821                        }
1822                        Err(err) => {
1823                            error = error.or(Some(err));
1824                            break 'us_loop;
1825                        }
1826                    }
1827                }
1828                Some(b'u') => {
1829                    // Unicode escape: \u{XXXXXX} (1-6 hex digits, max 0x10FFFF)
1830                    match parse_unicode_escape(bytes, idx, span) {
1831                        Ok((ch, new_idx)) => {
1832                            let mut ch_buf = [0u8; 4];
1833                            output.extend(ch.encode_utf8(&mut ch_buf).as_bytes());
1834                            idx = new_idx;
1835                        }
1836                        Err(err) => {
1837                            error = error.or(Some(err));
1838                            break 'us_loop;
1839                        }
1840                    }
1841                }
1842
1843                Some(other) => {
1844                    error = error.or(Some(ParseError::InvalidLiteral(
1845                        format!("unrecognized escape sequence '\\{}'", *other as char),
1846                        "string".into(),
1847                        Span::new(span.start + idx, span.end),
1848                    )));
1849                    break 'us_loop;
1850                }
1851                None => {
1852                    error = error.or(Some(ParseError::InvalidLiteral(
1853                        "incomplete escape sequence after '\\'".into(),
1854                        "string".into(),
1855                        Span::new(span.end.saturating_sub(1), span.end),
1856                    )));
1857                    break 'us_loop;
1858                }
1859            }
1860        } else {
1861            output.push(bytes[idx]);
1862            idx += 1;
1863        }
1864    }
1865
1866    (output, error)
1867}
1868
1869pub fn unescape_unquote_string(bytes: &[u8], span: Span) -> (String, Option<ParseError>) {
1870    if bytes.starts_with(b"\"") {
1871        // Needs unescaping
1872        let bytes = trim_quotes(bytes);
1873
1874        let (bytes, err) = unescape_string(bytes, span);
1875
1876        if let Ok(token) = String::from_utf8(bytes) {
1877            (token, err)
1878        } else {
1879            (String::new(), Some(ParseError::Expected("string", span)))
1880        }
1881    } else {
1882        let bytes = trim_quotes(bytes);
1883
1884        if let Ok(token) = String::from_utf8(bytes.into()) {
1885            (token, None)
1886        } else {
1887            (String::new(), Some(ParseError::Expected("string", span)))
1888        }
1889    }
1890}
1891
1892fn check_string_no_trailing_tokens(
1893    bytes: &[u8],
1894    span: Span,
1895    opening_quote_pos: usize,
1896    quote: u8,
1897) -> Result<(), ParseError> {
1898    let pos = bytes
1899        .iter()
1900        .rposition(|ch| *ch == quote)
1901        .expect("string begins with quote");
1902    if pos == bytes.len() - 1 {
1903        Ok(())
1904    } else if pos == opening_quote_pos {
1905        // this may look like an error, but it's not:
1906        // some code, like completions, requires allowing
1907        // unterminated strings at this stage.
1908        Ok(())
1909    } else {
1910        let span = Span::new(span.start + pos + 1, span.end);
1911        Err(ParseError::ExtraTokensAfterClosingDelimiter(span))
1912    }
1913}
1914
1915pub fn parse_string(working_set: &mut StateWorkingSet, span: Span) -> Expression {
1916    trace!("parsing: string");
1917
1918    let bytes = working_set.get_span_contents(span);
1919
1920    if bytes.is_empty() {
1921        working_set.error(ParseError::Expected("String", span));
1922        return Expression::garbage(working_set, span);
1923    }
1924
1925    // Check for bare word interpolation
1926    if is_bare_string_interpolation(bytes) {
1927        return parse_string_interpolation(working_set, span);
1928    }
1929
1930    // Check for unbalanced quotes:
1931    for quote in [b'\"', b'\''] {
1932        if bytes[0] == quote
1933            && let Err(err) = check_string_no_trailing_tokens(bytes, span, 0, quote)
1934        {
1935            working_set.error(err);
1936            return garbage(working_set, span);
1937        }
1938    }
1939
1940    let (s, err) = unescape_unquote_string(bytes, span);
1941    if let Some(err) = err {
1942        working_set.error(err);
1943    }
1944
1945    Expression::new(working_set, Expr::String(s), span, Type::String)
1946}
1947
1948fn is_quoted(bytes: &[u8]) -> bool {
1949    matches!(bytes, [b'\'', .., b'\''] | [b'"', .., b'"'])
1950}
1951
1952pub fn parse_string_strict(working_set: &mut StateWorkingSet, span: Span) -> Expression {
1953    trace!("parsing: string, with required delimiters");
1954
1955    let bytes = working_set.get_span_contents(span);
1956
1957    // Check for unbalanced quotes:
1958    {
1959        let bytes = if bytes.starts_with(b"$") {
1960            &bytes[1..]
1961        } else {
1962            bytes
1963        };
1964        if bytes.starts_with(b"\"") && (bytes.len() == 1 || !bytes.ends_with(b"\"")) {
1965            working_set.error(ParseError::Unclosed("\"", span));
1966            return garbage(working_set, span);
1967        }
1968        if bytes.starts_with(b"\'") && (bytes.len() == 1 || !bytes.ends_with(b"\'")) {
1969            working_set.error(ParseError::Unclosed("\'", span));
1970            return garbage(working_set, span);
1971        }
1972        if bytes.starts_with(b"r#") && (bytes.len() == 1 || !bytes.ends_with(b"#")) {
1973            working_set.error(ParseError::Unclosed("r#", span));
1974            return garbage(working_set, span);
1975        }
1976    }
1977
1978    let (bytes, quoted) = if (bytes.starts_with(b"\"") && bytes.ends_with(b"\"") && bytes.len() > 1)
1979        || (bytes.starts_with(b"\'") && bytes.ends_with(b"\'") && bytes.len() > 1)
1980    {
1981        (&bytes[1..(bytes.len() - 1)], true)
1982    } else if (bytes.starts_with(b"$\"") && bytes.ends_with(b"\"") && bytes.len() > 2)
1983        || (bytes.starts_with(b"$\'") && bytes.ends_with(b"\'") && bytes.len() > 2)
1984    {
1985        (&bytes[2..(bytes.len() - 1)], true)
1986    } else {
1987        (bytes, false)
1988    };
1989
1990    if let Ok(token) = String::from_utf8(bytes.into()) {
1991        trace!("-- found {token}");
1992
1993        if quoted {
1994            Expression::new(working_set, Expr::String(token), span, Type::String)
1995        } else if token.contains(' ') {
1996            working_set.error(ParseError::Expected("string", span));
1997
1998            garbage(working_set, span)
1999        } else {
2000            Expression::new(working_set, Expr::String(token), span, Type::String)
2001        }
2002    } else {
2003        working_set.error(ParseError::Expected("string", span));
2004        garbage(working_set, span)
2005    }
2006}