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