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