1use nu_protocol::{ParseError, Span};
2
3#[path = "delimiter_diagnostics.rs"]
4mod delimiter_diagnostics;
5use delimiter_diagnostics::{
6 closing_delimiter_str, quote_delimiter_str, unbalanced_closer, unclosed_from_open,
7};
8
9#[derive(Debug, PartialEq, Eq, Clone, Copy)]
10pub enum TokenContents {
11 Item,
12 Comment,
13 Pipe,
14 PipePipe,
15 AssignmentOperator,
16 ErrGreaterPipe,
17 OutErrGreaterPipe,
18 Semicolon,
19 OutGreaterThan,
20 OutGreaterGreaterThan,
21 ErrGreaterThan,
22 ErrGreaterGreaterThan,
23 OutErrGreaterThan,
24 OutErrGreaterGreaterThan,
25 Eol,
26}
27
28#[derive(Debug, PartialEq, Eq)]
29pub struct Token {
30 pub contents: TokenContents,
31 pub span: Span,
32}
33
34impl Token {
35 pub fn new(contents: TokenContents, span: Span) -> Token {
36 Token { contents, span }
37 }
38}
39
40#[derive(Clone, Copy, Debug)]
41pub enum BlockKind {
42 Paren,
43 CurlyBracket,
44 SquareBracket,
45 AngleBracket,
46}
47
48#[derive(Clone, Copy, Debug)]
54pub(crate) struct OpenFrame {
55 pub kind: BlockKind,
56 pub open_span: Span,
57}
58
59fn is_item_terminator(
63 block_level: &[OpenFrame],
64 c: u8,
65 additional_whitespace: &[u8],
66 special_tokens: &[u8],
67) -> bool {
68 block_level.is_empty()
69 && (c == b' '
70 || c == b'\t'
71 || c == b'\n'
72 || c == b'\r'
73 || c == b'|'
74 || c == b';'
75 || additional_whitespace.contains(&c)
76 || special_tokens.contains(&c))
77}
78
79pub fn is_assignment_operator(bytes: &[u8]) -> bool {
82 matches!(bytes, b"=" | b"+=" | b"++=" | b"-=" | b"*=" | b"/=")
83}
84
85fn is_special_item(block_level: &[OpenFrame], c: u8, special_tokens: &[u8]) -> bool {
90 block_level.is_empty() && special_tokens.contains(&c)
91}
92
93#[derive(Clone, Copy, Debug)]
99struct CloserLabelHint {
100 open_span: Span,
102 expected_span: Span,
104}
105
106fn continues_onto_next_line(c: u8) -> bool {
109 matches!(
110 c,
111 b'|' | b'{' | b'(' | b'[' | b',' | b':' | b'+' | b'-' | b'*' | b'/' | b'=' | b'.'
112 )
113}
114
115pub(crate) fn interp_subexpr_step<T>(stack: &mut Vec<(u8, T)>, byte: u8, open: T) -> bool {
126 match stack.last() {
127 Some(&(expected, _)) if expected != b')' => {
128 if expected == b'"' && byte == b'\\' {
129 return true;
130 }
131 if byte == expected {
132 stack.pop();
133 }
134 }
135 _ => match byte {
136 b'\'' | b'"' | b'`' => stack.push((byte, open)),
137 b'(' => stack.push((b')', open)),
138 b')' => {
139 stack.pop();
140 }
141 _ => {}
142 },
143 }
144 false
145}
146
147pub fn lex_item(
148 input: &[u8],
149 curr_offset: &mut usize,
150 span_offset: usize,
151 additional_whitespace: &[u8],
152 special_tokens: &[u8],
153 in_signature: bool,
154) -> (Token, Option<ParseError>) {
155 let mut quote_start: Option<(u8, Span)> = None;
157
158 let mut quote_is_interp = false;
162
163 let mut interp_expr_level: Vec<(u8, Span)> = vec![];
169
170 let mut in_comment = false;
171
172 let token_start = *curr_offset;
173
174 let mut block_level: Vec<OpenFrame> = vec![];
176
177 let mut closer_label_hint: Option<CloserLabelHint> = None;
181
182 let mut at_line_start = true;
184 let mut prev_line_continue = false;
186 let mut last_sig_char: Option<u8> = None;
187
188 let mut previous_char = None;
204 while let Some(c) = input.get(*curr_offset) {
205 let c = *c;
206
207 if let Some((start, open_span)) = quote_start {
208 if !interp_expr_level.is_empty() {
209 let open = Span::new(span_offset + *curr_offset, span_offset + *curr_offset + 1);
214 if interp_subexpr_step(&mut interp_expr_level, c, open)
215 && input.get(*curr_offset + 1).is_some()
216 {
217 *curr_offset += 2;
220 previous_char = Some(c);
221 at_line_start = false;
222 continue;
223 }
224 last_sig_char = Some(c);
225 at_line_start = false;
226 *curr_offset += 1;
227 previous_char = Some(c);
228 continue;
229 }
230 if c == b'\\' && start == b'"' {
232 if input.get(*curr_offset + 1).is_some() {
234 *curr_offset += 2;
236 previous_char = Some(c);
237 at_line_start = false;
238 continue;
239 } else {
240 let span = Span::new(span_offset + token_start, span_offset + *curr_offset);
241 let end_span = if span.end > span.start {
242 Span::new(span.end - 1, span.end)
243 } else {
244 span
245 };
246
247 return (
248 Token {
249 contents: TokenContents::Item,
250 span,
251 },
252 Some(unclosed_from_open(
253 input,
254 span_offset,
255 quote_delimiter_str(start),
256 open_span,
257 end_span,
258 )),
259 );
260 }
261 }
262 if c == start {
265 quote_start = None;
267 } else if quote_is_interp && c == b'(' {
268 interp_expr_level.push((
273 b')',
274 Span::new(span_offset + *curr_offset, span_offset + *curr_offset + 1),
275 ));
276 }
277 last_sig_char = Some(c);
278 at_line_start = false;
279 } else if c == b'#' && !in_comment {
280 in_comment = previous_char
282 .map(char::from)
283 .map(char::is_whitespace)
284 .unwrap_or(true);
285 } else if c == b'\n' || c == b'\r' {
286 in_comment = false;
287 if is_item_terminator(&block_level, c, additional_whitespace, special_tokens) {
288 break;
289 }
290 let is_newline_end = c == b'\n' || input.get(*curr_offset + 1) != Some(&b'\n');
293 if is_newline_end {
294 prev_line_continue = last_sig_char.is_some_and(continues_onto_next_line);
295 at_line_start = true;
296 last_sig_char = None;
297 }
298 } else if in_comment {
299 if is_item_terminator(&block_level, c, additional_whitespace, special_tokens) {
300 break;
301 }
302 } else if is_special_item(&block_level, c, special_tokens) && token_start == *curr_offset {
303 *curr_offset += 1;
304 break;
305 } else if c == b'\'' || c == b'"' || c == b'`' {
306 let open_span = Span::new(span_offset + *curr_offset, span_offset + *curr_offset + 1);
307 quote_start = Some((c, open_span));
308 quote_is_interp = c != b'`' && previous_char == Some(b'$');
311 last_sig_char = Some(c);
312 at_line_start = false;
313 } else if c == b'[' {
314 let open_span = Span::new(span_offset + *curr_offset, span_offset + *curr_offset + 1);
315 block_level.push(OpenFrame {
316 kind: BlockKind::SquareBracket,
317 open_span,
318 });
319 last_sig_char = Some(c);
320 at_line_start = false;
321 } else if c == b'<' && in_signature {
322 let open_span = Span::new(span_offset + *curr_offset, span_offset + *curr_offset + 1);
323 block_level.push(OpenFrame {
324 kind: BlockKind::AngleBracket,
325 open_span,
326 });
327 last_sig_char = Some(c);
328 at_line_start = false;
329 } else if c == b'>' && in_signature {
330 if let Some(OpenFrame {
331 kind: BlockKind::AngleBracket,
332 ..
333 }) = block_level.last()
334 {
335 let _ = block_level.pop();
336 }
337 last_sig_char = Some(c);
338 at_line_start = false;
339 } else if c == b']' {
340 if let Some(OpenFrame {
342 kind: BlockKind::SquareBracket,
343 ..
344 }) = block_level.last()
345 {
346 let _ = block_level.pop();
347 } else if !block_level.is_empty() {
348 *curr_offset += 1;
349 let span = Span::new(span_offset + token_start, span_offset + *curr_offset);
350 let close_span = Span::new(span.end - 1, span.end);
351 return (
352 Token {
353 contents: TokenContents::Item,
354 span,
355 },
356 Some(unbalanced_closer("]", "[", &block_level, close_span)),
357 );
358 }
359 last_sig_char = Some(c);
360 at_line_start = false;
361 } else if c == b'{' {
362 if closer_label_hint.is_none()
366 && let Some(frame) = block_level.last()
367 && matches!(frame.kind, BlockKind::SquareBracket)
368 {
369 closer_label_hint = Some(CloserLabelHint {
370 open_span: frame.open_span,
371 expected_span: Span::new(
372 span_offset + *curr_offset,
373 span_offset + *curr_offset + 1,
374 ),
375 });
376 }
377 let open_span = Span::new(span_offset + *curr_offset, span_offset + *curr_offset + 1);
378 block_level.push(OpenFrame {
379 kind: BlockKind::CurlyBracket,
380 open_span,
381 });
382 last_sig_char = Some(c);
383 at_line_start = false;
384 } else if c == b'}' {
385 if let Some(OpenFrame {
387 kind: BlockKind::CurlyBracket,
388 ..
389 }) = block_level.last()
390 {
391 let _ = block_level.pop();
392 } else {
393 *curr_offset += 1;
394 let span = Span::new(span_offset + token_start, span_offset + *curr_offset);
395 let close_span = Span::new(span.end - 1, span.end);
396 return (
397 Token {
398 contents: TokenContents::Item,
399 span,
400 },
401 Some(unbalanced_closer("}", "{", &block_level, close_span)),
402 );
403 }
404 last_sig_char = Some(c);
405 at_line_start = false;
406 } else if c == b'(' {
407 let open_span = Span::new(span_offset + *curr_offset, span_offset + *curr_offset + 1);
408 block_level.push(OpenFrame {
409 kind: BlockKind::Paren,
410 open_span,
411 });
412 last_sig_char = Some(c);
413 at_line_start = false;
414 } else if c == b')' {
415 if let Some(OpenFrame {
417 kind: BlockKind::Paren,
418 ..
419 }) = block_level.last()
420 {
421 let _ = block_level.pop();
422 } else {
423 *curr_offset += 1;
424 let span = Span::new(span_offset + token_start, span_offset + *curr_offset);
425 let close_span = Span::new(span.end - 1, span.end);
426 return (
427 Token {
428 contents: TokenContents::Item,
429 span,
430 },
431 Some(unbalanced_closer(")", "(", &block_level, close_span)),
432 );
433 }
434 last_sig_char = Some(c);
435 at_line_start = false;
436 } else if c == b'r' && input.get(*curr_offset + 1) == Some(b'#').as_ref() {
437 let lex_result = lex_raw_string(input, curr_offset, span_offset);
439 let span = Span::new(span_offset + token_start, span_offset + *curr_offset);
440 if let Err(e) = lex_result {
441 return (
442 Token {
443 contents: TokenContents::Item,
444 span,
445 },
446 Some(e),
447 );
448 }
449 last_sig_char = Some(b'#');
450 at_line_start = false;
451 } else if c == b'|' && is_redirection(&input[token_start..*curr_offset]) {
452 *curr_offset += 1;
454 break;
455 } else if is_item_terminator(&block_level, c, additional_whitespace, special_tokens) {
456 break;
457 } else if !c.is_ascii_whitespace() {
458 if c == b'|'
466 && at_line_start
467 && !prev_line_continue
468 && closer_label_hint.is_none()
469 && let Some(frame) = block_level
470 .iter()
471 .rev()
472 .find(|f| matches!(f.kind, BlockKind::CurlyBracket))
473 {
474 closer_label_hint = Some(CloserLabelHint {
475 open_span: frame.open_span,
476 expected_span: Span::new(
477 span_offset + *curr_offset,
478 span_offset + *curr_offset + 1,
479 ),
480 });
481 }
482 last_sig_char = Some(c);
483 at_line_start = false;
484 } else if at_line_start && (c == b' ' || c == b'\t') {
485 } else {
487 at_line_start = false;
488 }
489
490 *curr_offset += 1;
491 previous_char = Some(c);
492 }
493
494 let span = Span::new(span_offset + token_start, span_offset + *curr_offset);
495 let end_span = if span.end > span.start {
496 Span::new(span.end - 1, span.end)
497 } else {
498 span
499 };
500
501 if let Some((closer, open_span)) = interp_expr_level.first() {
506 let closer_str = match closer {
507 b')' => ")",
508 delim => quote_delimiter_str(*delim),
509 };
510 return (
511 Token {
512 contents: TokenContents::Item,
513 span,
514 },
515 Some(unclosed_from_open(
516 input,
517 span_offset,
518 closer_str,
519 *open_span,
520 end_span,
521 )),
522 );
523 }
524
525 if let Some((delim, open_span)) = quote_start {
526 return (
530 Token {
531 contents: TokenContents::Item,
532 span,
533 },
534 Some(unclosed_from_open(
535 input,
536 span_offset,
537 quote_delimiter_str(delim),
538 open_span,
539 end_span,
540 )),
541 );
542 }
543
544 if let Some(frame) = block_level.last() {
548 let (label_open, label_end) = closer_label_hint
549 .filter(|h| h.open_span == frame.open_span)
550 .map(|h| (h.open_span, h.expected_span))
551 .unwrap_or((frame.open_span, end_span));
552
553 let cause = unclosed_from_open(
554 input,
555 span_offset,
556 closing_delimiter_str(frame.kind),
557 label_open,
558 label_end,
559 );
560
561 return (
562 Token {
563 contents: TokenContents::Item,
564 span,
565 },
566 Some(cause),
567 );
568 }
569
570 if *curr_offset - token_start == 0 {
572 return (
573 Token {
574 contents: TokenContents::Item,
575 span,
576 },
577 Some(ParseError::UnexpectedEof("command".to_string(), span)),
578 );
579 }
580
581 let mut err = None;
582 let output = match &input[(span.start - span_offset)..(span.end - span_offset)] {
583 bytes if is_assignment_operator(bytes) => Token {
584 contents: TokenContents::AssignmentOperator,
585 span,
586 },
587 b"out>" | b"o>" => Token {
588 contents: TokenContents::OutGreaterThan,
589 span,
590 },
591 b"out>>" | b"o>>" => Token {
592 contents: TokenContents::OutGreaterGreaterThan,
593 span,
594 },
595 b"out>|" | b"o>|" => {
596 err = Some(ParseError::Expected(
597 "`|`. Redirecting stdout to a pipe is the same as normal piping.",
598 span,
599 ));
600 Token {
601 contents: TokenContents::Pipe,
603 span,
604 }
605 }
606 b"err>" | b"e>" => Token {
607 contents: TokenContents::ErrGreaterThan,
608 span,
609 },
610 b"err>>" | b"e>>" => Token {
611 contents: TokenContents::ErrGreaterGreaterThan,
612 span,
613 },
614 b"err>|" | b"e>|" => Token {
615 contents: TokenContents::ErrGreaterPipe,
616 span,
617 },
618 b"out+err>" | b"err+out>" | b"o+e>" | b"e+o>" => Token {
619 contents: TokenContents::OutErrGreaterThan,
620 span,
621 },
622 b"out+err>>" | b"err+out>>" | b"o+e>>" | b"e+o>>" => Token {
623 contents: TokenContents::OutErrGreaterGreaterThan,
624 span,
625 },
626 b"out+err>|" | b"err+out>|" | b"o+e>|" | b"e+o>|" => Token {
627 contents: TokenContents::OutErrGreaterPipe,
628 span,
629 },
630 b"&&" => {
631 err = Some(ParseError::ShellAndAnd(span));
632 Token {
633 contents: TokenContents::Pipe,
635 span,
636 }
637 }
638 b"2>" => {
639 err = Some(ParseError::ShellErrRedirect(span));
640 Token {
641 contents: TokenContents::ErrGreaterThan,
643 span,
644 }
645 }
646 b"2>&1" => {
647 err = Some(ParseError::ShellOutErrRedirect(span));
648 Token {
649 contents: TokenContents::Pipe,
651 span,
652 }
653 }
654 _ => Token {
655 contents: TokenContents::Item,
656 span,
657 },
658 };
659 (output, err)
660}
661
662fn lex_raw_string(
663 input: &[u8],
664 curr_offset: &mut usize,
665 span_offset: usize,
666) -> Result<(), ParseError> {
667 let mut prefix_sharp_cnt = 0;
677 let start = *curr_offset;
678 while let Some(b'#') = input.get(start + prefix_sharp_cnt + 1) {
679 prefix_sharp_cnt += 1;
680 }
681
682 *curr_offset += prefix_sharp_cnt + 1;
690 if input.get(*curr_offset) != Some(&b'\'') {
692 return Err(ParseError::Expected(
693 "'",
694 Span::new(span_offset + *curr_offset, span_offset + *curr_offset + 1),
695 ));
696 }
697
698 *curr_offset += 1;
699 let mut matches = false;
700 while let Some(ch) = input.get(*curr_offset) {
701 if *ch == b'#' {
703 let start_ch = input[*curr_offset - prefix_sharp_cnt];
704 let postfix = &input[*curr_offset - prefix_sharp_cnt + 1..=*curr_offset];
705 if start_ch == b'\'' && postfix.iter().all(|x| *x == b'#') {
706 matches = true;
707 break;
708 }
709 }
710 *curr_offset += 1
711 }
712 if !matches {
713 let mut expected = '\''.to_string();
714 expected.push_str(&"#".repeat(prefix_sharp_cnt));
715 return Err(ParseError::UnexpectedEof(
716 expected,
717 Span::new(span_offset + *curr_offset - 1, span_offset + *curr_offset),
718 ));
719 }
720 Ok(())
721}
722
723pub fn lex_signature(
724 input: &[u8],
725 span_offset: usize,
726 additional_whitespace: &[u8],
727 special_tokens: &[u8],
728 skip_comment: bool,
729) -> (Vec<Token>, Option<ParseError>) {
730 let mut state = LexState {
731 input,
732 output: Vec::new(),
733 error: None,
734 span_offset,
735 };
736 lex_internal(
737 &mut state,
738 additional_whitespace,
739 special_tokens,
740 skip_comment,
741 true,
742 None,
743 );
744 (state.output, state.error)
745}
746
747#[derive(Debug)]
748pub struct LexState<'a> {
749 pub input: &'a [u8],
750 pub output: Vec<Token>,
751 pub error: Option<ParseError>,
752 pub span_offset: usize,
753}
754
755pub fn lex_n_tokens(
761 state: &mut LexState,
762 additional_whitespace: &[u8],
763 special_tokens: &[u8],
764 skip_comment: bool,
765 max_tokens: usize,
766) -> isize {
767 let n_tokens = state.output.len();
768 lex_internal(
769 state,
770 additional_whitespace,
771 special_tokens,
772 skip_comment,
773 false,
774 Some(max_tokens),
775 );
776 let tokens_n_diff = (state.output.len() as isize) - (n_tokens as isize);
779 let next_offset = state.output.last().map(|token| token.span.end);
780 if let Some(next_offset) = next_offset {
781 state.input = &state.input[next_offset - state.span_offset..];
782 state.span_offset = next_offset;
783 }
784 tokens_n_diff
785}
786
787pub fn lex(
788 input: &[u8],
789 span_offset: usize,
790 additional_whitespace: &[u8],
791 special_tokens: &[u8],
792 skip_comment: bool,
793) -> (Vec<Token>, Option<ParseError>) {
794 let mut state = LexState {
795 input,
796 output: Vec::new(),
797 error: None,
798 span_offset,
799 };
800 lex_internal(
801 &mut state,
802 additional_whitespace,
803 special_tokens,
804 skip_comment,
805 false,
806 None,
807 );
808 (state.output, state.error)
809}
810
811fn lex_internal(
812 state: &mut LexState,
813 additional_whitespace: &[u8],
814 special_tokens: &[u8],
815 skip_comment: bool,
816 in_signature: bool,
818 max_tokens: Option<usize>,
819) {
820 let initial_output_len = state.output.len();
821
822 let mut curr_offset = 0;
823
824 let mut is_complete = true;
825 while let Some(c) = state.input.get(curr_offset) {
826 if max_tokens
827 .is_some_and(|max_tokens| state.output.len() >= initial_output_len + max_tokens)
828 {
829 break;
830 }
831 let c = *c;
832 if c == b'|' {
833 let idx = curr_offset;
835 let prev_idx = idx;
836 curr_offset += 1;
837
838 if let Some(c) = state.input.get(curr_offset)
840 && *c == b'|'
841 {
842 let idx = curr_offset;
843 curr_offset += 1;
844 state.output.push(Token::new(
845 TokenContents::PipePipe,
846 Span::new(state.span_offset + prev_idx, state.span_offset + idx + 1),
847 ));
848 continue;
849 }
850
851 if let Some(prev) = state.output.last_mut() {
856 match prev.contents {
857 TokenContents::Eol => {
858 *prev = Token::new(
859 TokenContents::Pipe,
860 Span::new(state.span_offset + idx, state.span_offset + idx + 1),
861 );
862 let mut offset = 2;
868 while state.output.len() > offset {
869 let index = state.output.len() - offset;
870 if state.output[index].contents == TokenContents::Comment
871 && state.output[index - 1].contents == TokenContents::Eol
872 {
873 state.output.remove(index - 1);
874 offset += 1;
875 } else {
876 break;
877 }
878 }
879 }
880 _ => {
881 state.output.push(Token::new(
882 TokenContents::Pipe,
883 Span::new(state.span_offset + idx, state.span_offset + idx + 1),
884 ));
885 }
886 }
887 } else {
888 state.output.push(Token::new(
889 TokenContents::Pipe,
890 Span::new(state.span_offset + idx, state.span_offset + idx + 1),
891 ));
892 }
893
894 is_complete = false;
895 } else if c == b';' {
896 if !is_complete && state.error.is_none() {
899 state.error = Some(ParseError::ExtraTokens(Span::new(
900 curr_offset,
901 curr_offset + 1,
902 )));
903 }
904 let idx = curr_offset;
905 curr_offset += 1;
906 state.output.push(Token::new(
907 TokenContents::Semicolon,
908 Span::new(state.span_offset + idx, state.span_offset + idx + 1),
909 ));
910 } else if c == b'\r' {
911 curr_offset += 1;
913 } else if c == b'\n' {
914 let idx = curr_offset;
916 curr_offset += 1;
917 if !additional_whitespace.contains(&c) {
918 state.output.push(Token::new(
919 TokenContents::Eol,
920 Span::new(state.span_offset + idx, state.span_offset + idx + 1),
921 ));
922 }
923 } else if c == b'#' {
924 let mut start = curr_offset;
927
928 while let Some(input) = state.input.get(curr_offset) {
929 if *input == b'\n' {
930 if !skip_comment {
931 state.output.push(Token::new(
932 TokenContents::Comment,
933 Span::new(state.span_offset + start, state.span_offset + curr_offset),
934 ));
935 }
936 start = curr_offset;
937
938 break;
939 } else {
940 curr_offset += 1;
941 }
942 }
943 if start != curr_offset && !skip_comment {
944 state.output.push(Token::new(
945 TokenContents::Comment,
946 Span::new(state.span_offset + start, state.span_offset + curr_offset),
947 ));
948 }
949 } else if c == b' ' || c == b'\t' || additional_whitespace.contains(&c) {
950 curr_offset += 1;
952 } else {
953 let (token, err) = lex_item(
954 state.input,
955 &mut curr_offset,
956 state.span_offset,
957 additional_whitespace,
958 special_tokens,
959 in_signature,
960 );
961 if state.error.is_none() {
962 state.error = err;
963 }
964 is_complete = true;
965 state.output.push(token);
966 }
967 }
968}
969
970fn is_redirection(token: &[u8]) -> bool {
972 matches!(
973 token,
974 b"o>" | b"out>" | b"e>" | b"err>" | b"o+e>" | b"e+o>" | b"out+err>" | b"err+out>"
975 )
976}