Oregami
Repositories/oxedyne/fe2o3

oxedyne/fe2o3/fe2o3_text/tests/annealer_corpus/nu_parser.rs

274 KiB, 1 run

created by r1870400018:11760, which is this file's identity for as long as the history lasts, whatever it is later renamed to

download · who wrote it · its history

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