1use super::*;
2use smallvec::SmallVec;
3
4#[derive(Debug, Clone, Copy)]
5enum ForHeaderSurface {
6 In {
7 in_span: Option<Span>,
8 },
9 Paren {
10 left_paren_span: Span,
11 right_paren_span: Span,
12 },
13}
14
15#[derive(Debug, Clone, Copy)]
16struct ZshCaseScanState {
17 position: Position,
18 paren_depth: usize,
19 bracket_depth: usize,
20 brace_depth: usize,
21 in_single: bool,
22 in_double: bool,
23 in_backtick: bool,
24 escaped: bool,
25}
26
27impl ZshCaseScanState {
28 fn new(position: Position) -> Self {
29 Self {
30 position,
31 paren_depth: 0,
32 bracket_depth: 0,
33 brace_depth: 0,
34 in_single: false,
35 in_double: false,
36 in_backtick: false,
37 escaped: false,
38 }
39 }
40}
41
42impl<'a> Parser<'a> {
43 fn apply_word_command_effects(&mut self, name: &Word, args: &[Word]) {
44 let Some(name) = self.literal_word_text(name) else {
45 return;
46 };
47
48 match name.as_str() {
49 "shopt" => {
50 let mut toggle = None;
51 for arg in args {
52 let Some(arg) = self.literal_word_text(arg) else {
53 continue;
54 };
55 match arg.as_str() {
56 "-s" => toggle = Some(true),
57 "-u" => toggle = Some(false),
58 "expand_aliases" => {
59 if let Some(toggle) = toggle {
60 self.expand_aliases = toggle;
61 }
62 }
63 _ => {}
64 }
65 }
66 }
67 "alias" => {
68 for arg in args {
69 let Some(arg) = self.literal_word_text(arg) else {
70 continue;
71 };
72 if arg == "--" {
73 continue;
74 }
75 let Some((alias_name, value)) = arg.split_once('=') else {
76 continue;
77 };
78 self.aliases
79 .insert(alias_name.to_string(), self.compile_alias_definition(value));
80 }
81 }
82 "unalias" => {
83 for arg in args {
84 let Some(arg) = self.literal_word_text(arg) else {
85 continue;
86 };
87 match arg.as_str() {
88 "--" => {}
89 "-a" => self.aliases.clear(),
90 _ => {
91 self.aliases.remove(arg.as_str());
92 }
93 }
94 }
95 }
96 _ => {}
97 }
98 }
99
100 fn apply_stmt_effects(&mut self, stmt: &Stmt) {
101 match &stmt.command {
102 AstCommand::Simple(simple) => {
103 self.apply_word_command_effects(&simple.name, &simple.args)
104 }
105 AstCommand::Binary(binary) if matches!(binary.op, BinaryOp::And | BinaryOp::Or) => {
106 self.apply_stmt_effects(&binary.left);
107 self.apply_stmt_effects(&binary.right);
108 }
109 _ => {}
110 }
111 }
112
113 fn apply_stmt_list_effects(&mut self, stmts: &[Stmt]) {
114 for stmt in stmts {
115 self.apply_stmt_effects(stmt);
116 }
117 }
118
119 fn parse_command_list_required(&mut self) -> Result<Vec<Stmt>> {
120 self.parse_command_list()?
121 .ok_or_else(|| self.error("expected command"))
122 }
123
124 fn skip_command_separators(&mut self) -> Result<()> {
125 loop {
126 self.skip_newlines()?;
127 if self.at(TokenKind::Semicolon) {
128 self.advance();
129 continue;
130 }
131 break;
132 }
133 Ok(())
134 }
135
136 fn is_recovery_separator(kind: TokenKind) -> bool {
137 matches!(
138 kind,
139 TokenKind::Newline
140 | TokenKind::Semicolon
141 | TokenKind::Background
142 | TokenKind::BackgroundPipe
143 | TokenKind::BackgroundBang
144 | TokenKind::And
145 | TokenKind::Or
146 | TokenKind::Pipe
147 | TokenKind::DoubleSemicolon
148 | TokenKind::SemiAmp
149 | TokenKind::SemiPipe
150 | TokenKind::DoubleSemiAmp
151 )
152 }
153
154 fn recover_to_command_boundary(&mut self, failed_offset: usize) -> bool {
155 let mut advanced = false;
156
157 while let Some(kind) = self.current_token_kind {
158 if Self::is_recovery_separator(kind) {
159 while let Some(kind) = self.current_token_kind {
160 if !Self::is_recovery_separator(kind) {
161 break;
162 }
163 self.advance();
164 advanced = true;
165 }
166 break;
167 }
168
169 let before_offset = self.current_span.start.offset;
170 self.advance();
171 advanced = true;
172
173 if self.current_token.is_none() {
174 break;
175 }
176
177 if self.current_span.start.offset > failed_offset
178 && before_offset != self.current_span.start.offset
179 {
180 continue;
181 }
182 }
183
184 advanced
185 }
186
187 fn parse_impl(&mut self) -> ParseResult {
188 let file_span =
189 Span::from_positions(Position::new(), Position::new().advanced_by(self.input));
190 let mut stmts = Vec::new();
191 let mut diagnostics = Vec::new();
192 let mut terminal_error = None;
193
194 while self.current_token.is_some() {
195 let checkpoint = self.current_span.start.offset;
196
197 if let Err(error) = self.tick() {
198 diagnostics.push(self.parse_diagnostic_from_error(error.clone()));
199 terminal_error.get_or_insert(error);
200 break;
201 }
202 if let Err(error) = self.skip_newlines() {
203 diagnostics.push(self.parse_diagnostic_from_error(error.clone()));
204 terminal_error.get_or_insert(error);
205 break;
206 }
207 if let Err(error) = self.check_error_token() {
208 diagnostics.push(self.parse_diagnostic_from_error(error.clone()));
209 let recovered = self.recover_to_command_boundary(checkpoint);
210 if recovered
211 || (self.current_token.is_some()
212 && self.current_span.start.offset < self.input.len())
213 {
214 terminal_error.get_or_insert(error);
215 }
216 if !recovered && terminal_error.is_some() {
217 break;
218 }
219 continue;
220 }
221 if self.current_token.is_none() {
222 break;
223 }
224
225 let command_start = self.current_span.start.offset;
226 match self.parse_command_list_required() {
227 Ok(command_stmts) => {
228 self.apply_stmt_list_effects(&command_stmts);
229 stmts.extend(command_stmts);
230 }
231 Err(error) => {
232 diagnostics.push(self.parse_diagnostic_from_error(error.clone()));
233 let recovered = self.recover_to_command_boundary(command_start);
234 if recovered
235 || (self.current_token.is_some()
236 && self.current_span.start.offset < self.input.len())
237 {
238 terminal_error.get_or_insert(error);
239 }
240 if !recovered && terminal_error.is_some() {
241 break;
242 }
243 }
244 }
245 }
246
247 let mut file = File {
248 body: Self::stmt_seq_with_span(file_span, stmts),
249 span: file_span,
250 };
251 self.attach_comments_to_file(&mut file);
252
253 let status = if terminal_error.is_some() {
254 ParseStatus::Fatal
255 } else if diagnostics.is_empty() {
256 ParseStatus::Clean
257 } else {
258 ParseStatus::Recovered
259 };
260
261 ParseResult {
262 file,
263 diagnostics,
264 status,
265 terminal_error,
266 syntax_facts: std::mem::take(&mut self.syntax_facts),
267 }
268 }
269
270 pub fn parse(mut self) -> ParseResult {
276 self.parse_impl()
277 }
278
279 #[cfg(feature = "benchmarking")]
280 #[doc(hidden)]
281 pub fn parse_with_benchmark_counters(self) -> (ParseResult, ParserBenchmarkCounters) {
282 let mut parser = self.rebuild_with_benchmark_counters();
283 let output = parser.parse_impl();
284 (output, parser.finish_benchmark_counters())
285 }
286
287 fn parse_command_list(&mut self) -> Result<Option<Vec<Stmt>>> {
288 self.tick()?;
289 let mut current = match self.parse_pipeline()? {
290 Some(stmt) => stmt,
291 None => return Ok(None),
292 };
293
294 let mut stmts = Vec::with_capacity(2);
295
296 loop {
297 let (op, terminator, allow_empty_tail) = match self.current_token_kind {
298 Some(TokenKind::And) => (Some(BinaryOp::And), None, false),
299 Some(TokenKind::Or) => (Some(BinaryOp::Or), None, false),
300 Some(TokenKind::Semicolon) => (None, Some(StmtTerminator::Semicolon), true),
301 Some(TokenKind::Background) => (
302 None,
303 Some(StmtTerminator::Background(BackgroundOperator::Plain)),
304 true,
305 ),
306 Some(TokenKind::BackgroundPipe) => (
307 None,
308 Some(StmtTerminator::Background(BackgroundOperator::Pipe)),
309 true,
310 ),
311 Some(TokenKind::BackgroundBang) => (
312 None,
313 Some(StmtTerminator::Background(BackgroundOperator::Bang)),
314 true,
315 ),
316 _ => break,
317 };
318 let operator_span = self.current_span;
319 self.advance();
320
321 self.skip_newlines()?;
322 if allow_empty_tail && self.current_token.is_none() {
323 current.terminator = terminator;
324 current.terminator_span = Some(operator_span);
325 stmts.push(current);
326 return Ok(Some(stmts));
327 }
328
329 if let Some(binary_op) = op {
330 if let Some(right) = self.parse_pipeline()? {
331 current = Self::binary_stmt(current, binary_op, operator_span, right);
332 } else {
333 break;
334 }
335 continue;
336 }
337
338 let Some(terminator) = terminator else {
339 unreachable!("list terminator should be present");
340 };
341 if let Some(next) = self.parse_pipeline()? {
342 current.terminator = Some(terminator);
343 current.terminator_span = Some(operator_span);
344 stmts.push(current);
345 current = next;
346 } else if allow_empty_tail {
347 if self
348 .current_keyword()
349 .is_some_and(Self::is_non_command_keyword)
350 {
351 break;
352 }
353 if matches!(
354 self.current_token_kind,
355 Some(TokenKind::Semicolon | TokenKind::Newline)
356 ) {
357 self.advance();
358 }
359 current.terminator = Some(terminator);
360 current.terminator_span = Some(operator_span);
361 stmts.push(current);
362 return Ok(Some(stmts));
363 } else {
364 break;
365 }
366 }
367
368 stmts.push(current);
369 Ok(Some(stmts))
370 }
371
372 fn parse_pipeline(&mut self) -> Result<Option<Stmt>> {
376 let start_span = self.current_span;
377
378 let negated = self.at(TokenKind::Word) && self.current_word_str() == Some("!");
380 if negated {
381 self.advance();
382 }
383
384 let mut stmt = match self.parse_command()? {
385 Some(cmd) => Self::lower_non_sequence_command_to_stmt(cmd),
386 None => {
387 if negated {
388 return Err(self.error("expected command after !"));
389 }
390 return Ok(None);
391 }
392 };
393
394 let mut saw_pipe = false;
395 while self.at_in_set(PIPE_OPERATOR_TOKENS) {
396 saw_pipe = true;
397 let op = if self.at(TokenKind::PipeBoth) {
398 BinaryOp::PipeAll
399 } else {
400 BinaryOp::Pipe
401 };
402 let operator_span = self.current_span;
403 self.advance();
404 self.skip_newlines()?;
405
406 if let Some(cmd) = self.parse_command()? {
407 let right = Self::lower_non_sequence_command_to_stmt(cmd);
408 stmt = Self::binary_stmt(stmt, op, operator_span, right);
409 } else {
410 return Err(self.error("expected command after |"));
411 }
412 }
413
414 if negated || saw_pipe {
415 stmt.negated = negated;
416 stmt.span = start_span.merge(self.current_span);
417 }
418 Ok(Some(stmt))
419 }
420
421 fn parse_compound_with_redirects(
422 &mut self,
423 parser: impl FnOnce(&mut Self) -> Result<CompoundCommand>,
424 ) -> Result<Option<Command>> {
425 let compound = parser(self)?;
426 let redirects = self.parse_trailing_redirects();
427 Ok(Some(Command::Compound(Box::new(compound), redirects)))
428 }
429
430 fn current_starts_prefix_redirect_compound(&self) -> bool {
431 match self.current_keyword() {
432 Some(Keyword::If)
433 | Some(Keyword::While)
434 | Some(Keyword::Until)
435 | Some(Keyword::Case)
436 | Some(Keyword::Select)
437 | Some(Keyword::Time)
438 | Some(Keyword::Coproc) => true,
439 Some(Keyword::For) => self.dialect == ShellDialect::Zsh,
440 Some(Keyword::Repeat) => self.zsh_short_repeat_enabled(),
441 Some(Keyword::Foreach) => self.zsh_short_loops_enabled(),
442 Some(Keyword::Function) => false,
443 None => matches!(
444 self.current_token_kind,
445 Some(
446 TokenKind::DoubleLeftBracket
447 | TokenKind::DoubleLeftParen
448 | TokenKind::LeftParen
449 | TokenKind::LeftBrace
450 )
451 ),
452 _ => false,
453 }
454 }
455
456 fn parse_prefix_redirected_compound_command(&mut self) -> Result<Option<Command>> {
457 if !self.current_token_kind.is_some_and(Self::is_redirect_kind) {
458 return Ok(None);
459 }
460
461 let checkpoint = self.checkpoint();
462 let mut redirects = self.parse_trailing_redirects();
463 if redirects.is_empty() || !self.current_starts_prefix_redirect_compound() {
464 self.restore(checkpoint);
465 return Ok(None);
466 }
467
468 let Some(mut command) = self.parse_command()? else {
469 self.restore(checkpoint);
470 return Ok(None);
471 };
472
473 match &mut command {
474 Command::Compound(_, trailing) => {
475 redirects.append(trailing);
476 *trailing = redirects;
477 Ok(Some(command))
478 }
479 _ => {
480 self.restore(checkpoint);
481 Ok(None)
482 }
483 }
484 }
485
486 fn classify_flow_control_name(&self, word: &Word) -> Option<FlowControlBuiltinKind> {
487 let name = self.single_literal_word_text(word)?;
488 match name {
489 "break" => Some(FlowControlBuiltinKind::Break),
490 "continue" => Some(FlowControlBuiltinKind::Continue),
491 "return" => Some(FlowControlBuiltinKind::Return),
492 "exit" => Some(FlowControlBuiltinKind::Exit),
493 _ => None,
494 }
495 }
496
497 fn classify_decl_variant_name(&self, word: &Word) -> Option<Name> {
498 let name = self.single_literal_word_text(word)?;
499 match name {
500 "declare" | "local" | "export" | "readonly" | "typeset" => Some(Name::from(name)),
501 _ => None,
502 }
503 }
504
505 fn classify_simple_command(&mut self, command: SimpleCommand) -> Command {
506 let kind = self.classify_flow_control_name(&command.name);
507
508 if let Some(kind) = kind {
509 let SimpleCommand {
510 args,
511 redirects,
512 assignments,
513 span,
514 ..
515 } = command;
516 let mut args = args.into_iter();
517
518 return match kind {
519 FlowControlBuiltinKind::Break => {
520 Command::Builtin(BuiltinCommand::Break(BreakCommand {
521 depth: args.next(),
522 extra_args: args.collect(),
523 redirects,
524 assignments,
525 span,
526 }))
527 }
528 FlowControlBuiltinKind::Continue => {
529 Command::Builtin(BuiltinCommand::Continue(ContinueCommand {
530 depth: args.next(),
531 extra_args: args.collect(),
532 redirects,
533 assignments,
534 span,
535 }))
536 }
537 FlowControlBuiltinKind::Return => {
538 Command::Builtin(BuiltinCommand::Return(ReturnCommand {
539 code: args.next(),
540 extra_args: args.collect(),
541 redirects,
542 assignments,
543 span,
544 }))
545 }
546 FlowControlBuiltinKind::Exit => {
547 Command::Builtin(BuiltinCommand::Exit(ExitCommand {
548 code: args.next(),
549 extra_args: args.collect(),
550 redirects,
551 assignments,
552 span,
553 }))
554 }
555 };
556 }
557
558 if let Some(variant) = self.classify_decl_variant_name(&command.name) {
559 let SimpleCommand {
560 name,
561 args,
562 redirects,
563 assignments,
564 span,
565 } = command;
566 return Command::Decl(Box::new(DeclClause {
567 variant,
568 variant_span: name.span,
569 operands: self.classify_decl_operands(args),
570 redirects,
571 assignments,
572 span,
573 }));
574 }
575
576 Command::Simple(command)
577 }
578
579 fn is_operand_like_double_paren_token(token: &LexedToken<'_>) -> bool {
580 match token.kind {
581 TokenKind::LiteralWord | TokenKind::QuotedWord => true,
582 TokenKind::Word => token.word_string().is_some_and(|text| {
583 !text.chars().all(|ch| ch.is_ascii_punctuation())
584 && !Self::word_contains_obvious_arithmetic_punctuation(&text)
585 }),
586 _ => false,
587 }
588 }
589
590 fn word_contains_obvious_arithmetic_punctuation(text: &str) -> bool {
591 text.chars().any(|ch| {
592 matches!(
593 ch,
594 ',' | '='
595 | '+'
596 | '*'
597 | '/'
598 | '%'
599 | '<'
600 | '>'
601 | '&'
602 | '|'
603 | '^'
604 | '!'
605 | '?'
606 | ':'
607 | '['
608 | ']'
609 )
610 })
611 }
612
613 fn suspicious_double_paren_is_command_style(
614 &mut self,
615 checkpoint: &ParserCheckpoint<'a>,
616 ) -> bool {
617 self.restore(checkpoint.clone());
618 let parses_as_arithmetic = self.parse_arithmetic_command().is_ok();
619 self.restore(checkpoint.clone());
620 !parses_as_arithmetic
621 }
622
623 fn looks_like_command_style_double_paren(&mut self) -> bool {
624 if self.current_token_kind != Some(TokenKind::DoubleLeftParen) {
625 return false;
626 }
627
628 let checkpoint = self.checkpoint();
629 self.advance();
630 let mut paren_depth = 0_i32;
631 let mut previous_top_level_operand = false;
632
633 loop {
634 match self.current_token_kind {
635 Some(TokenKind::DoubleLeftParen) => {
636 paren_depth += 2;
637 previous_top_level_operand = false;
638 self.advance();
639 }
640 Some(TokenKind::LeftParen) => {
641 paren_depth += 1;
642 previous_top_level_operand = false;
643 self.advance();
644 }
645 Some(TokenKind::DoubleRightParen) => {
646 if paren_depth == 0 {
647 self.restore(checkpoint);
648 return false;
649 }
650 if paren_depth == 1 {
651 self.restore(checkpoint);
652 return false;
653 }
654 paren_depth -= 2;
655 previous_top_level_operand = false;
656 self.advance();
657 }
658 Some(TokenKind::RightParen) => {
659 if paren_depth == 0 {
660 return self.suspicious_double_paren_is_command_style(&checkpoint);
661 }
662 paren_depth -= 1;
663 previous_top_level_operand = false;
664 self.advance();
665 }
666 Some(TokenKind::Newline) | Some(TokenKind::Semicolon) if paren_depth == 0 => {
667 previous_top_level_operand = false;
668 self.advance();
669 }
670 Some(TokenKind::Comment) if self.dialect == ShellDialect::Zsh => {
671 self.restore(checkpoint);
672 return false;
673 }
674 Some(_)
675 if paren_depth == 0
676 && self
677 .current_token
678 .as_ref()
679 .is_some_and(Self::is_operand_like_double_paren_token) =>
680 {
681 if previous_top_level_operand {
682 return self.suspicious_double_paren_is_command_style(&checkpoint);
683 }
684 previous_top_level_operand = true;
685 self.advance();
686 }
687 Some(_) => {
688 previous_top_level_operand = false;
689 self.advance();
690 }
691 None => {
692 self.restore(checkpoint);
693 return false;
694 }
695 }
696 }
697 }
698
699 fn split_current_double_left_paren(&mut self) {
700 let (left_span, right_span) = Self::split_double_left_paren(self.current_span);
701 self.set_current_kind(TokenKind::LeftParen, left_span);
702 self.synthetic_tokens
703 .push_front(SyntheticToken::punctuation(
704 TokenKind::LeftParen,
705 right_span,
706 ));
707 }
708
709 pub(super) fn split_current_double_right_paren(&mut self) {
710 let (left_span, right_span) = Self::split_double_right_paren(self.current_span);
711 self.set_current_kind(TokenKind::RightParen, left_span);
712 self.synthetic_tokens
713 .push_front(SyntheticToken::punctuation(
714 TokenKind::RightParen,
715 right_span,
716 ));
717 }
718
719 fn parse_command(&mut self) -> Result<Option<Command>> {
721 self.skip_newlines()?;
722 self.check_error_token()?;
723 self.maybe_expand_current_alias_chain();
724 self.check_error_token()?;
725
726 if !self.zsh_short_repeat_enabled() && self.looks_like_disabled_repeat_loop()? {
727 self.ensure_repeat_loop()?;
728 }
729 if !self.zsh_short_loops_enabled() && self.looks_like_disabled_foreach_loop()? {
730 self.ensure_foreach_loop()?;
731 }
732
733 if let Some(command) = self.parse_prefix_redirected_compound_command()? {
734 return Ok(Some(command));
735 }
736
737 if let Some(command) = self.try_parse_zsh_attached_parens_function()? {
738 return Ok(Some(command));
739 }
740
741 match self.current_keyword() {
743 Some(Keyword::If) => return self.parse_compound_with_redirects(|s| s.parse_if()),
744 Some(Keyword::For) => return self.parse_compound_with_redirects(|s| s.parse_for()),
745 Some(Keyword::Repeat) if self.zsh_short_repeat_enabled() => {
746 return self.parse_compound_with_redirects(|s| s.parse_repeat());
747 }
748 Some(Keyword::Foreach) if self.zsh_short_loops_enabled() => {
749 return self.parse_compound_with_redirects(|s| s.parse_foreach());
750 }
751 Some(Keyword::While) => {
752 return self.parse_compound_with_redirects(|s| s.parse_while());
753 }
754 Some(Keyword::Until) => {
755 return self.parse_compound_with_redirects(|s| s.parse_until());
756 }
757 Some(Keyword::Case) => return self.parse_compound_with_redirects(|s| s.parse_case()),
758 Some(Keyword::Select) => {
759 return self.parse_compound_with_redirects(|s| s.parse_select());
760 }
761 Some(Keyword::Time) => return self.parse_compound_with_redirects(|s| s.parse_time()),
762 Some(Keyword::Coproc) => {
763 return self.parse_compound_with_redirects(|s| s.parse_coproc());
764 }
765 Some(Keyword::Function) => return self.parse_function_keyword().map(Some),
766 _ => {}
767 }
768
769 if self.at(TokenKind::Word)
770 && let Some(word) = self.current_source_like_word_text()
771 && self.peek_next_is(TokenKind::LeftParen)
772 {
773 let checkpoint = self.checkpoint();
774 self.advance();
775 self.advance();
776 let is_right_paren = self.at(TokenKind::RightParen);
777 self.restore(checkpoint);
778 if is_right_paren {
779 if !word.contains('=') && !word.contains('[') {
782 return self.parse_function_posix().map(Some);
783 }
784 } else if word.contains('$') && !word.contains('=') {
785 return Err(self.error("unexpected '(' after command word"));
786 }
787 }
788
789 if self.at(TokenKind::DoubleLeftBracket) {
791 return self.parse_compound_with_redirects(|s| s.parse_conditional());
792 }
793
794 if self.at(TokenKind::DoubleLeftParen) {
796 if self.looks_like_command_style_double_paren() {
797 self.split_current_double_left_paren();
798 return self.parse_compound_with_redirects(|s| s.parse_subshell());
799 }
800
801 let checkpoint = self.checkpoint();
802 if let Ok(compound) = self.parse_arithmetic_command() {
803 let redirects = self.parse_trailing_redirects();
804 return Ok(Some(Command::Compound(Box::new(compound), redirects)));
805 }
806 self.restore(checkpoint);
807
808 self.split_current_double_left_paren();
809 return self.parse_compound_with_redirects(|s| s.parse_subshell());
810 }
811
812 if self.dialect == ShellDialect::Zsh && self.at(TokenKind::LeftParen) {
813 let checkpoint = self.checkpoint();
814 self.advance();
815 let is_right_paren = self.at(TokenKind::RightParen);
816 self.restore(checkpoint);
817 if is_right_paren {
818 return self.parse_anonymous_paren_function().map(Some);
819 }
820 }
821
822 if self.at(TokenKind::LeftParen) {
824 return self.parse_compound_with_redirects(|s| s.parse_subshell());
825 }
826
827 if self.at(TokenKind::LeftBrace) {
829 return self.parse_compound_with_redirects(|s| {
830 s.parse_brace_group(BraceBodyContext::Ordinary)
831 });
832 }
833
834 match self.parse_simple_command()? {
836 Some(cmd) => Ok(Some(self.classify_simple_command(cmd))),
837 None => Ok(None),
838 }
839 }
840
841 fn parse_if(&mut self) -> Result<CompoundCommand> {
843 let start_span = self.current_span;
844 self.push_depth()?;
845 self.advance(); self.skip_newlines()?;
847
848 let condition_start = self.current_span.start;
850 let allow_brace_syntax = self.zsh_brace_if_enabled();
851 let condition = self.parse_if_condition_until_body_start(allow_brace_syntax)?;
852 let condition_span = Span::from_positions(condition_start, self.current_span.start);
853 let condition = Self::stmt_seq_with_span(condition_span, condition);
854
855 let (mut syntax, then_branch, brace_style) = if allow_brace_syntax
856 && self.at(TokenKind::LeftBrace)
857 {
858 let (then_branch, left_brace_span, right_brace_span) = self
859 .parse_brace_enclosed_stmt_seq(
860 "syntax error: empty then clause",
861 BraceBodyContext::IfClause,
862 )?;
863 self.record_zsh_brace_if_span(left_brace_span);
864 (
865 IfSyntax::Brace {
866 left_brace_span,
867 right_brace_span,
868 },
869 then_branch,
870 true,
871 )
872 } else if let Some((then_branch, left_brace_span, right_brace_span)) = allow_brace_syntax
873 .then(|| self.try_parse_compact_zsh_brace_body(BraceBodyContext::IfClause))
874 .transpose()?
875 .flatten()
876 {
877 self.record_zsh_brace_if_span(left_brace_span);
878 (
879 IfSyntax::Brace {
880 left_brace_span,
881 right_brace_span,
882 },
883 then_branch,
884 true,
885 )
886 } else {
887 let then_span = self.current_span;
888 self.expect_keyword(Keyword::Then)?;
889 self.skip_newlines()?;
890
891 let then_start = self.current_span.start;
892 let then_branch = self.parse_compound_list_until(IF_BODY_TERMINATORS)?;
893 let then_branch_span = Span::from_positions(then_start, self.current_span.start);
894
895 let then_branch = if then_branch.is_empty() {
896 if self.dialect == ShellDialect::Zsh && self.is_keyword(Keyword::Elif) {
897 Self::stmt_seq_with_span(then_branch_span, Vec::new())
898 } else {
899 self.pop_depth();
900 return Err(self.error("syntax error: empty then clause"));
901 }
902 } else {
903 Self::stmt_seq_with_span(then_branch_span, then_branch)
904 };
905
906 (
907 IfSyntax::ThenFi {
908 then_span,
909 fi_span: Span::new(),
910 },
911 then_branch,
912 false,
913 )
914 };
915
916 let mut elif_branches = Vec::new();
918 while self.is_keyword(Keyword::Elif) {
919 self.advance(); self.skip_newlines()?;
921
922 let elif_condition_start = self.current_span.start;
923 let elif_condition = self.parse_if_condition_until_body_start(brace_style)?;
924 let elif_condition_span =
925 Span::from_positions(elif_condition_start, self.current_span.start);
926 let elif_condition = Self::stmt_seq_with_span(elif_condition_span, elif_condition);
927
928 let elif_body = if brace_style {
929 if self.at(TokenKind::LeftBrace) {
930 self.parse_brace_enclosed_stmt_seq(
931 "syntax error: empty elif clause",
932 BraceBodyContext::IfClause,
933 )?
934 .0
935 } else if let Some((body, _, _)) =
936 self.try_parse_compact_zsh_brace_body(BraceBodyContext::IfClause)?
937 {
938 body
939 } else {
940 self.pop_depth();
941 return Err(self.error("expected '{' to start elif clause"));
942 }
943 } else {
944 self.expect_keyword(Keyword::Then)?;
945 let elif_body_region_start = self.current_span.start;
946 self.skip_newlines()?;
947
948 let elif_body_start = self.current_span.start;
949 let elif_body = self.parse_compound_list_until(IF_BODY_TERMINATORS)?;
950 let elif_body_span = Span::from_positions(elif_body_start, self.current_span.start);
951
952 if elif_body.is_empty() {
953 if self.dialect == ShellDialect::Zsh
954 && self.has_recorded_comment_between(
955 elif_body_region_start.offset,
956 self.current_span.start.offset,
957 )
958 {
959 Self::stmt_seq_with_span(
960 Span::from_positions(elif_body_region_start, self.current_span.start),
961 Vec::new(),
962 )
963 } else {
964 self.pop_depth();
965 return Err(self.error("syntax error: empty elif clause"));
966 }
967 } else {
968 Self::stmt_seq_with_span(elif_body_span, elif_body)
969 }
970 };
971
972 elif_branches.push((elif_condition, elif_body));
973 }
974
975 let else_branch = if self.is_keyword(Keyword::Else) {
977 self.advance(); let else_region_start = self.current_span.start;
979 self.skip_newlines()?;
980 if brace_style {
981 if self.at(TokenKind::LeftBrace) {
982 Some(
983 self.parse_brace_enclosed_stmt_seq(
984 "syntax error: empty else clause",
985 BraceBodyContext::IfClause,
986 )?
987 .0,
988 )
989 } else if let Some((body, _, _)) =
990 self.try_parse_compact_zsh_brace_body(BraceBodyContext::IfClause)?
991 {
992 Some(body)
993 } else {
994 self.pop_depth();
995 return Err(self.error("expected '{' to start else clause"));
996 }
997 } else {
998 let else_start = self.current_span.start;
999 let branch = self.parse_compound_list(Keyword::Fi)?;
1000 let else_span = Span::from_positions(else_start, self.current_span.start);
1001
1002 if branch.is_empty() {
1003 if self.dialect == ShellDialect::Zsh
1004 && self.has_recorded_comment_between(
1005 else_region_start.offset,
1006 self.current_span.start.offset,
1007 )
1008 {
1009 Some(Self::stmt_seq_with_span(
1010 Span::from_positions(else_region_start, self.current_span.start),
1011 Vec::new(),
1012 ))
1013 } else {
1014 self.pop_depth();
1015 return Err(self.error("syntax error: empty else clause"));
1016 }
1017 } else {
1018 Some(Self::stmt_seq_with_span(else_span, branch))
1019 }
1020 }
1021 } else {
1022 None
1023 };
1024
1025 if !brace_style {
1026 self.expect_keyword(Keyword::Fi)?;
1027 if let IfSyntax::ThenFi { then_span, .. } = syntax {
1028 syntax = IfSyntax::ThenFi {
1029 then_span,
1030 fi_span: self.current_span,
1031 };
1032 }
1033 }
1034
1035 self.pop_depth();
1036 Ok(CompoundCommand::If(IfCommand {
1037 condition,
1038 then_branch,
1039 elif_branches,
1040 else_branch,
1041 syntax,
1042 span: start_span.merge(self.current_span),
1043 }))
1044 }
1045
1046 fn parse_for(&mut self) -> Result<CompoundCommand> {
1048 let start_span = self.current_span;
1049 self.push_depth()?;
1050 self.advance(); self.skip_newlines()?;
1052
1053 if self.at(TokenKind::DoubleLeftParen) {
1055 let result = self.parse_arithmetic_for_inner(start_span);
1056 self.pop_depth();
1057 return result;
1058 }
1059
1060 let allow_zsh_targets = self.dialect == ShellDialect::Zsh;
1061 let targets = match self.parse_for_targets(allow_zsh_targets) {
1062 Ok(targets) => targets,
1063 Err(error) => {
1064 self.pop_depth();
1065 return Err(error);
1066 }
1067 };
1068
1069 if allow_zsh_targets {
1070 self.skip_newlines()?;
1071 }
1072
1073 let (words, header) = if allow_zsh_targets && self.at(TokenKind::LeftParen) {
1074 let left_paren_span = self.current_span;
1075 self.advance();
1076
1077 let mut words = SmallVec::<[Word; 2]>::new();
1078 while !self.at(TokenKind::RightParen) {
1079 if self.at(TokenKind::Newline) {
1080 self.skip_newlines()?;
1081 continue;
1082 }
1083 match self.current_token_kind {
1084 Some(kind)
1085 if kind.is_word_like()
1086 || (self.dialect == ShellDialect::Zsh
1087 && matches!(kind, TokenKind::LeftParen)) =>
1088 {
1089 if self.dialect == ShellDialect::Zsh
1090 && self
1091 .current_token
1092 .as_ref()
1093 .is_some_and(|token| !token.flags.is_synthetic())
1094 {
1095 let start = self.current_span.start;
1096 if let Some((text, end)) = self.scan_source_word(start) {
1097 let span = Span::from_positions(start, end);
1098 let word = self.parse_word_with_context(&text, span, start, true);
1099 self.advance_past_word(&word);
1100 words.push(word);
1101 continue;
1102 }
1103 }
1104
1105 let word = self
1106 .take_current_word_and_advance()
1107 .ok_or_else(|| self.error("expected for word"))?;
1108 words.push(word);
1109 }
1110 Some(_) | None => {
1111 self.pop_depth();
1112 return Err(self.error("expected ')' after for word list"));
1113 }
1114 }
1115 }
1116
1117 let right_paren_span = self.current_span;
1118 self.advance();
1119 if self.at(TokenKind::Semicolon) {
1120 self.advance();
1121 }
1122 self.skip_newlines()?;
1123
1124 (
1125 Some(words),
1126 ForHeaderSurface::Paren {
1127 left_paren_span,
1128 right_paren_span,
1129 },
1130 )
1131 } else if self.is_keyword(Keyword::In) {
1132 let in_span = self.current_span;
1133 self.advance();
1134
1135 let (words, saw_separator) = self.parse_for_word_list_until_body_separator()?;
1136 if !saw_separator {
1137 self.pop_depth();
1138 return Err(self.error("expected ';' or newline before for loop body"));
1139 }
1140 (
1141 Some(words),
1142 ForHeaderSurface::In {
1143 in_span: Some(in_span),
1144 },
1145 )
1146 } else {
1147 if self.at(TokenKind::Semicolon) {
1148 self.advance();
1149 }
1150 self.skip_newlines()?;
1151 (None, ForHeaderSurface::In { in_span: None })
1152 };
1153
1154 let (body, syntax, end_span) = match header {
1155 ForHeaderSurface::In { in_span }
1156 if allow_zsh_targets && self.at(TokenKind::LeftBrace) =>
1157 {
1158 let (body, left_brace_span, right_brace_span) = self
1159 .parse_brace_enclosed_stmt_seq(
1160 "syntax error: empty for loop body",
1161 BraceBodyContext::Ordinary,
1162 )?;
1163 (
1164 body,
1165 ForSyntax::InBrace {
1166 in_span,
1167 left_brace_span,
1168 right_brace_span,
1169 },
1170 right_brace_span,
1171 )
1172 }
1173 ForHeaderSurface::Paren {
1174 left_paren_span,
1175 right_paren_span,
1176 } if allow_zsh_targets && self.at(TokenKind::LeftBrace) => {
1177 let (body, left_brace_span, right_brace_span) = self
1178 .parse_brace_enclosed_stmt_seq(
1179 "syntax error: empty for loop body",
1180 BraceBodyContext::Ordinary,
1181 )?;
1182 (
1183 body,
1184 ForSyntax::ParenBrace {
1185 left_paren_span,
1186 right_paren_span,
1187 left_brace_span,
1188 right_brace_span,
1189 },
1190 right_brace_span,
1191 )
1192 }
1193 ForHeaderSurface::In { in_span }
1194 if allow_zsh_targets && !self.is_keyword(Keyword::Do) =>
1195 {
1196 let stmt = self.parse_single_stmt_command()?;
1197 let span = stmt.span;
1198 (
1199 Self::stmt_seq_with_span(span, vec![stmt]),
1200 ForSyntax::InDirect { in_span },
1201 span,
1202 )
1203 }
1204 ForHeaderSurface::In { in_span } => {
1205 let do_span = if self.is_keyword(Keyword::Do) {
1206 self.current_span
1207 } else {
1208 self.pop_depth();
1209 return Err(self.error("expected 'do'"));
1210 };
1211 self.advance();
1212 self.skip_newlines()?;
1213
1214 let body_start = self.current_span.start;
1215 let body = self.parse_compound_list(Keyword::Done)?;
1216 let body_span = Span::from_positions(body_start, self.current_span.start);
1217 if body.is_empty() && self.dialect != ShellDialect::Zsh {
1218 self.pop_depth();
1219 return Err(self.error("syntax error: empty for loop body"));
1220 }
1221 if !self.is_keyword(Keyword::Done) {
1222 self.pop_depth();
1223 return Err(self.error("expected 'done'"));
1224 }
1225 let done_span = self.current_span;
1226 self.advance();
1227 let body = if body.is_empty() {
1228 Self::stmt_seq_with_span(body_span, Vec::new())
1229 } else {
1230 Self::stmt_seq_with_span(body_span, body)
1231 };
1232 (
1233 body,
1234 ForSyntax::InDoDone {
1235 in_span,
1236 do_span,
1237 done_span,
1238 },
1239 done_span,
1240 )
1241 }
1242 ForHeaderSurface::Paren {
1243 left_paren_span,
1244 right_paren_span,
1245 } if allow_zsh_targets && !self.is_keyword(Keyword::Do) => {
1246 let stmt = self.parse_single_stmt_command()?;
1247 let span = stmt.span;
1248 (
1249 Self::stmt_seq_with_span(span, vec![stmt]),
1250 ForSyntax::ParenDirect {
1251 left_paren_span,
1252 right_paren_span,
1253 },
1254 span,
1255 )
1256 }
1257 ForHeaderSurface::Paren {
1258 left_paren_span,
1259 right_paren_span,
1260 } => {
1261 let do_span = if self.is_keyword(Keyword::Do) {
1262 self.current_span
1263 } else {
1264 self.pop_depth();
1265 return Err(self.error("expected 'do'"));
1266 };
1267 self.advance();
1268 self.skip_newlines()?;
1269
1270 let body_start = self.current_span.start;
1271 let body = self.parse_compound_list(Keyword::Done)?;
1272 let body_span = Span::from_positions(body_start, self.current_span.start);
1273 if body.is_empty() && self.dialect != ShellDialect::Zsh {
1274 self.pop_depth();
1275 return Err(self.error("syntax error: empty for loop body"));
1276 }
1277 if !self.is_keyword(Keyword::Done) {
1278 self.pop_depth();
1279 return Err(self.error("expected 'done'"));
1280 }
1281 let done_span = self.current_span;
1282 self.advance();
1283 let body = if body.is_empty() {
1284 Self::stmt_seq_with_span(body_span, Vec::new())
1285 } else {
1286 Self::stmt_seq_with_span(body_span, body)
1287 };
1288 (
1289 body,
1290 ForSyntax::ParenDoDone {
1291 left_paren_span,
1292 right_paren_span,
1293 do_span,
1294 done_span,
1295 },
1296 done_span,
1297 )
1298 }
1299 };
1300
1301 self.pop_depth();
1302 Ok(CompoundCommand::For(ForCommand {
1303 targets: targets.into_vec(),
1304 words: words.map(SmallVec::into_vec),
1305 body,
1306 syntax,
1307 span: start_span.merge(end_span),
1308 }))
1309 }
1310
1311 fn parse_for_targets(&mut self, allow_zsh_targets: bool) -> Result<SmallVec<[ForTarget; 1]>> {
1312 let allow_digits = allow_zsh_targets;
1313 let first_target = self
1314 .current_for_target(allow_digits)
1315 .ok_or_else(|| Error::parse("expected variable name in for loop".to_string()))?;
1316 let first_word = first_target.word.clone();
1317 self.advance_past_word(&first_word);
1318
1319 let mut targets = SmallVec::from_vec(vec![first_target]);
1320 if !allow_zsh_targets {
1321 return Ok(targets);
1322 }
1323
1324 loop {
1325 if self.current_keyword() == Some(Keyword::In)
1326 || matches!(
1327 self.current_token_kind,
1328 Some(TokenKind::LeftParen | TokenKind::Semicolon | TokenKind::Newline)
1329 )
1330 || self.at(TokenKind::LeftBrace)
1331 || self.is_keyword(Keyword::Do)
1332 {
1333 break;
1334 }
1335
1336 let target = self
1337 .current_for_target(true)
1338 .ok_or_else(|| Error::parse("expected variable name in for loop".to_string()))?;
1339 let word = target.word.clone();
1340 self.advance_past_word(&word);
1341 targets.push(target);
1342 }
1343
1344 Ok(targets)
1345 }
1346
1347 fn current_for_target(&mut self, allow_digits: bool) -> Option<ForTarget> {
1348 let name = self.current_word_str().and_then(|name| {
1349 (Self::is_valid_identifier(name)
1350 || (allow_digits && name.bytes().all(|byte| byte.is_ascii_digit())))
1351 .then(|| Name::from(name))
1352 });
1353 let word = self.current_word()?;
1354 Some(ForTarget {
1355 span: word.span,
1356 word,
1357 name,
1358 })
1359 }
1360
1361 fn parse_for_word_list_until_body_separator(&mut self) -> Result<(SmallVec<[Word; 2]>, bool)> {
1362 let mut words = SmallVec::<[Word; 2]>::new();
1363 loop {
1364 match self.current_token_kind {
1365 Some(kind)
1366 if kind.is_word_like()
1367 || (self.dialect == ShellDialect::Zsh
1368 && matches!(kind, TokenKind::LeftParen)) =>
1369 {
1370 if self.dialect == ShellDialect::Zsh
1371 && self
1372 .current_token
1373 .as_ref()
1374 .is_some_and(|token| !token.flags.is_synthetic())
1375 {
1376 let start = self.current_span.start;
1377 if let Some((text, end)) = self.scan_source_word(start) {
1378 let span = Span::from_positions(start, end);
1379 let word = self.parse_word_with_context(&text, span, start, true);
1380 self.advance_past_word(&word);
1381 words.push(word);
1382 continue;
1383 }
1384 }
1385
1386 let word = self
1387 .take_current_word_and_advance()
1388 .ok_or_else(|| self.error("expected for word"))?;
1389 words.push(word);
1390 }
1391 Some(TokenKind::Semicolon) => {
1392 self.advance();
1393 self.skip_newlines()?;
1394 return Ok((words, true));
1395 }
1396 Some(TokenKind::Newline) => {
1397 self.skip_newlines()?;
1398 return Ok((words, true));
1399 }
1400 _ => return Ok((words, false)),
1401 }
1402 }
1403 }
1404
1405 fn parse_repeat(&mut self) -> Result<CompoundCommand> {
1407 self.ensure_repeat_loop()?;
1408 let start_span = self.current_span;
1409 self.push_depth()?;
1410 self.advance(); let count = match self.current_token_kind {
1413 Some(kind) if kind.is_word_like() => self.expect_word()?,
1414 _ => {
1415 self.pop_depth();
1416 return Err(self.error("expected loop count in repeat"));
1417 }
1418 };
1419
1420 let (syntax, body, end_span) = match self.current_token_kind {
1421 _ if self.is_keyword(Keyword::Do) => {
1422 let do_span = self.current_span;
1423 self.advance();
1424 self.skip_newlines()?;
1425
1426 let body_start = self.current_span.start;
1427 let body = self.parse_compound_list(Keyword::Done)?;
1428 let body_span = Span::from_positions(body_start, self.current_span.start);
1429 if body.is_empty() {
1430 self.pop_depth();
1431 return Err(self.error("syntax error: empty repeat loop body"));
1432 }
1433 if !self.is_keyword(Keyword::Done) {
1434 self.pop_depth();
1435 return Err(self.error("expected 'done'"));
1436 }
1437 let done_span = self.current_span;
1438 self.advance();
1439 (
1440 RepeatSyntax::DoDone { do_span, done_span },
1441 Self::stmt_seq_with_span(body_span, body),
1442 done_span,
1443 )
1444 }
1445 Some(TokenKind::LeftBrace) => {
1446 let (body, left_brace_span, right_brace_span) = self
1447 .parse_brace_enclosed_stmt_seq(
1448 "syntax error: empty repeat loop body",
1449 BraceBodyContext::Ordinary,
1450 )?;
1451 (
1452 RepeatSyntax::Brace {
1453 left_brace_span,
1454 right_brace_span,
1455 },
1456 body,
1457 right_brace_span,
1458 )
1459 }
1460 Some(TokenKind::Semicolon) => {
1461 self.advance();
1462 self.skip_newlines()?;
1463 if !self.is_keyword(Keyword::Do) {
1464 self.pop_depth();
1465 return Err(self.error("expected 'do' after repeat count"));
1466 }
1467 let do_span = self.current_span;
1468 self.advance();
1469 self.skip_newlines()?;
1470
1471 let body_start = self.current_span.start;
1472 let body = self.parse_compound_list(Keyword::Done)?;
1473 let body_span = Span::from_positions(body_start, self.current_span.start);
1474 if body.is_empty() {
1475 self.pop_depth();
1476 return Err(self.error("syntax error: empty repeat loop body"));
1477 }
1478 if !self.is_keyword(Keyword::Done) {
1479 self.pop_depth();
1480 return Err(self.error("expected 'done'"));
1481 }
1482 let done_span = self.current_span;
1483 self.advance();
1484 (
1485 RepeatSyntax::DoDone { do_span, done_span },
1486 Self::stmt_seq_with_span(body_span, body),
1487 done_span,
1488 )
1489 }
1490 Some(TokenKind::Newline) => {
1491 self.skip_newlines()?;
1492 if !self.is_keyword(Keyword::Do) {
1493 self.pop_depth();
1494 return Err(self.error("expected 'do' after repeat count"));
1495 }
1496 let do_span = self.current_span;
1497 self.advance();
1498 self.skip_newlines()?;
1499
1500 let body_start = self.current_span.start;
1501 let body = self.parse_compound_list(Keyword::Done)?;
1502 let body_span = Span::from_positions(body_start, self.current_span.start);
1503 if body.is_empty() {
1504 self.pop_depth();
1505 return Err(self.error("syntax error: empty repeat loop body"));
1506 }
1507 if !self.is_keyword(Keyword::Done) {
1508 self.pop_depth();
1509 return Err(self.error("expected 'done'"));
1510 }
1511 let done_span = self.current_span;
1512 self.advance();
1513 (
1514 RepeatSyntax::DoDone { do_span, done_span },
1515 Self::stmt_seq_with_span(body_span, body),
1516 done_span,
1517 )
1518 }
1519 _ => {
1520 let stmt = self.parse_single_stmt_command()?;
1521 let span = stmt.span;
1522 (
1523 RepeatSyntax::Direct,
1524 Self::stmt_seq_with_span(span, vec![stmt]),
1525 span,
1526 )
1527 }
1528 };
1529
1530 self.pop_depth();
1531 Ok(CompoundCommand::Repeat(RepeatCommand {
1532 count,
1533 body,
1534 syntax,
1535 span: start_span.merge(end_span),
1536 }))
1537 }
1538
1539 fn parse_foreach(&mut self) -> Result<CompoundCommand> {
1541 self.ensure_foreach_loop()?;
1542 let start_span = self.current_span;
1543 self.push_depth()?;
1544 self.advance(); let (variable, variable_span) = match self.current_name_token() {
1547 Some(pair) => pair,
1548 _ => {
1549 self.pop_depth();
1550 return Err(self.error("expected variable name in foreach"));
1551 }
1552 };
1553 self.advance();
1554
1555 let (words, body, syntax, end_span) = if self.at(TokenKind::LeftParen) {
1556 let left_paren_span = self.current_span;
1557 self.advance();
1558
1559 let mut words = SmallVec::<[Word; 2]>::new();
1560 while !self.at(TokenKind::RightParen) {
1561 match self.current_token_kind {
1562 Some(kind) if kind.is_word_like() => {
1563 let word = self
1564 .take_current_word_and_advance()
1565 .ok_or_else(|| self.error("expected foreach word"))?;
1566 words.push(word);
1567 }
1568 Some(_) | None => {
1569 self.pop_depth();
1570 return Err(self.error("expected ')' after foreach word list"));
1571 }
1572 }
1573 }
1574 if words.is_empty() {
1575 self.pop_depth();
1576 return Err(self.error("expected word list in foreach"));
1577 }
1578
1579 let right_paren_span = self.current_span;
1580 self.advance();
1581 if !self.at(TokenKind::LeftBrace) {
1582 self.pop_depth();
1583 return Err(self.error("expected '{' after foreach word list"));
1584 }
1585
1586 let (body, left_brace_span, right_brace_span) = self.parse_brace_enclosed_stmt_seq(
1587 "syntax error: empty foreach loop body",
1588 BraceBodyContext::Ordinary,
1589 )?;
1590 (
1591 words,
1592 body,
1593 ForeachSyntax::ParenBrace {
1594 left_paren_span,
1595 right_paren_span,
1596 left_brace_span,
1597 right_brace_span,
1598 },
1599 right_brace_span,
1600 )
1601 } else if self.is_keyword(Keyword::In) {
1602 let in_span = self.current_span;
1603 self.advance();
1604
1605 let mut words = SmallVec::<[Word; 2]>::new();
1606 let saw_separator = loop {
1607 match self.current_token_kind {
1608 _ if self.current_keyword() == Some(Keyword::Do) => break false,
1609 Some(kind) if kind.is_word_like() => {
1610 let word = self
1611 .take_current_word_and_advance()
1612 .ok_or_else(|| self.error("expected foreach word"))?;
1613 words.push(word);
1614 }
1615 Some(TokenKind::Semicolon) => {
1616 self.advance();
1617 break true;
1618 }
1619 Some(TokenKind::Newline) => {
1620 self.skip_newlines()?;
1621 break true;
1622 }
1623 _ => break false,
1624 }
1625 };
1626 if words.is_empty() {
1627 self.pop_depth();
1628 return Err(self.error("expected word list in foreach"));
1629 }
1630 if !saw_separator {
1631 self.pop_depth();
1632 return Err(self.error("expected ';' or newline before 'do' in foreach"));
1633 }
1634 if !self.is_keyword(Keyword::Do) {
1635 self.pop_depth();
1636 return Err(self.error("expected 'do' in foreach"));
1637 }
1638 let do_span = self.current_span;
1639 self.advance();
1640 self.skip_newlines()?;
1641
1642 let body_start = self.current_span.start;
1643 let body = self.parse_compound_list(Keyword::Done)?;
1644 let body_span = Span::from_positions(body_start, self.current_span.start);
1645 if body.is_empty() {
1646 self.pop_depth();
1647 return Err(self.error("syntax error: empty foreach loop body"));
1648 }
1649 if !self.is_keyword(Keyword::Done) {
1650 self.pop_depth();
1651 return Err(self.error("expected 'done'"));
1652 }
1653 let done_span = self.current_span;
1654 self.advance();
1655 (
1656 words,
1657 Self::stmt_seq_with_span(body_span, body),
1658 ForeachSyntax::InDoDone {
1659 in_span,
1660 do_span,
1661 done_span,
1662 },
1663 done_span,
1664 )
1665 } else {
1666 self.pop_depth();
1667 return Err(self.error("expected '(' or 'in' after foreach variable"));
1668 };
1669
1670 self.pop_depth();
1671 Ok(CompoundCommand::Foreach(ForeachCommand {
1672 variable,
1673 variable_span,
1674 words: words.into_vec(),
1675 body,
1676 syntax,
1677 span: start_span.merge(end_span),
1678 }))
1679 }
1680
1681 fn parse_select(&mut self) -> Result<CompoundCommand> {
1683 self.ensure_select_loop()?;
1684 let start_span = self.current_span;
1685 self.push_depth()?;
1686 self.advance(); self.skip_newlines()?;
1688
1689 let (variable, variable_span) = match self.current_name_token() {
1691 Some(pair) => pair,
1692 _ => {
1693 self.pop_depth();
1694 return Err(Error::parse("expected variable name in select".to_string()));
1695 }
1696 };
1697 self.advance();
1698
1699 if !self.is_keyword(Keyword::In) {
1701 self.pop_depth();
1702 return Err(Error::parse("expected 'in' in select".to_string()));
1703 }
1704 self.advance(); let mut words = SmallVec::<[Word; 2]>::new();
1708 loop {
1709 match self.current_token_kind {
1710 _ if self.current_keyword() == Some(Keyword::Do) => break,
1711 Some(kind) if kind.is_word_like() => {
1712 if let Some(word) = self.take_current_word_and_advance() {
1713 words.push(word);
1714 }
1715 }
1716 Some(TokenKind::Newline | TokenKind::Semicolon) => {
1717 self.advance();
1718 break;
1719 }
1720 _ => break,
1721 }
1722 }
1723
1724 self.skip_newlines()?;
1725
1726 self.expect_keyword(Keyword::Do)?;
1728 self.skip_newlines()?;
1729
1730 let body_start = self.current_span.start;
1732 let body = self.parse_compound_list(Keyword::Done)?;
1733 let body_span = Span::from_positions(body_start, self.current_span.start);
1734
1735 if body.is_empty() {
1737 self.pop_depth();
1738 return Err(self.error("syntax error: empty select loop body"));
1739 }
1740 let body = Self::stmt_seq_with_span(body_span, body);
1741
1742 self.expect_keyword(Keyword::Done)?;
1744
1745 self.pop_depth();
1746 Ok(CompoundCommand::Select(SelectCommand {
1747 variable,
1748 variable_span,
1749 words: words.into_vec(),
1750 body,
1751 span: start_span.merge(self.current_span),
1752 }))
1753 }
1754
1755 fn parse_arithmetic_for_inner(&mut self, start_span: Span) -> Result<CompoundCommand> {
1758 self.ensure_arithmetic_for()?;
1759 let left_paren_span = self.current_span;
1760 self.advance(); let mut paren_depth = 0_i32;
1763 let mut segment_start = left_paren_span.end;
1764 let mut init_span = None;
1765 let mut first_semicolon_span = None;
1766 let mut condition_span = None;
1767 let mut second_semicolon_span = None;
1768
1769 let right_paren_span = loop {
1770 match self.current_token_kind {
1771 Some(TokenKind::DoubleLeftParen) => {
1772 paren_depth += 2;
1773 self.advance();
1774 }
1775 Some(TokenKind::LeftParen) => {
1776 paren_depth += 1;
1777 self.advance();
1778 }
1779 Some(TokenKind::ProcessSubIn) | Some(TokenKind::ProcessSubOut) => {
1780 paren_depth += 1;
1781 self.advance();
1782 }
1783 Some(TokenKind::DoubleRightParen) => {
1784 if paren_depth == 0 {
1785 let right_paren_span = self.current_span;
1786 self.advance();
1787 break right_paren_span;
1788 }
1789 if paren_depth == 1 {
1790 break self.split_nested_arithmetic_close("arithmetic for header")?;
1791 }
1792 paren_depth -= 2;
1793 self.advance();
1794 }
1795 Some(TokenKind::RightParen) => {
1796 if paren_depth > 0 {
1797 paren_depth -= 1;
1798 }
1799 self.advance();
1800 }
1801 Some(TokenKind::DoubleSemicolon) if paren_depth == 0 => {
1802 let (first_span, second_span) = Self::split_double_semicolon(self.current_span);
1803 Self::record_arithmetic_for_separator(
1804 first_span,
1805 &mut segment_start,
1806 &mut init_span,
1807 &mut first_semicolon_span,
1808 &mut condition_span,
1809 &mut second_semicolon_span,
1810 )?;
1811 Self::record_arithmetic_for_separator(
1812 second_span,
1813 &mut segment_start,
1814 &mut init_span,
1815 &mut first_semicolon_span,
1816 &mut condition_span,
1817 &mut second_semicolon_span,
1818 )?;
1819 self.advance();
1820 }
1821 Some(TokenKind::Semicolon) if paren_depth == 0 => {
1822 Self::record_arithmetic_for_separator(
1823 self.current_span,
1824 &mut segment_start,
1825 &mut init_span,
1826 &mut first_semicolon_span,
1827 &mut condition_span,
1828 &mut second_semicolon_span,
1829 )?;
1830 self.advance();
1831 }
1832 Some(_) => {
1833 self.advance();
1834 }
1835 None => {
1836 return Err(Error::parse(
1837 "unexpected end of input in for loop".to_string(),
1838 ));
1839 }
1840 }
1841 };
1842
1843 let first_semicolon_span = first_semicolon_span
1844 .ok_or_else(|| Error::parse("expected ';' in arithmetic for header".to_string()))?;
1845 let second_semicolon_span = second_semicolon_span.ok_or_else(|| {
1846 Error::parse("expected second ';' in arithmetic for header".to_string())
1847 })?;
1848 let step_span = Self::optional_span(segment_start, right_paren_span.start);
1849 let init_ast =
1850 self.parse_explicit_arithmetic_span(init_span, "invalid arithmetic for init")?;
1851 let condition_ast = self
1852 .parse_explicit_arithmetic_span(condition_span, "invalid arithmetic for condition")?;
1853 let step_ast =
1854 self.parse_explicit_arithmetic_span(step_span, "invalid arithmetic for step")?;
1855
1856 self.skip_newlines()?;
1857
1858 if self.at(TokenKind::Semicolon) {
1860 self.advance();
1861 }
1862 self.skip_newlines()?;
1863
1864 let (body, end_span) = if self.at(TokenKind::LeftBrace) {
1865 let body = self.parse_brace_group(BraceBodyContext::Ordinary)?;
1866 let span = Self::compound_span(&body);
1867 (
1868 Self::stmt_seq_with_span(
1869 span,
1870 vec![Self::lower_non_sequence_command_to_stmt(Command::Compound(
1871 Box::new(body),
1872 SmallVec::<[Redirect; 1]>::new(),
1873 ))],
1874 ),
1875 self.current_span,
1876 )
1877 } else {
1878 self.expect_keyword(Keyword::Do)?;
1880 self.skip_newlines()?;
1881
1882 let body_start = self.current_span.start;
1884 let body = self.parse_compound_list(Keyword::Done)?;
1885 let body_span = Span::from_positions(body_start, self.current_span.start);
1886
1887 if body.is_empty() {
1889 return Err(self.error("syntax error: empty for loop body"));
1890 }
1891
1892 if !self.is_keyword(Keyword::Done) {
1894 return Err(self.error("expected 'done'"));
1895 }
1896 let done_span = self.current_span;
1897 self.advance();
1898 (Self::stmt_seq_with_span(body_span, body), done_span)
1899 };
1900
1901 Ok(CompoundCommand::ArithmeticFor(Box::new(
1902 ArithmeticForCommand {
1903 left_paren_span,
1904 init_span,
1905 init_ast,
1906 first_semicolon_span,
1907 condition_span,
1908 condition_ast,
1909 second_semicolon_span,
1910 step_span,
1911 step_ast,
1912 right_paren_span,
1913 body,
1914 span: start_span.merge(end_span),
1915 },
1916 )))
1917 }
1918
1919 fn parse_while(&mut self) -> Result<CompoundCommand> {
1921 let start_span = self.current_span;
1922 self.push_depth()?;
1923 self.advance(); self.skip_newlines()?;
1925
1926 let condition_start = self.current_span.start;
1928 let allow_brace_body = self.dialect == ShellDialect::Zsh && self.zsh_brace_bodies_enabled();
1929 let condition = self.parse_loop_condition_until_body_start(allow_brace_body)?;
1930 let condition_span = Span::from_positions(condition_start, self.current_span.start);
1931 let condition = Self::stmt_seq_with_span(condition_span, condition);
1932
1933 let (body, end_span) = if allow_brace_body && self.at(TokenKind::LeftBrace) {
1934 let body = self.parse_brace_group(BraceBodyContext::Ordinary)?;
1935 let span = Self::compound_span(&body);
1936 (
1937 Self::stmt_seq_with_span(
1938 span,
1939 vec![Self::lower_non_sequence_command_to_stmt(Command::Compound(
1940 Box::new(body),
1941 SmallVec::<[Redirect; 1]>::new(),
1942 ))],
1943 ),
1944 self.current_span,
1945 )
1946 } else if let Some((body, left_brace_span, right_brace_span)) = allow_brace_body
1947 .then(|| self.try_parse_compact_zsh_brace_body(BraceBodyContext::Ordinary))
1948 .transpose()?
1949 .flatten()
1950 {
1951 let brace_group = CompoundCommand::BraceGroup(body);
1952 let span = left_brace_span.merge(right_brace_span);
1953 (
1954 Self::stmt_seq_with_span(
1955 span,
1956 vec![Self::lower_non_sequence_command_to_stmt(Command::Compound(
1957 Box::new(brace_group),
1958 SmallVec::<[Redirect; 1]>::new(),
1959 ))],
1960 ),
1961 right_brace_span,
1962 )
1963 } else {
1964 self.expect_keyword(Keyword::Do)?;
1965 self.skip_newlines()?;
1966
1967 let body_start = self.current_span.start;
1968 let body = self.parse_compound_list(Keyword::Done)?;
1969 let body_span = Span::from_positions(body_start, self.current_span.start);
1970
1971 if body.is_empty() && self.dialect != ShellDialect::Zsh {
1972 self.pop_depth();
1973 return Err(self.error("syntax error: empty while loop body"));
1974 }
1975 let body = Self::stmt_seq_with_span(body_span, body);
1976
1977 self.expect_keyword(Keyword::Done)?;
1978 (body, self.current_span)
1979 };
1980
1981 self.pop_depth();
1982 Ok(CompoundCommand::While(WhileCommand {
1983 condition,
1984 body,
1985 span: start_span.merge(end_span),
1986 }))
1987 }
1988
1989 fn parse_until(&mut self) -> Result<CompoundCommand> {
1991 let start_span = self.current_span;
1992 self.push_depth()?;
1993 self.advance(); self.skip_newlines()?;
1995
1996 let condition_start = self.current_span.start;
1998 let allow_brace_body = self.dialect == ShellDialect::Zsh && self.zsh_brace_bodies_enabled();
1999 let condition = self.parse_loop_condition_until_body_start(allow_brace_body)?;
2000 let condition_span = Span::from_positions(condition_start, self.current_span.start);
2001 let condition = Self::stmt_seq_with_span(condition_span, condition);
2002
2003 let (body, end_span) = if allow_brace_body && self.at(TokenKind::LeftBrace) {
2004 let body = self.parse_brace_group(BraceBodyContext::Ordinary)?;
2005 let span = Self::compound_span(&body);
2006 (
2007 Self::stmt_seq_with_span(
2008 span,
2009 vec![Self::lower_non_sequence_command_to_stmt(Command::Compound(
2010 Box::new(body),
2011 SmallVec::<[Redirect; 1]>::new(),
2012 ))],
2013 ),
2014 self.current_span,
2015 )
2016 } else if let Some((body, left_brace_span, right_brace_span)) = allow_brace_body
2017 .then(|| self.try_parse_compact_zsh_brace_body(BraceBodyContext::Ordinary))
2018 .transpose()?
2019 .flatten()
2020 {
2021 let brace_group = CompoundCommand::BraceGroup(body);
2022 let span = left_brace_span.merge(right_brace_span);
2023 (
2024 Self::stmt_seq_with_span(
2025 span,
2026 vec![Self::lower_non_sequence_command_to_stmt(Command::Compound(
2027 Box::new(brace_group),
2028 SmallVec::<[Redirect; 1]>::new(),
2029 ))],
2030 ),
2031 right_brace_span,
2032 )
2033 } else {
2034 self.expect_keyword(Keyword::Do)?;
2035 self.skip_newlines()?;
2036
2037 let body_start = self.current_span.start;
2038 let body = self.parse_compound_list(Keyword::Done)?;
2039 let body_span = Span::from_positions(body_start, self.current_span.start);
2040
2041 if body.is_empty() && self.dialect != ShellDialect::Zsh {
2042 self.pop_depth();
2043 return Err(self.error("syntax error: empty until loop body"));
2044 }
2045 let body = Self::stmt_seq_with_span(body_span, body);
2046
2047 self.expect_keyword(Keyword::Done)?;
2048 (body, self.current_span)
2049 };
2050
2051 self.pop_depth();
2052 Ok(CompoundCommand::Until(UntilCommand {
2053 condition,
2054 body,
2055 span: start_span.merge(end_span),
2056 }))
2057 }
2058
2059 fn parse_case(&mut self) -> Result<CompoundCommand> {
2061 let start_span = self.current_span;
2062 self.push_depth()?;
2063 self.advance(); self.skip_newlines()?;
2065
2066 let word = self.expect_word()?;
2068 self.skip_newlines()?;
2069
2070 self.expect_keyword(Keyword::In)?;
2072 self.skip_newlines()?;
2073
2074 let mut cases = Vec::new();
2076 while !self.is_keyword(Keyword::Esac) && self.current_token.is_some() {
2077 self.skip_newlines()?;
2078 if self.is_keyword(Keyword::Esac) {
2079 break;
2080 }
2081
2082 let patterns = match self.parse_case_patterns() {
2083 Ok(patterns) => patterns,
2084 Err(err) => {
2085 self.pop_depth();
2086 return Err(err);
2087 }
2088 };
2089 self.skip_newlines()?;
2090
2091 let body_start = self.current_span.start;
2093 let mut commands = Vec::new();
2094 while !self.is_case_terminator()
2095 && !self.is_keyword(Keyword::Esac)
2096 && self.current_token.is_some()
2097 {
2098 commands.extend(self.parse_command_list_required()?);
2099 self.skip_newlines()?;
2100 }
2101
2102 let (terminator, terminator_span) = self.parse_case_terminator();
2103 let body_span = Span::from_positions(body_start, self.current_span.start);
2104 cases.push(CaseItem {
2105 patterns,
2106 body: Self::stmt_seq_with_span(body_span, commands),
2107 terminator,
2108 terminator_span,
2109 });
2110 self.skip_newlines()?;
2111 }
2112
2113 self.expect_keyword(Keyword::Esac)?;
2115
2116 self.pop_depth();
2117 Ok(CompoundCommand::Case(CaseCommand {
2118 word,
2119 cases,
2120 span: start_span.merge(self.current_span),
2121 }))
2122 }
2123
2124 fn parse_case_patterns(&mut self) -> Result<Vec<Pattern>> {
2125 self.record_zsh_case_group_parts_from_current_case_header();
2126 if self.dialect == ShellDialect::Zsh {
2127 self.parse_zsh_case_patterns()
2128 } else {
2129 self.parse_posix_case_patterns()
2130 }
2131 }
2132
2133 fn record_zsh_case_group_parts_from_current_case_header(&mut self) {
2134 let Ok((pattern_spans, _)) = self.scan_zsh_case_pattern_spans() else {
2135 return;
2136 };
2137
2138 for span in pattern_spans {
2139 let pattern = self.pattern_from_zsh_case_span(span);
2140 for (index, part) in pattern.parts.iter().enumerate() {
2141 if matches!(
2142 &part.kind,
2143 PatternPart::Group {
2144 kind: PatternGroupKind::ExactlyOne,
2145 ..
2146 }
2147 ) && part.span.slice(self.input).starts_with('(')
2148 {
2149 self.record_zsh_case_group_part(index, part.span);
2150 }
2151 }
2152 }
2153 }
2154
2155 fn parse_posix_case_patterns(&mut self) -> Result<Vec<Pattern>> {
2156 if self.at(TokenKind::LeftParen) {
2157 self.advance();
2158 }
2159
2160 let mut patterns = Vec::new();
2161 while self.at_word_like() {
2162 if let Some(word) = self.take_current_word_and_advance() {
2163 patterns.push(self.pattern_from_word(&word));
2164 }
2165
2166 if self.at(TokenKind::Pipe) {
2167 self.advance();
2168 } else {
2169 break;
2170 }
2171 }
2172
2173 if !self.at(TokenKind::RightParen) {
2174 return Err(self.error("expected ')' after case pattern"));
2175 }
2176 self.advance();
2177
2178 Ok(patterns)
2179 }
2180
2181 fn parse_zsh_case_patterns(&mut self) -> Result<Vec<Pattern>> {
2182 let (pattern_spans, delimiter_span) = self.scan_zsh_case_pattern_spans()?;
2183 let patterns = pattern_spans
2184 .into_iter()
2185 .map(|span| self.pattern_from_zsh_case_span(span))
2186 .collect::<Vec<_>>();
2187
2188 while self.current_token.is_some()
2189 && self.current_span.start.offset < delimiter_span.end.offset
2190 {
2191 self.advance();
2192 }
2193
2194 Ok(patterns)
2195 }
2196
2197 fn scan_zsh_case_pattern_spans(&self) -> Result<(Vec<Span>, Span)> {
2198 let start = self.current_span.start;
2199 let Some((spans, delimiter_span)) = self.try_scan_zsh_case_pattern_spans(start) else {
2200 return Err(self.error("expected ')' after case pattern"));
2201 };
2202 if spans.is_empty() {
2203 return Err(self.error("expected ')' after case pattern"));
2204 }
2205 Ok((spans, delimiter_span))
2206 }
2207
2208 fn try_scan_zsh_case_pattern_spans(&self, start: Position) -> Option<(Vec<Span>, Span)> {
2209 if self.input[start.offset..].starts_with('(')
2210 && let Some(wrapper_close) = self.scan_zsh_case_group_close(start)
2211 && self.case_wrapper_close_is_arm_delimiter(wrapper_close)
2212 {
2213 let inner_start = start.advanced_by("(");
2214 let inner_span = Span::from_positions(inner_start, wrapper_close.start);
2215 let patterns = self.split_zsh_case_pattern_alternatives(inner_span)?;
2216 return Some((patterns, wrapper_close));
2217 }
2218
2219 let delimiter_span = self.scan_zsh_case_arm_delimiter(start)?;
2220 let header_span = Span::from_positions(start, delimiter_span.start);
2221 let patterns = self.split_zsh_case_pattern_alternatives(header_span)?;
2222 Some((patterns, delimiter_span))
2223 }
2224
2225 fn case_wrapper_close_is_arm_delimiter(&self, close_span: Span) -> bool {
2226 self.input[close_span.end.offset..]
2227 .chars()
2228 .next()
2229 .is_none_or(char::is_whitespace)
2230 }
2231
2232 fn split_zsh_case_pattern_alternatives(&self, span: Span) -> Option<Vec<Span>> {
2233 let mut state = ZshCaseScanState::new(span.start);
2234 let mut chars = self.input[span.start.offset..span.end.offset]
2235 .chars()
2236 .peekable();
2237 let mut part_start = span.start;
2238 let mut parts = Vec::new();
2239
2240 while let Some(ch) = chars.peek().copied() {
2241 if state.escaped {
2242 state.escaped = false;
2243 Self::next_word_char_unwrap(&mut chars, &mut state.position);
2244 continue;
2245 }
2246
2247 match ch {
2248 '\\' if !state.in_single => {
2249 state.escaped = true;
2250 Self::next_word_char_unwrap(&mut chars, &mut state.position);
2251 }
2252 '\'' if !state.in_double && !state.in_backtick => {
2253 state.in_single = !state.in_single;
2254 Self::next_word_char_unwrap(&mut chars, &mut state.position);
2255 }
2256 '"' if !state.in_single && !state.in_backtick => {
2257 state.in_double = !state.in_double;
2258 Self::next_word_char_unwrap(&mut chars, &mut state.position);
2259 }
2260 '`' if !state.in_single && !state.in_double => {
2261 state.in_backtick = !state.in_backtick;
2262 Self::next_word_char_unwrap(&mut chars, &mut state.position);
2263 }
2264 '[' if !state.in_single
2265 && !state.in_double
2266 && !state.in_backtick
2267 && state.bracket_depth == 0 =>
2268 {
2269 state.bracket_depth += 1;
2270 Self::next_word_char_unwrap(&mut chars, &mut state.position);
2271 }
2272 '[' if !state.in_single && !state.in_double && !state.in_backtick => {
2273 state.bracket_depth += 1;
2274 Self::next_word_char_unwrap(&mut chars, &mut state.position);
2275 }
2276 ']' if !state.in_single
2277 && !state.in_double
2278 && !state.in_backtick
2279 && state.bracket_depth > 0 =>
2280 {
2281 state.bracket_depth -= 1;
2282 Self::next_word_char_unwrap(&mut chars, &mut state.position);
2283 }
2284 '{' if !state.in_single && !state.in_double && !state.in_backtick => {
2285 state.brace_depth += 1;
2286 Self::next_word_char_unwrap(&mut chars, &mut state.position);
2287 }
2288 '}' if !state.in_single
2289 && !state.in_double
2290 && !state.in_backtick
2291 && state.brace_depth > 0 =>
2292 {
2293 state.brace_depth -= 1;
2294 Self::next_word_char_unwrap(&mut chars, &mut state.position);
2295 }
2296 '(' if !state.in_single
2297 && !state.in_double
2298 && !state.in_backtick
2299 && state.bracket_depth == 0
2300 && state.brace_depth == 0 =>
2301 {
2302 state.paren_depth += 1;
2303 Self::next_word_char_unwrap(&mut chars, &mut state.position);
2304 }
2305 ')' if !state.in_single
2306 && !state.in_double
2307 && !state.in_backtick
2308 && state.bracket_depth == 0
2309 && state.brace_depth == 0
2310 && state.paren_depth > 0 =>
2311 {
2312 state.paren_depth -= 1;
2313 Self::next_word_char_unwrap(&mut chars, &mut state.position);
2314 }
2315 '|' if !state.in_single
2316 && !state.in_double
2317 && !state.in_backtick
2318 && state.bracket_depth == 0
2319 && state.brace_depth == 0
2320 && state.paren_depth == 0 =>
2321 {
2322 let end = state.position;
2323 let _ = Self::next_word_char_unwrap(&mut chars, &mut state.position);
2324 parts.push(
2325 self.trim_zsh_case_pattern_span(Span::from_positions(part_start, end))?,
2326 );
2327 part_start = state.position;
2328 }
2329 _ => {
2330 Self::next_word_char_unwrap(&mut chars, &mut state.position);
2331 }
2332 }
2333 }
2334
2335 parts.push(
2336 self.trim_zsh_case_pattern_span(Span::from_positions(part_start, state.position))?,
2337 );
2338 Some(parts)
2339 }
2340
2341 fn trim_zsh_case_pattern_span(&self, span: Span) -> Option<Span> {
2342 let text = span.slice(self.input);
2343 let trimmed_start = text.len() - text.trim_start_matches(char::is_whitespace).len();
2344 let trimmed_end = text.trim_end_matches(char::is_whitespace).len();
2345 let start = span.start.advanced_by(&text[..trimmed_start]);
2346 let end = span.start.advanced_by(&text[..trimmed_end]);
2347 Some(Span::from_positions(start, end))
2348 }
2349
2350 fn scan_zsh_case_group_close(&self, start: Position) -> Option<Span> {
2351 let mut state = ZshCaseScanState::new(start);
2352 let mut chars = self.input[start.offset..].chars().peekable();
2353
2354 if Self::next_word_char_unwrap(&mut chars, &mut state.position) != '(' {
2355 return None;
2356 }
2357 state.paren_depth = 1;
2358
2359 while let Some(ch) = chars.peek().copied() {
2360 let ch_start = state.position;
2361
2362 if state.escaped {
2363 state.escaped = false;
2364 Self::next_word_char_unwrap(&mut chars, &mut state.position);
2365 continue;
2366 }
2367
2368 match ch {
2369 '\\' if !state.in_single => {
2370 state.escaped = true;
2371 Self::next_word_char_unwrap(&mut chars, &mut state.position);
2372 }
2373 '\'' if !state.in_double && !state.in_backtick => {
2374 state.in_single = !state.in_single;
2375 Self::next_word_char_unwrap(&mut chars, &mut state.position);
2376 }
2377 '"' if !state.in_single && !state.in_backtick => {
2378 state.in_double = !state.in_double;
2379 Self::next_word_char_unwrap(&mut chars, &mut state.position);
2380 }
2381 '`' if !state.in_single && !state.in_double => {
2382 state.in_backtick = !state.in_backtick;
2383 Self::next_word_char_unwrap(&mut chars, &mut state.position);
2384 }
2385 '[' if !state.in_single && !state.in_double && !state.in_backtick => {
2386 state.bracket_depth += 1;
2387 Self::next_word_char_unwrap(&mut chars, &mut state.position);
2388 }
2389 ']' if !state.in_single
2390 && !state.in_double
2391 && !state.in_backtick
2392 && state.bracket_depth > 0 =>
2393 {
2394 state.bracket_depth -= 1;
2395 Self::next_word_char_unwrap(&mut chars, &mut state.position);
2396 }
2397 '{' if !state.in_single && !state.in_double && !state.in_backtick => {
2398 state.brace_depth += 1;
2399 Self::next_word_char_unwrap(&mut chars, &mut state.position);
2400 }
2401 '}' if !state.in_single
2402 && !state.in_double
2403 && !state.in_backtick
2404 && state.brace_depth > 0 =>
2405 {
2406 state.brace_depth -= 1;
2407 Self::next_word_char_unwrap(&mut chars, &mut state.position);
2408 }
2409 '(' if !state.in_single
2410 && !state.in_double
2411 && !state.in_backtick
2412 && state.bracket_depth == 0
2413 && state.brace_depth == 0 =>
2414 {
2415 state.paren_depth += 1;
2416 Self::next_word_char_unwrap(&mut chars, &mut state.position);
2417 }
2418 ')' if !state.in_single
2419 && !state.in_double
2420 && !state.in_backtick
2421 && state.bracket_depth == 0
2422 && state.brace_depth == 0
2423 && state.paren_depth > 0 =>
2424 {
2425 let _ = Self::next_word_char_unwrap(&mut chars, &mut state.position);
2426 state.paren_depth -= 1;
2427 if state.paren_depth == 0 {
2428 return Some(Span::from_positions(ch_start, state.position));
2429 }
2430 }
2431 _ => {
2432 Self::next_word_char_unwrap(&mut chars, &mut state.position);
2433 }
2434 }
2435 }
2436
2437 None
2438 }
2439
2440 fn scan_zsh_case_arm_delimiter(&self, start: Position) -> Option<Span> {
2441 let mut state = ZshCaseScanState::new(start);
2442 let mut chars = self.input[start.offset..].chars().peekable();
2443
2444 while let Some(ch) = chars.peek().copied() {
2445 let ch_start = state.position;
2446
2447 if state.escaped {
2448 state.escaped = false;
2449 Self::next_word_char_unwrap(&mut chars, &mut state.position);
2450 continue;
2451 }
2452
2453 match ch {
2454 '\\' if !state.in_single => {
2455 state.escaped = true;
2456 Self::next_word_char_unwrap(&mut chars, &mut state.position);
2457 }
2458 '\'' if !state.in_double && !state.in_backtick => {
2459 state.in_single = !state.in_single;
2460 Self::next_word_char_unwrap(&mut chars, &mut state.position);
2461 }
2462 '"' if !state.in_single && !state.in_backtick => {
2463 state.in_double = !state.in_double;
2464 Self::next_word_char_unwrap(&mut chars, &mut state.position);
2465 }
2466 '`' if !state.in_single && !state.in_double => {
2467 state.in_backtick = !state.in_backtick;
2468 Self::next_word_char_unwrap(&mut chars, &mut state.position);
2469 }
2470 '[' if !state.in_single && !state.in_double && !state.in_backtick => {
2471 state.bracket_depth += 1;
2472 Self::next_word_char_unwrap(&mut chars, &mut state.position);
2473 }
2474 ']' if !state.in_single
2475 && !state.in_double
2476 && !state.in_backtick
2477 && state.bracket_depth > 0 =>
2478 {
2479 state.bracket_depth -= 1;
2480 Self::next_word_char_unwrap(&mut chars, &mut state.position);
2481 }
2482 '{' if !state.in_single && !state.in_double && !state.in_backtick => {
2483 state.brace_depth += 1;
2484 Self::next_word_char_unwrap(&mut chars, &mut state.position);
2485 }
2486 '}' if !state.in_single
2487 && !state.in_double
2488 && !state.in_backtick
2489 && state.brace_depth > 0 =>
2490 {
2491 state.brace_depth -= 1;
2492 Self::next_word_char_unwrap(&mut chars, &mut state.position);
2493 }
2494 '(' if !state.in_single
2495 && !state.in_double
2496 && !state.in_backtick
2497 && state.bracket_depth == 0
2498 && state.brace_depth == 0 =>
2499 {
2500 state.paren_depth += 1;
2501 Self::next_word_char_unwrap(&mut chars, &mut state.position);
2502 }
2503 ')' if !state.in_single
2504 && !state.in_double
2505 && !state.in_backtick
2506 && state.bracket_depth == 0
2507 && state.brace_depth == 0 =>
2508 {
2509 let _ = Self::next_word_char_unwrap(&mut chars, &mut state.position);
2510 if state.paren_depth == 0 {
2511 return Some(Span::from_positions(ch_start, state.position));
2512 }
2513 state.paren_depth -= 1;
2514 }
2515 _ => {
2516 Self::next_word_char_unwrap(&mut chars, &mut state.position);
2517 }
2518 }
2519 }
2520
2521 None
2522 }
2523
2524 fn parse_time(&mut self) -> Result<CompoundCommand> {
2529 let start_span = self.current_span;
2530 self.advance(); self.skip_newlines()?;
2532
2533 let posix_format = if self.at(TokenKind::Word) && self.current_word_str() == Some("-p") {
2535 self.advance();
2536 self.skip_newlines()?;
2537 true
2538 } else {
2539 false
2540 };
2541
2542 let command = self.parse_pipeline()?.map(Box::new);
2545
2546 Ok(CompoundCommand::Time(TimeCommand {
2547 posix_format,
2548 command,
2549 span: start_span.merge(self.current_span),
2550 }))
2551 }
2552
2553 fn parse_coproc(&mut self) -> Result<CompoundCommand> {
2560 self.ensure_coproc()?;
2561 let start_span = self.current_span;
2562 self.advance(); self.skip_newlines()?;
2564
2565 let (name, name_span) = if self.at(TokenKind::Word) {
2568 if let Some(word) = self.current_word_str() {
2569 let word = word.to_string();
2570 let word_span = self.current_span;
2571 let is_compound_keyword = matches!(
2572 word.as_str(),
2573 "if" | "for" | "while" | "until" | "case" | "select" | "time" | "coproc"
2574 );
2575 let next_is_compound_start = matches!(
2576 self.peek_next_kind(),
2577 Some(TokenKind::LeftBrace | TokenKind::LeftParen)
2578 );
2579 if !is_compound_keyword && next_is_compound_start {
2580 self.advance(); self.skip_newlines()?;
2582 (Name::from(word), Some(word_span))
2583 } else {
2584 (Name::new_static("COPROC"), None)
2585 }
2586 } else {
2587 (Name::new_static("COPROC"), None)
2588 }
2589 } else {
2590 (Name::new_static("COPROC"), None)
2591 };
2592
2593 let body = self.parse_pipeline()?;
2595 let body = body.ok_or_else(|| self.error("coproc: missing command"))?;
2596
2597 Ok(CompoundCommand::Coproc(CoprocCommand {
2598 name,
2599 name_span,
2600 body: Box::new(body),
2601 span: start_span.merge(self.current_span),
2602 }))
2603 }
2604
2605 fn is_case_terminator(&self) -> bool {
2607 matches!(
2608 self.current_token_kind,
2609 Some(TokenKind::DoubleSemicolon | TokenKind::SemiAmp | TokenKind::DoubleSemiAmp)
2610 ) || (self.dialect == ShellDialect::Zsh
2611 && self.current_token_kind == Some(TokenKind::SemiPipe))
2612 }
2613
2614 fn parse_case_terminator(&mut self) -> (CaseTerminator, Option<Span>) {
2617 match self.current_token_kind {
2618 Some(TokenKind::SemiAmp) => {
2619 let span = self.current_span;
2620 self.advance();
2621 (CaseTerminator::FallThrough, Some(span))
2622 }
2623 Some(TokenKind::SemiPipe) => {
2624 let span = self.current_span;
2625 self.advance();
2626 (CaseTerminator::ContinueMatching, Some(span))
2627 }
2628 Some(TokenKind::DoubleSemiAmp) => {
2629 let span = self.current_span;
2630 self.advance();
2631 (CaseTerminator::Continue, Some(span))
2632 }
2633 Some(TokenKind::DoubleSemicolon) => {
2634 let span = self.current_span;
2635 self.advance();
2636 (CaseTerminator::Break, Some(span))
2637 }
2638 _ => (CaseTerminator::Break, None),
2639 }
2640 }
2641
2642 fn parse_subshell(&mut self) -> Result<CompoundCommand> {
2644 self.push_depth()?;
2645 self.advance(); self.skip_newlines()?;
2647
2648 let body_start = self.current_span.start;
2649 let mut commands = Vec::new();
2650 while !matches!(
2651 self.current_token_kind,
2652 Some(TokenKind::RightParen | TokenKind::DoubleRightParen) | None
2653 ) {
2654 self.skip_newlines()?;
2655 if matches!(
2656 self.current_token_kind,
2657 Some(TokenKind::RightParen | TokenKind::DoubleRightParen)
2658 ) {
2659 break;
2660 }
2661 commands.extend(self.parse_command_list_required()?);
2662 }
2663
2664 if self.at(TokenKind::DoubleRightParen) {
2665 self.set_current_kind(TokenKind::RightParen, self.current_span);
2667 } else if !self.at(TokenKind::RightParen) {
2668 self.pop_depth();
2669 return Err(Error::parse("expected ')' to close subshell".to_string()));
2670 } else {
2671 self.advance(); }
2673
2674 self.pop_depth();
2675 Ok(CompoundCommand::Subshell(Self::stmt_seq_with_span(
2676 Span::from_positions(body_start, self.current_span.start),
2677 commands,
2678 )))
2679 }
2680
2681 fn parse_brace_group(&mut self, context: BraceBodyContext) -> Result<CompoundCommand> {
2683 self.push_depth()?;
2684 let (body, left_brace_span, right_brace_span) =
2685 self.parse_brace_enclosed_stmt_seq("syntax error: empty brace group", context)?;
2686
2687 let always_span = self.peek_zsh_always_span();
2688 if let Some(span) = always_span {
2689 self.record_zsh_always_span(span);
2690 }
2691
2692 let compound = if self.dialect.features().zsh_always && self.is_keyword(Keyword::Always) {
2693 self.record_zsh_always_span(self.current_span);
2694 self.advance();
2695 self.skip_newlines()?;
2696 if !self.at(TokenKind::LeftBrace) {
2697 self.pop_depth();
2698 return Err(self.error("expected '{' after always"));
2699 }
2700 let (always_body, _, always_right_brace_span) = self.parse_brace_enclosed_stmt_seq(
2701 "syntax error: empty always clause",
2702 BraceBodyContext::Ordinary,
2703 )?;
2704 CompoundCommand::Always(AlwaysCommand {
2705 body,
2706 always_body,
2707 span: left_brace_span.merge(always_right_brace_span),
2708 })
2709 } else {
2710 let _ = right_brace_span;
2711 CompoundCommand::BraceGroup(body)
2712 };
2713
2714 self.pop_depth();
2715 Ok(compound)
2716 }
2717
2718 fn parse_brace_enclosed_stmt_seq(
2719 &mut self,
2720 empty_error: &str,
2721 context: BraceBodyContext,
2722 ) -> Result<(StmtSeq, Span, Span)> {
2723 let left_brace_span = self.current_span;
2724 self.advance();
2725 self.brace_group_depth += 1;
2726 self.brace_body_stack.push(context);
2727 self.skip_command_separators()?;
2728
2729 let body_start = self.current_span.start;
2730 let mut commands = Vec::new();
2731 while !matches!(self.current_token_kind, Some(TokenKind::RightBrace) | None) {
2732 self.skip_command_separators()?;
2733 if self.at(TokenKind::RightBrace) {
2734 break;
2735 }
2736 commands.extend(self.parse_command_list_required()?);
2737 }
2738
2739 if !self.at(TokenKind::RightBrace) {
2740 self.brace_body_stack.pop();
2741 self.brace_group_depth -= 1;
2742 return Err(Error::parse(
2743 "expected '}' to close brace group".to_string(),
2744 ));
2745 }
2746
2747 if commands.is_empty()
2748 && !(self.dialect == ShellDialect::Zsh && matches!(context, BraceBodyContext::Function))
2749 {
2750 self.brace_body_stack.pop();
2751 self.brace_group_depth -= 1;
2752 return Err(self.error(empty_error));
2753 }
2754
2755 let right_brace_span = self.current_span;
2756 self.advance();
2757 self.brace_body_stack.pop();
2758 self.brace_group_depth -= 1;
2759 Ok((
2760 Self::stmt_seq_with_span(
2761 Span::from_positions(body_start, right_brace_span.start),
2762 commands,
2763 ),
2764 left_brace_span,
2765 right_brace_span,
2766 ))
2767 }
2768
2769 fn parse_if_condition_until_body_start(&mut self, allow_brace_body: bool) -> Result<Vec<Stmt>> {
2770 let mut stmts = Vec::with_capacity(2);
2771
2772 loop {
2773 self.skip_newlines()?;
2774
2775 if !allow_brace_body
2776 && !stmts.is_empty()
2777 && self.current_brace_starts_zsh_if_body_fact()
2778 {
2779 self.record_zsh_brace_if_span(self.current_span);
2780 }
2781
2782 if self.at(TokenKind::Semicolon) {
2783 let checkpoint = self.checkpoint();
2784 self.advance();
2785 if let Err(error) = self.skip_newlines() {
2786 self.restore(checkpoint);
2787 return Err(error);
2788 }
2789 let brace_if_span = (!allow_brace_body
2790 && !stmts.is_empty()
2791 && self.current_brace_starts_zsh_if_body_fact())
2792 .then_some(self.current_span);
2793 if self.is_keyword(Keyword::Then)
2794 || (allow_brace_body && !stmts.is_empty() && self.at(TokenKind::LeftBrace))
2795 {
2796 if let Some(span) = brace_if_span {
2797 self.record_zsh_brace_if_span(span);
2798 }
2799 break;
2800 }
2801 self.restore(checkpoint);
2802 if let Some(span) = brace_if_span {
2803 self.record_zsh_brace_if_span(span);
2804 }
2805 }
2806
2807 if self.is_keyword(Keyword::Then)
2808 || (allow_brace_body
2809 && !stmts.is_empty()
2810 && (self.at(TokenKind::LeftBrace)
2811 || self.current_token_is_compact_zsh_brace_body()))
2812 {
2813 break;
2814 }
2815
2816 if self.current_token.is_none() {
2817 break;
2818 }
2819
2820 let command_stmts = self.parse_command_list_required()?;
2821 self.apply_stmt_list_effects(&command_stmts);
2822 stmts.extend(command_stmts);
2823 }
2824
2825 Ok(stmts)
2826 }
2827
2828 fn current_brace_starts_zsh_if_body_fact(&mut self) -> bool {
2829 if !self.at(TokenKind::LeftBrace) {
2830 return false;
2831 }
2832
2833 let checkpoint = self.checkpoint();
2834 let reaches_then = loop {
2835 if self.skip_newlines().is_err() {
2836 break false;
2837 }
2838 if self.is_keyword(Keyword::Then) {
2839 break true;
2840 }
2841 if self.current_token.is_none() {
2842 break false;
2843 }
2844 if self.parse_command_list_required().is_err() {
2845 break false;
2846 }
2847 };
2848 self.restore(checkpoint);
2849
2850 !reaches_then
2851 }
2852
2853 fn parse_loop_condition_until_body_start(
2854 &mut self,
2855 allow_brace_body: bool,
2856 ) -> Result<Vec<Stmt>> {
2857 let mut stmts = Vec::with_capacity(2);
2858
2859 loop {
2860 self.skip_newlines()?;
2861
2862 if self.is_keyword(Keyword::Do)
2863 || (allow_brace_body
2864 && !stmts.is_empty()
2865 && (self.at(TokenKind::LeftBrace)
2866 || self.current_token_is_compact_zsh_brace_body())
2867 && self.current_brace_starts_zsh_loop_body_fact())
2868 {
2869 break;
2870 }
2871
2872 if self.current_token.is_none() {
2873 break;
2874 }
2875
2876 let command_stmts = self.parse_command_list_required()?;
2877 self.apply_stmt_list_effects(&command_stmts);
2878 stmts.extend(command_stmts);
2879 }
2880
2881 Ok(stmts)
2882 }
2883
2884 fn current_brace_starts_zsh_loop_body_fact(&mut self) -> bool {
2885 let checkpoint = self.checkpoint();
2886 let reaches_do = loop {
2887 if self.skip_newlines().is_err() {
2888 break false;
2889 }
2890 if self.is_keyword(Keyword::Do) {
2891 break true;
2892 }
2893 if self.current_token.is_none() {
2894 break false;
2895 }
2896 if self.parse_command_list_required().is_err() {
2897 break false;
2898 }
2899 };
2900 self.restore(checkpoint);
2901
2902 !reaches_do
2903 }
2904
2905 fn current_token_is_compact_zsh_brace_body(&mut self) -> bool {
2906 self.current_source_like_word_text()
2907 .is_some_and(|text| text.starts_with('{') && text.ends_with('}'))
2908 }
2909
2910 fn peek_zsh_always_span(&mut self) -> Option<Span> {
2911 if !self.is_keyword(Keyword::Always) {
2912 return None;
2913 }
2914
2915 let always_span = self.current_span;
2916 let checkpoint = self.checkpoint();
2917 self.advance();
2918 let result = match self.skip_newlines() {
2919 Ok(()) if self.at(TokenKind::LeftBrace) => Some(always_span),
2920 Ok(()) => None,
2921 Err(_) => None,
2922 };
2923 self.restore(checkpoint);
2924 result
2925 }
2926
2927 fn has_recorded_comment_between(&self, start_offset: usize, end_offset: usize) -> bool {
2928 self.comments.iter().any(|comment| {
2929 let comment_start = usize::from(comment.range.start());
2930 comment_start >= start_offset && comment_start < end_offset
2931 })
2932 }
2933
2934 fn rebase_nested_parse_error(&self, error: Error, base: Position) -> Error {
2935 let Error::Parse {
2936 message,
2937 line,
2938 column,
2939 } = error;
2940
2941 if line == 0 {
2942 return Error::parse(message);
2943 }
2944
2945 let rebased_line = base.line + line.saturating_sub(1);
2946 let rebased_column = if line == 1 {
2947 base.column + column.saturating_sub(1)
2948 } else {
2949 column
2950 };
2951
2952 Error::parse_at(message, rebased_line, rebased_column)
2953 }
2954
2955 fn try_parse_compact_function_brace_body(&mut self) -> Result<Option<CompoundCommand>> {
2956 if self.dialect != ShellDialect::Zsh
2957 || !self.zsh_short_loops_enabled()
2958 || !self.at_word_like()
2959 {
2960 return Ok(None);
2961 }
2962
2963 let Some(body_text) = self.current_source_like_word_text() else {
2964 return Ok(None);
2965 };
2966 let body_text = body_text.into_owned();
2967 let Some(inner) = body_text
2968 .strip_prefix('{')
2969 .and_then(|body| body.strip_suffix('}'))
2970 else {
2971 return Ok(None);
2972 };
2973
2974 if !inner.is_empty()
2975 && !inner.chars().any(|ch| {
2976 matches!(
2977 ch,
2978 ' ' | '\t' | '\n' | ';' | '&' | '|' | '<' | '>' | '$' | '"' | '\'' | '(' | ')'
2979 )
2980 })
2981 {
2982 return Ok(None);
2983 }
2984
2985 let nested_profile = self
2986 .current_zsh_options()
2987 .cloned()
2988 .map(|options| ShellProfile::with_zsh_options(self.dialect, options))
2989 .unwrap_or_else(|| self.shell_profile.clone());
2990 let mut nested =
2991 Parser::with_limits_and_profile(inner, self.max_depth, self.max_fuel, nested_profile);
2992 nested.aliases = self.aliases.clone();
2993 nested.expand_aliases = self.expand_aliases;
2994 nested.expand_next_word = self.expand_next_word;
2995
2996 let inner_start = self.current_span.start.advanced_by("{");
2997 let mut output = nested.parse();
2998 if output.is_err() {
2999 return Err(self.rebase_nested_parse_error(output.strict_error(), inner_start));
3000 }
3001 Self::rebase_stmt_seq(&mut output.file.body, inner_start);
3002 self.advance();
3003 Ok(Some(CompoundCommand::BraceGroup(output.file.body)))
3004 }
3005
3006 fn try_parse_compact_zsh_brace_body(
3007 &mut self,
3008 context: BraceBodyContext,
3009 ) -> Result<Option<(StmtSeq, Span, Span)>> {
3010 if self.dialect != ShellDialect::Zsh
3011 || !self.zsh_brace_bodies_enabled()
3012 || !self.at_word_like()
3013 {
3014 return Ok(None);
3015 }
3016
3017 let Some(body_text) = self.current_source_like_word_text() else {
3018 return Ok(None);
3019 };
3020 let body_text = body_text.into_owned();
3021 let Some(inner) = body_text
3022 .strip_prefix('{')
3023 .and_then(|body| body.strip_suffix('}'))
3024 else {
3025 return Ok(None);
3026 };
3027
3028 if !inner.is_empty()
3029 && !inner.chars().any(|ch| {
3030 matches!(
3031 ch,
3032 ' ' | '\t' | '\n' | ';' | '&' | '|' | '<' | '>' | '$' | '"' | '\'' | '(' | ')'
3033 )
3034 })
3035 {
3036 return Ok(None);
3037 }
3038
3039 let nested_profile = self
3040 .current_zsh_options()
3041 .cloned()
3042 .map(|options| ShellProfile::with_zsh_options(self.dialect, options))
3043 .unwrap_or_else(|| self.shell_profile.clone());
3044 let mut nested =
3045 Parser::with_limits_and_profile(inner, self.max_depth, self.max_fuel, nested_profile);
3046 nested.aliases = self.aliases.clone();
3047 nested.expand_aliases = self.expand_aliases;
3048 nested.expand_next_word = self.expand_next_word;
3049
3050 let word_span = self.current_span;
3051 let inner_start = word_span.start.advanced_by("{");
3052 let mut output = nested.parse();
3053 if output.is_err() {
3054 return Err(self.rebase_nested_parse_error(output.strict_error(), inner_start));
3055 }
3056 Self::rebase_stmt_seq(&mut output.file.body, inner_start);
3057
3058 let left_brace_span = Span::from_positions(word_span.start, inner_start);
3059 let right_brace_start = word_span
3060 .start
3061 .advanced_by(&body_text[..body_text.len() - 1]);
3062 let right_brace_span = Span::from_positions(right_brace_start, word_span.end);
3063 let body = Self::stmt_seq_with_span(
3064 Span::from_positions(inner_start, right_brace_start),
3065 output.file.body.stmts,
3066 );
3067
3068 if body.is_empty()
3069 && !(self.dialect == ShellDialect::Zsh && matches!(context, BraceBodyContext::Function))
3070 {
3071 let message = match context {
3072 BraceBodyContext::Function => "syntax error: empty brace group",
3073 BraceBodyContext::IfClause => "syntax error: empty then clause",
3074 BraceBodyContext::Ordinary => "syntax error: empty brace group",
3075 };
3076 return Err(self.error(message));
3077 }
3078
3079 self.advance();
3080 Ok(Some((body, left_brace_span, right_brace_span)))
3081 }
3082
3083 fn should_consume_right_brace_as_literal_argument(
3084 &mut self,
3085 next_kind_after_right_brace: Option<TokenKind>,
3086 ) -> bool {
3087 if !self.current_token_has_leading_whitespace() {
3088 return false;
3089 }
3090
3091 if self.brace_group_depth == 0 {
3092 return true;
3093 }
3094
3095 if self.dialect != ShellDialect::Zsh {
3096 return true;
3097 }
3098
3099 next_kind_after_right_brace == Some(TokenKind::Semicolon)
3100 && self.current_token_is_tight_to_next_token()
3101 && self.next_token_after_tight_semicolon_is(TokenKind::RightBrace)
3102 }
3103
3104 fn next_token_after_tight_semicolon_is(&mut self, expected: TokenKind) -> bool {
3105 let checkpoint = self.checkpoint();
3106 self.advance();
3107 if !self.at(TokenKind::Semicolon) {
3108 self.restore(checkpoint);
3109 return false;
3110 }
3111 self.advance();
3112 let result = self.at(expected);
3113 self.restore(checkpoint);
3114 result
3115 }
3116
3117 fn parse_conditional(&mut self) -> Result<CompoundCommand> {
3120 self.ensure_double_bracket()?;
3121 let left_bracket_span = self.current_span;
3122 self.advance(); self.skip_conditional_newlines();
3124
3125 let expression = self.parse_conditional_or(false)?;
3126 self.skip_conditional_newlines();
3127
3128 let right_bracket_span = match self.current_token_kind {
3129 Some(TokenKind::DoubleRightBracket) => {
3130 let span = self.current_span;
3131 self.advance(); span
3133 }
3134 None => {
3135 return Err(crate::error::Error::parse(
3136 "unexpected end of input in [[ ]]".to_string(),
3137 ));
3138 }
3139 _ => return Err(self.error("expected ']]' to close conditional expression")),
3140 };
3141
3142 Ok(CompoundCommand::Conditional(ConditionalCommand {
3143 expression,
3144 span: left_bracket_span.merge(right_bracket_span),
3145 left_bracket_span,
3146 right_bracket_span,
3147 }))
3148 }
3149
3150 fn skip_conditional_newlines(&mut self) {
3151 while self.at(TokenKind::Newline) {
3152 self.advance();
3153 }
3154 }
3155
3156 fn parse_conditional_or(&mut self, stop_at_right_paren: bool) -> Result<ConditionalExpr> {
3157 let mut expr = self.parse_conditional_and(stop_at_right_paren)?;
3158
3159 loop {
3160 self.skip_conditional_newlines();
3161 if !self.at(TokenKind::Or) {
3162 break;
3163 }
3164
3165 let op_span = self.current_span;
3166 self.advance();
3167 let right = self.parse_conditional_and(stop_at_right_paren)?;
3168 expr = ConditionalExpr::Binary(ConditionalBinaryExpr {
3169 left: Box::new(expr),
3170 op: ConditionalBinaryOp::Or,
3171 op_span,
3172 right: Box::new(right),
3173 });
3174 }
3175
3176 Ok(expr)
3177 }
3178
3179 fn parse_conditional_and(&mut self, stop_at_right_paren: bool) -> Result<ConditionalExpr> {
3180 let mut expr = self.parse_conditional_term(stop_at_right_paren)?;
3181
3182 loop {
3183 self.skip_conditional_newlines();
3184 if !self.at(TokenKind::And) {
3185 break;
3186 }
3187
3188 let op_span = self.current_span;
3189 self.advance();
3190 let right = self.parse_conditional_term(stop_at_right_paren)?;
3191 expr = ConditionalExpr::Binary(ConditionalBinaryExpr {
3192 left: Box::new(expr),
3193 op: ConditionalBinaryOp::And,
3194 op_span,
3195 right: Box::new(right),
3196 });
3197 }
3198
3199 Ok(expr)
3200 }
3201
3202 fn parse_conditional_term(&mut self, stop_at_right_paren: bool) -> Result<ConditionalExpr> {
3203 self.skip_conditional_newlines();
3204
3205 if let Some(op) = self.current_conditional_unary_op() {
3206 let op_span = self.current_span;
3207 self.advance();
3208 self.skip_conditional_newlines();
3209
3210 let expr = if matches!(op, ConditionalUnaryOp::Not) {
3211 self.parse_conditional_term(stop_at_right_paren)?
3212 } else {
3213 if matches!(
3214 op,
3215 ConditionalUnaryOp::VariableSet | ConditionalUnaryOp::ReferenceVariable
3216 ) {
3217 let word = self.collect_conditional_context_word(stop_at_right_paren)?;
3218 self.conditional_var_ref_expr(word)
3219 } else {
3220 let word = self.parse_conditional_operand_word()?;
3221 ConditionalExpr::Word(word)
3222 }
3223 };
3224
3225 return Ok(ConditionalExpr::Unary(ConditionalUnaryExpr {
3226 op,
3227 op_span,
3228 expr: Box::new(expr),
3229 }));
3230 }
3231
3232 if self.at(TokenKind::DoubleLeftParen) {
3233 if self.dialect == ShellDialect::Zsh {
3234 let left_paren_span = self.current_span;
3235 if let Some(right_paren_span) = self.scan_arithmetic_command_close(left_paren_span)
3236 {
3237 let span = left_paren_span.merge(right_paren_span);
3238 let text = span.slice(self.input).to_string();
3239 while self.current_token.is_some()
3240 && self.current_span.start.offset < right_paren_span.end.offset
3241 {
3242 self.advance();
3243 }
3244 return Ok(ConditionalExpr::Word(
3245 self.parse_word_with_context(&text, span, span.start, true),
3246 ));
3247 }
3248 }
3249 self.split_current_double_left_paren();
3250 }
3251
3252 let left = if self.at(TokenKind::LeftParen) {
3253 let left_paren_span = self.current_span;
3254 self.advance();
3255 let expr = self.parse_conditional_or(true)?;
3256 self.skip_conditional_newlines();
3257 if self.at(TokenKind::DoubleRightParen) {
3258 self.split_current_double_right_paren();
3259 }
3260 if !self.at(TokenKind::RightParen) {
3261 return Err(self.error("expected ')' in conditional expression"));
3262 }
3263 let right_paren_span = self.current_span;
3264 self.advance();
3265 ConditionalExpr::Parenthesized(ConditionalParenExpr {
3266 left_paren_span,
3267 expr: Box::new(expr),
3268 right_paren_span,
3269 })
3270 } else {
3271 ConditionalExpr::Word(self.parse_conditional_operand_word()?)
3272 };
3273
3274 self.skip_conditional_newlines();
3275
3276 let Some(op) = self.current_conditional_comparison_op() else {
3277 return Ok(left);
3278 };
3279
3280 let op_span = self.current_span;
3281 self.advance();
3282 self.skip_conditional_newlines();
3283
3284 let right = match op {
3285 ConditionalBinaryOp::RegexMatch => {
3286 if self.at(TokenKind::LeftBrace) {
3287 return Err(self.error("expected conditional operand"));
3288 }
3289 ConditionalExpr::Regex(self.collect_conditional_context_word(stop_at_right_paren)?)
3290 }
3291 ConditionalBinaryOp::PatternEqShort
3292 | ConditionalBinaryOp::PatternEq
3293 | ConditionalBinaryOp::PatternNe => {
3294 let word = self.collect_conditional_context_word(stop_at_right_paren)?;
3295 ConditionalExpr::Pattern(self.pattern_from_conditional_word(&word))
3296 }
3297 _ => ConditionalExpr::Word(self.parse_conditional_operand_word()?),
3298 };
3299
3300 Ok(ConditionalExpr::Binary(ConditionalBinaryExpr {
3301 left: Box::new(left),
3302 op,
3303 op_span,
3304 right: Box::new(right),
3305 }))
3306 }
3307
3308 fn parse_conditional_operand_word(&mut self) -> Result<Word> {
3309 self.skip_conditional_newlines();
3310
3311 if let Some(word) = self.current_conditional_source_word(false) {
3312 self.advance_past_word(&word);
3313 self.restore_conditional_source_delimiter(word.span.end, false);
3314 return Ok(word);
3315 }
3316
3317 if let Some(word) = self.take_current_word_and_advance() {
3318 return Ok(word);
3319 }
3320
3321 let Some(word) = self.current_conditional_literal_word() else {
3322 return Err(self.error("expected conditional operand"));
3323 };
3324 self.advance_past_word(&word);
3325 Ok(word)
3326 }
3327
3328 fn conditional_var_ref_expr(&self, word: Word) -> ConditionalExpr {
3329 self.parse_var_ref_from_word(&word, SubscriptInterpretation::Contextual)
3330 .map(Box::new)
3331 .map(ConditionalExpr::VarRef)
3332 .unwrap_or(ConditionalExpr::Word(word))
3333 }
3334
3335 fn current_conditional_source_word(&mut self, stop_at_right_paren: bool) -> Option<Word> {
3336 let token = self.current_token.as_ref()?;
3337 if token.flags.is_synthetic() {
3338 return None;
3339 }
3340
3341 if matches!(
3342 self.current_token_kind,
3343 Some(TokenKind::QuotedWord | TokenKind::LiteralWord)
3344 ) {
3345 return None;
3346 }
3347
3348 let starts_with_paren = matches!(self.current_token_kind, Some(TokenKind::LeftParen));
3349 let starts_with_zsh_pattern_punct = matches!(
3350 self.current_token_kind,
3351 Some(TokenKind::RedirectIn | TokenKind::RedirectOut | TokenKind::RedirectReadWrite)
3352 ) && self.dialect == ShellDialect::Zsh;
3353
3354 if !starts_with_paren
3355 && !starts_with_zsh_pattern_punct
3356 && !self.current_token_kind.is_some_and(TokenKind::is_word_like)
3357 {
3358 return None;
3359 }
3360
3361 let start = self.current_span.start;
3362 let (text, end) =
3363 self.scan_conditional_source_word(start, stop_at_right_paren, starts_with_paren)?;
3364 let span = Span::from_positions(start, end);
3365 Some(self.parse_word_with_context(&text, span, start, true))
3366 }
3367
3368 fn scan_conditional_source_word(
3369 &self,
3370 start: Position,
3371 stop_at_right_paren: bool,
3372 starts_with_paren: bool,
3373 ) -> Option<(String, Position)> {
3374 if start.offset >= self.input.len() {
3375 return None;
3376 }
3377
3378 let mut cursor = start;
3379 let mut text = String::new();
3380 let mut paren_depth = 0_i32;
3381 let mut brace_depth = 0_i32;
3382 let mut bracket_depth = 0_i32;
3383 let mut in_single = false;
3384 let mut in_double = false;
3385 let mut in_backtick = false;
3386 let mut escaped = false;
3387 let mut prev_char = None;
3388
3389 while cursor.offset < self.input.len() {
3390 let rest = &self.input[cursor.offset..];
3391 if !in_single
3392 && !in_double
3393 && !in_backtick
3394 && !escaped
3395 && paren_depth == 0
3396 && brace_depth == 0
3397 && bracket_depth == 0
3398 {
3399 if rest.starts_with("]]")
3400 || rest.starts_with("&&")
3401 || rest.starts_with("||")
3402 || (!starts_with_paren && rest.starts_with(')'))
3403 || (stop_at_right_paren && rest.starts_with(')'))
3404 {
3405 break;
3406 }
3407
3408 let ch = rest.chars().next()?;
3409 if matches!(ch, ' ' | '\t' | '\n' | ';') {
3410 break;
3411 }
3412 }
3413
3414 let ch = self.input[cursor.offset..].chars().next()?;
3415 cursor.advance(ch);
3416 text.push(ch);
3417
3418 if escaped {
3419 escaped = false;
3420 prev_char = Some(ch);
3421 continue;
3422 }
3423
3424 match ch {
3425 '\\' if !in_single => escaped = true,
3426 '\'' if !in_double => in_single = !in_single,
3427 '"' if !in_single => in_double = !in_double,
3428 '`' if !in_single => in_backtick = !in_backtick,
3429 '(' if !in_single && !in_double && brace_depth == 0 => paren_depth += 1,
3430 ')' if !in_single && !in_double && brace_depth == 0 && paren_depth > 0 => {
3431 paren_depth -= 1
3432 }
3433 '{' if !in_single && !in_double && (brace_depth > 0 || prev_char == Some('$')) => {
3434 brace_depth += 1
3435 }
3436 '}' if !in_single && !in_double && brace_depth > 0 => brace_depth -= 1,
3437 '[' if !in_single && !in_double => bracket_depth += 1,
3438 ']' if !in_single && !in_double && bracket_depth > 0 => bracket_depth -= 1,
3439 _ => {}
3440 }
3441
3442 prev_char = Some(ch);
3443 }
3444
3445 (!text.is_empty()).then_some((text, cursor))
3446 }
3447
3448 fn conditional_source_delimiter_after(
3449 &self,
3450 end: Position,
3451 stop_at_right_paren: bool,
3452 ) -> Option<(TokenKind, Span)> {
3453 let mut cursor = end;
3454 while cursor.offset < self.input.len() {
3455 let rest = &self.input[cursor.offset..];
3456 let ch = rest.chars().next()?;
3457 if matches!(ch, ' ' | '\t') {
3458 cursor.advance(ch);
3459 continue;
3460 }
3461 break;
3462 }
3463
3464 let rest = self.input.get(cursor.offset..)?;
3465 let (kind, text) = if rest.starts_with("]]") {
3466 (TokenKind::DoubleRightBracket, "]]")
3467 } else if rest.starts_with("&&") {
3468 (TokenKind::And, "&&")
3469 } else if rest.starts_with("||") {
3470 (TokenKind::Or, "||")
3471 } else if stop_at_right_paren && rest.starts_with(')') {
3472 (TokenKind::RightParen, ")")
3473 } else {
3474 return None;
3475 };
3476
3477 Some((kind, Span::from_positions(cursor, cursor.advanced_by(text))))
3478 }
3479
3480 fn restore_conditional_source_delimiter(&mut self, end: Position, stop_at_right_paren: bool) {
3481 let Some((kind, span)) = self.conditional_source_delimiter_after(end, stop_at_right_paren)
3482 else {
3483 return;
3484 };
3485
3486 if self.current_token_kind == Some(kind) && self.current_span == span {
3487 return;
3488 }
3489
3490 if let Some(current_kind) = self.current_token_kind
3491 && matches!(
3492 current_kind,
3493 TokenKind::Newline
3494 | TokenKind::Semicolon
3495 | TokenKind::And
3496 | TokenKind::Or
3497 | TokenKind::RightParen
3498 | TokenKind::DoubleRightBracket
3499 )
3500 {
3501 self.synthetic_tokens
3502 .push_front(SyntheticToken::punctuation(current_kind, self.current_span));
3503 }
3504
3505 self.set_current_kind(kind, span);
3506 }
3507
3508 fn current_conditional_unary_op(&self) -> Option<ConditionalUnaryOp> {
3509 if !self.at(TokenKind::Word) {
3510 return None;
3511 }
3512 let word = self.current_word_str()?;
3513
3514 Some(match word {
3515 "!" => ConditionalUnaryOp::Not,
3516 "-e" | "-a" => ConditionalUnaryOp::Exists,
3517 "-f" => ConditionalUnaryOp::RegularFile,
3518 "-d" => ConditionalUnaryOp::Directory,
3519 "-c" => ConditionalUnaryOp::CharacterSpecial,
3520 "-b" => ConditionalUnaryOp::BlockSpecial,
3521 "-p" => ConditionalUnaryOp::NamedPipe,
3522 "-S" => ConditionalUnaryOp::Socket,
3523 "-L" | "-h" => ConditionalUnaryOp::Symlink,
3524 "-k" => ConditionalUnaryOp::Sticky,
3525 "-g" => ConditionalUnaryOp::SetGroupId,
3526 "-u" => ConditionalUnaryOp::SetUserId,
3527 "-G" => ConditionalUnaryOp::GroupOwned,
3528 "-O" => ConditionalUnaryOp::UserOwned,
3529 "-N" => ConditionalUnaryOp::Modified,
3530 "-r" => ConditionalUnaryOp::Readable,
3531 "-w" => ConditionalUnaryOp::Writable,
3532 "-x" => ConditionalUnaryOp::Executable,
3533 "-s" => ConditionalUnaryOp::NonEmptyFile,
3534 "-t" => ConditionalUnaryOp::FdTerminal,
3535 "-z" => ConditionalUnaryOp::EmptyString,
3536 "-n" => ConditionalUnaryOp::NonEmptyString,
3537 "-o" => ConditionalUnaryOp::OptionSet,
3538 "-v" => ConditionalUnaryOp::VariableSet,
3539 "-R" => ConditionalUnaryOp::ReferenceVariable,
3540 _ => return None,
3541 })
3542 }
3543
3544 fn current_conditional_comparison_op(&self) -> Option<ConditionalBinaryOp> {
3545 match self.current_token_kind? {
3546 TokenKind::Word => Some(match self.current_word_str()? {
3547 "=" => ConditionalBinaryOp::PatternEqShort,
3548 "==" => ConditionalBinaryOp::PatternEq,
3549 "!=" => ConditionalBinaryOp::PatternNe,
3550 "=~" => ConditionalBinaryOp::RegexMatch,
3551 "-nt" => ConditionalBinaryOp::NewerThan,
3552 "-ot" => ConditionalBinaryOp::OlderThan,
3553 "-ef" => ConditionalBinaryOp::SameFile,
3554 "-eq" => ConditionalBinaryOp::ArithmeticEq,
3555 "-ne" => ConditionalBinaryOp::ArithmeticNe,
3556 "-le" => ConditionalBinaryOp::ArithmeticLe,
3557 "-ge" => ConditionalBinaryOp::ArithmeticGe,
3558 "-lt" => ConditionalBinaryOp::ArithmeticLt,
3559 "-gt" => ConditionalBinaryOp::ArithmeticGt,
3560 _ => return None,
3561 }),
3562 TokenKind::RedirectIn => Some(ConditionalBinaryOp::LexicalBefore),
3563 TokenKind::RedirectOut => Some(ConditionalBinaryOp::LexicalAfter),
3564 _ => None,
3565 }
3566 }
3567
3568 fn collect_conditional_context_word(&mut self, stop_at_right_paren: bool) -> Result<Word> {
3569 self.skip_conditional_newlines();
3570
3571 if let Some(word) = self.current_conditional_source_word(stop_at_right_paren) {
3572 self.advance_past_word(&word);
3573 self.restore_conditional_source_delimiter(word.span.end, stop_at_right_paren);
3574 return Ok(word);
3575 }
3576
3577 let mut first_word: Option<Word> = None;
3578 let mut parts = Vec::new();
3579 let mut start = None;
3580 let mut end = None;
3581 let mut previous_end: Option<Position> = None;
3582 let mut composite = false;
3583 let mut paren_depth = 0usize;
3584
3585 loop {
3586 self.skip_conditional_newlines();
3587
3588 match self.current_token_kind {
3589 Some(TokenKind::DoubleRightBracket) => break,
3590 Some(TokenKind::And) | Some(TokenKind::Or) if paren_depth == 0 => break,
3591 Some(TokenKind::RightParen) if stop_at_right_paren && paren_depth == 0 => break,
3592 None => break,
3593 _ => {}
3594 }
3595
3596 if let Some(prev_end) = previous_end
3597 && prev_end.offset < self.current_span.start.offset
3598 {
3599 let gap_span = Span::from_positions(prev_end, self.current_span.start);
3600 let gap_text = gap_span.slice(self.input);
3601 if let Some(word) = first_word.take() {
3602 parts.extend(word.parts);
3603 }
3604 if Self::source_text_needs_quote_preserving_decode(gap_text) {
3605 let gap_word = self.decode_word_text_preserving_quotes_if_needed(
3606 gap_text,
3607 gap_span,
3608 gap_span.start,
3609 true,
3610 );
3611 parts.extend(gap_word.parts);
3612 } else {
3613 parts.push(WordPartNode::new(
3614 WordPart::Literal(LiteralText::source()),
3615 gap_span,
3616 ));
3617 }
3618 composite = true;
3619 }
3620
3621 match self.current_token_kind {
3622 Some(TokenKind::Word | TokenKind::LiteralWord | TokenKind::QuotedWord) => {
3623 let word = self
3624 .take_current_word()
3625 .ok_or_else(|| self.error("expected conditional operand"))?;
3626 if start.is_none() {
3627 start = Some(word.span.start);
3628 } else {
3629 if let Some(first) = first_word.take() {
3630 parts.extend(first.parts);
3631 }
3632 composite = true;
3633 }
3634 end = Some(word.span.end);
3635 if first_word.is_none() && !composite {
3636 first_word = Some(word);
3637 } else {
3638 parts.extend(word.parts);
3639 }
3640 previous_end = Some(self.current_span.end);
3641 self.advance();
3642 }
3643 Some(TokenKind::LeftParen) => {
3644 if start.is_none() {
3645 start = Some(self.current_span.start);
3646 }
3647 end = Some(self.current_span.end);
3648 if let Some(word) = first_word.take() {
3649 parts.extend(word.parts);
3650 }
3651 parts.push(WordPartNode::new(
3652 WordPart::Literal(LiteralText::owned("(")),
3653 self.current_span,
3654 ));
3655 previous_end = Some(self.current_span.end);
3656 paren_depth += 1;
3657 composite = true;
3658 self.advance();
3659 }
3660 Some(TokenKind::DoubleLeftParen) => {
3661 if start.is_none() {
3662 start = Some(self.current_span.start);
3663 }
3664 end = Some(self.current_span.end);
3665 if let Some(word) = first_word.take() {
3666 parts.extend(word.parts);
3667 }
3668 parts.push(WordPartNode::new(
3669 WordPart::Literal(LiteralText::owned("((")),
3670 self.current_span,
3671 ));
3672 previous_end = Some(self.current_span.end);
3673 paren_depth += 2;
3674 composite = true;
3675 self.advance();
3676 }
3677 Some(TokenKind::RightParen) => {
3678 if paren_depth == 0 {
3679 break;
3680 }
3681 if start.is_none() {
3682 start = Some(self.current_span.start);
3683 }
3684 end = Some(self.current_span.end);
3685 if let Some(word) = first_word.take() {
3686 parts.extend(word.parts);
3687 }
3688 parts.push(WordPartNode::new(
3689 WordPart::Literal(LiteralText::owned(")")),
3690 self.current_span,
3691 ));
3692 previous_end = Some(self.current_span.end);
3693 paren_depth = paren_depth.saturating_sub(1);
3694 composite = true;
3695 self.advance();
3696 }
3697 Some(TokenKind::DoubleRightParen) => {
3698 if start.is_none() {
3699 start = Some(self.current_span.start);
3700 }
3701 end = Some(self.current_span.end);
3702 if let Some(word) = first_word.take() {
3703 parts.extend(word.parts);
3704 }
3705 parts.push(WordPartNode::new(
3706 WordPart::Literal(LiteralText::owned("))")),
3707 self.current_span,
3708 ));
3709 previous_end = Some(self.current_span.end);
3710 paren_depth = paren_depth.saturating_sub(2);
3711 composite = true;
3712 self.advance();
3713 }
3714 Some(TokenKind::Pipe) => {
3715 if start.is_none() {
3716 start = Some(self.current_span.start);
3717 }
3718 end = Some(self.current_span.end);
3719 if let Some(word) = first_word.take() {
3720 parts.extend(word.parts);
3721 }
3722 parts.push(WordPartNode::new(
3723 WordPart::Literal(LiteralText::owned("|")),
3724 self.current_span,
3725 ));
3726 previous_end = Some(self.current_span.end);
3727 composite = true;
3728 self.advance();
3729 }
3730 Some(TokenKind::And) => {
3731 if paren_depth == 0 {
3732 break;
3733 }
3734 if start.is_none() {
3735 start = Some(self.current_span.start);
3736 }
3737 end = Some(self.current_span.end);
3738 if let Some(word) = first_word.take() {
3739 parts.extend(word.parts);
3740 }
3741 parts.push(WordPartNode::new(
3742 WordPart::Literal(LiteralText::owned("&&")),
3743 self.current_span,
3744 ));
3745 previous_end = Some(self.current_span.end);
3746 composite = true;
3747 self.advance();
3748 }
3749 Some(TokenKind::Or) => {
3750 if paren_depth == 0 {
3751 break;
3752 }
3753 if start.is_none() {
3754 start = Some(self.current_span.start);
3755 }
3756 end = Some(self.current_span.end);
3757 if let Some(word) = first_word.take() {
3758 parts.extend(word.parts);
3759 }
3760 parts.push(WordPartNode::new(
3761 WordPart::Literal(LiteralText::owned("||")),
3762 self.current_span,
3763 ));
3764 previous_end = Some(self.current_span.end);
3765 composite = true;
3766 self.advance();
3767 }
3768 Some(TokenKind::RedirectIn)
3769 | Some(TokenKind::RedirectOut)
3770 | Some(TokenKind::RedirectReadWrite) => {
3771 let literal = self.input
3772 [self.current_span.start.offset..self.current_span.end.offset]
3773 .to_string();
3774 if start.is_none() {
3775 start = Some(self.current_span.start);
3776 }
3777 end = Some(self.current_span.end);
3778 if let Some(word) = first_word.take() {
3779 parts.extend(word.parts);
3780 }
3781 parts.push(WordPartNode::new(
3782 WordPart::Literal(self.literal_text(
3783 literal,
3784 self.current_span.start,
3785 self.current_span.end,
3786 true,
3787 )),
3788 self.current_span,
3789 ));
3790 previous_end = Some(self.current_span.end);
3791 composite = true;
3792 self.advance();
3793 }
3794 _ => {
3795 let literal = self.input
3796 [self.current_span.start.offset..self.current_span.end.offset]
3797 .to_string();
3798 if literal.is_empty() {
3799 break;
3800 }
3801 if start.is_none() {
3802 start = Some(self.current_span.start);
3803 }
3804 end = Some(self.current_span.end);
3805 if let Some(word) = first_word.take() {
3806 parts.extend(word.parts);
3807 }
3808 parts.push(WordPartNode::new(
3809 WordPart::Literal(self.literal_text(
3810 literal,
3811 self.current_span.start,
3812 self.current_span.end,
3813 true,
3814 )),
3815 self.current_span,
3816 ));
3817 previous_end = Some(self.current_span.end);
3818 composite = true;
3819 self.advance();
3820 }
3821 }
3822 }
3823
3824 if !composite && let Some(word) = first_word {
3825 return Ok(word);
3826 }
3827
3828 let (start, end) = match (start, end) {
3829 (Some(start), Some(end)) => (start, end),
3830 _ => return Err(self.error("expected conditional operand")),
3831 };
3832
3833 Ok(self.word_with_parts(parts, Span::from_positions(start, end)))
3834 }
3835
3836 fn parse_arithmetic_command(&mut self) -> Result<CompoundCommand> {
3837 self.ensure_arithmetic_command()?;
3838 let left_paren_span = self.current_span;
3839 let Some(right_paren_span) = self.scan_arithmetic_command_close(left_paren_span) else {
3840 return Err(Error::parse(
3841 "unexpected end of input in arithmetic command".to_string(),
3842 ));
3843 };
3844 while self.current_token.is_some()
3845 && self.current_span.start.offset < right_paren_span.end.offset
3846 {
3847 self.advance();
3848 }
3849
3850 let expr_span = Self::optional_span(left_paren_span.end, right_paren_span.start);
3851 let expr_ast = self
3852 .parse_explicit_arithmetic_span(expr_span, "invalid arithmetic command")
3853 .ok()
3854 .flatten();
3855 Ok(CompoundCommand::Arithmetic(ArithmeticCommand {
3856 span: left_paren_span.merge(right_paren_span),
3857 left_paren_span,
3858 expr_span,
3859 expr_ast,
3860 right_paren_span,
3861 }))
3862 }
3863
3864 fn scan_arithmetic_command_close(&self, left_paren_span: Span) -> Option<Span> {
3865 let mut cursor = left_paren_span.end;
3866 let mut depth = 0_i32;
3867 let mut in_single = false;
3868 let mut in_double = false;
3869 let mut in_backtick = false;
3870 let mut escaped = false;
3871
3872 while cursor.offset < self.input.len() {
3873 let rest = &self.input[cursor.offset..];
3874
3875 if !in_single && !in_double && !in_backtick {
3876 if rest.starts_with("((") {
3877 depth += 2;
3878 cursor = cursor.advanced_by("((");
3879 continue;
3880 }
3881
3882 if rest.starts_with("))") {
3883 if depth == 0 {
3884 return Some(Span::from_positions(cursor, cursor.advanced_by("))")));
3885 }
3886 if depth == 1 {
3887 cursor.advance(')');
3888 return Some(Span::from_positions(cursor, cursor.advanced_by("))")));
3889 }
3890 depth -= 2;
3891 cursor = cursor.advanced_by("))");
3892 continue;
3893 }
3894 }
3895
3896 let ch = rest.chars().next()?;
3897 cursor.advance(ch);
3898
3899 if escaped {
3900 escaped = false;
3901 continue;
3902 }
3903
3904 match ch {
3905 '\\' if !in_single => escaped = true,
3906 '\'' if !in_double && !in_backtick => in_single = !in_single,
3907 '"' if !in_single && !in_backtick => in_double = !in_double,
3908 '`' if !in_single && !in_double => in_backtick = !in_backtick,
3909 '(' if !in_single && !in_double && !in_backtick => depth += 1,
3910 ')' if !in_single && !in_double && !in_backtick && depth > 0 => depth -= 1,
3911 _ => {}
3912 }
3913 }
3914
3915 None
3916 }
3917
3918 fn parse_function_body_command(&mut self, allow_bare_compound: bool) -> Result<Stmt> {
3919 if let Some(compound) = self.try_parse_compact_function_brace_body()? {
3920 let redirects = self.parse_trailing_redirects();
3921 return Ok(Self::lower_non_sequence_command_to_stmt(Command::Compound(
3922 Box::new(compound),
3923 redirects,
3924 )));
3925 }
3926
3927 let compound = match self.current_keyword() {
3928 Some(Keyword::If) if allow_bare_compound => self.parse_if()?,
3929 Some(Keyword::For) if allow_bare_compound => self.parse_for()?,
3930 Some(Keyword::Repeat) if allow_bare_compound && self.zsh_short_repeat_enabled() => {
3931 self.parse_repeat()?
3932 }
3933 Some(Keyword::Foreach) if allow_bare_compound && self.zsh_short_loops_enabled() => {
3934 self.parse_foreach()?
3935 }
3936 Some(Keyword::While) if allow_bare_compound => self.parse_while()?,
3937 Some(Keyword::Until) if allow_bare_compound => self.parse_until()?,
3938 Some(Keyword::Case) if allow_bare_compound => self.parse_case()?,
3939 Some(Keyword::Select) if allow_bare_compound => self.parse_select()?,
3940 _ => match self.current_token_kind {
3941 Some(TokenKind::LeftBrace) => self.parse_brace_group(BraceBodyContext::Function)?,
3942 Some(TokenKind::LeftParen) => self.parse_subshell()?,
3943 Some(TokenKind::DoubleLeftBracket) if allow_bare_compound => {
3944 self.parse_conditional()?
3945 }
3946 Some(TokenKind::DoubleLeftParen) if allow_bare_compound => {
3947 if self.looks_like_command_style_double_paren() {
3948 self.split_current_double_left_paren();
3949 self.parse_subshell()?
3950 } else {
3951 let checkpoint = self.checkpoint();
3952 if let Ok(compound) = self.parse_arithmetic_command() {
3953 compound
3954 } else {
3955 self.restore(checkpoint);
3956 self.split_current_double_left_paren();
3957 self.parse_subshell()?
3958 }
3959 }
3960 }
3961 _ => {
3962 return Err(Error::parse(
3963 "expected compound command for function body".to_string(),
3964 ));
3965 }
3966 },
3967 };
3968 let redirects = self.parse_trailing_redirects();
3969 Ok(Self::lower_non_sequence_command_to_stmt(Command::Compound(
3970 Box::new(compound),
3971 redirects,
3972 )))
3973 }
3974
3975 fn parse_function_header_entry(&mut self) -> Result<FunctionHeaderEntry> {
3976 let word = self
3977 .take_current_function_header_word_and_advance()
3978 .ok_or_else(|| self.error("expected function name"))?;
3979 Ok(self.function_header_entry_from_word(word))
3980 }
3981
3982 fn parse_function_keyword_header_entry(&mut self) -> Result<FunctionHeaderEntry> {
3983 let word = self
3984 .take_current_function_header_word_and_advance()
3985 .or_else(|| self.take_current_function_keyword_name_and_advance())
3986 .ok_or_else(|| self.error("expected function name"))?;
3987 Ok(self.function_header_entry_from_word(word))
3988 }
3989
3990 fn take_current_function_header_word_and_advance(&mut self) -> Option<Word> {
3991 let span = self.current_span;
3992 if let Some(token) = self.current_token.clone()
3993 && let Some(word) = self.simple_word_from_token(&token, span)
3994 {
3995 self.advance_past_word(&word);
3996 return Some(word);
3997 }
3998
3999 let token = self.current_token.take()?;
4000 let word = self.decode_word_from_token(&token, span);
4001 self.current_token = Some(token);
4002 if let Some(word) = word.as_ref() {
4003 self.advance_past_word(word);
4004 }
4005 word
4006 }
4007
4008 fn take_current_function_keyword_name_and_advance(&mut self) -> Option<Word> {
4009 let text = match self.current_token_kind? {
4010 TokenKind::DoubleLeftBracket => "[[",
4011 TokenKind::DoubleRightBracket => "]]",
4012 TokenKind::LeftBrace => "{",
4013 TokenKind::RightBrace => "}",
4014 _ => return None,
4015 };
4016 let word = Word::literal_with_span(text, self.current_span);
4017 self.advance();
4018 Some(word)
4019 }
4020
4021 fn function_header_entry_from_word(&self, word: Word) -> FunctionHeaderEntry {
4022 let static_name = self.literal_word_text(&word).map(Name::from);
4023 FunctionHeaderEntry { word, static_name }
4024 }
4025
4026 fn parse_function_parens_span(&mut self) -> Result<Span> {
4027 if !self.at(TokenKind::LeftParen) {
4028 return Err(self.error("expected '(' in function definition"));
4029 }
4030 let left_paren_span = self.current_span;
4031 self.advance();
4032
4033 if !self.at(TokenKind::RightParen) {
4034 return Err(Error::parse(
4035 "expected ')' in function definition".to_string(),
4036 ));
4037 }
4038 let right_paren_span = self.current_span;
4039 self.advance();
4040 Ok(left_paren_span.merge(right_paren_span))
4041 }
4042
4043 fn parse_zsh_function_body_stmt(&mut self) -> Result<Stmt> {
4044 self.skip_newlines()?;
4045
4046 if let Some(compound) = self.try_parse_compact_function_brace_body()? {
4047 let redirects = self.parse_trailing_redirects();
4048 return Ok(Self::lower_non_sequence_command_to_stmt(Command::Compound(
4049 Box::new(compound),
4050 redirects,
4051 )));
4052 }
4053
4054 if self.at(TokenKind::LeftBrace) {
4055 let compound = self.parse_brace_group(BraceBodyContext::Function)?;
4056 let redirects = self.parse_trailing_redirects();
4057 return Ok(Self::lower_non_sequence_command_to_stmt(Command::Compound(
4058 Box::new(compound),
4059 redirects,
4060 )));
4061 }
4062
4063 self.parse_single_stmt_command()
4064 }
4065
4066 fn parse_single_stmt_command(&mut self) -> Result<Stmt> {
4067 let mut stmt = self
4068 .parse_pipeline()?
4069 .ok_or_else(|| self.error("expected command"))?;
4070
4071 let Some(kind) = self.current_token_kind else {
4072 return Ok(stmt);
4073 };
4074 let operator = match kind {
4075 TokenKind::And => Some((Some(BinaryOp::And), None, false)),
4076 TokenKind::Or => Some((Some(BinaryOp::Or), None, false)),
4077 TokenKind::Semicolon => Some((None, Some(StmtTerminator::Semicolon), true)),
4078 TokenKind::Background => Some((
4079 None,
4080 Some(StmtTerminator::Background(BackgroundOperator::Plain)),
4081 true,
4082 )),
4083 TokenKind::BackgroundPipe => Some((
4084 None,
4085 Some(StmtTerminator::Background(BackgroundOperator::Pipe)),
4086 true,
4087 )),
4088 TokenKind::BackgroundBang => Some((
4089 None,
4090 Some(StmtTerminator::Background(BackgroundOperator::Bang)),
4091 true,
4092 )),
4093 _ => None,
4094 };
4095 let Some((binary_op, terminator, allow_empty_tail)) = operator else {
4096 return Ok(stmt);
4097 };
4098 let operator_span = self.current_span;
4099 self.advance();
4100
4101 if let Some(binary_op) = binary_op {
4102 self.skip_newlines()?;
4103 if let Some(right) = self.parse_pipeline()? {
4104 stmt = Self::binary_stmt(stmt, binary_op, operator_span, right);
4105 }
4106 return Ok(stmt);
4107 }
4108
4109 if allow_empty_tail
4110 && matches!(
4111 self.current_token_kind,
4112 Some(TokenKind::Semicolon | TokenKind::Newline)
4113 )
4114 {
4115 self.advance();
4116 }
4117
4118 stmt.terminator = terminator;
4119 stmt.terminator_span = Some(operator_span);
4120 Ok(stmt)
4121 }
4122
4123 fn parse_anonymous_function_args(&mut self) -> Result<SmallVec<[Word; 2]>> {
4124 let mut args = SmallVec::<[Word; 2]>::new();
4125 while self.current_token_kind.is_some_and(TokenKind::is_word_like) {
4126 let word = self
4127 .take_current_word_and_advance()
4128 .ok_or_else(|| self.error("expected anonymous function argument"))?;
4129 args.push(word);
4130 }
4131 Ok(args)
4132 }
4133
4134 fn parse_function_keyword(&mut self) -> Result<Command> {
4136 self.ensure_function_keyword()?;
4137 let start_span = self.current_span;
4138 self.advance(); self.skip_newlines()?;
4140
4141 if self.dialect == ShellDialect::Zsh {
4142 let mut entries = Vec::new();
4143 while self.current_token_kind.is_some_and(TokenKind::is_word_like) {
4144 entries.push(self.parse_function_header_entry()?);
4145 if self.at(TokenKind::LeftParen) {
4146 break;
4147 }
4148 }
4149
4150 let trailing_parens_span = if !entries.is_empty() && self.at(TokenKind::LeftParen) {
4151 Some(self.parse_function_parens_span()?)
4152 } else {
4153 None
4154 };
4155
4156 if entries.is_empty() {
4157 let body = self.parse_zsh_function_body_stmt()?;
4158 let args = self.parse_anonymous_function_args()?;
4159 let redirects = self.parse_trailing_redirects();
4160 let span = start_span.merge(self.current_span);
4161 return Ok(Command::AnonymousFunction(
4162 AnonymousFunctionCommand {
4163 surface: AnonymousFunctionSurface::FunctionKeyword {
4164 function_keyword_span: start_span,
4165 },
4166 body: Box::new(body),
4167 args: args.into_vec(),
4168 span,
4169 },
4170 redirects,
4171 ));
4172 }
4173
4174 let body = self.parse_zsh_function_body_stmt()?;
4175 let span = start_span.merge(self.current_span);
4176 return Ok(Command::Function(FunctionDef {
4177 header: FunctionHeader {
4178 function_keyword_span: Some(start_span),
4179 entries,
4180 trailing_parens_span,
4181 },
4182 body: Box::new(body),
4183 span,
4184 }));
4185 }
4186
4187 let entry = self.parse_function_keyword_header_entry()?;
4188 let saw_newline_after_name = self.skip_newlines_with_flag()?;
4189 let (trailing_parens_span, allow_bare_compound) = if self.at(TokenKind::LeftParen) {
4190 let parens_span = self.parse_function_parens_span()?;
4191 (Some(parens_span), self.skip_newlines_with_flag()?)
4192 } else {
4193 (None, saw_newline_after_name)
4194 };
4195
4196 let body = self.parse_function_body_command(allow_bare_compound)?;
4197 let span = start_span.merge(self.current_span);
4198
4199 Ok(Command::Function(FunctionDef {
4200 header: FunctionHeader {
4201 function_keyword_span: Some(start_span),
4202 entries: vec![entry],
4203 trailing_parens_span,
4204 },
4205 body: Box::new(body),
4206 span,
4207 }))
4208 }
4209
4210 fn parse_function_posix(&mut self) -> Result<Command> {
4212 let start_span = self.current_span;
4213 let entry = self.parse_function_header_entry()?;
4214 let trailing_parens_span = self.parse_function_parens_span()?;
4215
4216 self.finish_parse_function_posix(start_span, entry, trailing_parens_span)
4217 }
4218
4219 fn finish_parse_function_posix(
4220 &mut self,
4221 start_span: Span,
4222 entry: FunctionHeaderEntry,
4223 trailing_parens_span: Span,
4224 ) -> Result<Command> {
4225 let body = if self.dialect == ShellDialect::Zsh {
4226 self.parse_zsh_function_body_stmt()?
4227 } else {
4228 self.skip_newlines()?;
4229 self.parse_function_body_command(true)?
4230 };
4231
4232 Ok(Command::Function(FunctionDef {
4233 header: FunctionHeader {
4234 function_keyword_span: None,
4235 entries: vec![entry],
4236 trailing_parens_span: Some(trailing_parens_span),
4237 },
4238 body: Box::new(body),
4239 span: start_span.merge(self.current_span),
4240 }))
4241 }
4242
4243 fn try_parse_zsh_attached_parens_function(&mut self) -> Result<Option<Command>> {
4244 if self.dialect != ShellDialect::Zsh || !self.at_word_like() {
4245 return Ok(None);
4246 }
4247
4248 let Some(word_text) = self.current_source_like_word_text() else {
4249 return Ok(None);
4250 };
4251 let Some(header_text) = word_text.as_ref().strip_suffix("()") else {
4252 return Ok(None);
4253 };
4254 if header_text.is_empty() || header_text.contains('=') {
4255 return Ok(None);
4256 }
4257
4258 let checkpoint = self.checkpoint();
4259 self.advance();
4260 if let Err(error) = self.skip_newlines() {
4261 self.restore(checkpoint);
4262 return Err(error);
4263 }
4264 if !self.at(TokenKind::LeftBrace) {
4265 self.restore(checkpoint);
4266 return Ok(None);
4267 }
4268 self.restore(checkpoint);
4269
4270 let start_span = self.current_span;
4271 let header_span =
4272 Span::from_positions(start_span.start, start_span.start.advanced_by(header_text));
4273 let parens_span = Span::from_positions(header_span.end, start_span.end);
4274 let header_word =
4275 self.parse_word_with_context(header_text, header_span, header_span.start, true);
4276 let entry = self.function_header_entry_from_word(header_word);
4277 self.advance();
4278
4279 self.finish_parse_function_posix(start_span, entry, parens_span)
4280 .map(Some)
4281 }
4282
4283 fn parse_anonymous_paren_function(&mut self) -> Result<Command> {
4284 let start_span = self.current_span;
4285 let parens_span = self.parse_function_parens_span()?;
4286 let body = self.parse_zsh_function_body_stmt()?;
4287 let args = self.parse_anonymous_function_args()?;
4288 let redirects = self.parse_trailing_redirects();
4289 let span = start_span.merge(self.current_span);
4290 Ok(Command::AnonymousFunction(
4291 AnonymousFunctionCommand {
4292 surface: AnonymousFunctionSurface::Parens { parens_span },
4293 body: Box::new(body),
4294 args: args.into_vec(),
4295 span,
4296 },
4297 redirects,
4298 ))
4299 }
4300
4301 fn parse_compound_list(&mut self, terminator: Keyword) -> Result<Vec<Stmt>> {
4303 self.parse_compound_list_until(KeywordSet::single(terminator))
4304 }
4305
4306 fn parse_compound_list_until(&mut self, terminators: KeywordSet) -> Result<Vec<Stmt>> {
4308 let mut stmts = Vec::new();
4309
4310 loop {
4311 self.skip_command_separators()?;
4312
4313 if self
4315 .current_keyword()
4316 .is_some_and(|keyword| terminators.contains(keyword))
4317 {
4318 break;
4319 }
4320
4321 if self.current_token.is_none() {
4322 break;
4323 }
4324
4325 let command_stmts = self.parse_command_list_required()?;
4326 self.apply_stmt_list_effects(&command_stmts);
4327 stmts.extend(command_stmts);
4328 }
4329
4330 Ok(stmts)
4331 }
4332
4333 fn is_non_command_keyword(keyword: Keyword) -> bool {
4337 NON_COMMAND_KEYWORDS.contains(keyword)
4338 }
4339
4340 fn is_keyword(&self, keyword: Keyword) -> bool {
4342 self.current_keyword() == Some(keyword)
4343 }
4344
4345 fn expect_keyword(&mut self, keyword: Keyword) -> Result<()> {
4347 if self.is_keyword(keyword) {
4348 self.advance();
4349 Ok(())
4350 } else {
4351 Err(self.error(format!("expected '{}'", keyword)))
4352 }
4353 }
4354 fn parse_simple_command(&mut self) -> Result<Option<SimpleCommand>> {
4355 self.tick()?;
4356 self.skip_newlines()?;
4357 self.check_error_token()?;
4358 let start_span = self.current_span;
4359
4360 let mut assignments = SmallVec::<[Assignment; 1]>::new();
4361 let mut words = SmallVec::<[Word; 2]>::new();
4362 let mut redirects = SmallVec::<[Redirect; 1]>::new();
4363
4364 loop {
4365 self.check_error_token()?;
4366 let next_kind_after_right_brace = if self.at(TokenKind::RightBrace) {
4367 self.peek_next_kind()
4368 } else {
4369 None
4370 };
4371 let right_brace_is_literal_argument = self.at(TokenKind::RightBrace)
4372 && !words.is_empty()
4373 && self.should_consume_right_brace_as_literal_argument(next_kind_after_right_brace);
4374 match self.current_token_kind {
4375 Some(kind) if kind.is_word_like() => {
4376 if words.is_empty()
4379 && self
4380 .current_keyword()
4381 .is_some_and(Self::is_non_command_keyword)
4382 {
4383 break;
4384 }
4385
4386 let is_literal = kind == TokenKind::LiteralWord;
4387 let word_text =
4388 self.current_source_like_word_text_or_error("simple command word")?;
4389 let assignment_shape = (!is_literal && words.is_empty())
4390 .then(|| Self::is_assignment(word_text.as_ref()));
4391 let assignment_shape = assignment_shape.flatten();
4392
4393 if words.is_empty()
4395 && !is_literal
4396 && let Some((assignment, needs_advance)) = self
4397 .try_parse_assignment_with_shape(word_text.as_ref(), assignment_shape)
4398 {
4399 if needs_advance {
4400 self.advance();
4401 }
4402 assignments.push(assignment);
4403 continue;
4404 }
4405
4406 if words.is_empty()
4407 && !is_literal
4408 && assignment_shape.is_none()
4409 && word_text.contains('[')
4410 && let Some(assignment) =
4411 self.try_parse_split_indexed_assignment_from_text()
4412 {
4413 assignments.push(assignment);
4414 continue;
4415 }
4416
4417 if word_text.ends_with('=') && !words.is_empty() {
4420 let original_word = self.current_word_ref().cloned();
4421 let saved_span = self.current_span;
4422 self.advance();
4423 if let Some(word) =
4424 self.try_parse_compound_array_arg(word_text.as_ref(), saved_span)?
4425 {
4426 words.push(word);
4427 continue;
4428 }
4429 if let Some(word) = original_word {
4431 words.push(word);
4432 }
4433 continue;
4434 }
4435
4436 if let Some(word) = self.take_current_word_and_advance() {
4437 words.push(word);
4438 }
4439 }
4440 Some(TokenKind::LeftParen) if !words.is_empty() => {
4441 let Some(word) = self.take_current_word_and_advance() else {
4442 break;
4443 };
4444 words.push(word);
4445 }
4446 Some(TokenKind::DoubleRightBracket)
4447 if words.first().is_some_and(|word| {
4448 matches!(
4449 word.parts.as_slice(),
4450 [WordPartNode {
4451 kind: WordPart::ArithmeticExpansion {
4452 syntax: ArithmeticExpansionSyntax::DollarParenParen,
4453 ..
4454 },
4455 ..
4456 }]
4457 )
4458 }) =>
4459 {
4460 let span = self.current_span;
4461 let word = self.word_from_raw_text(span.slice(self.input), span);
4462 self.advance();
4463 words.push(word);
4464 }
4465 Some(TokenKind::Newline) => {
4466 let next_kind = self.peek_next_kind();
4467 let supports_fd_var = next_kind.is_some_and(|kind| {
4468 matches!(kind, TokenKind::HereDoc | TokenKind::HereDocStrip)
4469 || Self::redirect_supports_fd_var(kind)
4470 });
4471 if supports_fd_var {
4472 let (fd_var, fd_var_span) = self.pop_line_continuation_fd_var(&mut words);
4473 if let Some(fd_var) = fd_var {
4474 self.advance();
4475 if matches!(
4476 self.current_token_kind,
4477 Some(TokenKind::HereDoc | TokenKind::HereDocStrip)
4478 ) {
4479 self.parse_heredoc_redirect(
4480 self.current_token_kind == Some(TokenKind::HereDocStrip),
4481 &mut redirects,
4482 Some(fd_var),
4483 fd_var_span,
4484 )?;
4485 continue;
4486 }
4487
4488 if self.consume_non_heredoc_redirect(
4489 &mut redirects,
4490 Some(fd_var),
4491 fd_var_span,
4492 true,
4493 )? {
4494 continue;
4495 }
4496 }
4497 }
4498 break;
4499 }
4500 Some(kind) if Self::is_redirect_kind(kind) => {
4501 if matches!(kind, TokenKind::HereDoc | TokenKind::HereDocStrip) {
4502 let (fd_var, fd_var_span) = if words
4503 .last()
4504 .is_some_and(|word| self.word_is_attached_to_current_token(word))
4505 {
4506 self.pop_fd_var(&mut words)
4507 } else {
4508 (None, None)
4509 };
4510 self.parse_heredoc_redirect(
4511 kind == TokenKind::HereDocStrip,
4512 &mut redirects,
4513 fd_var,
4514 fd_var_span,
4515 )?;
4516 continue;
4517 }
4518
4519 let (fd_var, fd_var_span) = if Self::redirect_supports_fd_var(kind) {
4520 if words
4521 .last()
4522 .is_some_and(|word| self.word_is_attached_to_current_token(word))
4523 {
4524 self.pop_fd_var(&mut words)
4525 } else {
4526 (None, None)
4527 }
4528 } else {
4529 (None, None)
4530 };
4531
4532 if self.consume_non_heredoc_redirect(
4533 &mut redirects,
4534 fd_var,
4535 fd_var_span,
4536 true,
4537 )? {
4538 continue;
4539 }
4540 break;
4541 }
4542 Some(TokenKind::ProcessSubIn) | Some(TokenKind::ProcessSubOut) => {
4543 let word = self.expect_word()?;
4544 words.push(word);
4545 }
4546 Some(TokenKind::LeftBrace) if !words.is_empty() => {
4548 words.push(Word::literal_with_span("{", self.current_span));
4549 self.advance();
4550 }
4551 Some(TokenKind::RightBrace) if right_brace_is_literal_argument => {
4556 words.push(Word::literal_with_span("}", self.current_span));
4557 self.advance();
4558 }
4559 Some(TokenKind::Semicolon)
4560 | Some(TokenKind::Pipe)
4561 | Some(TokenKind::And)
4562 | Some(TokenKind::Or)
4563 | None => break,
4564 _ => break,
4565 }
4566 }
4567
4568 if words.is_empty() && (!assignments.is_empty() || !redirects.is_empty()) {
4570 return Ok(Some(SimpleCommand {
4571 name: Word::literal(""),
4572 args: SmallVec::new(),
4573 redirects,
4574 assignments,
4575 span: start_span.merge(self.current_span),
4576 }));
4577 }
4578
4579 if words.is_empty() {
4580 return Ok(None);
4581 }
4582
4583 let name = words.remove(0);
4584 let args = words;
4585
4586 Ok(Some(SimpleCommand {
4587 name,
4588 args,
4589 redirects,
4590 assignments,
4591 span: start_span.merge(self.current_span),
4592 }))
4593 }
4594
4595 fn pop_fd_var(&self, words: &mut SmallVec<[Word; 2]>) -> (Option<Name>, Option<Span>) {
4599 if let Some(last) = words.last()
4600 && last.parts.len() == 1
4601 && let WordPart::Literal(ref s) = last.parts[0].kind
4602 && let Some(span) = last.part_span(0)
4603 && let text = s.as_str(self.input, span)
4604 && text.starts_with('{')
4605 && text.ends_with('}')
4606 && text.len() > 2
4607 && text[1..text.len() - 1]
4608 .chars()
4609 .all(|c| c.is_alphanumeric() || c == '_')
4610 {
4611 let var_name = text[1..text.len() - 1].to_string();
4612 let start = last.span.start.advanced_by("{");
4613 let span = Span::from_positions(start, start.advanced_by(&var_name));
4614 words.pop();
4615 return (Some(Name::from(var_name)), Some(span));
4616 }
4617 (None, None)
4618 }
4619
4620 fn word_is_attached_to_current_token(&self, word: &Word) -> bool {
4621 let start = word.span.end.offset;
4622 let end = self.current_span.start.offset;
4623 let input_len = self.input.len();
4624 start <= end
4625 && end <= input_len
4626 && Self::fd_var_gap_allows_attachment(&self.input[start..end])
4627 }
4628
4629 fn pop_line_continuation_fd_var(
4630 &self,
4631 words: &mut SmallVec<[Word; 2]>,
4632 ) -> (Option<Name>, Option<Span>) {
4633 let Some(last) = words.last() else {
4634 return (None, None);
4635 };
4636 let Some(text) = self.single_literal_word_text(last) else {
4637 return (None, None);
4638 };
4639 let Some(fd_text) = text.strip_suffix('\\') else {
4640 return (None, None);
4641 };
4642 let Some((fd_var, fd_var_span)) = Self::fd_var_from_text(fd_text, last.span) else {
4643 return (None, None);
4644 };
4645 words.pop();
4646 (Some(fd_var), Some(fd_var_span))
4647 }
4648}