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