Skip to main content

nu_parser/
parse_literals.rs

1#![allow(clippy::byte_char_slices)]
2
3use crate::{
4    Token, TokenContents,
5    lex::{interp_subexpr_step, 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        let open = ParseError::opener_span(span, 1);
452        working_set.error(ParseError::unclosed("'", open, span));
453        return garbage(working_set, span);
454    }
455
456    // check for unbalanced # and single quotes.
457    let postfix_bytes = &bytes[bytes.len() - expect_postfix_sharp_cnt..bytes.len()];
458    if postfix_bytes.iter().any(|b| *b != b'#') {
459        working_set.error(ParseError::unbalanced("prefix #", "postfix #", span));
460        return garbage(working_set, span);
461    }
462    // check for unblanaced single quotes.
463    if bytes[1 + prefix_sharp_cnt] != b'\''
464        || bytes[bytes.len() - expect_postfix_sharp_cnt - 1] != b'\''
465    {
466        let open = ParseError::opener_span(span, 1);
467        working_set.error(ParseError::unclosed("'", open, span));
468        return garbage(working_set, span);
469    }
470
471    let bytes = &bytes[prefix_sharp_cnt + 1 + 1..bytes.len() - 1 - prefix_sharp_cnt];
472    if let Ok(token) = String::from_utf8(bytes.into()) {
473        Expression::new(working_set, Expr::RawString(token), span, Type::String)
474    } else {
475        working_set.error(ParseError::Expected("utf8 raw-string", span));
476        garbage(working_set, span)
477    }
478}
479
480pub fn parse_paren_expr(
481    working_set: &mut StateWorkingSet,
482    span: Span,
483    shape: &SyntaxShape,
484) -> Expression {
485    let starting_error_count = working_set.parse_errors.len();
486
487    if let Some(expr) = parse_range(working_set, span) {
488        return expr;
489    }
490
491    working_set.parse_errors.truncate(starting_error_count);
492
493    if let SyntaxShape::Signature = shape {
494        return parse_signature(working_set, span, false);
495    }
496
497    if let SyntaxShape::ExternalSignature = shape {
498        return parse_signature(working_set, span, true);
499    }
500
501    let starting_scope_count = working_set.delta.scope.len();
502    let fcp_expr = parse_full_cell_path(working_set, None, span, None);
503    let fcp_error_count = working_set.parse_errors.len();
504    if fcp_error_count > starting_error_count {
505        let malformed_subexpr = working_set.parse_errors[starting_error_count..]
506            .first()
507            .is_some_and(|e| match e {
508                ParseError::Unclosed(right, ..) if (*right == ")") => true,
509                ParseError::Unbalanced(left, right, ..) if *left == "(" && *right == ")" => true,
510                _ => false,
511            });
512        if malformed_subexpr {
513            working_set.parse_errors.truncate(starting_error_count);
514            while working_set.delta.scope.len() > starting_scope_count {
515                working_set.exit_scope();
516            }
517            if matches!(shape, SyntaxShape::GlobPattern) {
518                parse_glob_pattern(working_set, span)
519            } else {
520                parse_string_interpolation(working_set, span)
521            }
522        } else {
523            fcp_expr
524        }
525    } else {
526        fcp_expr
527    }
528}
529
530pub fn parse_brace_expr(
531    working_set: &mut StateWorkingSet,
532    span: Span,
533    shape: &SyntaxShape,
534    input_type: Option<&Type>,
535) -> Expression {
536    // Try to detect what kind of value we're about to parse
537    // FIXME: In the future, we should work over the token stream so we only have to do this once
538    // before parsing begins
539
540    // FIXME: we're still using the shape because we rely on it to know how to handle syntax where
541    // the parse is ambiguous. We'll need to update the parts of the grammar where this is ambiguous
542    // and then revisit the parsing.
543
544    if span.end <= (span.start + 1) {
545        working_set.error(ParseError::ExpectedWithStringMsg(
546            format!("non-block value: {shape}"),
547            span,
548        ));
549        return Expression::garbage(working_set, span);
550    }
551    let bytes = working_set.get_span_contents(Span::new(span.start + 1, span.end - 1));
552    let (tokens, _) = lex(bytes, span.start + 1, &[b'\r', b'\n', b'\t'], &[b':'], true);
553
554    match tokens.as_slice() {
555        // If we're empty, that means an empty record or closure
556        [] => match shape {
557            SyntaxShape::Closure(_) => parse_closure_expression(working_set, shape, span, None),
558            SyntaxShape::Block => parse_block_expression(working_set, span, input_type),
559            SyntaxShape::MatchBlock => parse_match_block_expression(working_set, span, input_type),
560            _ => parse_record(working_set, span),
561        },
562        [
563            Token {
564                contents: TokenContents::Pipe | TokenContents::PipePipe,
565                ..
566            },
567            ..,
568        ] => {
569            if let SyntaxShape::Block = shape {
570                working_set.error(ParseError::Mismatch("block".into(), "closure".into(), span));
571                return Expression::garbage(working_set, span);
572            }
573            parse_closure_expression(working_set, shape, span, None)
574        }
575        [_, third, ..] if working_set.get_span_contents(third.span) == b":" => {
576            parse_full_cell_path(working_set, None, span, None)
577        }
578        [second, ..] => {
579            let second_bytes = working_set.get_span_contents(second.span);
580            match shape {
581                SyntaxShape::Closure(_) => parse_closure_expression(working_set, shape, span, None),
582                SyntaxShape::Block => parse_block_expression(working_set, span, input_type),
583                SyntaxShape::MatchBlock => {
584                    parse_match_block_expression(working_set, span, input_type)
585                }
586                // For edge case of `{}.foo?`, #17896
587                _ if second_bytes == b"}" => parse_full_cell_path(working_set, None, span, None),
588                _ if extract_spread_record(second_bytes.into_spanned(second.span)).is_some() => {
589                    parse_record(working_set, span)
590                }
591                SyntaxShape::Any => parse_closure_expression(working_set, shape, span, None),
592                _ => {
593                    working_set.error(ParseError::ExpectedWithStringMsg(
594                        format!("non-block value: {shape}"),
595                        span,
596                    ));
597
598                    Expression::garbage(working_set, span)
599                }
600            }
601        }
602    }
603}
604
605pub fn parse_string_interpolation(working_set: &mut StateWorkingSet, span: Span) -> Expression {
606    #[derive(PartialEq, Eq, Debug)]
607    enum InterpolationMode {
608        String,
609        Expression,
610    }
611
612    let contents = working_set.get_span_contents(span);
613
614    let mut double_quote = false;
615
616    let (start, end) = if contents.starts_with(b"$\"") {
617        double_quote = true;
618
619        if let Err(err) = check_string_no_trailing_tokens(contents, span, 1, b'\"') {
620            working_set.error(err);
621            return garbage(working_set, span);
622        }
623
624        let end = if contents.ends_with(b"\"") && contents.len() > 2 {
625            span.end - 1
626        } else {
627            span.end
628        };
629        (span.start + 2, end)
630    } else if contents.starts_with(b"$'") {
631        if let Err(err) = check_string_no_trailing_tokens(contents, span, 1, b'\'') {
632            working_set.error(err);
633            return garbage(working_set, span);
634        }
635
636        let end = if contents.ends_with(b"'") && contents.len() > 2 {
637            span.end - 1
638        } else {
639            span.end
640        };
641        (span.start + 2, end)
642    } else {
643        (span.start, span.end)
644    };
645
646    let inner_span = Span::new(start, end);
647    let contents = working_set.get_span_contents(inner_span).to_vec();
648
649    let mut output = vec![];
650    let mut mode = InterpolationMode::String;
651    let mut token_start = start;
652
653    // Expected closers inside a subexpression, matched by the same shared step
654    // the lexer uses (`interp_subexpr_step`), so both scans agree on where the
655    // string ends.
656    let mut delimiter_stack: Vec<(u8, ())> = vec![];
657
658    let mut consecutive_backslashes: usize = 0;
659
660    let mut b = start;
661
662    while b != end {
663        let current_byte = contents[b - start];
664
665        if mode == InterpolationMode::String {
666            let preceding_consecutive_backslashes = consecutive_backslashes;
667
668            let is_backslash = current_byte == b'\\';
669            consecutive_backslashes = if is_backslash {
670                preceding_consecutive_backslashes + 1
671            } else {
672                0
673            };
674
675            if current_byte == b'('
676                && (!double_quote || preceding_consecutive_backslashes.is_multiple_of(2))
677            {
678                mode = InterpolationMode::Expression;
679                if token_start < b {
680                    let span = Span::new(token_start, b);
681                    let str_contents = working_set.get_span_contents(span);
682
683                    let (str_contents, err) = if double_quote {
684                        unescape_string(str_contents, span)
685                    } else {
686                        (str_contents.to_vec(), None)
687                    };
688                    if let Some(err) = err {
689                        working_set.error(err);
690                    }
691
692                    output.push(Expression::new(
693                        working_set,
694                        Expr::String(String::from_utf8_lossy(&str_contents).to_string()),
695                        span,
696                        Type::String,
697                    ));
698                    token_start = b;
699                }
700            }
701        }
702
703        if mode == InterpolationMode::Expression {
704            if interp_subexpr_step(&mut delimiter_stack, current_byte, ()) && b + 1 < end {
705                // Escape inside a nested double-quoted string: skip the
706                // escaped byte too, so `\"` does not close the string.
707                b += 2;
708                continue;
709            }
710            if current_byte == b')' && delimiter_stack.is_empty() {
711                mode = InterpolationMode::String;
712
713                if token_start < b {
714                    let span = Span::new(token_start, b + 1);
715
716                    let expr = parse_full_cell_path(working_set, None, span, None);
717                    output.push(expr);
718                }
719
720                token_start = b + 1;
721                continue;
722            }
723        }
724        b += 1;
725    }
726
727    match mode {
728        InterpolationMode::String => {
729            if token_start < end {
730                let span = Span::new(token_start, end);
731                let str_contents = working_set.get_span_contents(span);
732
733                let (str_contents, err) = if double_quote {
734                    unescape_string(str_contents, span)
735                } else {
736                    (str_contents.to_vec(), None)
737                };
738                if let Some(err) = err {
739                    working_set.error(err);
740                }
741
742                output.push(Expression::new(
743                    working_set,
744                    Expr::String(String::from_utf8_lossy(&str_contents).to_string()),
745                    span,
746                    Type::String,
747                ));
748            }
749        }
750        InterpolationMode::Expression => {
751            if token_start < end {
752                let span = Span::new(token_start, end);
753                let expr = parse_full_cell_path(working_set, None, span, None);
754                output.push(expr);
755            }
756        }
757    }
758
759    Expression::new(
760        working_set,
761        Expr::StringInterpolation(output),
762        span,
763        Type::String,
764    )
765}
766
767pub fn parse_variable_expr(
768    working_set: &mut StateWorkingSet,
769    span: Span,
770    input_type: Option<&Type>,
771) -> Expression {
772    let contents = working_set.get_span_contents(span);
773
774    if contents == b"$nu" {
775        return Expression::new(
776            working_set,
777            Expr::Var(nu_protocol::NU_VARIABLE_ID),
778            span,
779            Type::Any,
780        );
781    } else if contents == b"$in" {
782        return Expression::new(
783            working_set,
784            Expr::Var(nu_protocol::IN_VARIABLE_ID),
785            span,
786            input_type.cloned().unwrap_or(Type::Any),
787        );
788    } else if contents == b"$env" {
789        return Expression::new(
790            working_set,
791            Expr::Var(nu_protocol::ENV_VARIABLE_ID),
792            span,
793            Type::Any,
794        );
795    } else if contents.strip_prefix(b"$") == Some(nu_protocol::LAST_RESULT_VAR_NAME.as_bytes()) {
796        // Interactive last-result special (`LAST_VARIABLE_ID`). The name is reserved
797        // (see `RESERVED_VARIABLE_NAMES`); rename site-wide via `LAST_RESULT_VAR_NAME`.
798        return Expression::new(
799            working_set,
800            Expr::Var(nu_protocol::LAST_VARIABLE_ID),
801            span,
802            Type::Any,
803        );
804    }
805
806    let name = if contents.starts_with(b"$") {
807        String::from_utf8_lossy(&contents[1..]).to_string()
808    } else {
809        String::from_utf8_lossy(contents).to_string()
810    };
811
812    let bytes = working_set.get_span_contents(span);
813    let suggestion = || {
814        DidYouMean::new(
815            &working_set.list_variables(),
816            working_set.get_span_contents(span),
817        )
818    };
819
820    // Bare `$` (often from `$ env` with a space) is a very common config typo.
821    if bytes == b"$" {
822        working_set.error(ParseError::LabeledErrorWithHelp {
823            error: "Incomplete variable".into(),
824            label: "expected a variable name after `$`".into(),
825            help: "Variable names must be attached to `$` with no space (e.g. `$env`, `$in`). \
826                   Did you mean `$env`?"
827                .into(),
828            span,
829        });
830        return garbage(working_set, span);
831    }
832
833    if !is_variable(bytes) {
834        working_set.error(ParseError::ExpectedWithDidYouMean(
835            "valid variable name",
836            suggestion(),
837            span,
838        ));
839        garbage(working_set, span)
840    } else if let Some(id) = working_set.find_variable(bytes) {
841        Expression::new(
842            working_set,
843            Expr::Var(id),
844            span,
845            working_set.get_variable(id).ty.clone(),
846        )
847    } else if working_set.get_env_var(&name).is_some() {
848        working_set.error(ParseError::EnvVarNotVar(name, span));
849        garbage(working_set, span)
850    } else {
851        working_set.error(ParseError::VariableNotFound(suggestion(), span));
852        garbage(working_set, span)
853    }
854}
855
856pub fn parse_cell_path(
857    working_set: &mut StateWorkingSet,
858    tokens: impl Iterator<Item = Token>,
859    expect_dot: bool,
860) -> Vec<PathMember> {
861    enum TokenType {
862        Dot,              // .
863        DotOrSign,        // . or ? or !
864        DotOrExclamation, // . or !
865        DotOrQuestion,    // . or ?
866        PathMember,       // an int or string, like `1` or `foo`
867    }
868
869    enum ModifyMember {
870        No,
871        Optional,
872        Insensitive,
873    }
874
875    impl TokenType {
876        fn expect(&mut self, byte: u8) -> Result<ModifyMember, &'static str> {
877            match (&*self, byte) {
878                (Self::PathMember, _) => {
879                    *self = Self::DotOrSign;
880                    Ok(ModifyMember::No)
881                }
882                (
883                    Self::Dot | Self::DotOrSign | Self::DotOrExclamation | Self::DotOrQuestion,
884                    b'.',
885                ) => {
886                    *self = Self::PathMember;
887                    Ok(ModifyMember::No)
888                }
889                (Self::DotOrSign, b'!') => {
890                    *self = Self::DotOrQuestion;
891                    Ok(ModifyMember::Insensitive)
892                }
893                (Self::DotOrSign, b'?') => {
894                    *self = Self::DotOrExclamation;
895                    Ok(ModifyMember::Optional)
896                }
897                (Self::DotOrSign, _) => Err(". or ! or ?"),
898                (Self::DotOrExclamation, b'!') => {
899                    *self = Self::Dot;
900                    Ok(ModifyMember::Insensitive)
901                }
902                (Self::DotOrExclamation, _) => Err(". or !"),
903                (Self::DotOrQuestion, b'?') => {
904                    *self = Self::Dot;
905                    Ok(ModifyMember::Optional)
906                }
907                (Self::DotOrQuestion, _) => Err(". or ?"),
908                (Self::Dot, _) => Err("."),
909            }
910        }
911    }
912
913    // Parsing a cell path is essentially a state machine, and this is the state
914    let mut expected_token = if expect_dot {
915        TokenType::Dot
916    } else {
917        TokenType::PathMember
918    };
919
920    let mut tail = vec![];
921
922    for path_element in tokens {
923        let bytes = working_set.get_span_contents(path_element.span);
924
925        // both parse_int and parse_string require their source to be non-empty
926        // all cases where `bytes` is empty is an error
927        let Some((&first, rest)) = bytes.split_first() else {
928            working_set.error(ParseError::Expected("string", path_element.span));
929            return tail;
930        };
931        let single_char = rest.is_empty();
932
933        if let TokenType::PathMember = expected_token {
934            let starting_error_count = working_set.parse_errors.len();
935
936            let expr = parse_int(working_set, path_element.span);
937            working_set.parse_errors.truncate(starting_error_count);
938
939            match expr {
940                Expression {
941                    expr: Expr::Int(val),
942                    span,
943                    ..
944                } => {
945                    if val < 0 {
946                        working_set.error(ParseError::InvalidLiteral(
947                            "negative index is not supported".into(),
948                            "cell path".into(),
949                            span,
950                        ));
951                        return tail;
952                    }
953                    tail.push(PathMember::Int {
954                        val: val as usize,
955                        span,
956                        optional: false,
957                    })
958                }
959                _ => {
960                    let result = parse_string(working_set, path_element.span);
961                    match result {
962                        Expression {
963                            expr: Expr::String(string),
964                            span,
965                            ..
966                        } => {
967                            tail.push(PathMember::String {
968                                val: string,
969                                span,
970                                optional: false,
971                                casing: Casing::Sensitive,
972                            });
973                        }
974                        _ => {
975                            working_set.error(ParseError::Expected("string", path_element.span));
976                            return tail;
977                        }
978                    }
979                }
980            }
981            expected_token = TokenType::DotOrSign;
982        } else {
983            match expected_token.expect(if single_char { first } else { b' ' }) {
984                Ok(modify) => {
985                    if let Some(last) = tail.last_mut() {
986                        match modify {
987                            ModifyMember::No => {}
988                            ModifyMember::Optional => last.make_optional(),
989                            ModifyMember::Insensitive => last.make_insensitive(),
990                        }
991                    };
992                }
993                Err(expected) => {
994                    working_set.error(ParseError::Expected(expected, path_element.span));
995                    return tail;
996                }
997            }
998        }
999    }
1000
1001    tail
1002}
1003
1004pub fn parse_simple_cell_path(working_set: &mut StateWorkingSet, span: Span) -> Expression {
1005    let source = working_set.get_span_contents(span);
1006
1007    let (tokens, err) = lex(
1008        source,
1009        span.start,
1010        &[b'\n', b'\r'],
1011        &[b'.', b'?', b'!'],
1012        true,
1013    );
1014    if let Some(err) = err {
1015        working_set.error(err)
1016    }
1017
1018    let tokens = tokens.into_iter().peekable();
1019
1020    let cell_path = parse_cell_path(working_set, tokens, false);
1021
1022    Expression::new(
1023        working_set,
1024        Expr::CellPath(CellPath { members: cell_path }),
1025        span,
1026        Type::CellPath,
1027    )
1028}
1029
1030pub fn parse_full_cell_path(
1031    working_set: &mut StateWorkingSet,
1032    implicit_head: Option<VarId>,
1033    span: Span,
1034    input_type: Option<&Type>,
1035) -> Expression {
1036    trace!("parsing: full cell path");
1037    let full_cell_span = span;
1038    let source = working_set.get_span_contents(span);
1039
1040    let (tokens, err) = lex(
1041        source,
1042        span.start,
1043        &[b'\n', b'\r'],
1044        &[b'.', b'?', b'!'],
1045        true,
1046    );
1047    if let Some(err) = err {
1048        working_set.error(err)
1049    }
1050
1051    let mut tokens = tokens.into_iter().peekable();
1052    if let Some(head) = tokens.peek() {
1053        let bytes = working_set.get_span_contents(head.span);
1054        let (head, expect_dot) = if bytes.starts_with(b"(") {
1055            trace!("parsing: paren-head of full cell path");
1056
1057            let head_span = head.span;
1058            let mut start = head.span.start;
1059            let mut end = head.span.end;
1060            let mut is_closed = true;
1061
1062            if bytes.starts_with(b"(") {
1063                start += 1;
1064            }
1065            if bytes.ends_with(b")") {
1066                end -= 1;
1067            } else {
1068                let open = Span::new(
1069                    head_span.start,
1070                    head_span.start.saturating_add(1).min(head_span.end),
1071                );
1072                working_set.error(ParseError::unclosed(")", open, Span::new(end, end)));
1073                is_closed = false;
1074            }
1075
1076            let span = Span::new(start, end);
1077
1078            let source = working_set.get_span_contents(span);
1079
1080            let (output, err) = lex(source, span.start, &[b'\n', b'\r'], &[], true);
1081            if let Some(err) = err {
1082                working_set.error(err)
1083            }
1084
1085            // Creating a Type scope to parse the new block. This will keep track of
1086            // the previous input type found in that block
1087            let output = parse_block(working_set, &output, span, is_closed, true, None);
1088
1089            let ty = output.output_type();
1090
1091            let block_id = working_set.add_block(Arc::new(output));
1092            tokens.next();
1093
1094            (
1095                Expression::new(working_set, Expr::Subexpression(block_id), head_span, ty),
1096                true,
1097            )
1098        } else if bytes.starts_with(b"[") {
1099            trace!("parsing: table head of full cell path");
1100
1101            let output = parse_table_expression(working_set, head.span, &SyntaxShape::Any);
1102
1103            tokens.next();
1104
1105            (output, true)
1106        } else if bytes.starts_with(b"{") {
1107            trace!("parsing: record head of full cell path");
1108            let output = parse_record(working_set, head.span);
1109
1110            tokens.next();
1111
1112            (output, true)
1113        } else if bytes.starts_with(b"$") {
1114            trace!("parsing: $variable head of full cell path");
1115
1116            let out = parse_variable_expr(working_set, head.span, input_type);
1117
1118            tokens.next();
1119
1120            (out, true)
1121        } else if let Some(var_id) = implicit_head {
1122            trace!("parsing: implicit head of full cell path");
1123            (
1124                Expression::new(working_set, Expr::Var(var_id), head.span, Type::Any),
1125                false,
1126            )
1127        } else {
1128            working_set.error(ParseError::Mismatch(
1129                "variable or subexpression".into(),
1130                String::from_utf8_lossy(bytes).to_string(),
1131                span,
1132            ));
1133            return garbage(working_set, span);
1134        };
1135
1136        let tail = parse_cell_path(working_set, tokens, expect_dot);
1137        let ty = if !tail.is_empty() {
1138            if nu_experimental::CELL_PATH_TYPES.get() {
1139                head.ty
1140                    .follow_cell_path(&tail)
1141                    .map(|ty| ty.into_owned())
1142                    .unwrap_or(Type::Any)
1143            } else {
1144                Type::Any
1145            }
1146        } else {
1147            head.ty.clone()
1148        };
1149
1150        Expression::new(
1151            working_set,
1152            Expr::FullCellPath(Box::new(FullCellPath { head, tail })),
1153            full_cell_span,
1154            ty,
1155        )
1156    } else {
1157        garbage(working_set, span)
1158    }
1159}
1160
1161enum PathLikeKind {
1162    Directory,
1163    Filepath,
1164    Glob,
1165}
1166
1167impl PathLikeKind {
1168    /// Returns the name used for trace logging during parsing.
1169    fn trace_name(&self) -> &'static str {
1170        match self {
1171            PathLikeKind::Directory => "directory",
1172            PathLikeKind::Filepath => "filepath",
1173            PathLikeKind::Glob => "glob pattern",
1174        }
1175    }
1176
1177    /// Returns the error message displayed when parsing fails.
1178    fn error_msg(&self) -> &'static str {
1179        match self {
1180            PathLikeKind::Directory => "directory",
1181            PathLikeKind::Filepath => "filepath",
1182            PathLikeKind::Glob => "glob pattern string",
1183        }
1184    }
1185
1186    /// Constructs the appropriate `Expr` and its corresponding `Type` for a simple (non-interpolated) path.
1187    fn to_expr(&self, token: String, quoted: bool) -> (Expr, Type) {
1188        match self {
1189            PathLikeKind::Directory => (Expr::Directory(token, quoted), Type::String),
1190            PathLikeKind::Filepath => (Expr::Filepath(token, quoted), Type::String),
1191            PathLikeKind::Glob => (Expr::GlobPattern(token, quoted), Type::Glob),
1192        }
1193    }
1194
1195    /// Constructs the appropriate interpolation `Expr` for a path containing subexpressions.
1196    fn to_interpolation_expr(&self, exprs: Vec<Expression>, quoted: bool) -> Expr {
1197        match self {
1198            PathLikeKind::Directory | PathLikeKind::Filepath => Expr::StringInterpolation(exprs),
1199            PathLikeKind::Glob => Expr::GlobInterpolation(exprs, quoted),
1200        }
1201    }
1202}
1203
1204/// Common helper for parsing path-like expressions (filepath, directory, glob pattern).
1205///
1206/// This function consolidates the repetitive logic for parsing path types, including:
1207/// - Bare word interpolation detection
1208/// - Escape sequence processing
1209/// - Quote state tracking
1210/// - Error handling
1211///
1212/// # Arguments
1213///
1214/// * `working_set` - The current parser state
1215/// * `span` - The source span of the expression
1216/// * `kind` - The kind of path-like expression to parse
1217fn parse_path_like(
1218    working_set: &mut StateWorkingSet,
1219    span: Span,
1220    kind: PathLikeKind,
1221) -> Expression {
1222    let bytes = working_set.get_span_contents(span);
1223    let quoted = is_quoted(bytes);
1224    trace!("parsing: {}", kind.trace_name());
1225
1226    // Check for bare word interpolation
1227    if is_bare_string_interpolation(bytes) {
1228        let interpolation_expr = parse_string_interpolation(working_set, span);
1229
1230        // Convert StringInterpolation to the appropriate interpolation type
1231        if let Expr::StringInterpolation(exprs) = interpolation_expr.expr {
1232            return Expression::new(
1233                working_set,
1234                kind.to_interpolation_expr(exprs, quoted),
1235                span,
1236                interpolation_expr.ty.clone(),
1237            );
1238        }
1239
1240        return interpolation_expr;
1241    }
1242
1243    let (token, err) = unescape_unquote_string(bytes, span);
1244    let is_quoted_internal = is_quoted(bytes);
1245
1246    if err.is_none() {
1247        trace!("-- found {token}");
1248
1249        let (expr, ty) = kind.to_expr(token, is_quoted_internal);
1250
1251        Expression::new(working_set, expr, span, ty)
1252    } else {
1253        working_set.error(ParseError::Expected(kind.error_msg(), span));
1254
1255        garbage(working_set, span)
1256    }
1257}
1258
1259fn is_bare_string_interpolation(bytes: &[u8]) -> bool {
1260    match bytes {
1261        [] => false,
1262        [b'\'' | b'"' | b'`', ..] => false,
1263        _ => bytes.contains(&b'('),
1264    }
1265}
1266
1267pub fn parse_directory(working_set: &mut StateWorkingSet, span: Span) -> Expression {
1268    parse_path_like(working_set, span, PathLikeKind::Directory)
1269}
1270
1271pub fn parse_filepath(working_set: &mut StateWorkingSet, span: Span) -> Expression {
1272    parse_path_like(working_set, span, PathLikeKind::Filepath)
1273}
1274
1275pub fn parse_datetime(working_set: &mut StateWorkingSet, span: Span) -> Expression {
1276    trace!("parsing: datetime");
1277
1278    let bytes = working_set.get_span_contents(span);
1279
1280    if bytes.len() < 6
1281        || !bytes[0].is_ascii_digit()
1282        || !bytes[1].is_ascii_digit()
1283        || !bytes[2].is_ascii_digit()
1284        || !bytes[3].is_ascii_digit()
1285        || bytes[4] != b'-'
1286    {
1287        working_set.error(ParseError::Expected("datetime", span));
1288        return garbage(working_set, span);
1289    }
1290
1291    let token = String::from_utf8_lossy(bytes).to_string();
1292
1293    if let Ok(datetime) = chrono::DateTime::parse_from_rfc3339(&token) {
1294        return Expression::new(working_set, Expr::DateTime(datetime), span, Type::Date);
1295    }
1296
1297    // Just the date
1298    let just_date = token.clone() + "T00:00:00+00:00";
1299    if let Ok(datetime) = chrono::DateTime::parse_from_rfc3339(&just_date) {
1300        return Expression::new(working_set, Expr::DateTime(datetime), span, Type::Date);
1301    }
1302
1303    // Date and time, assume UTC
1304    let datetime = token + "+00:00";
1305    if let Ok(datetime) = chrono::DateTime::parse_from_rfc3339(&datetime) {
1306        return Expression::new(working_set, Expr::DateTime(datetime), span, Type::Date);
1307    }
1308
1309    working_set.error(ParseError::Expected("datetime", span));
1310
1311    garbage(working_set, span)
1312}
1313
1314/// Parse a duration type, eg '10day'
1315pub fn parse_duration(working_set: &mut StateWorkingSet, span: Span) -> Expression {
1316    trace!("parsing: duration");
1317
1318    let bytes = working_set.get_span_contents(span);
1319
1320    match parse_unit_value(bytes, span, DURATION_UNIT_GROUPS, Type::Duration, |x| x) {
1321        Some(Ok(expr)) => {
1322            let span_id = working_set.add_span(span);
1323            expr.with_span_id(span_id)
1324        }
1325        Some(Err(mk_err_for)) => {
1326            working_set.error(mk_err_for("duration"));
1327            garbage(working_set, span)
1328        }
1329        None => {
1330            working_set.error(ParseError::Expected("duration with valid units", span));
1331            garbage(working_set, span)
1332        }
1333    }
1334}
1335
1336/// Parse a unit type, eg '10kb'
1337pub fn parse_filesize(working_set: &mut StateWorkingSet, span: Span) -> Expression {
1338    trace!("parsing: filesize");
1339
1340    let bytes = working_set.get_span_contents(span);
1341
1342    // the hex digit `b` might be mistaken for the unit `b`, so check that first
1343    if bytes.starts_with(b"0x") {
1344        working_set.error(ParseError::Expected("filesize with valid units", span));
1345        return garbage(working_set, span);
1346    }
1347
1348    match parse_unit_value(bytes, span, FILESIZE_UNIT_GROUPS, Type::Filesize, |x| {
1349        x.to_ascii_uppercase()
1350    }) {
1351        Some(Ok(expr)) => {
1352            let span_id = working_set.add_span(span);
1353            expr.with_span_id(span_id)
1354        }
1355        Some(Err(mk_err_for)) => {
1356            working_set.error(mk_err_for("filesize"));
1357            garbage(working_set, span)
1358        }
1359        None => {
1360            working_set.error(ParseError::Expected("filesize with valid units", span));
1361            garbage(working_set, span)
1362        }
1363    }
1364}
1365
1366type ParseUnitResult<'res> = Result<Expression, Box<dyn Fn(&'res str) -> ParseError>>;
1367type UnitGroup<'unit> = (Unit, &'unit str, Option<(Unit, i64)>);
1368
1369pub fn parse_unit_value<'res>(
1370    bytes: &[u8],
1371    span: Span,
1372    unit_groups: &[UnitGroup],
1373    ty: Type,
1374    transform: fn(String) -> String,
1375) -> Option<ParseUnitResult<'res>> {
1376    if bytes.len() < 2
1377        || !(bytes[0].is_ascii_digit()
1378            || (bytes[0] == b'.' && bytes[1].is_ascii_digit())
1379            || (bytes[0] == b'-' && bytes[1].is_ascii_digit()))
1380    {
1381        return None;
1382    }
1383
1384    // Bail if not UTF-8
1385    let value = transform(str::from_utf8(bytes).ok()?.into());
1386
1387    if let Some((unit, name, convert)) = unit_groups.iter().find(|x| value.ends_with(x.1)) {
1388        let lhs_len = value.len() - name.len();
1389        let lhs = strip_underscores(&value.as_bytes()[..lhs_len]);
1390        let lhs_span = Span::new(span.start, span.start + lhs_len);
1391        let unit_span = Span::new(span.start + lhs_len, span.end);
1392        if lhs.ends_with('$') {
1393            // If `parse_unit_value` has higher precedence over `parse_range`,
1394            // a variable with the name of a unit could otherwise not be used as the end of a range.
1395            return None;
1396        }
1397
1398        let (decimal_part, number_part) = modf(match lhs.parse::<f64>() {
1399            Ok(it) => it,
1400            Err(_) => {
1401                let mk_err = move |name| {
1402                    ParseError::LabeledError(
1403                        format!("{name} value must be a number"),
1404                        "not a number".into(),
1405                        lhs_span,
1406                    )
1407                };
1408                return Some(Err(Box::new(mk_err)));
1409            }
1410        });
1411
1412        let mut unit = match convert {
1413            Some(convert_to) => convert_to.0,
1414            None => *unit,
1415        };
1416
1417        let num_float = match convert {
1418            Some(convert_to) => {
1419                (number_part * convert_to.1 as f64) + (decimal_part * convert_to.1 as f64)
1420            }
1421            None => number_part,
1422        };
1423
1424        // Convert all durations to nanoseconds, and filesizes to bytes,
1425        // to minimize loss of precision
1426        let factor = match ty {
1427            Type::Filesize => unit_to_byte_factor(&unit),
1428            Type::Duration => unit_to_ns_factor(&unit),
1429            _ => None,
1430        };
1431
1432        let num = match factor {
1433            Some(factor) => {
1434                let num_base = num_float * factor;
1435                if i64::MIN as f64 <= num_base && num_base <= i64::MAX as f64 {
1436                    unit = if ty == Type::Filesize {
1437                        Unit::Filesize(FilesizeUnit::B)
1438                    } else {
1439                        Unit::Nanosecond
1440                    };
1441                    num_base as i64
1442                } else {
1443                    // not safe to convert, because of the overflow
1444                    num_float as i64
1445                }
1446            }
1447            None => num_float as i64,
1448        };
1449
1450        trace!("-- found {num} {unit:?}");
1451        let value = ValueWithUnit {
1452            expr: Expression::new_unknown(Expr::Int(num), lhs_span, Type::Number),
1453            unit: Spanned {
1454                item: unit,
1455                span: unit_span,
1456            },
1457        };
1458        let expr = Expression::new_unknown(Expr::ValueWithUnit(Box::new(value)), span, ty);
1459
1460        Some(Ok(expr))
1461    } else {
1462        None
1463    }
1464}
1465
1466pub const FILESIZE_UNIT_GROUPS: &[UnitGroup] = &[
1467    (
1468        Unit::Filesize(FilesizeUnit::KB),
1469        "KB",
1470        Some((Unit::Filesize(FilesizeUnit::B), 1000)),
1471    ),
1472    (
1473        Unit::Filesize(FilesizeUnit::MB),
1474        "MB",
1475        Some((Unit::Filesize(FilesizeUnit::KB), 1000)),
1476    ),
1477    (
1478        Unit::Filesize(FilesizeUnit::GB),
1479        "GB",
1480        Some((Unit::Filesize(FilesizeUnit::MB), 1000)),
1481    ),
1482    (
1483        Unit::Filesize(FilesizeUnit::TB),
1484        "TB",
1485        Some((Unit::Filesize(FilesizeUnit::GB), 1000)),
1486    ),
1487    (
1488        Unit::Filesize(FilesizeUnit::PB),
1489        "PB",
1490        Some((Unit::Filesize(FilesizeUnit::TB), 1000)),
1491    ),
1492    (
1493        Unit::Filesize(FilesizeUnit::EB),
1494        "EB",
1495        Some((Unit::Filesize(FilesizeUnit::PB), 1000)),
1496    ),
1497    (
1498        Unit::Filesize(FilesizeUnit::KiB),
1499        "KIB",
1500        Some((Unit::Filesize(FilesizeUnit::B), 1024)),
1501    ),
1502    (
1503        Unit::Filesize(FilesizeUnit::MiB),
1504        "MIB",
1505        Some((Unit::Filesize(FilesizeUnit::KiB), 1024)),
1506    ),
1507    (
1508        Unit::Filesize(FilesizeUnit::GiB),
1509        "GIB",
1510        Some((Unit::Filesize(FilesizeUnit::MiB), 1024)),
1511    ),
1512    (
1513        Unit::Filesize(FilesizeUnit::TiB),
1514        "TIB",
1515        Some((Unit::Filesize(FilesizeUnit::GiB), 1024)),
1516    ),
1517    (
1518        Unit::Filesize(FilesizeUnit::PiB),
1519        "PIB",
1520        Some((Unit::Filesize(FilesizeUnit::TiB), 1024)),
1521    ),
1522    (
1523        Unit::Filesize(FilesizeUnit::EiB),
1524        "EIB",
1525        Some((Unit::Filesize(FilesizeUnit::PiB), 1024)),
1526    ),
1527    (Unit::Filesize(FilesizeUnit::B), "B", None),
1528];
1529
1530pub const DURATION_UNIT_GROUPS: &[UnitGroup] = &[
1531    (Unit::Nanosecond, "ns", None),
1532    // todo start adding aliases for duration units here
1533    (Unit::Microsecond, "us", Some((Unit::Nanosecond, 1000))),
1534    (
1535        // µ Micro Sign
1536        Unit::Microsecond,
1537        "\u{00B5}s",
1538        Some((Unit::Nanosecond, 1000)),
1539    ),
1540    (
1541        // μ Greek small letter Mu
1542        Unit::Microsecond,
1543        "\u{03BC}s",
1544        Some((Unit::Nanosecond, 1000)),
1545    ),
1546    (Unit::Millisecond, "ms", Some((Unit::Microsecond, 1000))),
1547    (Unit::Second, "sec", Some((Unit::Millisecond, 1000))),
1548    (Unit::Minute, "min", Some((Unit::Second, 60))),
1549    (Unit::Hour, "hr", Some((Unit::Minute, 60))),
1550    (Unit::Day, "day", Some((Unit::Minute, 1440))),
1551    (Unit::Week, "wk", Some((Unit::Day, 7))),
1552];
1553
1554fn unit_to_ns_factor(unit: &Unit) -> Option<f64> {
1555    match unit {
1556        Unit::Nanosecond => Some(1.0),
1557        Unit::Microsecond => Some(1_000.0),
1558        Unit::Millisecond => Some(1_000_000.0),
1559        Unit::Second => Some(1_000_000_000.0),
1560        Unit::Minute => Some(60.0 * 1_000_000_000.0),
1561        Unit::Hour => Some(60.0 * 60.0 * 1_000_000_000.0),
1562        Unit::Day => Some(24.0 * 60.0 * 60.0 * 1_000_000_000.0),
1563        Unit::Week => Some(7.0 * 24.0 * 60.0 * 60.0 * 1_000_000_000.0),
1564        _ => None,
1565    }
1566}
1567
1568fn unit_to_byte_factor(unit: &Unit) -> Option<f64> {
1569    match unit {
1570        Unit::Filesize(FilesizeUnit::B) => Some(1.0),
1571        Unit::Filesize(FilesizeUnit::KB) => Some(1_000.0),
1572        Unit::Filesize(FilesizeUnit::MB) => Some(1_000_000.0),
1573        Unit::Filesize(FilesizeUnit::GB) => Some(1_000_000_000.0),
1574        Unit::Filesize(FilesizeUnit::TB) => Some(1_000_000_000_000.0),
1575        Unit::Filesize(FilesizeUnit::PB) => Some(1_000_000_000_000_000.0),
1576        Unit::Filesize(FilesizeUnit::EB) => Some(1_000_000_000_000_000_000.0),
1577        Unit::Filesize(FilesizeUnit::KiB) => Some(1024.0),
1578        Unit::Filesize(FilesizeUnit::MiB) => Some(1024.0 * 1024.0),
1579        Unit::Filesize(FilesizeUnit::GiB) => Some(1024.0 * 1024.0 * 1024.0),
1580        Unit::Filesize(FilesizeUnit::TiB) => Some(1024.0 * 1024.0 * 1024.0 * 1024.0),
1581        Unit::Filesize(FilesizeUnit::PiB) => Some(1024.0 * 1024.0 * 1024.0 * 1024.0 * 1024.0),
1582        Unit::Filesize(FilesizeUnit::EiB) => {
1583            Some(1024.0 * 1024.0 * 1024.0 * 1024.0 * 1024.0 * 1024.0)
1584        }
1585        _ => None,
1586    }
1587}
1588
1589// Borrowed from libm at https://github.com/rust-lang/libm/blob/master/src/math/modf.rs
1590fn modf(x: f64) -> (f64, f64) {
1591    let rv2: f64;
1592    let mut u = x.to_bits();
1593    let e = (((u >> 52) & 0x7ff) as i32) - 0x3ff;
1594
1595    /* no fractional part */
1596    if e >= 52 {
1597        rv2 = x;
1598        if e == 0x400 && (u << 12) != 0 {
1599            /* nan */
1600            return (x, rv2);
1601        }
1602        u &= 1 << 63;
1603        return (f64::from_bits(u), rv2);
1604    }
1605
1606    /* no integral part*/
1607    if e < 0 {
1608        u &= 1 << 63;
1609        rv2 = f64::from_bits(u);
1610        return (x, rv2);
1611    }
1612
1613    let mask = ((!0) >> 12) >> e;
1614    if (u & mask) == 0 {
1615        rv2 = x;
1616        u &= 1 << 63;
1617        return (f64::from_bits(u), rv2);
1618    }
1619    u &= !mask;
1620    rv2 = f64::from_bits(u);
1621    (x - rv2, rv2)
1622}
1623
1624pub fn parse_glob_pattern(working_set: &mut StateWorkingSet, span: Span) -> Expression {
1625    parse_path_like(working_set, span, PathLikeKind::Glob)
1626}
1627
1628fn parse_hex_escape(bytes: &[u8], start_idx: usize, span: Span) -> Result<(u8, usize), ParseError> {
1629    let hex_digits = bytes.get(start_idx + 1..start_idx + 3).ok_or_else(|| {
1630        ParseError::InvalidLiteral(
1631            "incomplete hex escape '\\xHH', expected 2 hex digits".into(),
1632            "string".into(),
1633            Span::new(span.start + start_idx, span.end),
1634        )
1635    })?;
1636    if !hex_digits.iter().all(u8::is_ascii_hexdigit) {
1637        return Err(ParseError::InvalidLiteral(
1638            "invalid hex escape '\\xHH', expected exactly 2 hex digits".into(),
1639            "string".into(),
1640            Span::new(span.start + start_idx, span.end),
1641        ));
1642    }
1643    str::from_utf8(hex_digits)
1644        .ok()
1645        .and_then(|s| u8::from_str_radix(s, 0x10).ok())
1646        .map(|byte_val| (byte_val, start_idx + 3))
1647        .ok_or_else(|| {
1648            ParseError::InvalidLiteral(
1649                "invalid hex escape '\\xHH'".into(),
1650                "string".into(),
1651                Span::new(span.start + start_idx, span.end),
1652            )
1653        })
1654}
1655
1656fn parse_unicode_escape(
1657    bytes: &[u8],
1658    start_idx: usize,
1659    span: Span,
1660) -> Result<(char, usize), ParseError> {
1661    let mut slice = &bytes[(start_idx + 1)..];
1662    let mut current_idx = start_idx + 1;
1663
1664    // NOTE: this is a more defensive approach meant to avoid reading too much, but requires
1665    //       changing error messages
1666    // read no more than 8 bytes "{xxxxxx}"
1667    // slice = &slice[..(8.min(slice.len()))];
1668
1669    slice = slice.strip_prefix(b"{").ok_or_else(|| {
1670        ParseError::InvalidLiteral(
1671            "invalid unicode escape '\\u{...}', must be 1-6 hex digits, max codepoint 0x10FFFF"
1672                .into(),
1673            "string".into(),
1674            Span::new(span.start + start_idx, span.end),
1675        )
1676    })?;
1677    current_idx += 1;
1678
1679    let end = slice.iter().position(|b| *b == b'}').ok_or_else(|| {
1680        ParseError::InvalidLiteral(
1681            "incomplete unicode escape '\\u{...}', missing closing '}'".into(),
1682            "string".into(),
1683            Span::new(span.start + start_idx, span.end),
1684        )
1685    })?;
1686    let digits = &slice[..end];
1687    current_idx += end; // the digits
1688    current_idx += 1; // closing brace
1689    let current_idx = current_idx;
1690
1691    let ch = Some(digits)
1692        .filter(|b| (1..=6).contains(&b.len()))
1693        .and_then(|b| str::from_utf8(b).ok())
1694        .and_then(|s| u32::from_str_radix(s, 0x10).ok())
1695        .and_then(char::from_u32)
1696        .ok_or_else(|| {
1697            ParseError::InvalidLiteral(
1698                "invalid unicode escape '\\u{...}', must be 1-6 hex digits, max codepoint 0x10FFFF"
1699                    .into(),
1700                "string".into(),
1701                Span::new(span.start + start_idx, span.end),
1702            )
1703        })?;
1704
1705    Ok((ch, current_idx))
1706}
1707
1708pub fn unescape_string(bytes: &[u8], span: Span) -> (Vec<u8>, Option<ParseError>) {
1709    let mut output = Vec::new();
1710    let mut error = None;
1711
1712    let mut idx = 0;
1713
1714    if !bytes.contains(&b'\\') {
1715        return (bytes.to_vec(), None);
1716    }
1717
1718    'us_loop: while idx < bytes.len() {
1719        if bytes[idx] == b'\\' {
1720            // We're in an escape
1721            idx += 1;
1722
1723            match bytes.get(idx) {
1724                Some(b'"') => {
1725                    output.push(b'"');
1726                    idx += 1;
1727                }
1728                Some(b'\'') => {
1729                    output.push(b'\'');
1730                    idx += 1;
1731                }
1732                Some(b'\\') => {
1733                    output.push(b'\\');
1734                    idx += 1;
1735                }
1736                Some(b'/') => {
1737                    output.push(b'/');
1738                    idx += 1;
1739                }
1740                Some(b'(') => {
1741                    output.push(b'(');
1742                    idx += 1;
1743                }
1744                Some(b')') => {
1745                    output.push(b')');
1746                    idx += 1;
1747                }
1748                Some(b'{') => {
1749                    output.push(b'{');
1750                    idx += 1;
1751                }
1752                Some(b'}') => {
1753                    output.push(b'}');
1754                    idx += 1;
1755                }
1756                Some(b'$') => {
1757                    output.push(b'$');
1758                    idx += 1;
1759                }
1760                Some(b'^') => {
1761                    output.push(b'^');
1762                    idx += 1;
1763                }
1764                Some(b'#') => {
1765                    output.push(b'#');
1766                    idx += 1;
1767                }
1768                Some(b'|') => {
1769                    output.push(b'|');
1770                    idx += 1;
1771                }
1772                Some(b'~') => {
1773                    output.push(b'~');
1774                    idx += 1;
1775                }
1776                Some(b'a') => {
1777                    output.push(0x7);
1778                    idx += 1;
1779                }
1780                Some(b'b') => {
1781                    output.push(0x8);
1782                    idx += 1;
1783                }
1784                Some(b'e') => {
1785                    output.push(0x1b);
1786                    idx += 1;
1787                }
1788                Some(b'f') => {
1789                    output.push(0xc);
1790                    idx += 1;
1791                }
1792                Some(b'n') => {
1793                    output.push(b'\n');
1794                    idx += 1;
1795                }
1796                Some(b'r') => {
1797                    output.push(b'\r');
1798                    idx += 1;
1799                }
1800                Some(b't') => {
1801                    output.push(b'\t');
1802                    idx += 1;
1803                }
1804                Some(b'0') => {
1805                    output.push(b'\0');
1806                    idx += 1;
1807                }
1808                Some(b'x') => {
1809                    // Hex escape: \xHH (exactly 2 hex digits)
1810                    match parse_hex_escape(bytes, idx, span) {
1811                        Ok((byte_val, new_idx)) => {
1812                            output.push(byte_val);
1813                            idx = new_idx;
1814                        }
1815                        Err(err) => {
1816                            error = error.or(Some(err));
1817                            break 'us_loop;
1818                        }
1819                    }
1820                }
1821                Some(b'u') => {
1822                    // Unicode escape: \u{XXXXXX} (1-6 hex digits, max 0x10FFFF)
1823                    match parse_unicode_escape(bytes, idx, span) {
1824                        Ok((ch, new_idx)) => {
1825                            let mut ch_buf = [0u8; 4];
1826                            output.extend(ch.encode_utf8(&mut ch_buf).as_bytes());
1827                            idx = new_idx;
1828                        }
1829                        Err(err) => {
1830                            error = error.or(Some(err));
1831                            break 'us_loop;
1832                        }
1833                    }
1834                }
1835
1836                Some(other) => {
1837                    error = error.or(Some(ParseError::InvalidLiteral(
1838                        format!("unrecognized escape sequence '\\{}'", *other as char),
1839                        "string".into(),
1840                        Span::new(span.start + idx, span.end),
1841                    )));
1842                    break 'us_loop;
1843                }
1844                None => {
1845                    error = error.or(Some(ParseError::InvalidLiteral(
1846                        "incomplete escape sequence after '\\'".into(),
1847                        "string".into(),
1848                        Span::new(span.end.saturating_sub(1), span.end),
1849                    )));
1850                    break 'us_loop;
1851                }
1852            }
1853        } else {
1854            output.push(bytes[idx]);
1855            idx += 1;
1856        }
1857    }
1858
1859    (output, error)
1860}
1861
1862pub fn unescape_unquote_string(bytes: &[u8], span: Span) -> (String, Option<ParseError>) {
1863    if bytes.starts_with(b"\"") {
1864        // Needs unescaping
1865        let bytes = trim_quotes(bytes);
1866
1867        let (bytes, err) = unescape_string(bytes, span);
1868
1869        if let Ok(token) = String::from_utf8(bytes) {
1870            (token, err)
1871        } else {
1872            (String::new(), Some(ParseError::Expected("string", span)))
1873        }
1874    } else {
1875        let bytes = trim_quotes(bytes);
1876
1877        if let Ok(token) = String::from_utf8(bytes.into()) {
1878            (token, None)
1879        } else {
1880            (String::new(), Some(ParseError::Expected("string", span)))
1881        }
1882    }
1883}
1884
1885fn check_string_no_trailing_tokens(
1886    bytes: &[u8],
1887    span: Span,
1888    opening_quote_pos: usize,
1889    quote: u8,
1890) -> Result<(), ParseError> {
1891    let pos = bytes
1892        .iter()
1893        .rposition(|ch| *ch == quote)
1894        .expect("string begins with quote");
1895    if pos == bytes.len() - 1 {
1896        Ok(())
1897    } else if pos == opening_quote_pos {
1898        // this may look like an error, but it's not:
1899        // some code, like completions, requires allowing
1900        // unterminated strings at this stage.
1901        Ok(())
1902    } else {
1903        let span = Span::new(span.start + pos + 1, span.end);
1904        Err(ParseError::ExtraTokensAfterClosingDelimiter(span))
1905    }
1906}
1907
1908pub fn parse_string(working_set: &mut StateWorkingSet, span: Span) -> Expression {
1909    trace!("parsing: string");
1910
1911    let bytes = working_set.get_span_contents(span);
1912
1913    if bytes.is_empty() {
1914        working_set.error(ParseError::Expected("String", span));
1915        return Expression::garbage(working_set, span);
1916    }
1917
1918    // Check for bare word interpolation
1919    if is_bare_string_interpolation(bytes) {
1920        return parse_string_interpolation(working_set, span);
1921    }
1922
1923    // Check for unbalanced quotes:
1924    for quote in [b'\"', b'\''] {
1925        if bytes[0] == quote
1926            && let Err(err) = check_string_no_trailing_tokens(bytes, span, 0, quote)
1927        {
1928            working_set.error(err);
1929            return garbage(working_set, span);
1930        }
1931    }
1932
1933    let (s, err) = unescape_unquote_string(bytes, span);
1934    if let Some(err) = err {
1935        working_set.error(err);
1936    }
1937
1938    Expression::new(working_set, Expr::String(s), span, Type::String)
1939}
1940
1941fn is_quoted(bytes: &[u8]) -> bool {
1942    matches!(bytes, [b'\'', .., b'\''] | [b'"', .., b'"'])
1943}
1944
1945pub fn parse_string_strict(working_set: &mut StateWorkingSet, span: Span) -> Expression {
1946    trace!("parsing: string, with required delimiters");
1947
1948    let bytes = working_set.get_span_contents(span);
1949
1950    // Check for unbalanced quotes:
1951    {
1952        let bytes = if bytes.starts_with(b"$") {
1953            &bytes[1..]
1954        } else {
1955            bytes
1956        };
1957        if bytes.starts_with(b"\"") && (bytes.len() == 1 || !bytes.ends_with(b"\"")) {
1958            let open = ParseError::opener_span(span, 1);
1959            working_set.error(ParseError::unclosed("\"", open, span));
1960            return garbage(working_set, span);
1961        }
1962        if bytes.starts_with(b"\'") && (bytes.len() == 1 || !bytes.ends_with(b"\'")) {
1963            let open = ParseError::opener_span(span, 1);
1964            working_set.error(ParseError::unclosed("\'", open, span));
1965            return garbage(working_set, span);
1966        }
1967        if bytes.starts_with(b"r#") && (bytes.len() == 1 || !bytes.ends_with(b"#")) {
1968            let open = ParseError::opener_span(span, 2);
1969            working_set.error(ParseError::unclosed("r#", open, span));
1970            return garbage(working_set, span);
1971        }
1972    }
1973
1974    let (bytes, quoted) = if (bytes.starts_with(b"\"") && bytes.ends_with(b"\"") && bytes.len() > 1)
1975        || (bytes.starts_with(b"\'") && bytes.ends_with(b"\'") && bytes.len() > 1)
1976    {
1977        (&bytes[1..(bytes.len() - 1)], true)
1978    } else if (bytes.starts_with(b"$\"") && bytes.ends_with(b"\"") && bytes.len() > 2)
1979        || (bytes.starts_with(b"$\'") && bytes.ends_with(b"\'") && bytes.len() > 2)
1980    {
1981        (&bytes[2..(bytes.len() - 1)], true)
1982    } else {
1983        (bytes, false)
1984    };
1985
1986    if let Ok(token) = String::from_utf8(bytes.into()) {
1987        trace!("-- found {token}");
1988
1989        if quoted {
1990            Expression::new(working_set, Expr::String(token), span, Type::String)
1991        } else if token.contains(' ') {
1992            working_set.error(ParseError::Expected("string", span));
1993
1994            garbage(working_set, span)
1995        } else {
1996            Expression::new(working_set, Expr::String(token), span, Type::String)
1997        }
1998    } else {
1999        working_set.error(ParseError::Expected("string", span));
2000        garbage(working_set, span)
2001    }
2002}