1#![cfg_attr(not(test), warn(clippy::unwrap_used))]
6
7mod arithmetic;
8mod commands;
9mod heredocs;
10mod lexer;
11mod redirects;
12mod words;
13
14use std::{
15 borrow::Cow,
16 collections::{HashMap, HashSet, VecDeque},
17 sync::Arc,
18};
19
20pub use lexer::{
21 HeredocRead, LexedToken, LexedWord, LexedWordSegment, LexedWordSegmentKind, Lexer,
22 LexerErrorKind,
23};
24use memchr::{memchr, memchr2, memchr3};
25use smallvec::SmallVec;
26
27use shuck_ast::{
28 AlwaysCommand, AnonymousFunctionCommand, AnonymousFunctionSurface, ArithmeticCommand,
29 ArithmeticExpansionSyntax, ArithmeticExpr, ArithmeticExprNode, ArithmeticForCommand,
30 ArithmeticLvalue, ArrayElem, ArrayExpr, ArrayKind, Assignment, AssignmentValue,
31 BackgroundOperator, BinaryCommand, BinaryOp, BourneParameterExpansion, BraceExpansionKind,
32 BraceQuoteContext, BraceSyntax, BraceSyntaxKind, BreakCommand as AstBreakCommand,
33 BuiltinCommand as AstBuiltinCommand, CaseCommand, CaseItem, CaseTerminator,
34 Command as AstCommand, CommandSubstitutionSyntax, Comment, CompoundCommand,
35 ConditionalBinaryExpr, ConditionalBinaryOp, ConditionalCommand, ConditionalExpr,
36 ConditionalParenExpr, ConditionalUnaryExpr, ConditionalUnaryOp,
37 ContinueCommand as AstContinueCommand, CoprocCommand, DeclClause as AstDeclClause, DeclOperand,
38 ExitCommand as AstExitCommand, File, ForCommand, ForSyntax, ForTarget, ForeachCommand,
39 ForeachSyntax, FunctionDef, FunctionHeader, FunctionHeaderEntry, Heredoc, HeredocBody,
40 HeredocBodyMode, HeredocBodyPart, HeredocBodyPartNode, HeredocDelimiter, IfCommand, IfSyntax,
41 LiteralText, Name, ParameterExpansion, ParameterExpansionSyntax, ParameterOp, Pattern,
42 PatternGroupKind, PatternPart, PatternPartNode, Position, PrefixMatchKind, Redirect,
43 RedirectKind, RedirectTarget, RepeatCommand, RepeatSyntax, ReturnCommand as AstReturnCommand,
44 SelectCommand, SimpleCommand as AstSimpleCommand, SourceText, Span, Stmt, StmtSeq,
45 StmtTerminator, Subscript, SubscriptInterpretation, SubscriptKind, SubscriptSelector, TextSize,
46 TimeCommand, TokenKind, UntilCommand, VarRef, WhileCommand, Word, WordPart, WordPartNode,
47 ZshDefaultingOp, ZshExpansionOperation, ZshExpansionTarget, ZshGlobQualifier,
48 ZshGlobQualifierGroup, ZshGlobQualifierKind, ZshGlobSegment, ZshInlineGlobControl, ZshModifier,
49 ZshParameterExpansion, ZshPatternOp, ZshQualifiedGlob, ZshReplacementOp, ZshTrimOp,
50};
51
52use crate::error::{Error, Result};
53
54const DEFAULT_MAX_AST_DEPTH: usize = 100;
56
57const HARD_MAX_AST_DEPTH: usize = 100;
67
68const DEFAULT_MAX_PARSER_OPERATIONS: usize = 100_000;
70
71#[derive(Debug, Clone, Copy, PartialEq, Eq)]
73pub enum ParseStatus {
74 Clean,
76 Recovered,
78 Fatal,
80}
81
82#[derive(Debug, Clone, PartialEq, Eq)]
84pub struct ZshCaseGroupPart {
85 pub pattern_part_index: usize,
87 pub span: Span,
89}
90
91#[derive(Debug, Clone, Default, PartialEq, Eq)]
93pub struct SyntaxFacts {
94 pub zsh_brace_if_spans: Vec<Span>,
96 pub zsh_always_spans: Vec<Span>,
98 pub zsh_case_group_parts: Vec<ZshCaseGroupPart>,
100}
101
102#[derive(Debug, Clone)]
105pub struct ParseResult {
106 pub file: File,
108 pub diagnostics: Vec<ParseDiagnostic>,
110 pub status: ParseStatus,
112 pub terminal_error: Option<Error>,
114 pub syntax_facts: SyntaxFacts,
116}
117
118impl ParseResult {
119 pub fn is_ok(&self) -> bool {
121 self.status == ParseStatus::Clean
122 }
123
124 pub fn is_err(&self) -> bool {
126 !self.is_ok()
127 }
128
129 pub fn strict_error(&self) -> Error {
134 self.terminal_error.clone().unwrap_or_else(|| {
135 let diagnostic = self
136 .diagnostics
137 .first()
138 .expect("non-clean parse result should include a diagnostic or terminal error");
139 Error::parse_at(
140 diagnostic.message.clone(),
141 diagnostic.span.start.line,
142 diagnostic.span.start.column,
143 )
144 })
145 }
146
147 pub fn unwrap(self) -> Self {
149 if self.is_ok() {
150 self
151 } else {
152 panic!(
153 "called `ParseResult::unwrap()` on a non-clean parse: {}",
154 self.strict_error()
155 )
156 }
157 }
158
159 pub fn expect(self, message: &str) -> Self {
161 if self.is_ok() {
162 self
163 } else {
164 panic!("{message}: {}", self.strict_error())
165 }
166 }
167
168 pub fn unwrap_err(self) -> Error {
170 if self.is_err() {
171 self.strict_error()
172 } else {
173 panic!("called `ParseResult::unwrap_err()` on a clean parse")
174 }
175 }
176
177 pub fn expect_err(self, message: &str) -> Error {
180 if self.is_err() {
181 self.strict_error()
182 } else {
183 panic!("{message}")
184 }
185 }
186}
187
188#[cfg(feature = "benchmarking")]
189#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
190#[doc(hidden)]
191pub struct ParserBenchmarkCounters {
192 pub lexer_current_position_calls: u64,
194 pub parser_set_current_spanned_calls: u64,
196 pub parser_advance_raw_calls: u64,
198}
199
200#[derive(Debug, Clone)]
201struct SimpleCommand {
202 name: Word,
203 args: SmallVec<[Word; 2]>,
204 redirects: SmallVec<[Redirect; 1]>,
205 assignments: SmallVec<[Assignment; 1]>,
206 span: Span,
207}
208
209#[derive(Debug, Clone)]
210struct BreakCommand {
211 depth: Option<Word>,
212 extra_args: SmallVec<[Word; 2]>,
213 redirects: SmallVec<[Redirect; 1]>,
214 assignments: SmallVec<[Assignment; 1]>,
215 span: Span,
216}
217
218#[derive(Debug, Clone)]
219struct ContinueCommand {
220 depth: Option<Word>,
221 extra_args: SmallVec<[Word; 2]>,
222 redirects: SmallVec<[Redirect; 1]>,
223 assignments: SmallVec<[Assignment; 1]>,
224 span: Span,
225}
226
227#[derive(Debug, Clone)]
228struct ReturnCommand {
229 code: Option<Word>,
230 extra_args: SmallVec<[Word; 2]>,
231 redirects: SmallVec<[Redirect; 1]>,
232 assignments: SmallVec<[Assignment; 1]>,
233 span: Span,
234}
235
236#[derive(Debug, Clone)]
237struct ExitCommand {
238 code: Option<Word>,
239 extra_args: SmallVec<[Word; 2]>,
240 redirects: SmallVec<[Redirect; 1]>,
241 assignments: SmallVec<[Assignment; 1]>,
242 span: Span,
243}
244
245#[derive(Debug, Clone)]
246enum BuiltinCommand {
247 Break(BreakCommand),
248 Continue(ContinueCommand),
249 Return(ReturnCommand),
250 Exit(ExitCommand),
251}
252
253#[derive(Debug, Clone)]
254struct DeclClause {
255 variant: Name,
256 variant_span: Span,
257 operands: SmallVec<[DeclOperand; 2]>,
258 redirects: SmallVec<[Redirect; 1]>,
259 assignments: SmallVec<[Assignment; 1]>,
260 span: Span,
261}
262
263#[derive(Debug, Clone)]
264enum Command {
265 Simple(SimpleCommand),
266 Builtin(BuiltinCommand),
267 Decl(Box<DeclClause>),
268 Compound(Box<CompoundCommand>, SmallVec<[Redirect; 1]>),
269 Function(FunctionDef),
270 AnonymousFunction(AnonymousFunctionCommand, SmallVec<[Redirect; 1]>),
271}
272
273#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
275pub enum ShellDialect {
276 Posix,
278 Mksh,
280 #[default]
282 Bash,
283 Zsh,
285}
286
287#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
289pub enum OptionValue {
290 On,
292 Off,
294 #[default]
296 Unknown,
297}
298
299impl OptionValue {
300 pub const fn is_definitely_on(self) -> bool {
302 matches!(self, Self::On)
303 }
304
305 pub const fn is_definitely_off(self) -> bool {
307 matches!(self, Self::Off)
308 }
309
310 pub const fn merge(self, other: Self) -> Self {
312 match (self, other) {
313 (Self::On, Self::On) => Self::On,
314 (Self::Off, Self::Off) => Self::Off,
315 _ => Self::Unknown,
316 }
317 }
318}
319
320#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
322pub enum ZshEmulationMode {
323 Zsh,
325 Sh,
327 Ksh,
329 Csh,
331}
332
333#[derive(Debug, Clone, PartialEq, Eq, Hash)]
335pub struct ZshOptionState {
336 pub sh_word_split: OptionValue,
338 pub glob_subst: OptionValue,
340 pub rc_expand_param: OptionValue,
342 pub glob: OptionValue,
344 pub nomatch: OptionValue,
346 pub null_glob: OptionValue,
348 pub csh_null_glob: OptionValue,
350 pub extended_glob: OptionValue,
352 pub ksh_glob: OptionValue,
354 pub sh_glob: OptionValue,
356 pub bare_glob_qual: OptionValue,
358 pub glob_dots: OptionValue,
360 pub equals: OptionValue,
362 pub magic_equal_subst: OptionValue,
364 pub sh_file_expansion: OptionValue,
366 pub glob_assign: OptionValue,
368 pub ignore_braces: OptionValue,
370 pub ignore_close_braces: OptionValue,
372 pub brace_ccl: OptionValue,
374 pub ksh_arrays: OptionValue,
376 pub ksh_zero_subscript: OptionValue,
378 pub short_loops: OptionValue,
380 pub short_repeat: OptionValue,
382 pub rc_quotes: OptionValue,
384 pub interactive_comments: OptionValue,
386 pub c_bases: OptionValue,
388 pub octal_zeroes: OptionValue,
390}
391
392#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
393enum ZshOptionField {
394 ShWordSplit,
395 GlobSubst,
396 RcExpandParam,
397 Glob,
398 Nomatch,
399 NullGlob,
400 CshNullGlob,
401 ExtendedGlob,
402 KshGlob,
403 ShGlob,
404 BareGlobQual,
405 GlobDots,
406 Equals,
407 MagicEqualSubst,
408 ShFileExpansion,
409 GlobAssign,
410 IgnoreBraces,
411 IgnoreCloseBraces,
412 BraceCcl,
413 KshArrays,
414 KshZeroSubscript,
415 ShortLoops,
416 ShortRepeat,
417 RcQuotes,
418 InteractiveComments,
419 CBases,
420 OctalZeroes,
421}
422
423impl ZshOptionState {
424 pub const fn zsh_default() -> Self {
426 Self {
427 sh_word_split: OptionValue::Off,
428 glob_subst: OptionValue::Off,
429 rc_expand_param: OptionValue::Off,
430 glob: OptionValue::On,
431 nomatch: OptionValue::On,
432 null_glob: OptionValue::Off,
433 csh_null_glob: OptionValue::Off,
434 extended_glob: OptionValue::Off,
435 ksh_glob: OptionValue::Off,
436 sh_glob: OptionValue::Off,
437 bare_glob_qual: OptionValue::On,
438 glob_dots: OptionValue::Off,
439 equals: OptionValue::On,
440 magic_equal_subst: OptionValue::Off,
441 sh_file_expansion: OptionValue::Off,
442 glob_assign: OptionValue::Off,
443 ignore_braces: OptionValue::Off,
444 ignore_close_braces: OptionValue::Off,
445 brace_ccl: OptionValue::Off,
446 ksh_arrays: OptionValue::Off,
447 ksh_zero_subscript: OptionValue::Off,
448 short_loops: OptionValue::On,
449 short_repeat: OptionValue::On,
450 rc_quotes: OptionValue::Off,
451 interactive_comments: OptionValue::On,
452 c_bases: OptionValue::Off,
453 octal_zeroes: OptionValue::Off,
454 }
455 }
456
457 pub fn for_emulate(mode: ZshEmulationMode) -> Self {
459 let mut state = Self::zsh_default();
460 match mode {
461 ZshEmulationMode::Zsh => {}
462 ZshEmulationMode::Sh => {
463 state.sh_word_split = OptionValue::On;
464 state.glob_subst = OptionValue::On;
465 state.sh_glob = OptionValue::On;
466 state.sh_file_expansion = OptionValue::On;
467 state.bare_glob_qual = OptionValue::Off;
468 state.ksh_arrays = OptionValue::Off;
469 }
470 ZshEmulationMode::Ksh => {
471 state.sh_word_split = OptionValue::On;
472 state.glob_subst = OptionValue::On;
473 state.ksh_glob = OptionValue::On;
474 state.ksh_arrays = OptionValue::On;
475 state.sh_glob = OptionValue::On;
476 state.bare_glob_qual = OptionValue::Off;
477 }
478 ZshEmulationMode::Csh => {
479 state.csh_null_glob = OptionValue::On;
480 state.sh_word_split = OptionValue::Off;
481 state.glob_subst = OptionValue::Off;
482 }
483 }
484 state
485 }
486
487 pub fn apply_setopt(&mut self, name: &str) -> bool {
491 self.apply_named_option(name, true)
492 }
493
494 pub fn apply_unsetopt(&mut self, name: &str) -> bool {
498 self.apply_named_option(name, false)
499 }
500
501 fn set_field(&mut self, field: ZshOptionField, value: OptionValue) {
502 match field {
503 ZshOptionField::ShWordSplit => self.sh_word_split = value,
504 ZshOptionField::GlobSubst => self.glob_subst = value,
505 ZshOptionField::RcExpandParam => self.rc_expand_param = value,
506 ZshOptionField::Glob => self.glob = value,
507 ZshOptionField::Nomatch => self.nomatch = value,
508 ZshOptionField::NullGlob => self.null_glob = value,
509 ZshOptionField::CshNullGlob => self.csh_null_glob = value,
510 ZshOptionField::ExtendedGlob => self.extended_glob = value,
511 ZshOptionField::KshGlob => self.ksh_glob = value,
512 ZshOptionField::ShGlob => self.sh_glob = value,
513 ZshOptionField::BareGlobQual => self.bare_glob_qual = value,
514 ZshOptionField::GlobDots => self.glob_dots = value,
515 ZshOptionField::Equals => self.equals = value,
516 ZshOptionField::MagicEqualSubst => self.magic_equal_subst = value,
517 ZshOptionField::ShFileExpansion => self.sh_file_expansion = value,
518 ZshOptionField::GlobAssign => self.glob_assign = value,
519 ZshOptionField::IgnoreBraces => self.ignore_braces = value,
520 ZshOptionField::IgnoreCloseBraces => self.ignore_close_braces = value,
521 ZshOptionField::BraceCcl => self.brace_ccl = value,
522 ZshOptionField::KshArrays => self.ksh_arrays = value,
523 ZshOptionField::KshZeroSubscript => self.ksh_zero_subscript = value,
524 ZshOptionField::ShortLoops => self.short_loops = value,
525 ZshOptionField::ShortRepeat => self.short_repeat = value,
526 ZshOptionField::RcQuotes => self.rc_quotes = value,
527 ZshOptionField::InteractiveComments => self.interactive_comments = value,
528 ZshOptionField::CBases => self.c_bases = value,
529 ZshOptionField::OctalZeroes => self.octal_zeroes = value,
530 }
531 }
532
533 fn field(&self, field: ZshOptionField) -> OptionValue {
534 match field {
535 ZshOptionField::ShWordSplit => self.sh_word_split,
536 ZshOptionField::GlobSubst => self.glob_subst,
537 ZshOptionField::RcExpandParam => self.rc_expand_param,
538 ZshOptionField::Glob => self.glob,
539 ZshOptionField::Nomatch => self.nomatch,
540 ZshOptionField::NullGlob => self.null_glob,
541 ZshOptionField::CshNullGlob => self.csh_null_glob,
542 ZshOptionField::ExtendedGlob => self.extended_glob,
543 ZshOptionField::KshGlob => self.ksh_glob,
544 ZshOptionField::ShGlob => self.sh_glob,
545 ZshOptionField::BareGlobQual => self.bare_glob_qual,
546 ZshOptionField::GlobDots => self.glob_dots,
547 ZshOptionField::Equals => self.equals,
548 ZshOptionField::MagicEqualSubst => self.magic_equal_subst,
549 ZshOptionField::ShFileExpansion => self.sh_file_expansion,
550 ZshOptionField::GlobAssign => self.glob_assign,
551 ZshOptionField::IgnoreBraces => self.ignore_braces,
552 ZshOptionField::IgnoreCloseBraces => self.ignore_close_braces,
553 ZshOptionField::BraceCcl => self.brace_ccl,
554 ZshOptionField::KshArrays => self.ksh_arrays,
555 ZshOptionField::KshZeroSubscript => self.ksh_zero_subscript,
556 ZshOptionField::ShortLoops => self.short_loops,
557 ZshOptionField::ShortRepeat => self.short_repeat,
558 ZshOptionField::RcQuotes => self.rc_quotes,
559 ZshOptionField::InteractiveComments => self.interactive_comments,
560 ZshOptionField::CBases => self.c_bases,
561 ZshOptionField::OctalZeroes => self.octal_zeroes,
562 }
563 }
564
565 pub fn merge(&self, other: &Self) -> Self {
567 let mut merged = Self::zsh_default();
568 for field in ZshOptionField::ALL {
569 merged.set_field(field, self.field(field).merge(other.field(field)));
570 }
571 merged
572 }
573
574 fn apply_named_option(&mut self, name: &str, enable: bool) -> bool {
575 let Some((field, value)) = parse_zsh_option_assignment(name, enable) else {
576 return false;
577 };
578 self.set_field(
579 field,
580 if value {
581 OptionValue::On
582 } else {
583 OptionValue::Off
584 },
585 );
586 true
587 }
588}
589
590impl ZshOptionField {
591 const ALL: [Self; 27] = [
592 Self::ShWordSplit,
593 Self::GlobSubst,
594 Self::RcExpandParam,
595 Self::Glob,
596 Self::Nomatch,
597 Self::NullGlob,
598 Self::CshNullGlob,
599 Self::ExtendedGlob,
600 Self::KshGlob,
601 Self::ShGlob,
602 Self::BareGlobQual,
603 Self::GlobDots,
604 Self::Equals,
605 Self::MagicEqualSubst,
606 Self::ShFileExpansion,
607 Self::GlobAssign,
608 Self::IgnoreBraces,
609 Self::IgnoreCloseBraces,
610 Self::BraceCcl,
611 Self::KshArrays,
612 Self::KshZeroSubscript,
613 Self::ShortLoops,
614 Self::ShortRepeat,
615 Self::RcQuotes,
616 Self::InteractiveComments,
617 Self::CBases,
618 Self::OctalZeroes,
619 ];
620}
621
622#[derive(Debug, Clone, PartialEq, Eq, Hash)]
624pub struct ShellProfile {
625 pub dialect: ShellDialect,
627 pub options: Option<ZshOptionState>,
629}
630
631impl ShellProfile {
632 pub fn native(dialect: ShellDialect) -> Self {
634 Self {
635 dialect,
636 options: (dialect == ShellDialect::Zsh).then(ZshOptionState::zsh_default),
637 }
638 }
639
640 pub fn with_zsh_options(dialect: ShellDialect, options: ZshOptionState) -> Self {
642 Self {
643 dialect,
644 options: (dialect == ShellDialect::Zsh).then_some(options),
645 }
646 }
647
648 pub fn zsh_options(&self) -> Option<&ZshOptionState> {
650 self.options.as_ref()
651 }
652}
653
654fn parse_zsh_option_assignment(name: &str, enable: bool) -> Option<(ZshOptionField, bool)> {
655 let mut normalized = String::with_capacity(name.len());
656 for ch in name.chars() {
657 if matches!(ch, '_' | '-') {
658 continue;
659 }
660 normalized.push(ch.to_ascii_lowercase());
661 }
662
663 let (normalized, invert) = if let Some(rest) = normalized.strip_prefix("no") {
664 (rest, true)
665 } else {
666 (normalized.as_str(), false)
667 };
668
669 let field = match normalized {
670 "shwordsplit" => ZshOptionField::ShWordSplit,
671 "globsubst" => ZshOptionField::GlobSubst,
672 "rcexpandparam" => ZshOptionField::RcExpandParam,
673 "glob" | "noglob" => ZshOptionField::Glob,
674 "nomatch" => ZshOptionField::Nomatch,
675 "nullglob" => ZshOptionField::NullGlob,
676 "cshnullglob" => ZshOptionField::CshNullGlob,
677 "extendedglob" => ZshOptionField::ExtendedGlob,
678 "kshglob" => ZshOptionField::KshGlob,
679 "shglob" => ZshOptionField::ShGlob,
680 "bareglobqual" => ZshOptionField::BareGlobQual,
681 "globdots" => ZshOptionField::GlobDots,
682 "equals" => ZshOptionField::Equals,
683 "magicequalsubst" => ZshOptionField::MagicEqualSubst,
684 "shfileexpansion" => ZshOptionField::ShFileExpansion,
685 "globassign" => ZshOptionField::GlobAssign,
686 "ignorebraces" => ZshOptionField::IgnoreBraces,
687 "ignoreclosebraces" => ZshOptionField::IgnoreCloseBraces,
688 "braceccl" => ZshOptionField::BraceCcl,
689 "ksharrays" => ZshOptionField::KshArrays,
690 "kshzerosubscript" => ZshOptionField::KshZeroSubscript,
691 "shortloops" => ZshOptionField::ShortLoops,
692 "shortrepeat" => ZshOptionField::ShortRepeat,
693 "rcquotes" => ZshOptionField::RcQuotes,
694 "interactivecomments" => ZshOptionField::InteractiveComments,
695 "cbases" => ZshOptionField::CBases,
696 "octalzeroes" => ZshOptionField::OctalZeroes,
697 _ => return None,
698 };
699
700 Some((field, if invert { !enable } else { enable }))
701}
702
703#[derive(Debug, Clone)]
704pub(crate) struct ZshOptionTimeline {
705 initial: ZshOptionState,
706 entries: Arc<[ZshOptionTimelineEntry]>,
707}
708
709#[derive(Debug, Clone)]
710struct ZshOptionTimelineEntry {
711 offset: usize,
712 state: ZshOptionState,
713}
714
715impl ZshOptionTimeline {
716 fn build(input: &str, shell_profile: &ShellProfile) -> Option<Self> {
717 let initial = shell_profile.zsh_options()?.clone();
718 if !might_mutate_zsh_parser_options(input) {
719 return Some(Self {
720 initial,
721 entries: Arc::from([]),
722 });
723 }
724
725 let entries = ZshOptionPrescanner::new(input, initial.clone()).scan();
726 Some(Self {
727 initial,
728 entries: entries.into(),
729 })
730 }
731
732 fn options_at(&self, offset: usize) -> &ZshOptionState {
733 let next_index = self.entries.partition_point(|entry| entry.offset <= offset);
734 if next_index == 0 {
735 &self.initial
736 } else {
737 &self.entries[next_index - 1].state
738 }
739 }
740}
741
742fn might_mutate_zsh_parser_options(input: &str) -> bool {
743 input.contains("setopt")
744 || input.contains("unsetopt")
745 || input.contains("emulate")
746 || input.contains("set -o")
747 || input.contains("set +o")
748}
749
750#[derive(Debug, Clone)]
751struct ZshOptionPrescanner<'a> {
752 input: &'a str,
753 offset: usize,
754 state: ZshOptionState,
755 entries: Vec<ZshOptionTimelineEntry>,
756}
757
758#[derive(Debug, Clone)]
759enum PrescanToken {
760 Word {
761 text: String,
762 end: usize,
763 },
764 Separator {
765 kind: PrescanSeparator,
766 start: usize,
767 end: usize,
768 },
769}
770
771#[derive(Debug, Clone, Copy, PartialEq, Eq)]
772enum PrescanSeparator {
773 Newline,
774 Semicolon,
775 Pipe,
776 Ampersand,
777 OpenParen,
778 CloseParen,
779 OpenBrace,
780 CloseBrace,
781}
782
783#[derive(Debug, Clone)]
784struct PrescanLocalScope {
785 saved_state: ZshOptionState,
786 brace_depth: usize,
787 paren_depth: usize,
788 compounds: Vec<PrescanCompound>,
789}
790
791#[derive(Debug, Clone, Copy, PartialEq, Eq)]
792enum PrescanCompound {
793 If,
794 Loop,
795 Case,
796}
797
798impl PrescanLocalScope {
799 fn simple(saved_state: ZshOptionState) -> Self {
800 Self {
801 saved_state,
802 brace_depth: 0,
803 paren_depth: 0,
804 compounds: Vec::new(),
805 }
806 }
807
808 fn brace_group(saved_state: ZshOptionState) -> Self {
809 Self {
810 brace_depth: 1,
811 ..Self::simple(saved_state)
812 }
813 }
814
815 fn subshell(saved_state: ZshOptionState) -> Self {
816 Self {
817 paren_depth: 1,
818 ..Self::simple(saved_state)
819 }
820 }
821
822 fn update_for_command(&mut self, words: &[String]) {
823 let Some(command) = words.first().map(String::as_str) else {
824 return;
825 };
826
827 match command {
828 "if" => self.compounds.push(PrescanCompound::If),
829 "case" => self.compounds.push(PrescanCompound::Case),
830 "for" | "select" | "while" | "until" => {
831 self.compounds.push(PrescanCompound::Loop);
832 }
833 "repeat" if words.iter().any(|word| word == "do") => {
834 self.compounds.push(PrescanCompound::Loop);
835 }
836 "fi" => self.pop_compound(PrescanCompound::If),
837 "done" => self.pop_compound(PrescanCompound::Loop),
838 "esac" => self.pop_compound(PrescanCompound::Case),
839 _ => {}
840 }
841 }
842
843 fn is_complete(&self) -> bool {
844 self.brace_depth == 0 && self.paren_depth == 0 && self.compounds.is_empty()
845 }
846
847 fn pop_compound(&mut self, compound: PrescanCompound) {
848 if self.compounds.last().copied() == Some(compound) {
849 self.compounds.pop();
850 }
851 }
852}
853
854#[derive(Debug, Clone, Copy, PartialEq, Eq)]
855enum PrescanFunctionHeaderState {
856 None,
857 AfterWord,
858 AfterFunctionKeyword,
859 AfterFunctionName,
860 AfterWordOpenParen,
861 AfterFunctionNameOpenParen,
862 ReadyForBrace,
863}
864
865impl<'a> ZshOptionPrescanner<'a> {
866 fn new(input: &'a str, state: ZshOptionState) -> Self {
867 Self {
868 input,
869 offset: 0,
870 state,
871 entries: Vec::new(),
872 }
873 }
874
875 fn scan(mut self) -> Vec<ZshOptionTimelineEntry> {
876 let mut words = Vec::new();
877 let mut command_end = 0usize;
878 let mut local_scopes = Vec::new();
879 let mut function_header = PrescanFunctionHeaderState::None;
880
881 while let Some(token) = self.next_token() {
882 match token {
883 PrescanToken::Word { text, end } => {
884 if is_prescan_function_body_start(function_header) {
885 local_scopes.push(PrescanLocalScope::simple(self.state.clone()));
886 function_header = PrescanFunctionHeaderState::None;
887 }
888 command_end = end;
889 function_header = match function_header {
890 PrescanFunctionHeaderState::None => {
891 if text == "function" {
892 PrescanFunctionHeaderState::AfterFunctionKeyword
893 } else {
894 PrescanFunctionHeaderState::AfterWord
895 }
896 }
897 PrescanFunctionHeaderState::AfterFunctionKeyword => {
898 PrescanFunctionHeaderState::AfterFunctionName
899 }
900 _ => PrescanFunctionHeaderState::None,
901 };
902 words.push(text);
903 }
904 PrescanToken::Separator { kind, start, end } => {
905 self.finish_command(&words, command_end.max(start));
906 if let Some(scope) = local_scopes.last_mut() {
907 scope.update_for_command(&words);
908 }
909 if matches!(
910 kind,
911 PrescanSeparator::Newline | PrescanSeparator::Semicolon
912 ) {
913 self.restore_completed_local_scopes(&mut local_scopes, end);
914 }
915 words.clear();
916 command_end = end;
917
918 match kind {
919 PrescanSeparator::Newline => {
920 if !matches!(
921 function_header,
922 PrescanFunctionHeaderState::AfterFunctionName
923 | PrescanFunctionHeaderState::ReadyForBrace
924 ) {
925 function_header = PrescanFunctionHeaderState::None;
926 }
927 }
928 PrescanSeparator::Semicolon
929 | PrescanSeparator::Pipe
930 | PrescanSeparator::Ampersand => {
931 function_header = PrescanFunctionHeaderState::None;
932 }
933 PrescanSeparator::OpenParen => {
934 if is_prescan_function_body_start(function_header) {
935 local_scopes.push(PrescanLocalScope::subshell(self.state.clone()));
936 function_header = PrescanFunctionHeaderState::None;
937 } else {
938 let next_header = match function_header {
939 PrescanFunctionHeaderState::AfterWord => {
940 PrescanFunctionHeaderState::AfterWordOpenParen
941 }
942 PrescanFunctionHeaderState::AfterFunctionName => {
943 PrescanFunctionHeaderState::AfterFunctionNameOpenParen
944 }
945 _ => PrescanFunctionHeaderState::None,
946 };
947 if !matches!(
948 next_header,
949 PrescanFunctionHeaderState::AfterWordOpenParen
950 | PrescanFunctionHeaderState::AfterFunctionNameOpenParen
951 ) {
952 local_scopes
953 .push(PrescanLocalScope::subshell(self.state.clone()));
954 }
955 function_header = next_header;
956 }
957 }
958 PrescanSeparator::CloseParen => {
959 let closes_function_header = matches!(
960 function_header,
961 PrescanFunctionHeaderState::AfterWordOpenParen
962 | PrescanFunctionHeaderState::AfterFunctionNameOpenParen
963 );
964 function_header = if closes_function_header {
965 PrescanFunctionHeaderState::ReadyForBrace
966 } else {
967 PrescanFunctionHeaderState::None
968 };
969 if !closes_function_header {
970 if let Some(scope) = local_scopes.last_mut()
971 && scope.paren_depth > 0
972 {
973 scope.paren_depth -= 1;
974 }
975 self.restore_completed_local_scopes(&mut local_scopes, end);
976 }
977 }
978 PrescanSeparator::OpenBrace => {
979 if is_prescan_function_body_start(function_header) {
980 local_scopes
981 .push(PrescanLocalScope::brace_group(self.state.clone()));
982 } else if let Some(scope) = local_scopes.last_mut() {
983 scope.brace_depth += 1;
984 }
985 function_header = PrescanFunctionHeaderState::None;
986 }
987 PrescanSeparator::CloseBrace => {
988 if let Some(scope) = local_scopes.last_mut()
989 && scope.brace_depth > 0
990 {
991 scope.brace_depth -= 1;
992 }
993 self.restore_completed_local_scopes(&mut local_scopes, end);
994 function_header = PrescanFunctionHeaderState::None;
995 }
996 }
997 }
998 }
999 }
1000
1001 self.finish_command(&words, command_end.max(self.input.len()));
1002 if let Some(scope) = local_scopes.last_mut() {
1003 scope.update_for_command(&words);
1004 }
1005 self.restore_completed_local_scopes(&mut local_scopes, self.input.len());
1006 self.entries
1007 }
1008
1009 fn finish_command(&mut self, words: &[String], end_offset: usize) {
1010 let mut next = self.state.clone();
1011 if !apply_prescan_command_effects(words, &mut next) || next == self.state {
1012 return;
1013 }
1014
1015 self.state = next.clone();
1016 self.entries.push(ZshOptionTimelineEntry {
1017 offset: end_offset,
1018 state: next,
1019 });
1020 }
1021
1022 fn next_token(&mut self) -> Option<PrescanToken> {
1023 loop {
1024 self.skip_horizontal_whitespace();
1025 let ch = self.peek_char()?;
1026
1027 if ch == '#' && self.state.interactive_comments.is_definitely_on() {
1028 self.skip_comment();
1029 continue;
1030 }
1031
1032 return match ch {
1033 '\n' => {
1034 let start = self.offset;
1035 self.advance_char();
1036 Some(PrescanToken::Separator {
1037 kind: PrescanSeparator::Newline,
1038 start,
1039 end: self.offset,
1040 })
1041 }
1042 ';' | '|' | '&' | '(' | ')' | '{' | '}' => {
1043 let start = self.offset;
1044 self.advance_char();
1045 if matches!(ch, '|' | '&' | ';') && self.peek_char() == Some(ch) {
1046 self.advance_char();
1047 }
1048 let kind = match ch {
1049 ';' => PrescanSeparator::Semicolon,
1050 '|' => PrescanSeparator::Pipe,
1051 '&' => PrescanSeparator::Ampersand,
1052 '(' => PrescanSeparator::OpenParen,
1053 ')' => PrescanSeparator::CloseParen,
1054 '{' => PrescanSeparator::OpenBrace,
1055 '}' => PrescanSeparator::CloseBrace,
1056 _ => unreachable!(),
1057 };
1058 Some(PrescanToken::Separator {
1059 kind,
1060 start,
1061 end: self.offset,
1062 })
1063 }
1064 _ => self
1065 .read_word()
1066 .map(|(text, end)| PrescanToken::Word { text, end }),
1067 };
1068 }
1069 }
1070
1071 fn skip_horizontal_whitespace(&mut self) {
1072 while let Some(ch) = self.peek_char() {
1073 match ch {
1074 ' ' | '\t' => {
1075 self.advance_char();
1076 }
1077 '\\' if self.second_char() == Some('\n') => {
1078 self.advance_char();
1079 self.advance_char();
1080 }
1081 _ => break,
1082 }
1083 }
1084 }
1085
1086 fn skip_comment(&mut self) {
1087 while let Some(ch) = self.peek_char() {
1088 if ch == '\n' {
1089 break;
1090 }
1091 self.advance_char();
1092 }
1093 }
1094
1095 fn read_word(&mut self) -> Option<(String, usize)> {
1096 let mut text = String::new();
1097
1098 while let Some(ch) = self.peek_char() {
1099 if is_prescan_separator(ch) {
1100 break;
1101 }
1102
1103 match ch {
1104 ' ' | '\t' => break,
1105 '\\' => {
1106 self.advance_char();
1107 match self.peek_char() {
1108 Some('\n') => {
1109 self.advance_char();
1110 }
1111 Some(next) => {
1112 text.push(next);
1113 self.advance_char();
1114 }
1115 None => text.push('\\'),
1116 }
1117 }
1118 '\'' => {
1119 self.advance_char();
1120 while let Some(next) = self.peek_char() {
1121 if next == '\'' {
1122 if self.state.rc_quotes.is_definitely_on()
1123 && self.second_char() == Some('\'')
1124 {
1125 text.push('\'');
1126 self.advance_char();
1127 self.advance_char();
1128 continue;
1129 }
1130 self.advance_char();
1131 break;
1132 }
1133 text.push(next);
1134 self.advance_char();
1135 }
1136 }
1137 '"' => {
1138 self.advance_char();
1139 while let Some(next) = self.peek_char() {
1140 if next == '"' {
1141 self.advance_char();
1142 break;
1143 }
1144 if next == '\\' {
1145 self.advance_char();
1146 if let Some(escaped) = self.peek_char() {
1147 text.push(escaped);
1148 self.advance_char();
1149 }
1150 continue;
1151 }
1152 text.push(next);
1153 self.advance_char();
1154 }
1155 }
1156 _ => {
1157 text.push(ch);
1158 self.advance_char();
1159 }
1160 }
1161 }
1162
1163 (!text.is_empty()).then_some((text, self.offset))
1164 }
1165
1166 fn peek_char(&self) -> Option<char> {
1167 self.input[self.offset..].chars().next()
1168 }
1169
1170 fn second_char(&self) -> Option<char> {
1171 let mut chars = self.input[self.offset..].chars();
1172 chars.next()?;
1173 chars.next()
1174 }
1175
1176 fn advance_char(&mut self) -> Option<char> {
1177 let ch = self.peek_char()?;
1178 self.offset += ch.len_utf8();
1179 Some(ch)
1180 }
1181
1182 fn restore_completed_local_scopes(
1183 &mut self,
1184 local_scopes: &mut Vec<PrescanLocalScope>,
1185 offset: usize,
1186 ) {
1187 while local_scopes
1188 .last()
1189 .is_some_and(PrescanLocalScope::is_complete)
1190 {
1191 let scope = local_scopes.pop().expect("scope just matched");
1192 if self.state != scope.saved_state {
1193 self.state = scope.saved_state.clone();
1194 self.entries.push(ZshOptionTimelineEntry {
1195 offset,
1196 state: scope.saved_state,
1197 });
1198 } else {
1199 self.state = scope.saved_state;
1200 }
1201 }
1202 }
1203}
1204
1205fn is_prescan_separator(ch: char) -> bool {
1206 matches!(ch, '\n' | ';' | '|' | '&' | '(' | ')' | '{' | '}')
1207}
1208
1209fn is_prescan_function_body_start(state: PrescanFunctionHeaderState) -> bool {
1210 matches!(
1211 state,
1212 PrescanFunctionHeaderState::AfterFunctionName | PrescanFunctionHeaderState::ReadyForBrace
1213 )
1214}
1215
1216fn apply_prescan_command_effects(words: &[String], state: &mut ZshOptionState) -> bool {
1217 let Some((command, args_index)) = normalize_prescan_command(words) else {
1218 return false;
1219 };
1220
1221 match command {
1222 "setopt" => {
1223 let mut changed = false;
1224 for arg in &words[args_index..] {
1225 changed = state.apply_setopt(arg) || changed;
1226 }
1227 changed
1228 }
1229 "unsetopt" => {
1230 let mut changed = false;
1231 for arg in &words[args_index..] {
1232 changed = state.apply_unsetopt(arg) || changed;
1233 }
1234 changed
1235 }
1236 "set" => apply_prescan_set_builtin(&words[args_index..], state),
1237 "emulate" => apply_prescan_emulate(&words[args_index..], state),
1238 _ => false,
1239 }
1240}
1241
1242fn normalize_prescan_command(words: &[String]) -> Option<(&str, usize)> {
1243 let mut index = 0usize;
1244
1245 while let Some(word) = words.get(index) {
1246 if is_prescan_assignment_word(word) {
1247 index += 1;
1248 continue;
1249 }
1250 match word.as_str() {
1251 "noglob" => {
1252 index += 1;
1253 continue;
1254 }
1255 "command" => {
1256 index = skip_prescan_command_wrapper_options(words, index + 1)?;
1257 continue;
1258 }
1259 "builtin" => {
1260 index = skip_prescan_wrapper_options(words, index + 1);
1261 continue;
1262 }
1263 "exec" => {
1264 index = skip_prescan_exec_wrapper_options(words, index + 1);
1265 continue;
1266 }
1267 _ => {}
1268 }
1269 return Some((word.as_str(), index + 1));
1270 }
1271
1272 None
1273}
1274
1275fn skip_prescan_command_wrapper_options(words: &[String], mut index: usize) -> Option<usize> {
1276 while let Some(word) = words.get(index) {
1277 if word == "--" {
1278 index += 1;
1279 break;
1280 }
1281 if word.starts_with('-') && word != "-" {
1282 if word
1283 .strip_prefix('-')
1284 .is_some_and(|flags| flags.chars().any(|flag| matches!(flag, 'v' | 'V')))
1285 {
1286 return None;
1287 }
1288 index += 1;
1289 continue;
1290 }
1291 break;
1292 }
1293 Some(index)
1294}
1295
1296fn skip_prescan_wrapper_options(words: &[String], mut index: usize) -> usize {
1297 while let Some(word) = words.get(index) {
1298 if word == "--" {
1299 index += 1;
1300 break;
1301 }
1302 if word.starts_with('-') && word != "-" {
1303 index += 1;
1304 continue;
1305 }
1306 break;
1307 }
1308 index
1309}
1310
1311fn skip_prescan_exec_wrapper_options(words: &[String], mut index: usize) -> usize {
1312 while let Some(word) = words.get(index) {
1313 if word == "--" {
1314 index += 1;
1315 break;
1316 }
1317 if word == "-a" {
1318 index = (index + 2).min(words.len());
1319 continue;
1320 }
1321 if word.starts_with('-') && word != "-" {
1322 index += 1;
1323 continue;
1324 }
1325 break;
1326 }
1327 index
1328}
1329
1330fn is_prescan_assignment_word(word: &str) -> bool {
1331 let Some((name, _value)) = word.split_once('=') else {
1332 return false;
1333 };
1334 !name.is_empty()
1335 && !name.starts_with('-')
1336 && name
1337 .chars()
1338 .all(|ch| ch.is_ascii_alphanumeric() || ch == '_')
1339}
1340
1341fn apply_prescan_set_builtin(words: &[String], state: &mut ZshOptionState) -> bool {
1342 let mut changed = false;
1343 let mut index = 0usize;
1344
1345 while let Some(word) = words.get(index) {
1346 match word.as_str() {
1347 "-o" | "+o" => {
1348 let enable = word.starts_with('-');
1349 if let Some(name) = words.get(index + 1) {
1350 changed = if enable {
1351 state.apply_setopt(name)
1352 } else {
1353 state.apply_unsetopt(name)
1354 } || changed;
1355 }
1356 index += 2;
1357 }
1358 _ => {
1359 if let Some(name) = word.strip_prefix("-o") {
1360 changed = state.apply_setopt(name) || changed;
1361 } else if let Some(name) = word.strip_prefix("+o") {
1362 changed = state.apply_unsetopt(name) || changed;
1363 }
1364 index += 1;
1365 }
1366 }
1367 }
1368
1369 changed
1370}
1371
1372fn apply_prescan_emulate(words: &[String], state: &mut ZshOptionState) -> bool {
1373 let mut changed = false;
1374 let mut mode = None;
1375 let mut pending_option: Option<bool> = None;
1376 let mut explicit_updates = Vec::new();
1377 let mut index = 0usize;
1378
1379 while let Some(word) = words.get(index) {
1380 if let Some(enable) = pending_option.take() {
1381 explicit_updates.push((word.clone(), enable));
1382 index += 1;
1383 continue;
1384 }
1385
1386 match word.as_str() {
1387 "-o" | "+o" => {
1388 pending_option = Some(word.starts_with('-'));
1389 index += 1;
1390 continue;
1391 }
1392 "zsh" | "sh" | "ksh" | "csh" if mode.is_none() => {
1393 mode = Some(match word.as_str() {
1394 "zsh" => ZshEmulationMode::Zsh,
1395 "sh" => ZshEmulationMode::Sh,
1396 "ksh" => ZshEmulationMode::Ksh,
1397 "csh" => ZshEmulationMode::Csh,
1398 _ => unreachable!(),
1399 });
1400 index += 1;
1401 continue;
1402 }
1403 _ if mode.is_none() && word.starts_with('-') => {
1404 for flag in word[1..].chars() {
1405 if flag == 'o' {
1406 pending_option = Some(true);
1407 }
1408 }
1409 index += 1;
1410 continue;
1411 }
1412 _ => {}
1413 }
1414
1415 index += 1;
1416 }
1417
1418 if let Some(mode) = mode {
1419 *state = ZshOptionState::for_emulate(mode);
1420 changed = true;
1421 }
1422
1423 for (name, enable) in explicit_updates {
1424 changed = if enable {
1425 state.apply_setopt(&name)
1426 } else {
1427 state.apply_unsetopt(&name)
1428 } || changed;
1429 }
1430
1431 changed
1432}
1433
1434#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1435struct DialectFeatures {
1436 double_bracket: bool,
1437 arithmetic_command: bool,
1438 arithmetic_for: bool,
1439 function_keyword: bool,
1440 select_loop: bool,
1441 coproc_keyword: bool,
1442 zsh_repeat_loop: bool,
1443 zsh_foreach_loop: bool,
1444 zsh_parameter_modifiers: bool,
1445 zsh_brace_if: bool,
1446 zsh_always: bool,
1447 zsh_background_operators: bool,
1448 zsh_glob_qualifiers: bool,
1449}
1450
1451impl ShellDialect {
1452 pub fn from_name(name: &str) -> Self {
1454 match name.trim().to_ascii_lowercase().as_str() {
1455 "sh" | "dash" | "ksh" | "posix" => Self::Posix,
1456 "mksh" => Self::Mksh,
1457 "zsh" => Self::Zsh,
1458 _ => Self::Bash,
1459 }
1460 }
1461
1462 const fn features(self) -> DialectFeatures {
1463 match self {
1464 Self::Posix => DialectFeatures {
1465 double_bracket: false,
1466 arithmetic_command: false,
1467 arithmetic_for: false,
1468 function_keyword: true,
1469 select_loop: false,
1470 coproc_keyword: false,
1471 zsh_repeat_loop: false,
1472 zsh_foreach_loop: false,
1473 zsh_parameter_modifiers: false,
1474 zsh_brace_if: false,
1475 zsh_always: false,
1476 zsh_background_operators: false,
1477 zsh_glob_qualifiers: false,
1478 },
1479 Self::Mksh => DialectFeatures {
1480 double_bracket: true,
1481 arithmetic_command: true,
1482 arithmetic_for: false,
1483 function_keyword: true,
1484 select_loop: true,
1485 coproc_keyword: false,
1486 zsh_repeat_loop: false,
1487 zsh_foreach_loop: false,
1488 zsh_parameter_modifiers: false,
1489 zsh_brace_if: false,
1490 zsh_always: false,
1491 zsh_background_operators: false,
1492 zsh_glob_qualifiers: false,
1493 },
1494 Self::Bash => DialectFeatures {
1495 double_bracket: true,
1496 arithmetic_command: true,
1497 arithmetic_for: true,
1498 function_keyword: true,
1499 select_loop: true,
1500 coproc_keyword: true,
1501 zsh_repeat_loop: false,
1502 zsh_foreach_loop: false,
1503 zsh_parameter_modifiers: false,
1504 zsh_brace_if: false,
1505 zsh_always: false,
1506 zsh_background_operators: false,
1507 zsh_glob_qualifiers: false,
1508 },
1509 Self::Zsh => DialectFeatures {
1510 double_bracket: true,
1511 arithmetic_command: true,
1512 arithmetic_for: true,
1513 function_keyword: true,
1514 select_loop: true,
1515 coproc_keyword: true,
1516 zsh_repeat_loop: true,
1517 zsh_foreach_loop: true,
1518 zsh_parameter_modifiers: true,
1519 zsh_brace_if: true,
1520 zsh_always: true,
1521 zsh_background_operators: true,
1522 zsh_glob_qualifiers: true,
1523 },
1524 }
1525 }
1526}
1527
1528#[derive(Clone)]
1530pub struct Parser<'a> {
1531 input: &'a str,
1532 lexer: Lexer<'a>,
1533 synthetic_tokens: VecDeque<SyntheticToken>,
1534 alias_replays: Vec<AliasReplay>,
1535 current_token: Option<LexedToken<'a>>,
1536 current_word_cache: Option<Word>,
1537 current_token_kind: Option<TokenKind>,
1538 current_keyword: Option<Keyword>,
1539 current_span: Span,
1541 peeked_token: Option<LexedToken<'a>>,
1543 max_depth: usize,
1545 current_depth: usize,
1547 fuel: usize,
1549 max_fuel: usize,
1551 comments: Vec<Comment>,
1553 aliases: HashMap<String, AliasDefinition>,
1555 expand_aliases: bool,
1557 expand_next_word: bool,
1560 brace_group_depth: usize,
1562 brace_body_stack: Vec<BraceBodyContext>,
1565 syntax_facts: SyntaxFacts,
1566 shell_profile: ShellProfile,
1567 zsh_timeline: Option<Arc<ZshOptionTimeline>>,
1568 dialect: ShellDialect,
1569 #[cfg(feature = "benchmarking")]
1570 benchmark_counters: Option<ParserBenchmarkCounters>,
1571}
1572
1573#[derive(Clone)]
1574struct ParserCheckpoint<'a> {
1575 lexer: Lexer<'a>,
1576 synthetic_tokens: VecDeque<SyntheticToken>,
1577 alias_replays: Vec<AliasReplay>,
1578 current_token: Option<LexedToken<'a>>,
1579 current_word_cache: Option<Word>,
1580 current_token_kind: Option<TokenKind>,
1581 current_keyword: Option<Keyword>,
1582 current_span: Span,
1583 peeked_token: Option<LexedToken<'a>>,
1584 current_depth: usize,
1585 fuel: usize,
1586 comments: Vec<Comment>,
1587 expand_next_word: bool,
1588 brace_group_depth: usize,
1589 brace_body_stack: Vec<BraceBodyContext>,
1590 syntax_facts: SyntaxFacts,
1591 #[cfg(feature = "benchmarking")]
1592 benchmark_counters: Option<ParserBenchmarkCounters>,
1593}
1594
1595#[derive(Debug, Clone, PartialEq, Eq)]
1597pub struct ParseDiagnostic {
1598 pub message: String,
1600 pub span: Span,
1602}
1603
1604#[derive(Debug, Clone)]
1605struct AliasDefinition {
1606 tokens: Arc<[LexedToken<'static>]>,
1607 expands_next_word: bool,
1608}
1609
1610#[derive(Debug, Clone)]
1611struct AliasReplay {
1612 tokens: Arc<[LexedToken<'static>]>,
1613 next_index: usize,
1614 base: Position,
1615}
1616
1617impl AliasReplay {
1618 fn new(alias: &AliasDefinition, base: Position) -> Self {
1619 Self {
1620 tokens: Arc::clone(&alias.tokens),
1621 next_index: 0,
1622 base,
1623 }
1624 }
1625
1626 fn next_token<'b>(&mut self) -> Option<LexedToken<'b>> {
1627 let token = self.tokens.get(self.next_index)?.clone();
1628 self.next_index += 1;
1629 Some(token.into_owned().rebased(self.base).with_synthetic_flag())
1630 }
1631}
1632
1633#[derive(Debug, Clone, Copy)]
1634struct SyntheticToken {
1635 kind: TokenKind,
1636 span: Span,
1637}
1638
1639impl SyntheticToken {
1640 const fn punctuation(kind: TokenKind, span: Span) -> Self {
1641 Self { kind, span }
1642 }
1643
1644 fn materialize<'b>(self) -> LexedToken<'b> {
1645 LexedToken::punctuation(self.kind).with_span(self.span)
1646 }
1647}
1648
1649#[derive(Debug, Clone, Copy)]
1650enum FlowControlBuiltinKind {
1651 Break,
1652 Continue,
1653 Return,
1654 Exit,
1655}
1656
1657#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1658enum BraceBodyContext {
1659 Ordinary,
1660 Function,
1661 IfClause,
1662}
1663
1664#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1665struct TokenSet(u64);
1666
1667impl TokenSet {
1668 const fn contains(self, kind: TokenKind) -> bool {
1669 self.0 & (1u64 << kind as u8) != 0
1670 }
1671}
1672
1673macro_rules! token_set {
1674 ($($kind:path),+ $(,)?) => {
1675 TokenSet(0 $(| (1u64 << ($kind as u8)))+)
1676 };
1677}
1678
1679#[repr(u8)]
1680#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1681enum Keyword {
1682 If,
1683 For,
1684 Repeat,
1685 Foreach,
1686 While,
1687 Until,
1688 Case,
1689 Select,
1690 Time,
1691 Coproc,
1692 Function,
1693 Always,
1694 Then,
1695 Else,
1696 Elif,
1697 Fi,
1698 Do,
1699 Done,
1700 Esac,
1701 In,
1702}
1703
1704impl Keyword {
1705 const fn as_str(self) -> &'static str {
1706 match self {
1707 Self::If => "if",
1708 Self::For => "for",
1709 Self::Repeat => "repeat",
1710 Self::Foreach => "foreach",
1711 Self::While => "while",
1712 Self::Until => "until",
1713 Self::Case => "case",
1714 Self::Select => "select",
1715 Self::Time => "time",
1716 Self::Coproc => "coproc",
1717 Self::Function => "function",
1718 Self::Always => "always",
1719 Self::Then => "then",
1720 Self::Else => "else",
1721 Self::Elif => "elif",
1722 Self::Fi => "fi",
1723 Self::Do => "do",
1724 Self::Done => "done",
1725 Self::Esac => "esac",
1726 Self::In => "in",
1727 }
1728 }
1729}
1730
1731impl std::fmt::Display for Keyword {
1732 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1733 f.write_str(self.as_str())
1734 }
1735}
1736
1737#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1738struct KeywordSet(u32);
1739
1740impl KeywordSet {
1741 const fn single(keyword: Keyword) -> Self {
1742 Self(1u32 << keyword as u8)
1743 }
1744
1745 const fn contains(self, keyword: Keyword) -> bool {
1746 self.0 & (1u32 << keyword as u8) != 0
1747 }
1748}
1749
1750macro_rules! keyword_set {
1751 ($($keyword:ident),+ $(,)?) => {
1752 KeywordSet(0 $(| (1u32 << (Keyword::$keyword as u8)))+)
1753 };
1754}
1755
1756const PIPE_OPERATOR_TOKENS: TokenSet = token_set![TokenKind::Pipe, TokenKind::PipeBoth];
1757const REDIRECT_TOKENS: TokenSet = token_set![
1758 TokenKind::RedirectOut,
1759 TokenKind::Clobber,
1760 TokenKind::RedirectAppend,
1761 TokenKind::RedirectIn,
1762 TokenKind::RedirectReadWrite,
1763 TokenKind::HereString,
1764 TokenKind::HereDoc,
1765 TokenKind::HereDocStrip,
1766 TokenKind::RedirectBoth,
1767 TokenKind::RedirectBothAppend,
1768 TokenKind::DupOutput,
1769 TokenKind::RedirectFd,
1770 TokenKind::RedirectFdAppend,
1771 TokenKind::DupFd,
1772 TokenKind::DupInput,
1773 TokenKind::DupFdIn,
1774 TokenKind::DupFdClose,
1775 TokenKind::RedirectFdIn,
1776 TokenKind::RedirectFdReadWrite,
1777];
1778const NON_COMMAND_KEYWORDS: KeywordSet =
1779 keyword_set![Then, Else, Elif, Fi, Do, Done, Esac, In, Always];
1780const IF_BODY_TERMINATORS: KeywordSet = keyword_set![Elif, Else, Fi];
1781
1782impl<'a> Parser<'a> {
1783 pub fn new(input: &'a str) -> Self {
1785 Self::with_limits_and_profile(
1786 input,
1787 DEFAULT_MAX_AST_DEPTH,
1788 DEFAULT_MAX_PARSER_OPERATIONS,
1789 ShellProfile::native(ShellDialect::Bash),
1790 )
1791 }
1792
1793 pub fn with_dialect(input: &'a str, dialect: ShellDialect) -> Self {
1795 Self::with_profile(input, ShellProfile::native(dialect))
1796 }
1797
1798 pub fn with_profile(input: &'a str, shell_profile: ShellProfile) -> Self {
1800 Self::with_limits_and_profile(
1801 input,
1802 DEFAULT_MAX_AST_DEPTH,
1803 DEFAULT_MAX_PARSER_OPERATIONS,
1804 shell_profile,
1805 )
1806 }
1807
1808 pub fn with_max_depth(input: &'a str, max_depth: usize) -> Self {
1810 Self::with_limits_and_profile(
1811 input,
1812 max_depth,
1813 DEFAULT_MAX_PARSER_OPERATIONS,
1814 ShellProfile::native(ShellDialect::Bash),
1815 )
1816 }
1817
1818 pub fn with_fuel(input: &'a str, max_fuel: usize) -> Self {
1820 Self::with_limits_and_profile(
1821 input,
1822 DEFAULT_MAX_AST_DEPTH,
1823 max_fuel,
1824 ShellProfile::native(ShellDialect::Bash),
1825 )
1826 }
1827
1828 pub fn with_limits(input: &'a str, max_depth: usize, max_fuel: usize) -> Self {
1834 Self::with_limits_and_profile(
1835 input,
1836 max_depth,
1837 max_fuel,
1838 ShellProfile::native(ShellDialect::Bash),
1839 )
1840 }
1841
1842 pub fn with_limits_and_dialect(
1844 input: &'a str,
1845 max_depth: usize,
1846 max_fuel: usize,
1847 dialect: ShellDialect,
1848 ) -> Self {
1849 Self::with_limits_and_profile(input, max_depth, max_fuel, ShellProfile::native(dialect))
1850 }
1851
1852 pub fn with_limits_and_profile(
1854 input: &'a str,
1855 max_depth: usize,
1856 max_fuel: usize,
1857 shell_profile: ShellProfile,
1858 ) -> Self {
1859 Self::with_limits_and_profile_and_benchmarking(
1860 input,
1861 max_depth,
1862 max_fuel,
1863 shell_profile,
1864 false,
1865 )
1866 }
1867
1868 fn with_limits_and_profile_and_benchmarking(
1869 input: &'a str,
1870 max_depth: usize,
1871 max_fuel: usize,
1872 shell_profile: ShellProfile,
1873 benchmark_counters_enabled: bool,
1874 ) -> Self {
1875 #[cfg(not(feature = "benchmarking"))]
1876 let _ = benchmark_counters_enabled;
1877
1878 let zsh_timeline = (shell_profile.dialect == ShellDialect::Zsh)
1879 .then(|| ZshOptionTimeline::build(input, &shell_profile))
1880 .flatten()
1881 .map(Arc::new);
1882 let mut lexer = Lexer::with_max_subst_depth_and_profile(
1883 input,
1884 max_depth.min(HARD_MAX_AST_DEPTH),
1885 &shell_profile,
1886 zsh_timeline.clone(),
1887 );
1888 #[cfg(feature = "benchmarking")]
1889 if benchmark_counters_enabled {
1890 lexer.enable_benchmark_counters();
1891 }
1892 let mut comments = Vec::new();
1893 let (current_token, current_token_kind, current_keyword, current_span) = loop {
1894 match lexer.next_lexed_token_with_comments() {
1895 Some(st) if st.kind == TokenKind::Comment => {
1896 comments.push(Comment {
1897 range: st.span.to_range(),
1898 });
1899 }
1900 Some(st) => {
1901 break (
1902 Some(st.clone()),
1903 Some(st.kind),
1904 Self::keyword_from_token(&st),
1905 st.span,
1906 );
1907 }
1908 None => break (None, None, None, Span::new()),
1909 }
1910 };
1911 Self {
1912 input,
1913 lexer,
1914 synthetic_tokens: VecDeque::new(),
1915 alias_replays: Vec::new(),
1916 current_token,
1917 current_word_cache: None,
1918 current_token_kind,
1919 current_keyword,
1920 current_span,
1921 peeked_token: None,
1922 max_depth: max_depth.min(HARD_MAX_AST_DEPTH),
1923 current_depth: 0,
1924 fuel: max_fuel,
1925 max_fuel,
1926 comments,
1927 aliases: HashMap::new(),
1928 expand_aliases: false,
1929 expand_next_word: false,
1930 brace_group_depth: 0,
1931 brace_body_stack: Vec::new(),
1932 syntax_facts: SyntaxFacts::default(),
1933 dialect: shell_profile.dialect,
1934 shell_profile,
1935 zsh_timeline,
1936 #[cfg(feature = "benchmarking")]
1937 benchmark_counters: benchmark_counters_enabled.then(ParserBenchmarkCounters::default),
1938 }
1939 }
1940
1941 #[cfg(feature = "benchmarking")]
1942 fn rebuild_with_benchmark_counters(&self) -> Self {
1943 Self::with_limits_and_profile_and_benchmarking(
1944 self.input,
1945 self.max_depth,
1946 self.max_fuel,
1947 self.shell_profile.clone(),
1948 true,
1949 )
1950 }
1951
1952 pub fn dialect(&self) -> ShellDialect {
1954 self.dialect
1955 }
1956
1957 pub fn shell_profile(&self) -> &ShellProfile {
1959 &self.shell_profile
1960 }
1961
1962 fn zsh_options_at_offset(&self, offset: usize) -> Option<&ZshOptionState> {
1963 self.zsh_timeline
1964 .as_ref()
1965 .map(|timeline| timeline.options_at(offset))
1966 .or_else(|| self.shell_profile.zsh_options())
1967 }
1968
1969 fn current_zsh_options(&self) -> Option<&ZshOptionState> {
1970 self.zsh_options_at_offset(self.current_span.start.offset)
1971 }
1972
1973 fn zsh_short_loops_enabled(&self) -> bool {
1974 self.dialect.features().zsh_foreach_loop
1975 && !self
1976 .current_zsh_options()
1977 .is_some_and(|options| options.short_loops.is_definitely_off())
1978 }
1979
1980 fn zsh_short_repeat_enabled(&self) -> bool {
1981 self.dialect.features().zsh_repeat_loop
1982 && !self
1983 .current_zsh_options()
1984 .is_some_and(|options| options.short_repeat.is_definitely_off())
1985 }
1986
1987 fn zsh_brace_bodies_enabled(&self) -> bool {
1988 self.dialect.features().zsh_brace_if
1989 && !self
1990 .current_zsh_options()
1991 .is_some_and(|options| options.ignore_braces.is_definitely_on())
1992 }
1993
1994 fn zsh_brace_if_enabled(&self) -> bool {
1995 self.zsh_brace_bodies_enabled()
1996 }
1997
1998 fn zsh_glob_qualifiers_enabled_at(&self, offset: usize) -> bool {
1999 self.dialect.features().zsh_glob_qualifiers
2000 && !self.zsh_options_at_offset(offset).is_some_and(|options| {
2001 options.ignore_braces.is_definitely_on()
2002 || options.bare_glob_qual.is_definitely_off()
2003 })
2004 }
2005
2006 fn brace_syntax_enabled_at(&self, offset: usize) -> bool {
2007 !self.zsh_options_at_offset(offset).is_some_and(|options| {
2008 options.ignore_braces.is_definitely_on()
2009 || options.ignore_close_braces.is_definitely_on()
2010 })
2011 }
2012
2013 pub fn current_span(&self) -> Span {
2015 self.current_span
2016 }
2017
2018 pub fn parse_word_string(input: &str) -> Word {
2021 let mut parser = Parser::new(input);
2022 let start = Position::new();
2023 parser.parse_word_with_context(
2024 input,
2025 Span::from_positions(start, start.advanced_by(input)),
2026 start,
2027 true,
2028 )
2029 }
2030
2031 pub fn parse_word_string_with_limits(input: &str, max_depth: usize, max_fuel: usize) -> Word {
2034 Self::parse_word_string_with_limits_and_dialect(
2035 input,
2036 max_depth,
2037 max_fuel,
2038 ShellDialect::Bash,
2039 )
2040 }
2041
2042 pub fn parse_word_string_with_limits_and_dialect(
2044 input: &str,
2045 max_depth: usize,
2046 max_fuel: usize,
2047 dialect: ShellDialect,
2048 ) -> Word {
2049 let mut parser = Parser::with_limits_and_profile(
2050 input,
2051 max_depth,
2052 max_fuel,
2053 ShellProfile::native(dialect),
2054 );
2055 let start = Position::new();
2056 parser.parse_word_with_context(
2057 input,
2058 Span::from_positions(start, start.advanced_by(input)),
2059 start,
2060 true,
2061 )
2062 }
2063
2064 pub fn parse_word_fragment(source: &str, text: &str, span: Span) -> Word {
2067 let mut parser = Parser::new(text);
2068 let source_backed = span.end.offset <= source.len() && span.slice(source) == text;
2069 let start = Position::new();
2070 let fragment_span = Span::from_positions(start, start.advanced_by(text));
2071 let mut word = parser.parse_word_with_context(text, fragment_span, start, source_backed);
2072 if !source_backed {
2073 Self::materialize_word_source_backing(&mut word, text);
2074 }
2075 Self::rebase_word(&mut word, span.start);
2076 word.span = span;
2077 word
2078 }
2079
2080 fn maybe_record_comment(&mut self, token: &LexedToken<'_>) {
2081 if token.kind == TokenKind::Comment && !token.flags.is_synthetic() {
2082 self.comments.push(Comment {
2083 range: token.span.to_range(),
2084 });
2085 }
2086 }
2087
2088 fn record_zsh_brace_if_span(&mut self, span: Span) {
2089 if !self.syntax_facts.zsh_brace_if_spans.contains(&span) {
2090 self.syntax_facts.zsh_brace_if_spans.push(span);
2091 }
2092 }
2093
2094 fn record_zsh_always_span(&mut self, span: Span) {
2095 if !self.syntax_facts.zsh_always_spans.contains(&span) {
2096 self.syntax_facts.zsh_always_spans.push(span);
2097 }
2098 }
2099
2100 fn record_zsh_case_group_part(&mut self, pattern_part_index: usize, span: Span) {
2101 if !self
2102 .syntax_facts
2103 .zsh_case_group_parts
2104 .iter()
2105 .any(|fact| fact.pattern_part_index == pattern_part_index && fact.span == span)
2106 {
2107 self.syntax_facts
2108 .zsh_case_group_parts
2109 .push(ZshCaseGroupPart {
2110 pattern_part_index,
2111 span,
2112 });
2113 }
2114 }
2115
2116 fn word_text_needs_parse(text: &str) -> bool {
2117 memchr3(b'$', b'`', b'\0', text.as_bytes()).is_some()
2118 }
2119
2120 fn word_with_parts(&self, parts: Vec<WordPartNode>, span: Span) -> Word {
2121 let brace_syntax = self.brace_syntax_from_parts(&parts, span.start.offset);
2122 Word {
2123 parts,
2124 span,
2125 brace_syntax,
2126 }
2127 }
2128
2129 fn heredoc_body_with_parts(
2130 &self,
2131 parts: Vec<HeredocBodyPartNode>,
2132 span: Span,
2133 mode: HeredocBodyMode,
2134 source_backed: bool,
2135 ) -> HeredocBody {
2136 HeredocBody {
2137 mode,
2138 source_backed,
2139 parts,
2140 span,
2141 }
2142 }
2143
2144 fn heredoc_body_part_from_word_part_node(part: WordPartNode) -> HeredocBodyPartNode {
2145 let kind = match part.kind {
2146 WordPart::Literal(text) => HeredocBodyPart::Literal(text),
2147 WordPart::Variable(name) => HeredocBodyPart::Variable(name),
2148 WordPart::CommandSubstitution { body, syntax } => {
2149 HeredocBodyPart::CommandSubstitution { body, syntax }
2150 }
2151 WordPart::ArithmeticExpansion {
2152 expression,
2153 expression_ast,
2154 expression_word_ast,
2155 syntax,
2156 } => HeredocBodyPart::ArithmeticExpansion {
2157 expression,
2158 expression_ast,
2159 expression_word_ast,
2160 syntax,
2161 },
2162 WordPart::Parameter(parameter) => HeredocBodyPart::Parameter(Box::new(parameter)),
2163 other => panic!("unsupported heredoc body part: {other:?}"),
2164 };
2165
2166 HeredocBodyPartNode::new(kind, part.span)
2167 }
2168
2169 fn brace_syntax_from_parts(&self, parts: &[WordPartNode], offset: usize) -> Vec<BraceSyntax> {
2170 if !self.brace_syntax_enabled_at(offset) {
2171 return Vec::new();
2172 }
2173 let mut brace_syntax = Vec::new();
2174 self.collect_brace_syntax_from_parts(parts, BraceQuoteContext::Unquoted, &mut brace_syntax);
2175 brace_syntax
2176 }
2177
2178 fn collect_brace_syntax_from_parts(
2179 &self,
2180 parts: &[WordPartNode],
2181 quote_context: BraceQuoteContext,
2182 out: &mut Vec<BraceSyntax>,
2183 ) {
2184 for part in parts {
2185 match &part.kind {
2186 WordPart::Literal(text) => Self::scan_brace_syntax_text(
2187 text.as_str(self.input, part.span),
2188 part.span.start,
2189 quote_context,
2190 out,
2191 ),
2192 WordPart::ZshQualifiedGlob(glob) => {
2193 self.collect_brace_syntax_from_zsh_qualified_glob(glob, quote_context, out)
2194 }
2195 WordPart::SingleQuoted { value, .. } => Self::scan_brace_syntax_text(
2196 value.slice(self.input),
2197 value.span().start,
2198 BraceQuoteContext::SingleQuoted,
2199 out,
2200 ),
2201 WordPart::DoubleQuoted { parts, .. } => self.collect_brace_syntax_from_parts(
2202 parts,
2203 BraceQuoteContext::DoubleQuoted,
2204 out,
2205 ),
2206 WordPart::Variable(_)
2207 | WordPart::CommandSubstitution { .. }
2208 | WordPart::ArithmeticExpansion { .. }
2209 | WordPart::Parameter(_)
2210 | WordPart::ParameterExpansion { .. }
2211 | WordPart::Length(_)
2212 | WordPart::ArrayAccess(_)
2213 | WordPart::ArrayLength(_)
2214 | WordPart::ArrayIndices(_)
2215 | WordPart::Substring { .. }
2216 | WordPart::ArraySlice { .. }
2217 | WordPart::IndirectExpansion { .. }
2218 | WordPart::PrefixMatch { .. }
2219 | WordPart::ProcessSubstitution { .. }
2220 | WordPart::Transformation { .. } => {}
2221 }
2222 }
2223 }
2224
2225 fn collect_brace_syntax_from_pattern(
2226 &self,
2227 pattern: &Pattern,
2228 quote_context: BraceQuoteContext,
2229 out: &mut Vec<BraceSyntax>,
2230 ) {
2231 for (part, span) in pattern.parts_with_spans() {
2232 match part {
2233 PatternPart::Literal(text) => Self::scan_brace_syntax_text(
2234 text.as_str(self.input, span),
2235 span.start,
2236 quote_context,
2237 out,
2238 ),
2239 PatternPart::CharClass(text) => Self::scan_brace_syntax_text(
2240 text.slice(self.input),
2241 text.span().start,
2242 quote_context,
2243 out,
2244 ),
2245 PatternPart::Group { patterns, .. } => {
2246 for pattern in patterns {
2247 self.collect_brace_syntax_from_pattern(pattern, quote_context, out);
2248 }
2249 }
2250 PatternPart::Word(word) => {
2251 self.collect_brace_syntax_from_parts(&word.parts, quote_context, out)
2252 }
2253 PatternPart::AnyString | PatternPart::AnyChar => {}
2254 }
2255 }
2256 }
2257
2258 fn collect_brace_syntax_from_zsh_qualified_glob(
2259 &self,
2260 glob: &ZshQualifiedGlob,
2261 quote_context: BraceQuoteContext,
2262 out: &mut Vec<BraceSyntax>,
2263 ) {
2264 for segment in &glob.segments {
2265 if let ZshGlobSegment::Pattern(pattern) = segment {
2266 self.collect_brace_syntax_from_pattern(pattern, quote_context, out);
2267 }
2268 }
2269 }
2270
2271 fn scan_brace_syntax_text(
2272 text: &str,
2273 base: Position,
2274 quote_context: BraceQuoteContext,
2275 out: &mut Vec<BraceSyntax>,
2276 ) {
2277 let bytes = text.as_bytes();
2278 let mut index = 0;
2279 let mut position = base;
2280
2281 while index < bytes.len() {
2282 let next_special = if matches!(quote_context, BraceQuoteContext::Unquoted) {
2283 memchr2(b'{', b'\\', &bytes[index..]).map(|relative| index + relative)
2284 } else {
2285 memchr(b'{', &bytes[index..]).map(|relative| index + relative)
2286 };
2287
2288 let Some(next_index) = next_special else {
2289 return;
2290 };
2291
2292 if next_index > index {
2293 position = position.advanced_by(&text[index..next_index]);
2294 index = next_index;
2295 }
2296
2297 if matches!(quote_context, BraceQuoteContext::Unquoted) && bytes[index] == b'\\' {
2298 let escaped_start = index;
2299 index += 1;
2300 if let Some(next) = text[index..].chars().next() {
2301 index += next.len_utf8();
2302 }
2303 position = position.advanced_by(&text[escaped_start..index]);
2304 continue;
2305 }
2306
2307 let brace_start = position;
2308 if let Some(len) = Self::template_placeholder_len(text, index, quote_context) {
2309 let brace_end = brace_start.advanced_by(&text[index..index + len]);
2310 out.push(BraceSyntax {
2311 kind: BraceSyntaxKind::TemplatePlaceholder,
2312 span: Span::from_positions(brace_start, brace_end),
2313 quote_context,
2314 });
2315 position = brace_end;
2316 index += len;
2317 continue;
2318 }
2319
2320 if let Some((len, kind)) = Self::brace_construct_len(text, index, quote_context) {
2321 let brace_end = brace_start.advanced_by(&text[index..index + len]);
2322 out.push(BraceSyntax {
2323 kind,
2324 span: Span::from_positions(brace_start, brace_end),
2325 quote_context,
2326 });
2327 position = brace_end;
2328 index += len;
2329 continue;
2330 }
2331
2332 position.advance('{');
2333 index += '{'.len_utf8();
2334 }
2335 }
2336
2337 fn text_position(base: Position, text: &str, offset: usize) -> Position {
2338 base.advanced_by(&text[..offset])
2339 }
2340
2341 fn template_placeholder_len(
2342 text: &str,
2343 start: usize,
2344 quote_context: BraceQuoteContext,
2345 ) -> Option<usize> {
2346 text.get(start..).filter(|rest| rest.starts_with("{{"))?;
2347
2348 let mut index = start + "{{".len();
2349 let mut depth = 1usize;
2350
2351 while index < text.len() {
2352 if matches!(quote_context, BraceQuoteContext::Unquoted)
2353 && text[index..].starts_with('\\')
2354 {
2355 index += 1;
2356 if let Some(next) = text[index..].chars().next() {
2357 index += next.len_utf8();
2358 }
2359 continue;
2360 }
2361
2362 if text[index..].starts_with("{{") {
2363 depth += 1;
2364 index += "{{".len();
2365 continue;
2366 }
2367
2368 if text[index..].starts_with("}}") {
2369 depth -= 1;
2370 index += "}}".len();
2371 if depth == 0 {
2372 return Some(index - start);
2373 }
2374 continue;
2375 }
2376
2377 index += text[index..].chars().next()?.len_utf8();
2378 }
2379
2380 None
2381 }
2382
2383 fn brace_construct_len(
2384 text: &str,
2385 start: usize,
2386 quote_context: BraceQuoteContext,
2387 ) -> Option<(usize, BraceSyntaxKind)> {
2388 text.get(start..).filter(|rest| rest.starts_with('{'))?;
2389
2390 #[derive(Clone, Copy, PartialEq, Eq)]
2391 enum QuoteState {
2392 Single,
2393 Double,
2394 }
2395
2396 let mut index = start + '{'.len_utf8();
2397 let mut depth = 1usize;
2398 let mut has_comma = false;
2399 let mut has_dot_dot = false;
2400 let mut saw_unquoted_whitespace = false;
2401 let mut prev_char = None;
2402 let mut quote_state = None;
2403
2404 while index < text.len() {
2405 if matches!(quote_context, BraceQuoteContext::Unquoted)
2406 && quote_state.is_none()
2407 && text[index..].starts_with('\\')
2408 {
2409 index += 1;
2410 if let Some(next) = text[index..].chars().next() {
2411 index += next.len_utf8();
2412 }
2413 prev_char = None;
2414 continue;
2415 }
2416
2417 let ch = text[index..].chars().next()?;
2418 index += ch.len_utf8();
2419
2420 if matches!(quote_context, BraceQuoteContext::Unquoted) {
2421 match quote_state {
2422 None => match ch {
2423 '\'' => {
2424 quote_state = Some(QuoteState::Single);
2425 prev_char = None;
2426 continue;
2427 }
2428 '"' => {
2429 quote_state = Some(QuoteState::Double);
2430 prev_char = None;
2431 continue;
2432 }
2433 '{' => depth += 1,
2434 '}' => {
2435 depth -= 1;
2436 if depth == 0 {
2437 let kind = if saw_unquoted_whitespace {
2438 BraceSyntaxKind::Literal
2439 } else if has_comma {
2440 BraceSyntaxKind::Expansion(BraceExpansionKind::CommaList)
2441 } else if has_dot_dot {
2442 BraceSyntaxKind::Expansion(BraceExpansionKind::Sequence)
2443 } else {
2444 BraceSyntaxKind::Literal
2445 };
2446 return Some((index - start, kind));
2447 }
2448 }
2449 ',' if depth == 1 => has_comma = true,
2450 '.' if depth == 1 && prev_char == Some('.') => has_dot_dot = true,
2451 c if c.is_whitespace() => saw_unquoted_whitespace = true,
2452 _ => {}
2453 },
2454 Some(QuoteState::Single) => {
2455 if ch == '\'' {
2456 quote_state = None;
2457 }
2458 }
2459 Some(QuoteState::Double) => match ch {
2460 '\\' => {
2461 if let Some(next) = text[index..].chars().next() {
2462 index += next.len_utf8();
2463 }
2464 prev_char = None;
2465 continue;
2466 }
2467 '"' => quote_state = None,
2468 _ => {}
2469 },
2470 }
2471 } else {
2472 match ch {
2473 '{' => depth += 1,
2474 '}' => {
2475 depth -= 1;
2476 if depth == 0 {
2477 let kind = if has_comma {
2478 BraceSyntaxKind::Expansion(BraceExpansionKind::CommaList)
2479 } else if has_dot_dot {
2480 BraceSyntaxKind::Expansion(BraceExpansionKind::Sequence)
2481 } else {
2482 BraceSyntaxKind::Literal
2483 };
2484 return Some((index - start, kind));
2485 }
2486 }
2487 ',' if depth == 1 => has_comma = true,
2488 '.' if depth == 1 && prev_char == Some('.') => has_dot_dot = true,
2489 _ => {}
2490 }
2491 }
2492
2493 prev_char = Some(ch);
2494 }
2495
2496 None
2497 }
2498
2499 fn maybe_parse_zsh_qualified_glob_word(
2500 &mut self,
2501 text: &str,
2502 span: Span,
2503 source_backed: bool,
2504 ) -> Option<Word> {
2505 if !self.zsh_glob_qualifiers_enabled_at(span.start.offset)
2506 || text.is_empty()
2507 || text.contains('=')
2508 || text.contains(['\x00', '\\', '\'', '"', '$', '`'])
2509 || text.chars().any(char::is_whitespace)
2510 {
2511 return None;
2512 }
2513
2514 let (segments, qualifiers) =
2515 self.parse_zsh_qualified_glob_segments(text, span, source_backed)?;
2516 if !segments.iter().any(|segment| {
2517 matches!(
2518 segment,
2519 ZshGlobSegment::Pattern(pattern) if Self::pattern_has_glob_syntax(pattern)
2520 )
2521 }) {
2522 return None;
2523 }
2524
2525 Some(self.word_with_parts(
2526 vec![WordPartNode::new(
2527 WordPart::ZshQualifiedGlob(ZshQualifiedGlob {
2528 span,
2529 segments,
2530 qualifiers,
2531 }),
2532 span,
2533 )],
2534 span,
2535 ))
2536 }
2537
2538 fn parse_zsh_qualified_glob_segments(
2539 &mut self,
2540 text: &str,
2541 span: Span,
2542 source_backed: bool,
2543 ) -> Option<(Vec<ZshGlobSegment>, Option<ZshGlobQualifierGroup>)> {
2544 let mut segments = Vec::new();
2545 let mut qualifiers = None;
2546 let mut pattern_start = 0usize;
2547 let mut index = 0usize;
2548
2549 while index < text.len() {
2550 if text[index..].starts_with("(#") {
2551 if let Some((len, control)) =
2552 self.parse_zsh_inline_glob_control(text, span.start, index)
2553 {
2554 self.push_zsh_pattern_segment(
2555 &mut segments,
2556 text,
2557 span.start,
2558 pattern_start,
2559 index,
2560 source_backed,
2561 );
2562 segments.push(ZshGlobSegment::InlineControl(control));
2563 index += len;
2564 pattern_start = index;
2565 continue;
2566 }
2567
2568 let suffix_start = Self::text_position(span.start, text, index);
2569 if let Some(group) = self.parse_zsh_terminal_glob_qualifier_group(
2570 &text[index..],
2571 suffix_start,
2572 source_backed,
2573 ) {
2574 self.push_zsh_pattern_segment(
2575 &mut segments,
2576 text,
2577 span.start,
2578 pattern_start,
2579 index,
2580 source_backed,
2581 );
2582 qualifiers = Some(group);
2583 index = text.len();
2584 pattern_start = index;
2585 break;
2586 }
2587
2588 return None;
2589 }
2590
2591 if text[index..].starts_with('(') {
2592 let suffix_start = Self::text_position(span.start, text, index);
2593 if let Some(group) = self.parse_zsh_terminal_glob_qualifier_group(
2594 &text[index..],
2595 suffix_start,
2596 source_backed,
2597 ) && matches!(group.kind, ZshGlobQualifierKind::Classic)
2598 {
2599 self.push_zsh_pattern_segment(
2600 &mut segments,
2601 text,
2602 span.start,
2603 pattern_start,
2604 index,
2605 source_backed,
2606 );
2607 qualifiers = Some(group);
2608 index = text.len();
2609 pattern_start = index;
2610 break;
2611 }
2612 }
2613
2614 index += text[index..].chars().next()?.len_utf8();
2615 }
2616
2617 self.push_zsh_pattern_segment(
2618 &mut segments,
2619 text,
2620 span.start,
2621 pattern_start,
2622 text.len(),
2623 source_backed,
2624 );
2625
2626 segments
2627 .iter()
2628 .any(|segment| matches!(segment, ZshGlobSegment::Pattern(_)))
2629 .then_some((segments, qualifiers))
2630 }
2631
2632 fn push_zsh_pattern_segment(
2633 &mut self,
2634 segments: &mut Vec<ZshGlobSegment>,
2635 text: &str,
2636 base: Position,
2637 start: usize,
2638 end: usize,
2639 source_backed: bool,
2640 ) {
2641 if start >= end {
2642 return;
2643 }
2644
2645 let start_position = Self::text_position(base, text, start);
2646 let end_position = Self::text_position(base, text, end);
2647 let span = Span::from_positions(start_position, end_position);
2648 let pattern_word =
2649 self.decode_word_text(&text[start..end], span, span.start, source_backed);
2650 segments.push(ZshGlobSegment::Pattern(
2651 self.pattern_from_word(&pattern_word),
2652 ));
2653 }
2654
2655 fn parse_zsh_inline_glob_control(
2656 &self,
2657 text: &str,
2658 base: Position,
2659 start: usize,
2660 ) -> Option<(usize, ZshInlineGlobControl)> {
2661 let (len, control) = if text[start..].starts_with("(#i)") {
2662 (
2663 "(#i)".len(),
2664 ZshInlineGlobControl::CaseInsensitive {
2665 span: Span::from_positions(
2666 Self::text_position(base, text, start),
2667 Self::text_position(base, text, start + "(#i)".len()),
2668 ),
2669 },
2670 )
2671 } else if text[start..].starts_with("(#b)") {
2672 (
2673 "(#b)".len(),
2674 ZshInlineGlobControl::Backreferences {
2675 span: Span::from_positions(
2676 Self::text_position(base, text, start),
2677 Self::text_position(base, text, start + "(#b)".len()),
2678 ),
2679 },
2680 )
2681 } else if text[start..].starts_with("(#s)") {
2682 (
2683 "(#s)".len(),
2684 ZshInlineGlobControl::StartAnchor {
2685 span: Span::from_positions(
2686 Self::text_position(base, text, start),
2687 Self::text_position(base, text, start + "(#s)".len()),
2688 ),
2689 },
2690 )
2691 } else if text[start..].starts_with("(#e)") {
2692 (
2693 "(#e)".len(),
2694 ZshInlineGlobControl::EndAnchor {
2695 span: Span::from_positions(
2696 Self::text_position(base, text, start),
2697 Self::text_position(base, text, start + "(#e)".len()),
2698 ),
2699 },
2700 )
2701 } else {
2702 return None;
2703 };
2704
2705 Some((len, control))
2706 }
2707
2708 fn parse_zsh_terminal_glob_qualifier_group(
2709 &self,
2710 text: &str,
2711 base: Position,
2712 source_backed: bool,
2713 ) -> Option<ZshGlobQualifierGroup> {
2714 let (kind, prefix_len, inner) = if let Some(inner) = text
2715 .strip_prefix("(#q")
2716 .and_then(|rest| rest.strip_suffix(')'))
2717 {
2718 (ZshGlobQualifierKind::HashQ, "(#q".len(), inner)
2719 } else {
2720 let inner = text.strip_prefix('(')?.strip_suffix(')')?;
2721 (ZshGlobQualifierKind::Classic, "(".len(), inner)
2722 };
2723
2724 let fragments = self.parse_zsh_glob_qualifier_fragments(
2725 inner,
2726 Self::text_position(base, text, prefix_len),
2727 source_backed,
2728 )?;
2729
2730 Some(ZshGlobQualifierGroup {
2731 span: Span::from_positions(base, Self::text_position(base, text, text.len())),
2732 kind,
2733 fragments,
2734 })
2735 }
2736
2737 fn parse_zsh_glob_qualifier_fragments(
2738 &self,
2739 text: &str,
2740 base: Position,
2741 source_backed: bool,
2742 ) -> Option<Vec<ZshGlobQualifier>> {
2743 let mut fragments = Vec::new();
2744 let mut index = 0;
2745 let mut saw_non_letter_fragment = false;
2746
2747 while index < text.len() {
2748 let start = index;
2749 let ch = text[index..].chars().next()?;
2750
2751 match ch {
2752 '^' => {
2753 index += ch.len_utf8();
2754 fragments.push(ZshGlobQualifier::Negation {
2755 span: Span::from_positions(
2756 Self::text_position(base, text, start),
2757 Self::text_position(base, text, index),
2758 ),
2759 });
2760 saw_non_letter_fragment = true;
2761 }
2762 '.' | '/' | '-' | 'A'..='Z' => {
2763 index += ch.len_utf8();
2764 fragments.push(ZshGlobQualifier::Flag {
2765 name: ch,
2766 span: Span::from_positions(
2767 Self::text_position(base, text, start),
2768 Self::text_position(base, text, index),
2769 ),
2770 });
2771 saw_non_letter_fragment = true;
2772 }
2773 '[' => {
2774 index += ch.len_utf8();
2775 let number_start = index;
2776 while matches!(text[index..].chars().next(), Some('0'..='9')) {
2777 index += 1;
2778 }
2779 if number_start == index {
2780 return None;
2781 }
2782
2783 let start_text = self.zsh_glob_qualifier_source_text(
2784 text,
2785 base,
2786 number_start,
2787 index,
2788 source_backed,
2789 );
2790 let end_text = if text[index..].starts_with(',') {
2791 index += 1;
2792 let range_start = index;
2793 while matches!(text[index..].chars().next(), Some('0'..='9')) {
2794 index += 1;
2795 }
2796 if range_start == index {
2797 return None;
2798 }
2799 Some(self.zsh_glob_qualifier_source_text(
2800 text,
2801 base,
2802 range_start,
2803 index,
2804 source_backed,
2805 ))
2806 } else {
2807 None
2808 };
2809
2810 if !text[index..].starts_with(']') {
2811 return None;
2812 }
2813 index += "]".len();
2814 fragments.push(ZshGlobQualifier::NumericArgument {
2815 span: Span::from_positions(
2816 Self::text_position(base, text, start),
2817 Self::text_position(base, text, index),
2818 ),
2819 start: start_text,
2820 end: end_text,
2821 });
2822 saw_non_letter_fragment = true;
2823 }
2824 'a'..='z' => {
2825 index += ch.len_utf8();
2826 while matches!(text[index..].chars().next(), Some('a'..='z' | 'A'..='Z')) {
2827 index += 1;
2828 }
2829 if index - start <= 1 {
2830 return None;
2831 }
2832 fragments.push(ZshGlobQualifier::LetterSequence {
2833 text: self.zsh_glob_qualifier_source_text(
2834 text,
2835 base,
2836 start,
2837 index,
2838 source_backed,
2839 ),
2840 span: Span::from_positions(
2841 Self::text_position(base, text, start),
2842 Self::text_position(base, text, index),
2843 ),
2844 });
2845 }
2846 _ => return None,
2847 }
2848 }
2849
2850 (!fragments.is_empty() && saw_non_letter_fragment).then_some(fragments)
2851 }
2852
2853 fn zsh_glob_qualifier_source_text(
2854 &self,
2855 text: &str,
2856 base: Position,
2857 start: usize,
2858 end: usize,
2859 source_backed: bool,
2860 ) -> SourceText {
2861 let span = Span::from_positions(
2862 Self::text_position(base, text, start),
2863 Self::text_position(base, text, end),
2864 );
2865 if source_backed {
2866 SourceText::source(span)
2867 } else {
2868 SourceText::cooked(span, text[start..end].to_string())
2869 }
2870 }
2871
2872 fn pattern_has_glob_syntax(pattern: &Pattern) -> bool {
2873 pattern.parts.iter().any(|part| match &part.kind {
2874 PatternPart::AnyString | PatternPart::AnyChar | PatternPart::CharClass(_) => true,
2875 PatternPart::Group { .. } => true,
2876 PatternPart::Word(word) => Self::pattern_has_glob_word(word),
2877 PatternPart::Literal(_) => false,
2878 })
2879 }
2880
2881 fn pattern_has_glob_word(word: &Word) -> bool {
2882 word.parts
2883 .iter()
2884 .any(|part| !matches!(part.kind, WordPart::Literal(_)))
2885 }
2886
2887 fn simple_word_from_token(&mut self, token: &LexedToken<'_>, span: Span) -> Option<Word> {
2888 let word = token.word()?;
2889 let source_backed = !token.flags.is_synthetic();
2890
2891 if self.zsh_glob_qualifiers_enabled_at(span.start.offset)
2892 && let Some(segment) = word.single_segment()
2893 && segment.kind() == LexedWordSegmentKind::Plain
2894 && let Some(word) = self.maybe_parse_zsh_qualified_glob_word(
2895 segment.as_str(),
2896 span,
2897 segment.span().is_some() && source_backed && segment.text_is_source_backed(),
2898 )
2899 {
2900 return Some(word);
2901 }
2902 let mut parts = Vec::new();
2903
2904 for segment in word.segments() {
2905 let source_backed = segment.span().is_some() && !token.flags.is_synthetic();
2906 let content_span = Self::segment_content_span(segment, span);
2907 let raw_text = segment.as_str();
2908 let use_source_slice = source_backed
2909 && match segment.kind() {
2910 LexedWordSegmentKind::Plain => {
2911 segment.text_is_source_backed()
2912 || raw_text.contains("${") && raw_text.contains('/')
2913 || !raw_text.contains("$(")
2914 }
2915 _ => segment.text_is_source_backed(),
2916 };
2917 let text = if use_source_slice {
2918 content_span.slice(self.input)
2919 } else {
2920 raw_text
2921 };
2922 match segment.kind() {
2923 LexedWordSegmentKind::Plain
2924 | LexedWordSegmentKind::DoubleQuoted
2925 | LexedWordSegmentKind::DollarDoubleQuoted
2926 if Self::word_text_needs_parse(text) =>
2927 {
2928 return None;
2929 }
2930 LexedWordSegmentKind::Plain
2931 | LexedWordSegmentKind::SingleQuoted
2932 | LexedWordSegmentKind::DollarSingleQuoted
2933 | LexedWordSegmentKind::DoubleQuoted
2934 | LexedWordSegmentKind::DollarDoubleQuoted => {}
2935 LexedWordSegmentKind::Composite => return None,
2936 }
2937
2938 let wrapper_span = Self::segment_wrapper_span(segment, span);
2939 let part = match segment.kind() {
2940 LexedWordSegmentKind::Plain => {
2941 self.literal_part_from_text(text, content_span, source_backed)
2942 }
2943 LexedWordSegmentKind::SingleQuoted => {
2944 self.single_quoted_part_from_text(text, content_span, wrapper_span, false)
2945 }
2946 LexedWordSegmentKind::DollarSingleQuoted => {
2947 self.single_quoted_part_from_text(text, content_span, wrapper_span, true)
2948 }
2949 LexedWordSegmentKind::DoubleQuoted => self.double_quoted_literal_part_from_text(
2950 text,
2951 content_span,
2952 wrapper_span,
2953 source_backed,
2954 false,
2955 ),
2956 LexedWordSegmentKind::DollarDoubleQuoted => self
2957 .double_quoted_literal_part_from_text(
2958 text,
2959 content_span,
2960 wrapper_span,
2961 source_backed,
2962 true,
2963 ),
2964 LexedWordSegmentKind::Composite => unreachable!(),
2965 };
2966 parts.push(part);
2967 }
2968
2969 Some(self.word_with_parts(parts, span))
2970 }
2971
2972 fn segment_content_span(segment: &LexedWordSegment<'_>, fallback: Span) -> Span {
2973 segment
2974 .span()
2975 .or_else(|| segment.wrapper_span())
2976 .unwrap_or(fallback)
2977 }
2978
2979 fn segment_wrapper_span(segment: &LexedWordSegment<'_>, fallback: Span) -> Span {
2980 segment
2981 .wrapper_span()
2982 .or_else(|| segment.span())
2983 .unwrap_or(fallback)
2984 }
2985
2986 fn literal_part_from_text(&self, text: &str, span: Span, source_backed: bool) -> WordPartNode {
2987 WordPartNode::new(
2988 WordPart::Literal(self.literal_text_from_str(
2989 text,
2990 span.start,
2991 span.end,
2992 source_backed,
2993 )),
2994 span,
2995 )
2996 }
2997
2998 fn single_quoted_part_from_text(
2999 &self,
3000 text: &str,
3001 content_span: Span,
3002 wrapper_span: Span,
3003 dollar: bool,
3004 ) -> WordPartNode {
3005 WordPartNode::new(
3006 WordPart::SingleQuoted {
3007 value: self.source_text_from_str(text, content_span.start, content_span.end),
3008 dollar,
3009 },
3010 wrapper_span,
3011 )
3012 }
3013
3014 fn double_quoted_literal_part_from_text(
3015 &self,
3016 text: &str,
3017 content_span: Span,
3018 wrapper_span: Span,
3019 source_backed: bool,
3020 dollar: bool,
3021 ) -> WordPartNode {
3022 WordPartNode::new(
3023 WordPart::DoubleQuoted {
3024 parts: vec![self.literal_part_from_text(text, content_span, source_backed)],
3025 dollar,
3026 },
3027 wrapper_span,
3028 )
3029 }
3030
3031 fn decode_word_from_token(&mut self, token: &LexedToken<'_>, span: Span) -> Option<Word> {
3032 let word = token.word()?;
3033
3034 if word.single_segment().is_none()
3035 && !token.flags.is_synthetic()
3036 && let Some(source_text) = token.source_slice(self.input)
3037 {
3038 return Some(self.parse_word_with_context(source_text, span, span.start, true));
3039 }
3040
3041 if let Some(segment) = word.single_segment() {
3042 let content_span = Self::segment_content_span(segment, span);
3043 let wrapper_span = Self::segment_wrapper_span(segment, span);
3044 let source_backed = segment.span().is_some() && !token.flags.is_synthetic();
3045 let raw_text = segment.as_str();
3046 let use_source_slice = source_backed
3047 && match segment.kind() {
3048 LexedWordSegmentKind::Plain => {
3049 segment.text_is_source_backed()
3050 || raw_text.contains("${") && raw_text.contains('/')
3051 || !raw_text.contains("$(")
3052 }
3053 _ => segment.text_is_source_backed(),
3054 };
3055 let text = if use_source_slice {
3056 content_span.slice(self.input)
3057 } else {
3058 raw_text
3059 };
3060 let decode_text = if source_backed
3061 && !self.source_matches(content_span, text)
3062 && matches!(
3063 segment.kind(),
3064 LexedWordSegmentKind::DoubleQuoted | LexedWordSegmentKind::DollarDoubleQuoted
3065 )
3066 && !text.contains("$(")
3067 {
3068 content_span.slice(self.input)
3069 } else {
3070 text
3071 };
3072 let preserve_escaped_expansion_literals =
3073 source_backed && self.source_matches(content_span, decode_text);
3074
3075 return match segment.kind() {
3076 LexedWordSegmentKind::SingleQuoted => Some(self.word_with_parts(
3077 vec![self.single_quoted_part_from_text(
3078 text,
3079 content_span,
3080 wrapper_span,
3081 false,
3082 )],
3083 span,
3084 )),
3085 LexedWordSegmentKind::DollarSingleQuoted => Some(self.word_with_parts(
3086 vec![self.single_quoted_part_from_text(text, content_span, wrapper_span, true)],
3087 span,
3088 )),
3089 LexedWordSegmentKind::Plain if Self::word_text_needs_parse(text) => Some(
3090 self.decode_word_text_preserving_quotes_if_needed_with_escape_mode(
3091 text,
3092 span,
3093 content_span.start,
3094 source_backed,
3095 preserve_escaped_expansion_literals,
3096 ),
3097 ),
3098 LexedWordSegmentKind::DoubleQuoted | LexedWordSegmentKind::DollarDoubleQuoted
3099 if Self::word_text_needs_parse(text) =>
3100 {
3101 let inner = self.decode_quoted_segment_text(
3102 decode_text,
3103 content_span,
3104 content_span.start,
3105 source_backed,
3106 );
3107 Some(self.word_with_parts(
3108 vec![WordPartNode::new(
3109 WordPart::DoubleQuoted {
3110 parts: inner.parts,
3111 dollar: matches!(
3112 segment.kind(),
3113 LexedWordSegmentKind::DollarDoubleQuoted
3114 ),
3115 },
3116 wrapper_span,
3117 )],
3118 span,
3119 ))
3120 }
3121 LexedWordSegmentKind::Plain => Some(self.word_with_parts(
3122 vec![self.literal_part_from_text(text, content_span, source_backed)],
3123 span,
3124 )),
3125 LexedWordSegmentKind::DoubleQuoted => Some(self.word_with_parts(
3126 vec![self.double_quoted_literal_part_from_text(
3127 text,
3128 content_span,
3129 wrapper_span,
3130 source_backed,
3131 false,
3132 )],
3133 span,
3134 )),
3135 LexedWordSegmentKind::DollarDoubleQuoted => Some(self.word_with_parts(
3136 vec![self.double_quoted_literal_part_from_text(
3137 text,
3138 content_span,
3139 wrapper_span,
3140 source_backed,
3141 true,
3142 )],
3143 span,
3144 )),
3145 LexedWordSegmentKind::Composite => None,
3146 };
3147 }
3148
3149 let mut parts = Vec::new();
3150 let mut cursor = span.start;
3151
3152 for segment in word.segments() {
3153 let raw_text = segment.as_str();
3154 let content_span = if let Some(segment_span) = segment.span() {
3155 cursor = segment_span.end;
3156 segment_span
3157 } else {
3158 let start = cursor;
3159 let end = start.advanced_by(raw_text);
3160 cursor = end;
3161 Span::from_positions(start, end)
3162 };
3163 let wrapper_span = segment.wrapper_span().unwrap_or(content_span);
3164 let source_backed = segment.span().is_some() && !token.flags.is_synthetic();
3165 let use_source_slice = source_backed
3166 && match segment.kind() {
3167 LexedWordSegmentKind::Plain => {
3168 segment.text_is_source_backed()
3169 || raw_text.contains("${") && raw_text.contains('/')
3170 || !raw_text.contains("$(")
3171 }
3172 _ => segment.text_is_source_backed(),
3173 };
3174 let text = if use_source_slice {
3175 content_span.slice(self.input)
3176 } else {
3177 raw_text
3178 };
3179 let preserve_escaped_expansion_literals = source_backed;
3180
3181 match segment.kind() {
3182 LexedWordSegmentKind::SingleQuoted => parts.push(
3183 self.single_quoted_part_from_text(text, content_span, wrapper_span, false),
3184 ),
3185 LexedWordSegmentKind::DollarSingleQuoted => parts.push(
3186 self.single_quoted_part_from_text(text, content_span, wrapper_span, true),
3187 ),
3188 LexedWordSegmentKind::Plain => {
3189 if Self::word_text_needs_parse(text) {
3190 parts.extend(
3191 self.decode_word_text_preserving_quotes_if_needed_with_escape_mode(
3192 text,
3193 content_span,
3194 content_span.start,
3195 source_backed,
3196 preserve_escaped_expansion_literals,
3197 )
3198 .parts,
3199 );
3200 } else {
3201 parts.push(self.literal_part_from_text(text, content_span, source_backed));
3202 }
3203 }
3204 LexedWordSegmentKind::DoubleQuoted | LexedWordSegmentKind::DollarDoubleQuoted => {
3205 if Self::word_text_needs_parse(text) {
3206 let inner = self.decode_quoted_segment_text(
3207 text,
3208 content_span,
3209 content_span.start,
3210 source_backed,
3211 );
3212 parts.push(WordPartNode::new(
3213 WordPart::DoubleQuoted {
3214 parts: inner.parts,
3215 dollar: matches!(
3216 segment.kind(),
3217 LexedWordSegmentKind::DollarDoubleQuoted
3218 ),
3219 },
3220 wrapper_span,
3221 ));
3222 } else {
3223 parts.push(self.double_quoted_literal_part_from_text(
3224 text,
3225 content_span,
3226 wrapper_span,
3227 source_backed,
3228 matches!(segment.kind(), LexedWordSegmentKind::DollarDoubleQuoted),
3229 ));
3230 }
3231 }
3232 LexedWordSegmentKind::Composite => return None,
3233 }
3234 }
3235
3236 Some(self.word_with_parts(parts, span))
3237 }
3238
3239 fn current_word_ref(&mut self) -> Option<&Word> {
3240 if self.current_word_cache.is_none() {
3241 self.current_word_cache = self.current_word();
3242 }
3243
3244 self.current_word_cache.as_ref()
3245 }
3246
3247 fn current_word(&mut self) -> Option<Word> {
3248 if let Some(word) = self.current_word_cache.as_ref() {
3249 return Some(word.clone());
3250 }
3251
3252 if let Some(word) = self.current_zsh_glob_word_from_source() {
3253 self.current_word_cache = Some(word.clone());
3254 return Some(word);
3255 }
3256
3257 let span = self.current_span;
3258 if let Some(token) = self.current_token.clone()
3259 && let Some(word) = self.simple_word_from_token(&token, span)
3260 {
3261 return Some(word);
3262 }
3263
3264 let token = self.current_token.take()?;
3265 let word = self.decode_word_from_token(&token, span);
3266 self.current_token = Some(token);
3267 if let Some(word) = word.as_ref() {
3268 self.current_word_cache = Some(word.clone());
3269 }
3270 word
3271 }
3272
3273 fn take_current_word(&mut self) -> Option<Word> {
3274 if let Some(word) = self.current_word_cache.take() {
3275 return Some(word);
3276 }
3277
3278 if let Some(word) = self.current_zsh_glob_word_from_source() {
3279 return Some(word);
3280 }
3281
3282 let span = self.current_span;
3283 if let Some(token) = self.current_token.clone()
3284 && let Some(word) = self.simple_word_from_token(&token, span)
3285 {
3286 return Some(word);
3287 }
3288
3289 let token = self.current_token.take()?;
3290 let word = self.decode_word_from_token(&token, span);
3291 self.current_token = Some(token);
3292 word
3293 }
3294
3295 fn take_current_word_and_advance(&mut self) -> Option<Word> {
3296 let word = self.take_current_word()?;
3297 self.advance_past_word(&word);
3298 Some(word)
3299 }
3300
3301 fn current_zsh_glob_word_from_source(&mut self) -> Option<Word> {
3302 if !matches!(
3303 self.current_token_kind,
3304 Some(TokenKind::LeftParen | TokenKind::Word)
3305 ) {
3306 return None;
3307 }
3308
3309 let start = self.current_span.start;
3310 if !self.source_word_contains_zsh_glob_control(start) {
3311 return None;
3312 }
3313 let (text, end) = self.scan_source_word(start)?;
3314 if !text.contains("(#") {
3315 return None;
3316 }
3317 let span = Span::from_positions(start, end);
3318 if self.zsh_glob_qualifiers_enabled_at(span.start.offset)
3319 && let Some(word) = self.maybe_parse_zsh_qualified_glob_word(&text, span, true)
3320 {
3321 return Some(word);
3322 }
3323
3324 Some(self.parse_word_with_context(&text, span, start, true))
3325 }
3326
3327 fn source_word_contains_zsh_glob_control(&self, start: Position) -> bool {
3328 if start.offset >= self.input.len() {
3329 return false;
3330 }
3331
3332 let source = &self.input[start.offset..];
3333 let mut chars = source.chars().peekable();
3334 let mut cursor = start;
3335 let mut paren_depth = 0_i32;
3336 let mut brace_depth = 0_i32;
3337 let mut in_single = false;
3338 let mut in_double = false;
3339 let mut in_backtick = false;
3340 let mut escaped = false;
3341 let mut prev_char = None;
3342
3343 while let Some(&ch) = chars.peek() {
3344 if !in_single
3345 && !in_double
3346 && !in_backtick
3347 && paren_depth == 0
3348 && brace_depth == 0
3349 && matches!(ch, ' ' | '\t' | '\n' | ';' | '|' | '&' | '>' | '<' | ')')
3350 {
3351 break;
3352 }
3353
3354 let ch = Self::next_word_char_unwrap(&mut chars, &mut cursor);
3355 if prev_char == Some('(') && ch == '#' {
3356 return true;
3357 }
3358
3359 if escaped {
3360 escaped = false;
3361 prev_char = Some(ch);
3362 continue;
3363 }
3364
3365 match ch {
3366 '\\' if !in_single => escaped = true,
3367 '\'' if !in_double => in_single = !in_single,
3368 '"' if !in_single => in_double = !in_double,
3369 '`' if !in_single => in_backtick = !in_backtick,
3370 '(' if !in_single && !in_double => paren_depth += 1,
3371 ')' if !in_single && !in_double && paren_depth > 0 => paren_depth -= 1,
3372 '{' if !in_single && !in_double => brace_depth += 1,
3373 '}' if !in_single && !in_double && brace_depth > 0 => brace_depth -= 1,
3374 _ => {}
3375 }
3376
3377 prev_char = Some(ch);
3378 }
3379
3380 false
3381 }
3382
3383 fn scan_source_word(&self, start: Position) -> Option<(String, Position)> {
3384 if start.offset >= self.input.len() {
3385 return None;
3386 }
3387
3388 let source = &self.input[start.offset..];
3389 let mut chars = source.chars().peekable();
3390 let mut cursor = start;
3391 let mut text = String::new();
3392 let mut paren_depth = 0_i32;
3393 let mut brace_depth = 0_i32;
3394 let mut in_single = false;
3395 let mut in_double = false;
3396 let mut in_backtick = false;
3397 let mut escaped = false;
3398
3399 while let Some(&ch) = chars.peek() {
3400 if !in_single
3401 && !in_double
3402 && !in_backtick
3403 && paren_depth == 0
3404 && brace_depth == 0
3405 && matches!(ch, ' ' | '\t' | '\n' | ';' | '|' | '&' | '>' | '<' | ')')
3406 {
3407 break;
3408 }
3409
3410 let ch = Self::next_word_char_unwrap(&mut chars, &mut cursor);
3411 text.push(ch);
3412
3413 if escaped {
3414 escaped = false;
3415 continue;
3416 }
3417
3418 match ch {
3419 '\\' if !in_single => escaped = true,
3420 '\'' if !in_double => in_single = !in_single,
3421 '"' if !in_single => in_double = !in_double,
3422 '`' if !in_single => in_backtick = !in_backtick,
3423 '(' if !in_single && !in_double => paren_depth += 1,
3424 ')' if !in_single && !in_double && paren_depth > 0 => paren_depth -= 1,
3425 '{' if !in_single && !in_double => brace_depth += 1,
3426 '}' if !in_single && !in_double && brace_depth > 0 => brace_depth -= 1,
3427 _ => {}
3428 }
3429 }
3430
3431 (!text.is_empty()).then_some((text, cursor))
3432 }
3433
3434 fn advance_past_word(&mut self, word: &Word) {
3435 let stop_after_synthetic = self
3436 .current_token
3437 .as_ref()
3438 .is_some_and(|token| token.flags.is_synthetic());
3439 while self.current_token.is_some() && self.current_span.start.offset < word.span.end.offset
3440 {
3441 self.advance();
3442 if stop_after_synthetic
3443 && self
3444 .current_token
3445 .as_ref()
3446 .is_none_or(|token| !token.flags.is_synthetic())
3447 {
3448 break;
3449 }
3450 }
3451 }
3452
3453 fn token_source_like_word_text(&self, token: &LexedToken<'a>) -> Option<Cow<'a, str>> {
3454 token
3455 .source_slice(self.input)
3456 .map(Cow::Borrowed)
3457 .or_else(|| {
3458 (token.span.start.offset <= token.span.end.offset
3459 && token.span.end.offset <= self.input.len())
3460 .then(|| Cow::Borrowed(&self.input[token.span.start.offset..token.span.end.offset]))
3461 })
3462 .or_else(|| token.word_string().map(Cow::Owned))
3463 }
3464
3465 fn current_source_like_word_text(&self) -> Option<Cow<'a, str>> {
3466 self.current_token_kind
3467 .filter(|kind| kind.is_word_like())
3468 .and(self.current_token.as_ref())
3469 .and_then(|token| self.token_source_like_word_text(token))
3470 }
3471
3472 fn current_source_like_word_text_or_error(
3473 &self,
3474 context: &'static str,
3475 ) -> Result<Cow<'a, str>> {
3476 self.current_source_like_word_text().ok_or_else(|| {
3477 self.error(format!(
3478 "internal parser error: missing source text for {context}"
3479 ))
3480 })
3481 }
3482
3483 fn keyword_from_token(token: &LexedToken<'_>) -> Option<Keyword> {
3484 (token.kind == TokenKind::Word)
3485 .then(|| token.word_text())
3486 .flatten()
3487 .and_then(Self::classify_keyword)
3488 }
3489
3490 fn current_conditional_literal_word(&self) -> Option<Word> {
3491 match self.current_token_kind? {
3492 TokenKind::LeftBrace | TokenKind::RightBrace => Some(Word::literal_with_span(
3493 self.input[self.current_span.start.offset..self.current_span.end.offset]
3494 .to_string(),
3495 self.current_span,
3496 )),
3497 _ => None,
3498 }
3499 }
3500
3501 fn current_name_token(&self) -> Option<(Name, Span)> {
3502 self.current_token_kind
3503 .filter(|kind| kind.is_word_like())
3504 .and_then(|_| self.current_word_str())
3505 .map(|word| (Name::from(word), self.current_span))
3506 }
3507
3508 fn current_static_token_text(&self) -> Option<(String, bool)> {
3509 let token = self.current_token.as_ref()?;
3510 let raw_text = token.word_string()?;
3511 let text_had_escape_markers = raw_text.contains('\x00');
3512 let text = if text_had_escape_markers {
3513 raw_text.replace('\x00', "")
3514 } else {
3515 raw_text
3516 };
3517
3518 match token.kind {
3519 TokenKind::LiteralWord => Some((text, true)),
3520 TokenKind::QuotedWord if !Self::word_text_needs_parse(&text) => Some((text, true)),
3521 TokenKind::Word if !Self::word_text_needs_parse(&text) => Some((
3522 text,
3523 token.flags.has_cooked_text() || text_had_escape_markers,
3524 )),
3525 _ => None,
3526 }
3527 }
3528
3529 fn nested_stmt_seq_from_source(&mut self, source: &str, base: Position) -> StmtSeq {
3530 let remaining_depth = self.max_depth.saturating_sub(self.current_depth);
3531 let nested_profile = self
3532 .current_zsh_options()
3533 .cloned()
3534 .map(|options| ShellProfile::with_zsh_options(self.dialect, options))
3535 .unwrap_or_else(|| self.shell_profile.clone());
3536 let inner_parser =
3537 Parser::with_limits_and_profile(source, remaining_depth, self.fuel, nested_profile);
3538 let mut output = inner_parser.parse();
3539 if output.is_ok() {
3540 Self::materialize_stmt_seq_source_backing(&mut output.file.body, source);
3541 Self::rebase_file(&mut output.file, base);
3542 output.file.body
3543 } else {
3544 StmtSeq {
3545 leading_comments: Vec::new(),
3546 stmts: Vec::new(),
3547 trailing_comments: Vec::new(),
3548 span: Span::from_positions(base, base),
3549 }
3550 }
3551 }
3552
3553 fn nested_stmt_seq_from_current_input(&mut self, start: Position, end: Position) -> StmtSeq {
3554 if start.offset > end.offset || end.offset > self.input.len() {
3555 return StmtSeq {
3556 leading_comments: Vec::new(),
3557 stmts: Vec::new(),
3558 trailing_comments: Vec::new(),
3559 span: Span::from_positions(start, start),
3560 };
3561 }
3562 let source = &self.input[start.offset..end.offset];
3563 self.nested_stmt_seq_from_source(source, start)
3564 }
3565
3566 fn merge_optional_span(primary: Span, other: Span) -> Span {
3567 if other == Span::new() {
3568 primary
3569 } else {
3570 primary.merge(other)
3571 }
3572 }
3573
3574 fn redirect_span(operator_span: Span, target: &Word) -> Span {
3575 Self::merge_optional_span(operator_span, target.span)
3576 }
3577
3578 fn optional_span(start: Position, end: Position) -> Option<Span> {
3579 (start.offset < end.offset).then(|| Span::from_positions(start, end))
3580 }
3581
3582 fn split_nested_arithmetic_close(&mut self, context: &'static str) -> Result<Span> {
3583 let right_paren_start = self.current_span.start.advanced_by(")");
3584 self.advance();
3585
3586 if self.at(TokenKind::RightParen) {
3587 let right_paren_span = Span::from_positions(right_paren_start, self.current_span.end);
3588 self.advance();
3589 Ok(right_paren_span)
3590 } else {
3591 Err(Error::parse(format!(
3592 "expected ')' after '))' in {context}"
3593 )))
3594 }
3595 }
3596
3597 fn split_double_semicolon(span: Span) -> (Span, Span) {
3598 let middle = span.start.advanced_by(";");
3599 (
3600 Span::from_positions(span.start, middle),
3601 Span::from_positions(middle, span.end),
3602 )
3603 }
3604
3605 fn split_double_left_paren(span: Span) -> (Span, Span) {
3606 let middle = span.start.advanced_by("(");
3607 (
3608 Span::from_positions(span.start, middle),
3609 Span::from_positions(middle, span.end),
3610 )
3611 }
3612
3613 fn split_double_right_paren(span: Span) -> (Span, Span) {
3614 let middle = span.start.advanced_by(")");
3615 (
3616 Span::from_positions(span.start, middle),
3617 Span::from_positions(middle, span.end),
3618 )
3619 }
3620
3621 fn record_arithmetic_for_separator(
3622 semicolon_span: Span,
3623 segment_start: &mut Position,
3624 init_span: &mut Option<Span>,
3625 first_semicolon_span: &mut Option<Span>,
3626 condition_span: &mut Option<Span>,
3627 second_semicolon_span: &mut Option<Span>,
3628 ) -> Result<()> {
3629 if first_semicolon_span.is_none() {
3630 *init_span = Self::optional_span(*segment_start, semicolon_span.start);
3631 *first_semicolon_span = Some(semicolon_span);
3632 *segment_start = semicolon_span.end;
3633 return Ok(());
3634 }
3635
3636 if second_semicolon_span.is_none() {
3637 *condition_span = Self::optional_span(*segment_start, semicolon_span.start);
3638 *second_semicolon_span = Some(semicolon_span);
3639 *segment_start = semicolon_span.end;
3640 return Ok(());
3641 }
3642
3643 Err(Error::parse(
3644 "unexpected ';' in arithmetic for header".to_string(),
3645 ))
3646 }
3647
3648 fn rebase_file(file: &mut File, base: Position) {
3649 file.span = file.span.rebased(base);
3650 Self::rebase_stmt_seq(&mut file.body, base);
3651 }
3652
3653 fn rebase_comments(comments: &mut [Comment], base: Position) {
3654 let base_offset = TextSize::new(base.offset as u32);
3655 for comment in comments {
3656 comment.range = comment.range.offset_by(base_offset);
3657 }
3658 }
3659
3660 fn rebase_stmt_seq(sequence: &mut StmtSeq, base: Position) {
3661 sequence.span = sequence.span.rebased(base);
3662 Self::rebase_comments(&mut sequence.leading_comments, base);
3663 for stmt in &mut sequence.stmts {
3664 Self::rebase_stmt(stmt, base);
3665 }
3666 Self::rebase_comments(&mut sequence.trailing_comments, base);
3667 }
3668
3669 fn rebase_stmt(stmt: &mut Stmt, base: Position) {
3670 stmt.span = stmt.span.rebased(base);
3671 Self::rebase_comments(&mut stmt.leading_comments, base);
3672 stmt.terminator_span = stmt.terminator_span.map(|span| span.rebased(base));
3673 if let Some(comment) = &mut stmt.inline_comment {
3674 let base_offset = TextSize::new(base.offset as u32);
3675 comment.range = comment.range.offset_by(base_offset);
3676 }
3677 Self::rebase_redirects(&mut stmt.redirects, base);
3678 Self::rebase_ast_command(&mut stmt.command, base);
3679 }
3680
3681 fn rebase_ast_command(command: &mut AstCommand, base: Position) {
3682 match command {
3683 AstCommand::Simple(simple) => {
3684 simple.span = simple.span.rebased(base);
3685 Self::rebase_word(&mut simple.name, base);
3686 Self::rebase_words(&mut simple.args, base);
3687 Self::rebase_assignments(&mut simple.assignments, base);
3688 }
3689 AstCommand::Builtin(builtin) => match builtin {
3690 AstBuiltinCommand::Break(command) => {
3691 command.span = command.span.rebased(base);
3692 if let Some(depth) = &mut command.depth {
3693 Self::rebase_word(depth, base);
3694 }
3695 Self::rebase_words(&mut command.extra_args, base);
3696 Self::rebase_assignments(&mut command.assignments, base);
3697 }
3698 AstBuiltinCommand::Continue(command) => {
3699 command.span = command.span.rebased(base);
3700 if let Some(depth) = &mut command.depth {
3701 Self::rebase_word(depth, base);
3702 }
3703 Self::rebase_words(&mut command.extra_args, base);
3704 Self::rebase_assignments(&mut command.assignments, base);
3705 }
3706 AstBuiltinCommand::Return(command) => {
3707 command.span = command.span.rebased(base);
3708 if let Some(code) = &mut command.code {
3709 Self::rebase_word(code, base);
3710 }
3711 Self::rebase_words(&mut command.extra_args, base);
3712 Self::rebase_assignments(&mut command.assignments, base);
3713 }
3714 AstBuiltinCommand::Exit(command) => {
3715 command.span = command.span.rebased(base);
3716 if let Some(code) = &mut command.code {
3717 Self::rebase_word(code, base);
3718 }
3719 Self::rebase_words(&mut command.extra_args, base);
3720 Self::rebase_assignments(&mut command.assignments, base);
3721 }
3722 },
3723 AstCommand::Decl(decl) => {
3724 decl.span = decl.span.rebased(base);
3725 decl.variant_span = decl.variant_span.rebased(base);
3726 Self::rebase_assignments(&mut decl.assignments, base);
3727 for operand in &mut decl.operands {
3728 match operand {
3729 DeclOperand::Flag(word) | DeclOperand::Dynamic(word) => {
3730 Self::rebase_word(word, base);
3731 }
3732 DeclOperand::Name(name) => Self::rebase_var_ref(name, base),
3733 DeclOperand::Assignment(assignment) => {
3734 Self::rebase_assignments(std::slice::from_mut(assignment), base);
3735 }
3736 }
3737 }
3738 }
3739 AstCommand::Binary(binary) => {
3740 binary.span = binary.span.rebased(base);
3741 binary.op_span = binary.op_span.rebased(base);
3742 Self::rebase_stmt(binary.left.as_mut(), base);
3743 Self::rebase_stmt(binary.right.as_mut(), base);
3744 }
3745 AstCommand::Compound(compound) => Self::rebase_compound(compound, base),
3746 AstCommand::Function(function) => {
3747 function.span = function.span.rebased(base);
3748 if let Some(span) = &mut function.header.function_keyword_span {
3749 *span = span.rebased(base);
3750 }
3751 if let Some(span) = &mut function.header.trailing_parens_span {
3752 *span = span.rebased(base);
3753 }
3754 for entry in &mut function.header.entries {
3755 Self::rebase_word(&mut entry.word, base);
3756 }
3757 Self::rebase_stmt(function.body.as_mut(), base);
3758 }
3759 AstCommand::AnonymousFunction(function) => {
3760 function.span = function.span.rebased(base);
3761 function.surface = match function.surface {
3762 AnonymousFunctionSurface::FunctionKeyword {
3763 function_keyword_span,
3764 } => AnonymousFunctionSurface::FunctionKeyword {
3765 function_keyword_span: function_keyword_span.rebased(base),
3766 },
3767 AnonymousFunctionSurface::Parens { parens_span } => {
3768 AnonymousFunctionSurface::Parens {
3769 parens_span: parens_span.rebased(base),
3770 }
3771 }
3772 };
3773 Self::rebase_stmt(function.body.as_mut(), base);
3774 Self::rebase_words(&mut function.args, base);
3775 }
3776 }
3777 }
3778
3779 fn rebase_subscript(subscript: &mut Subscript, base: Position) {
3780 subscript.text.rebased(base);
3781 if let Some(raw) = &mut subscript.raw {
3782 raw.rebased(base);
3783 }
3784 if let Some(word) = &mut subscript.word_ast {
3785 Self::rebase_word(word, base);
3786 }
3787 if let Some(expr) = &mut subscript.arithmetic_ast {
3788 Self::rebase_arithmetic_expr(expr, base);
3789 }
3790 }
3791
3792 fn rebase_var_ref(reference: &mut VarRef, base: Position) {
3793 reference.span = reference.span.rebased(base);
3794 reference.name_span = reference.name_span.rebased(base);
3795 if let Some(subscript) = &mut reference.subscript {
3796 Self::rebase_subscript(subscript, base);
3797 }
3798 }
3799
3800 fn rebase_array_expr(array: &mut ArrayExpr, base: Position) {
3801 array.span = array.span.rebased(base);
3802 for element in &mut array.elements {
3803 match element {
3804 ArrayElem::Sequential(word) => Self::rebase_word(word, base),
3805 ArrayElem::Keyed { key, value } | ArrayElem::KeyedAppend { key, value } => {
3806 Self::rebase_subscript(key, base);
3807 Self::rebase_word(value, base);
3808 }
3809 }
3810 }
3811 }
3812
3813 fn rebase_compound(compound: &mut CompoundCommand, base: Position) {
3814 match compound {
3815 CompoundCommand::If(command) => {
3816 command.span = command.span.rebased(base);
3817 command.syntax = match command.syntax {
3818 IfSyntax::ThenFi { then_span, fi_span } => IfSyntax::ThenFi {
3819 then_span: then_span.rebased(base),
3820 fi_span: fi_span.rebased(base),
3821 },
3822 IfSyntax::Brace {
3823 left_brace_span,
3824 right_brace_span,
3825 } => IfSyntax::Brace {
3826 left_brace_span: left_brace_span.rebased(base),
3827 right_brace_span: right_brace_span.rebased(base),
3828 },
3829 };
3830 Self::rebase_stmt_seq(&mut command.condition, base);
3831 Self::rebase_stmt_seq(&mut command.then_branch, base);
3832 for (condition, body) in &mut command.elif_branches {
3833 Self::rebase_stmt_seq(condition, base);
3834 Self::rebase_stmt_seq(body, base);
3835 }
3836 if let Some(else_branch) = &mut command.else_branch {
3837 Self::rebase_stmt_seq(else_branch, base);
3838 }
3839 }
3840 CompoundCommand::For(command) => {
3841 command.span = command.span.rebased(base);
3842 for target in &mut command.targets {
3843 target.span = target.span.rebased(base);
3844 }
3845 if let Some(words) = &mut command.words {
3846 Self::rebase_words(words, base);
3847 }
3848 command.syntax = match command.syntax {
3849 ForSyntax::InDoDone {
3850 in_span,
3851 do_span,
3852 done_span,
3853 } => ForSyntax::InDoDone {
3854 in_span: in_span.map(|span| span.rebased(base)),
3855 do_span: do_span.rebased(base),
3856 done_span: done_span.rebased(base),
3857 },
3858 ForSyntax::InDirect { in_span } => ForSyntax::InDirect {
3859 in_span: in_span.map(|span| span.rebased(base)),
3860 },
3861 ForSyntax::InBrace {
3862 in_span,
3863 left_brace_span,
3864 right_brace_span,
3865 } => ForSyntax::InBrace {
3866 in_span: in_span.map(|span| span.rebased(base)),
3867 left_brace_span: left_brace_span.rebased(base),
3868 right_brace_span: right_brace_span.rebased(base),
3869 },
3870 ForSyntax::ParenDoDone {
3871 left_paren_span,
3872 right_paren_span,
3873 do_span,
3874 done_span,
3875 } => ForSyntax::ParenDoDone {
3876 left_paren_span: left_paren_span.rebased(base),
3877 right_paren_span: right_paren_span.rebased(base),
3878 do_span: do_span.rebased(base),
3879 done_span: done_span.rebased(base),
3880 },
3881 ForSyntax::ParenDirect {
3882 left_paren_span,
3883 right_paren_span,
3884 } => ForSyntax::ParenDirect {
3885 left_paren_span: left_paren_span.rebased(base),
3886 right_paren_span: right_paren_span.rebased(base),
3887 },
3888 ForSyntax::ParenBrace {
3889 left_paren_span,
3890 right_paren_span,
3891 left_brace_span,
3892 right_brace_span,
3893 } => ForSyntax::ParenBrace {
3894 left_paren_span: left_paren_span.rebased(base),
3895 right_paren_span: right_paren_span.rebased(base),
3896 left_brace_span: left_brace_span.rebased(base),
3897 right_brace_span: right_brace_span.rebased(base),
3898 },
3899 };
3900 Self::rebase_stmt_seq(&mut command.body, base);
3901 }
3902 CompoundCommand::Repeat(command) => {
3903 command.span = command.span.rebased(base);
3904 Self::rebase_word(&mut command.count, base);
3905 command.syntax = match command.syntax {
3906 RepeatSyntax::DoDone { do_span, done_span } => RepeatSyntax::DoDone {
3907 do_span: do_span.rebased(base),
3908 done_span: done_span.rebased(base),
3909 },
3910 RepeatSyntax::Direct => RepeatSyntax::Direct,
3911 RepeatSyntax::Brace {
3912 left_brace_span,
3913 right_brace_span,
3914 } => RepeatSyntax::Brace {
3915 left_brace_span: left_brace_span.rebased(base),
3916 right_brace_span: right_brace_span.rebased(base),
3917 },
3918 };
3919 Self::rebase_stmt_seq(&mut command.body, base);
3920 }
3921 CompoundCommand::Foreach(command) => {
3922 command.span = command.span.rebased(base);
3923 command.variable_span = command.variable_span.rebased(base);
3924 Self::rebase_words(&mut command.words, base);
3925 command.syntax = match command.syntax {
3926 ForeachSyntax::ParenBrace {
3927 left_paren_span,
3928 right_paren_span,
3929 left_brace_span,
3930 right_brace_span,
3931 } => ForeachSyntax::ParenBrace {
3932 left_paren_span: left_paren_span.rebased(base),
3933 right_paren_span: right_paren_span.rebased(base),
3934 left_brace_span: left_brace_span.rebased(base),
3935 right_brace_span: right_brace_span.rebased(base),
3936 },
3937 ForeachSyntax::InDoDone {
3938 in_span,
3939 do_span,
3940 done_span,
3941 } => ForeachSyntax::InDoDone {
3942 in_span: in_span.rebased(base),
3943 do_span: do_span.rebased(base),
3944 done_span: done_span.rebased(base),
3945 },
3946 };
3947 Self::rebase_stmt_seq(&mut command.body, base);
3948 }
3949 CompoundCommand::ArithmeticFor(command) => {
3950 command.span = command.span.rebased(base);
3951 command.left_paren_span = command.left_paren_span.rebased(base);
3952 command.init_span = command.init_span.map(|span| span.rebased(base));
3953 if let Some(expr) = &mut command.init_ast {
3954 Self::rebase_arithmetic_expr(expr, base);
3955 }
3956 command.first_semicolon_span = command.first_semicolon_span.rebased(base);
3957 command.condition_span = command.condition_span.map(|span| span.rebased(base));
3958 if let Some(expr) = &mut command.condition_ast {
3959 Self::rebase_arithmetic_expr(expr, base);
3960 }
3961 command.second_semicolon_span = command.second_semicolon_span.rebased(base);
3962 command.step_span = command.step_span.map(|span| span.rebased(base));
3963 if let Some(expr) = &mut command.step_ast {
3964 Self::rebase_arithmetic_expr(expr, base);
3965 }
3966 command.right_paren_span = command.right_paren_span.rebased(base);
3967 Self::rebase_stmt_seq(&mut command.body, base);
3968 }
3969 CompoundCommand::While(command) => {
3970 command.span = command.span.rebased(base);
3971 Self::rebase_stmt_seq(&mut command.condition, base);
3972 Self::rebase_stmt_seq(&mut command.body, base);
3973 }
3974 CompoundCommand::Until(command) => {
3975 command.span = command.span.rebased(base);
3976 Self::rebase_stmt_seq(&mut command.condition, base);
3977 Self::rebase_stmt_seq(&mut command.body, base);
3978 }
3979 CompoundCommand::Case(command) => {
3980 command.span = command.span.rebased(base);
3981 Self::rebase_word(&mut command.word, base);
3982 for case in &mut command.cases {
3983 Self::rebase_patterns(&mut case.patterns, base);
3984 Self::rebase_stmt_seq(&mut case.body, base);
3985 }
3986 }
3987 CompoundCommand::Select(command) => {
3988 command.span = command.span.rebased(base);
3989 command.variable_span = command.variable_span.rebased(base);
3990 Self::rebase_words(&mut command.words, base);
3991 Self::rebase_stmt_seq(&mut command.body, base);
3992 }
3993 CompoundCommand::Subshell(commands) | CompoundCommand::BraceGroup(commands) => {
3994 Self::rebase_stmt_seq(commands, base);
3995 }
3996 CompoundCommand::Arithmetic(command) => {
3997 command.span = command.span.rebased(base);
3998 command.left_paren_span = command.left_paren_span.rebased(base);
3999 command.expr_span = command.expr_span.map(|span| span.rebased(base));
4000 if let Some(expr) = &mut command.expr_ast {
4001 Self::rebase_arithmetic_expr(expr, base);
4002 }
4003 command.right_paren_span = command.right_paren_span.rebased(base);
4004 }
4005 CompoundCommand::Time(command) => {
4006 command.span = command.span.rebased(base);
4007 if let Some(inner) = &mut command.command {
4008 Self::rebase_stmt(inner.as_mut(), base);
4009 }
4010 }
4011 CompoundCommand::Conditional(command) => {
4012 command.span = command.span.rebased(base);
4013 command.left_bracket_span = command.left_bracket_span.rebased(base);
4014 command.right_bracket_span = command.right_bracket_span.rebased(base);
4015 Self::rebase_conditional_expr(&mut command.expression, base);
4016 }
4017 CompoundCommand::Coproc(command) => {
4018 command.span = command.span.rebased(base);
4019 command.name_span = command.name_span.map(|span| span.rebased(base));
4020 Self::rebase_stmt(command.body.as_mut(), base);
4021 }
4022 CompoundCommand::Always(command) => {
4023 command.span = command.span.rebased(base);
4024 Self::rebase_stmt_seq(&mut command.body, base);
4025 Self::rebase_stmt_seq(&mut command.always_body, base);
4026 }
4027 }
4028 }
4029
4030 fn materialize_stmt_seq_source_backing(sequence: &mut StmtSeq, source: &str) {
4031 for stmt in &mut sequence.stmts {
4032 Self::materialize_stmt_source_backing(stmt, source);
4033 }
4034 }
4035
4036 fn materialize_stmt_source_backing(stmt: &mut Stmt, source: &str) {
4037 Self::materialize_ast_command_source_backing(&mut stmt.command, source);
4038 }
4039
4040 fn materialize_ast_command_source_backing(command: &mut AstCommand, source: &str) {
4041 match command {
4042 AstCommand::Simple(simple) => {
4043 Self::materialize_word_source_backing(&mut simple.name, source);
4044 }
4045 AstCommand::Builtin(_) | AstCommand::Decl(_) => {}
4046 AstCommand::Binary(binary) => {
4047 Self::materialize_stmt_source_backing(binary.left.as_mut(), source);
4048 Self::materialize_stmt_source_backing(binary.right.as_mut(), source);
4049 }
4050 AstCommand::Compound(compound) => {
4051 Self::materialize_compound_source_backing(compound, source);
4052 }
4053 AstCommand::Function(function) => {
4054 Self::materialize_stmt_source_backing(function.body.as_mut(), source);
4055 }
4056 AstCommand::AnonymousFunction(function) => {
4057 Self::materialize_stmt_source_backing(function.body.as_mut(), source);
4058 }
4059 }
4060 }
4061
4062 fn materialize_compound_source_backing(compound: &mut CompoundCommand, source: &str) {
4063 match compound {
4064 CompoundCommand::If(command) => {
4065 Self::materialize_stmt_seq_source_backing(&mut command.condition, source);
4066 Self::materialize_stmt_seq_source_backing(&mut command.then_branch, source);
4067 for (condition, body) in &mut command.elif_branches {
4068 Self::materialize_stmt_seq_source_backing(condition, source);
4069 Self::materialize_stmt_seq_source_backing(body, source);
4070 }
4071 if let Some(else_branch) = &mut command.else_branch {
4072 Self::materialize_stmt_seq_source_backing(else_branch, source);
4073 }
4074 }
4075 CompoundCommand::For(command) => {
4076 Self::materialize_stmt_seq_source_backing(&mut command.body, source)
4077 }
4078 CompoundCommand::Repeat(command) => {
4079 Self::materialize_stmt_seq_source_backing(&mut command.body, source)
4080 }
4081 CompoundCommand::Foreach(command) => {
4082 Self::materialize_stmt_seq_source_backing(&mut command.body, source)
4083 }
4084 CompoundCommand::ArithmeticFor(command) => {
4085 Self::materialize_stmt_seq_source_backing(&mut command.body, source)
4086 }
4087 CompoundCommand::While(command) => {
4088 Self::materialize_stmt_seq_source_backing(&mut command.condition, source);
4089 Self::materialize_stmt_seq_source_backing(&mut command.body, source);
4090 }
4091 CompoundCommand::Until(command) => {
4092 Self::materialize_stmt_seq_source_backing(&mut command.condition, source);
4093 Self::materialize_stmt_seq_source_backing(&mut command.body, source);
4094 }
4095 CompoundCommand::Case(command) => {
4096 for case in &mut command.cases {
4097 Self::materialize_stmt_seq_source_backing(&mut case.body, source);
4098 }
4099 }
4100 CompoundCommand::Select(command) => {
4101 Self::materialize_stmt_seq_source_backing(&mut command.body, source)
4102 }
4103 CompoundCommand::Subshell(commands) | CompoundCommand::BraceGroup(commands) => {
4104 Self::materialize_stmt_seq_source_backing(commands, source);
4105 }
4106 CompoundCommand::Arithmetic(_) => {}
4107 CompoundCommand::Time(command) => {
4108 if let Some(inner) = &mut command.command {
4109 Self::materialize_stmt_source_backing(inner.as_mut(), source);
4110 }
4111 }
4112 CompoundCommand::Conditional(_) => {}
4113 CompoundCommand::Coproc(command) => {
4114 Self::materialize_stmt_source_backing(command.body.as_mut(), source);
4115 }
4116 CompoundCommand::Always(command) => {
4117 Self::materialize_stmt_seq_source_backing(&mut command.body, source);
4118 Self::materialize_stmt_seq_source_backing(&mut command.always_body, source);
4119 }
4120 }
4121 }
4122
4123 fn rebase_words(words: &mut [Word], base: Position) {
4124 for word in words {
4125 Self::rebase_word(word, base);
4126 }
4127 }
4128
4129 fn rebase_patterns(patterns: &mut [Pattern], base: Position) {
4130 for pattern in patterns {
4131 Self::rebase_pattern(pattern, base);
4132 }
4133 }
4134
4135 fn materialize_literal_text_source_backing(text: &mut LiteralText, span: Span, source: &str) {
4136 match text {
4137 LiteralText::Source => {
4138 *text = LiteralText::owned(span.slice(source).to_string());
4139 }
4140 LiteralText::CookedSource(cooked) => {
4141 *text = LiteralText::owned(cooked.to_string());
4142 }
4143 LiteralText::Owned(_) => {}
4144 }
4145 }
4146
4147 fn materialize_source_text_source_backing(text: &mut SourceText, source: &str) {
4148 if text.is_source_backed() {
4149 let span = text.span();
4150 let cooked = text.slice(source).to_string();
4151 *text = SourceText::cooked(span, cooked);
4152 }
4153 }
4154
4155 fn materialize_word_source_backing(word: &mut Word, source: &str) {
4156 for part in &mut word.parts {
4157 Self::materialize_word_part_source_backing(part, source);
4158 }
4159 }
4160
4161 fn materialize_pattern_source_backing(pattern: &mut Pattern, source: &str) {
4162 for part in &mut pattern.parts {
4163 Self::materialize_pattern_part_source_backing(part, source);
4164 }
4165 }
4166
4167 fn materialize_pattern_part_source_backing(part: &mut PatternPartNode, source: &str) {
4168 match &mut part.kind {
4169 PatternPart::Literal(text) => {
4170 Self::materialize_literal_text_source_backing(text, part.span, source);
4171 }
4172 PatternPart::CharClass(text) => {
4173 Self::materialize_source_text_source_backing(text, source);
4174 }
4175 PatternPart::Group { patterns, .. } => {
4176 for pattern in patterns {
4177 Self::materialize_pattern_source_backing(pattern, source);
4178 }
4179 }
4180 PatternPart::Word(word) => Self::materialize_word_source_backing(word, source),
4181 PatternPart::AnyString | PatternPart::AnyChar => {}
4182 }
4183 }
4184
4185 fn materialize_word_part_source_backing(part: &mut WordPartNode, source: &str) {
4186 match &mut part.kind {
4187 WordPart::Literal(text) => {
4188 Self::materialize_literal_text_source_backing(text, part.span, source);
4189 }
4190 WordPart::ZshQualifiedGlob(glob) => {
4191 Self::materialize_zsh_qualified_glob_source_backing(glob, source);
4192 }
4193 WordPart::SingleQuoted { value, .. } => {
4194 Self::materialize_source_text_source_backing(value, source);
4195 }
4196 WordPart::DoubleQuoted { parts, .. } => {
4197 for part in parts {
4198 Self::materialize_word_part_source_backing(part, source);
4199 }
4200 }
4201 WordPart::Parameter(parameter) => {
4202 Self::materialize_source_text_source_backing(&mut parameter.raw_body, source);
4203 Self::materialize_parameter_expansion_syntax_source_backing(
4204 &mut parameter.syntax,
4205 source,
4206 );
4207 }
4208 WordPart::ParameterExpansion {
4209 reference,
4210 operator,
4211 operand,
4212 operand_word_ast,
4213 ..
4214 } => {
4215 Self::materialize_var_ref_source_backing(reference, source);
4216 Self::materialize_parameter_operator_source_backing(operator, source);
4217 if let Some(operand) = operand {
4218 Self::materialize_source_text_source_backing(operand, source);
4219 }
4220 if let Some(word_ast) = operand_word_ast {
4221 Self::materialize_word_source_backing(word_ast, source);
4222 }
4223 }
4224 WordPart::ArrayAccess(reference)
4225 | WordPart::Length(reference)
4226 | WordPart::ArrayLength(reference)
4227 | WordPart::ArrayIndices(reference)
4228 | WordPart::Transformation { reference, .. } => {
4229 Self::materialize_var_ref_source_backing(reference, source);
4230 }
4231 WordPart::Substring {
4232 reference,
4233 offset,
4234 offset_ast,
4235 offset_word_ast,
4236 length,
4237 length_ast,
4238 length_word_ast,
4239 ..
4240 }
4241 | WordPart::ArraySlice {
4242 reference,
4243 offset,
4244 offset_ast,
4245 offset_word_ast,
4246 length,
4247 length_ast,
4248 length_word_ast,
4249 ..
4250 } => {
4251 Self::materialize_var_ref_source_backing(reference, source);
4252 Self::materialize_source_text_source_backing(offset, source);
4253 Self::materialize_word_source_backing(offset_word_ast, source);
4254 if let Some(expr) = offset_ast {
4255 Self::materialize_arithmetic_expr_source_backing(expr, source);
4256 }
4257 if let Some(length) = length {
4258 Self::materialize_source_text_source_backing(length, source);
4259 }
4260 if let Some(word_ast) = length_word_ast {
4261 Self::materialize_word_source_backing(word_ast, source);
4262 }
4263 if let Some(expr) = length_ast {
4264 Self::materialize_arithmetic_expr_source_backing(expr, source);
4265 }
4266 }
4267 WordPart::IndirectExpansion {
4268 reference,
4269 operator,
4270 operand,
4271 operand_word_ast,
4272 ..
4273 } => {
4274 Self::materialize_var_ref_source_backing(reference, source);
4275 if let Some(operator) = operator {
4276 Self::materialize_parameter_operator_source_backing(operator, source);
4277 }
4278 if let Some(operand) = operand {
4279 Self::materialize_source_text_source_backing(operand, source);
4280 }
4281 if let Some(word_ast) = operand_word_ast {
4282 Self::materialize_word_source_backing(word_ast, source);
4283 }
4284 }
4285 WordPart::ArithmeticExpansion {
4286 expression,
4287 expression_ast,
4288 expression_word_ast,
4289 ..
4290 } => {
4291 Self::materialize_source_text_source_backing(expression, source);
4292 Self::materialize_word_source_backing(expression_word_ast, source);
4293 if let Some(expr) = expression_ast {
4294 Self::materialize_arithmetic_expr_source_backing(expr, source);
4295 }
4296 }
4297 WordPart::CommandSubstitution { .. }
4298 | WordPart::ProcessSubstitution { .. }
4299 | WordPart::Variable(_)
4300 | WordPart::PrefixMatch { .. } => {}
4301 }
4302 }
4303
4304 fn materialize_var_ref_source_backing(reference: &mut VarRef, source: &str) {
4305 if let Some(subscript) = &mut reference.subscript {
4306 Self::materialize_subscript_source_backing(subscript, source);
4307 }
4308 }
4309
4310 fn materialize_subscript_source_backing(subscript: &mut Subscript, source: &str) {
4311 Self::materialize_source_text_source_backing(&mut subscript.text, source);
4312 if let Some(raw) = &mut subscript.raw {
4313 Self::materialize_source_text_source_backing(raw, source);
4314 }
4315 if let Some(word_ast) = &mut subscript.word_ast {
4316 Self::materialize_word_source_backing(word_ast, source);
4317 }
4318 if let Some(expr) = &mut subscript.arithmetic_ast {
4319 Self::materialize_arithmetic_expr_source_backing(expr, source);
4320 }
4321 }
4322
4323 fn materialize_zsh_qualified_glob_source_backing(glob: &mut ZshQualifiedGlob, source: &str) {
4324 for segment in &mut glob.segments {
4325 match segment {
4326 ZshGlobSegment::Pattern(pattern) => {
4327 Self::materialize_pattern_source_backing(pattern, source);
4328 }
4329 ZshGlobSegment::InlineControl(_) => {}
4330 }
4331 }
4332 if let Some(qualifiers) = &mut glob.qualifiers {
4333 for fragment in &mut qualifiers.fragments {
4334 match fragment {
4335 ZshGlobQualifier::LetterSequence { text, .. } => {
4336 Self::materialize_source_text_source_backing(text, source);
4337 }
4338 ZshGlobQualifier::NumericArgument { start, end, .. } => {
4339 Self::materialize_source_text_source_backing(start, source);
4340 if let Some(end) = end {
4341 Self::materialize_source_text_source_backing(end, source);
4342 }
4343 }
4344 ZshGlobQualifier::Negation { .. } | ZshGlobQualifier::Flag { .. } => {}
4345 }
4346 }
4347 }
4348 }
4349
4350 fn materialize_parameter_expansion_syntax_source_backing(
4351 syntax: &mut ParameterExpansionSyntax,
4352 source: &str,
4353 ) {
4354 match syntax {
4355 ParameterExpansionSyntax::Bourne(syntax) => match syntax {
4356 BourneParameterExpansion::Access { reference }
4357 | BourneParameterExpansion::Length { reference }
4358 | BourneParameterExpansion::Indices { reference }
4359 | BourneParameterExpansion::Transformation { reference, .. } => {
4360 Self::materialize_var_ref_source_backing(reference, source);
4361 }
4362 BourneParameterExpansion::Indirect {
4363 reference,
4364 operator,
4365 operand,
4366 operand_word_ast,
4367 ..
4368 } => {
4369 Self::materialize_var_ref_source_backing(reference, source);
4370 if let Some(operator) = operator {
4371 Self::materialize_parameter_operator_source_backing(operator, source);
4372 }
4373 if let Some(operand) = operand {
4374 Self::materialize_source_text_source_backing(operand, source);
4375 }
4376 if let Some(word_ast) = operand_word_ast {
4377 Self::materialize_word_source_backing(word_ast, source);
4378 }
4379 }
4380 BourneParameterExpansion::PrefixMatch { .. } => {}
4381 BourneParameterExpansion::Slice {
4382 reference,
4383 offset,
4384 offset_ast,
4385 offset_word_ast,
4386 length,
4387 length_ast,
4388 length_word_ast,
4389 } => {
4390 Self::materialize_var_ref_source_backing(reference, source);
4391 Self::materialize_source_text_source_backing(offset, source);
4392 Self::materialize_word_source_backing(offset_word_ast, source);
4393 if let Some(expr) = offset_ast {
4394 Self::materialize_arithmetic_expr_source_backing(expr, source);
4395 }
4396 if let Some(length) = length {
4397 Self::materialize_source_text_source_backing(length, source);
4398 }
4399 if let Some(word_ast) = length_word_ast {
4400 Self::materialize_word_source_backing(word_ast, source);
4401 }
4402 if let Some(expr) = length_ast {
4403 Self::materialize_arithmetic_expr_source_backing(expr, source);
4404 }
4405 }
4406 BourneParameterExpansion::Operation {
4407 reference,
4408 operator,
4409 operand,
4410 operand_word_ast,
4411 ..
4412 } => {
4413 Self::materialize_var_ref_source_backing(reference, source);
4414 Self::materialize_parameter_operator_source_backing(operator, source);
4415 if let Some(operand) = operand {
4416 Self::materialize_source_text_source_backing(operand, source);
4417 }
4418 if let Some(word_ast) = operand_word_ast {
4419 Self::materialize_word_source_backing(word_ast, source);
4420 }
4421 }
4422 },
4423 ParameterExpansionSyntax::Zsh(syntax) => {
4424 match &mut syntax.target {
4425 ZshExpansionTarget::Reference(reference) => {
4426 Self::materialize_var_ref_source_backing(reference, source);
4427 }
4428 ZshExpansionTarget::Word(word) => {
4429 Self::materialize_word_source_backing(word, source);
4430 }
4431 ZshExpansionTarget::Nested(parameter) => {
4432 Self::materialize_source_text_source_backing(
4433 &mut parameter.raw_body,
4434 source,
4435 );
4436 Self::materialize_parameter_expansion_syntax_source_backing(
4437 &mut parameter.syntax,
4438 source,
4439 );
4440 }
4441 ZshExpansionTarget::Empty => {}
4442 }
4443 for modifier in &mut syntax.modifiers {
4444 if let Some(argument) = &mut modifier.argument {
4445 Self::materialize_source_text_source_backing(argument, source);
4446 }
4447 if let Some(argument_word_ast) = &mut modifier.argument_word_ast {
4448 Self::materialize_word_source_backing(argument_word_ast, source);
4449 }
4450 }
4451 if let Some(operation) = &mut syntax.operation {
4452 match operation {
4453 ZshExpansionOperation::PatternOperation {
4454 operand,
4455 operand_word_ast,
4456 ..
4457 }
4458 | ZshExpansionOperation::Defaulting {
4459 operand,
4460 operand_word_ast,
4461 ..
4462 }
4463 | ZshExpansionOperation::TrimOperation {
4464 operand,
4465 operand_word_ast,
4466 ..
4467 } => {
4468 Self::materialize_source_text_source_backing(operand, source);
4469 Self::materialize_word_source_backing(operand_word_ast, source);
4470 }
4471 ZshExpansionOperation::Unknown { text, word_ast } => {
4472 Self::materialize_source_text_source_backing(text, source);
4473 Self::materialize_word_source_backing(word_ast, source);
4474 }
4475 ZshExpansionOperation::ReplacementOperation {
4476 pattern,
4477 pattern_word_ast,
4478 replacement,
4479 replacement_word_ast,
4480 ..
4481 } => {
4482 Self::materialize_source_text_source_backing(pattern, source);
4483 Self::materialize_word_source_backing(pattern_word_ast, source);
4484 if let Some(replacement) = replacement {
4485 Self::materialize_source_text_source_backing(replacement, source);
4486 }
4487 if let Some(replacement_word_ast) = replacement_word_ast {
4488 Self::materialize_word_source_backing(replacement_word_ast, source);
4489 }
4490 }
4491 ZshExpansionOperation::Slice {
4492 offset,
4493 offset_word_ast,
4494 length,
4495 length_word_ast,
4496 } => {
4497 Self::materialize_source_text_source_backing(offset, source);
4498 Self::materialize_word_source_backing(offset_word_ast, source);
4499 if let Some(length) = length {
4500 Self::materialize_source_text_source_backing(length, source);
4501 }
4502 if let Some(length_word_ast) = length_word_ast {
4503 Self::materialize_word_source_backing(length_word_ast, source);
4504 }
4505 }
4506 }
4507 }
4508 }
4509 }
4510 }
4511
4512 fn materialize_parameter_operator_source_backing(operator: &mut ParameterOp, source: &str) {
4513 match operator {
4514 ParameterOp::RemovePrefixShort { pattern }
4515 | ParameterOp::RemovePrefixLong { pattern }
4516 | ParameterOp::RemoveSuffixShort { pattern }
4517 | ParameterOp::RemoveSuffixLong { pattern } => {
4518 Self::materialize_pattern_source_backing(pattern, source);
4519 }
4520 ParameterOp::ReplaceFirst {
4521 pattern,
4522 replacement,
4523 replacement_word_ast,
4524 }
4525 | ParameterOp::ReplaceAll {
4526 pattern,
4527 replacement,
4528 replacement_word_ast,
4529 } => {
4530 Self::materialize_pattern_source_backing(pattern, source);
4531 Self::materialize_source_text_source_backing(replacement, source);
4532 Self::materialize_word_source_backing(replacement_word_ast, source);
4533 }
4534 ParameterOp::UseDefault
4535 | ParameterOp::AssignDefault
4536 | ParameterOp::UseReplacement
4537 | ParameterOp::Error
4538 | ParameterOp::UpperFirst
4539 | ParameterOp::UpperAll
4540 | ParameterOp::LowerFirst
4541 | ParameterOp::LowerAll => {}
4542 }
4543 }
4544
4545 fn materialize_arithmetic_expr_source_backing(expr: &mut ArithmeticExprNode, source: &str) {
4546 match &mut expr.kind {
4547 ArithmeticExpr::Number(text) => {
4548 Self::materialize_source_text_source_backing(text, source);
4549 }
4550 ArithmeticExpr::Variable(_) => {}
4551 ArithmeticExpr::Indexed { index, .. } => {
4552 Self::materialize_arithmetic_expr_source_backing(index, source);
4553 }
4554 ArithmeticExpr::ShellWord(word) => {
4555 Self::materialize_word_source_backing(word, source);
4556 }
4557 ArithmeticExpr::Parenthesized { expression } => {
4558 Self::materialize_arithmetic_expr_source_backing(expression, source);
4559 }
4560 ArithmeticExpr::Unary { expr, .. } | ArithmeticExpr::Postfix { expr, .. } => {
4561 Self::materialize_arithmetic_expr_source_backing(expr, source);
4562 }
4563 ArithmeticExpr::Binary { left, right, .. } => {
4564 Self::materialize_arithmetic_expr_source_backing(left, source);
4565 Self::materialize_arithmetic_expr_source_backing(right, source);
4566 }
4567 ArithmeticExpr::Conditional {
4568 condition,
4569 then_expr,
4570 else_expr,
4571 } => {
4572 Self::materialize_arithmetic_expr_source_backing(condition, source);
4573 Self::materialize_arithmetic_expr_source_backing(then_expr, source);
4574 Self::materialize_arithmetic_expr_source_backing(else_expr, source);
4575 }
4576 ArithmeticExpr::Assignment { target, value, .. } => {
4577 Self::materialize_arithmetic_lvalue_source_backing(target, source);
4578 Self::materialize_arithmetic_expr_source_backing(value, source);
4579 }
4580 }
4581 }
4582
4583 fn materialize_arithmetic_lvalue_source_backing(target: &mut ArithmeticLvalue, source: &str) {
4584 match target {
4585 ArithmeticLvalue::Variable(_) => {}
4586 ArithmeticLvalue::Indexed { index, .. } => {
4587 Self::materialize_arithmetic_expr_source_backing(index, source);
4588 }
4589 }
4590 }
4591
4592 fn rebase_word(word: &mut Word, base: Position) {
4593 word.span = word.span.rebased(base);
4594 for brace in &mut word.brace_syntax {
4595 brace.span = brace.span.rebased(base);
4596 }
4597 Self::rebase_word_parts(&mut word.parts, base);
4598 }
4599
4600 fn rebase_heredoc_body(body: &mut HeredocBody, base: Position) {
4601 body.span = body.span.rebased(base);
4602 for part in &mut body.parts {
4603 Self::rebase_heredoc_body_part(part, base);
4604 }
4605 }
4606
4607 fn rebase_pattern(pattern: &mut Pattern, base: Position) {
4608 pattern.span = pattern.span.rebased(base);
4609 Self::rebase_pattern_parts(&mut pattern.parts, base);
4610 }
4611
4612 fn rebase_word_parts(parts: &mut [WordPartNode], base: Position) {
4613 for part in parts {
4614 Self::rebase_word_part(part, base);
4615 }
4616 }
4617
4618 fn rebase_pattern_parts(parts: &mut [PatternPartNode], base: Position) {
4619 for part in parts {
4620 part.span = part.span.rebased(base);
4621 match &mut part.kind {
4622 PatternPart::CharClass(text) => text.rebased(base),
4623 PatternPart::Group { patterns, .. } => Self::rebase_patterns(patterns, base),
4624 PatternPart::Word(word) => Self::rebase_word(word, base),
4625 PatternPart::Literal(_) | PatternPart::AnyString | PatternPart::AnyChar => {}
4626 }
4627 }
4628 }
4629
4630 fn rebase_heredoc_body_part(part: &mut HeredocBodyPartNode, base: Position) {
4631 part.span = part.span.rebased(base);
4632 match &mut part.kind {
4633 HeredocBodyPart::Literal(_) | HeredocBodyPart::Variable(_) => {}
4634 HeredocBodyPart::CommandSubstitution { body, .. } => Self::rebase_stmt_seq(body, base),
4635 HeredocBodyPart::ArithmeticExpansion {
4636 expression,
4637 expression_ast,
4638 expression_word_ast,
4639 ..
4640 } => {
4641 expression.rebased(base);
4642 Self::rebase_word(expression_word_ast, base);
4643 if let Some(expr) = expression_ast {
4644 Self::rebase_arithmetic_expr(expr, base);
4645 }
4646 }
4647 HeredocBodyPart::Parameter(parameter) => {
4648 parameter.span = parameter.span.rebased(base);
4649 parameter.raw_body.rebased(base);
4650 Self::rebase_parameter_expansion_syntax(&mut parameter.syntax, base);
4651 }
4652 }
4653 }
4654
4655 fn rebase_word_part(part: &mut WordPartNode, base: Position) {
4656 part.span = part.span.rebased(base);
4657 match &mut part.kind {
4658 WordPart::ZshQualifiedGlob(glob) => Self::rebase_zsh_qualified_glob(glob, base),
4659 WordPart::SingleQuoted { value, .. } => value.rebased(base),
4660 WordPart::DoubleQuoted { parts, .. } => Self::rebase_word_parts(parts, base),
4661 WordPart::Parameter(parameter) => {
4662 parameter.span = parameter.span.rebased(base);
4663 parameter.raw_body.rebased(base);
4664 Self::rebase_parameter_expansion_syntax(&mut parameter.syntax, base);
4665 }
4666 WordPart::ParameterExpansion {
4667 reference,
4668 operator,
4669 operand,
4670 operand_word_ast,
4671 ..
4672 } => {
4673 Self::rebase_var_ref(reference, base);
4674 match operator {
4675 ParameterOp::RemovePrefixShort { pattern }
4676 | ParameterOp::RemovePrefixLong { pattern }
4677 | ParameterOp::RemoveSuffixShort { pattern }
4678 | ParameterOp::RemoveSuffixLong { pattern } => {
4679 Self::rebase_pattern(pattern, base);
4680 }
4681 ParameterOp::ReplaceFirst {
4682 pattern,
4683 replacement,
4684 ..
4685 }
4686 | ParameterOp::ReplaceAll {
4687 pattern,
4688 replacement,
4689 ..
4690 } => {
4691 Self::rebase_pattern(pattern, base);
4692 replacement.rebased(base);
4693 }
4694 ParameterOp::UseDefault
4695 | ParameterOp::AssignDefault
4696 | ParameterOp::UseReplacement
4697 | ParameterOp::Error
4698 | ParameterOp::UpperFirst
4699 | ParameterOp::UpperAll
4700 | ParameterOp::LowerFirst
4701 | ParameterOp::LowerAll => {}
4702 }
4703 if let Some(operand) = operand {
4704 operand.rebased(base);
4705 }
4706 if let Some(word_ast) = operand_word_ast {
4707 Self::rebase_word(word_ast, base);
4708 }
4709 }
4710 WordPart::ArrayAccess(reference)
4711 | WordPart::Length(reference)
4712 | WordPart::ArrayLength(reference)
4713 | WordPart::ArrayIndices(reference)
4714 | WordPart::Transformation { reference, .. } => Self::rebase_var_ref(reference, base),
4715 WordPart::Substring {
4716 reference,
4717 offset,
4718 offset_ast,
4719 offset_word_ast,
4720 length,
4721 length_ast,
4722 length_word_ast,
4723 ..
4724 }
4725 | WordPart::ArraySlice {
4726 reference,
4727 offset,
4728 offset_ast,
4729 offset_word_ast,
4730 length,
4731 length_ast,
4732 length_word_ast,
4733 ..
4734 } => {
4735 Self::rebase_var_ref(reference, base);
4736 offset.rebased(base);
4737 Self::rebase_word(offset_word_ast, base);
4738 if let Some(expr) = offset_ast {
4739 Self::rebase_arithmetic_expr(expr, base);
4740 }
4741 if let Some(length) = length {
4742 length.rebased(base);
4743 }
4744 if let Some(word_ast) = length_word_ast {
4745 Self::rebase_word(word_ast, base);
4746 }
4747 if let Some(expr) = length_ast {
4748 Self::rebase_arithmetic_expr(expr, base);
4749 }
4750 }
4751 WordPart::IndirectExpansion {
4752 reference,
4753 operator,
4754 operand,
4755 operand_word_ast,
4756 ..
4757 } => {
4758 Self::rebase_var_ref(reference, base);
4759 if let Some(operator) = operator {
4760 Self::rebase_parameter_operator(operator, base);
4761 }
4762 if let Some(operand) = operand {
4763 operand.rebased(base);
4764 }
4765 if let Some(word_ast) = operand_word_ast {
4766 Self::rebase_word(word_ast, base);
4767 }
4768 }
4769 WordPart::ArithmeticExpansion {
4770 expression,
4771 expression_ast,
4772 expression_word_ast,
4773 ..
4774 } => {
4775 expression.rebased(base);
4776 Self::rebase_word(expression_word_ast, base);
4777 if let Some(expr) = expression_ast {
4778 Self::rebase_arithmetic_expr(expr, base);
4779 }
4780 }
4781 WordPart::CommandSubstitution { body, .. }
4782 | WordPart::ProcessSubstitution { body, .. } => Self::rebase_stmt_seq(body, base),
4783 WordPart::Literal(_) | WordPart::Variable(_) | WordPart::PrefixMatch { .. } => {}
4784 }
4785 }
4786
4787 fn rebase_zsh_qualified_glob(glob: &mut ZshQualifiedGlob, base: Position) {
4788 glob.span = glob.span.rebased(base);
4789 for segment in &mut glob.segments {
4790 Self::rebase_zsh_glob_segment(segment, base);
4791 }
4792 if let Some(qualifiers) = &mut glob.qualifiers {
4793 Self::rebase_zsh_glob_qualifier_group(qualifiers, base);
4794 }
4795 }
4796
4797 fn rebase_zsh_glob_segment(segment: &mut ZshGlobSegment, base: Position) {
4798 match segment {
4799 ZshGlobSegment::Pattern(pattern) => Self::rebase_pattern(pattern, base),
4800 ZshGlobSegment::InlineControl(control) => {
4801 Self::rebase_zsh_inline_glob_control(control, base)
4802 }
4803 }
4804 }
4805
4806 fn rebase_zsh_inline_glob_control(control: &mut ZshInlineGlobControl, base: Position) {
4807 match control {
4808 ZshInlineGlobControl::CaseInsensitive { span }
4809 | ZshInlineGlobControl::Backreferences { span }
4810 | ZshInlineGlobControl::StartAnchor { span }
4811 | ZshInlineGlobControl::EndAnchor { span } => {
4812 *span = span.rebased(base);
4813 }
4814 }
4815 }
4816
4817 fn rebase_zsh_glob_qualifier_group(group: &mut ZshGlobQualifierGroup, base: Position) {
4818 group.span = group.span.rebased(base);
4819 for fragment in &mut group.fragments {
4820 match fragment {
4821 ZshGlobQualifier::Negation { span } | ZshGlobQualifier::Flag { span, .. } => {
4822 *span = span.rebased(base);
4823 }
4824 ZshGlobQualifier::LetterSequence { text, span } => {
4825 *span = span.rebased(base);
4826 text.rebased(base);
4827 }
4828 ZshGlobQualifier::NumericArgument { span, start, end } => {
4829 *span = span.rebased(base);
4830 start.rebased(base);
4831 if let Some(end) = end {
4832 end.rebased(base);
4833 }
4834 }
4835 }
4836 }
4837 }
4838
4839 fn rebase_parameter_expansion_syntax(syntax: &mut ParameterExpansionSyntax, base: Position) {
4840 match syntax {
4841 ParameterExpansionSyntax::Bourne(syntax) => match syntax {
4842 BourneParameterExpansion::Access { reference }
4843 | BourneParameterExpansion::Length { reference }
4844 | BourneParameterExpansion::Indices { reference }
4845 | BourneParameterExpansion::Transformation { reference, .. } => {
4846 Self::rebase_var_ref(reference, base);
4847 }
4848 BourneParameterExpansion::Indirect {
4849 reference,
4850 operand,
4851 operator,
4852 operand_word_ast,
4853 ..
4854 } => {
4855 Self::rebase_var_ref(reference, base);
4856 if let Some(operator) = operator {
4857 Self::rebase_parameter_operator(operator, base);
4858 }
4859 if let Some(operand) = operand {
4860 operand.rebased(base);
4861 }
4862 if let Some(word_ast) = operand_word_ast {
4863 Self::rebase_word(word_ast, base);
4864 }
4865 }
4866 BourneParameterExpansion::PrefixMatch { .. } => {}
4867 BourneParameterExpansion::Slice {
4868 reference,
4869 offset,
4870 offset_ast,
4871 offset_word_ast,
4872 length,
4873 length_ast,
4874 length_word_ast,
4875 } => {
4876 Self::rebase_var_ref(reference, base);
4877 offset.rebased(base);
4878 Self::rebase_word(offset_word_ast, base);
4879 if let Some(expr) = offset_ast {
4880 Self::rebase_arithmetic_expr(expr, base);
4881 }
4882 if let Some(length) = length {
4883 length.rebased(base);
4884 }
4885 if let Some(word_ast) = length_word_ast {
4886 Self::rebase_word(word_ast, base);
4887 }
4888 if let Some(expr) = length_ast {
4889 Self::rebase_arithmetic_expr(expr, base);
4890 }
4891 }
4892 BourneParameterExpansion::Operation {
4893 reference,
4894 operator,
4895 operand,
4896 operand_word_ast,
4897 ..
4898 } => {
4899 Self::rebase_var_ref(reference, base);
4900 Self::rebase_parameter_operator(operator, base);
4901 if let Some(operand) = operand {
4902 operand.rebased(base);
4903 }
4904 if let Some(word_ast) = operand_word_ast {
4905 Self::rebase_word(word_ast, base);
4906 }
4907 }
4908 },
4909 ParameterExpansionSyntax::Zsh(syntax) => {
4910 match &mut syntax.target {
4911 ZshExpansionTarget::Reference(reference) => {
4912 Self::rebase_var_ref(reference, base)
4913 }
4914 ZshExpansionTarget::Word(word) => Self::rebase_word(word, base),
4915 ZshExpansionTarget::Nested(parameter) => {
4916 parameter.span = parameter.span.rebased(base);
4917 parameter.raw_body.rebased(base);
4918 Self::rebase_parameter_expansion_syntax(&mut parameter.syntax, base);
4919 }
4920 ZshExpansionTarget::Empty => {}
4921 }
4922 for modifier in &mut syntax.modifiers {
4923 modifier.span = modifier.span.rebased(base);
4924 if let Some(argument) = &mut modifier.argument {
4925 argument.rebased(base);
4926 }
4927 if let Some(argument_word_ast) = &mut modifier.argument_word_ast {
4928 Self::rebase_word(argument_word_ast, base);
4929 }
4930 }
4931 if let Some(length_prefix) = &mut syntax.length_prefix {
4932 *length_prefix = length_prefix.rebased(base);
4933 }
4934 if let Some(operation) = &mut syntax.operation {
4935 match operation {
4936 ZshExpansionOperation::PatternOperation {
4937 operand,
4938 operand_word_ast,
4939 ..
4940 }
4941 | ZshExpansionOperation::Defaulting {
4942 operand,
4943 operand_word_ast,
4944 ..
4945 }
4946 | ZshExpansionOperation::TrimOperation {
4947 operand,
4948 operand_word_ast,
4949 ..
4950 } => {
4951 operand.rebased(base);
4952 Self::rebase_word(operand_word_ast, base);
4953 }
4954 ZshExpansionOperation::Unknown { text, word_ast } => {
4955 text.rebased(base);
4956 Self::rebase_word(word_ast, base);
4957 }
4958 ZshExpansionOperation::ReplacementOperation {
4959 pattern,
4960 pattern_word_ast,
4961 replacement,
4962 replacement_word_ast,
4963 ..
4964 } => {
4965 pattern.rebased(base);
4966 Self::rebase_word(pattern_word_ast, base);
4967 if let Some(replacement) = replacement {
4968 replacement.rebased(base);
4969 }
4970 if let Some(replacement_word_ast) = replacement_word_ast {
4971 Self::rebase_word(replacement_word_ast, base);
4972 }
4973 }
4974 ZshExpansionOperation::Slice {
4975 offset,
4976 offset_word_ast,
4977 length,
4978 length_word_ast,
4979 } => {
4980 offset.rebased(base);
4981 Self::rebase_word(offset_word_ast, base);
4982 if let Some(length) = length {
4983 length.rebased(base);
4984 }
4985 if let Some(length_word_ast) = length_word_ast {
4986 Self::rebase_word(length_word_ast, base);
4987 }
4988 }
4989 }
4990 }
4991 }
4992 }
4993 }
4994
4995 fn rebase_parameter_operator(operator: &mut ParameterOp, base: Position) {
4996 match operator {
4997 ParameterOp::RemovePrefixShort { pattern }
4998 | ParameterOp::RemovePrefixLong { pattern }
4999 | ParameterOp::RemoveSuffixShort { pattern }
5000 | ParameterOp::RemoveSuffixLong { pattern } => {
5001 Self::rebase_pattern(pattern, base);
5002 }
5003 ParameterOp::ReplaceFirst {
5004 pattern,
5005 replacement,
5006 replacement_word_ast,
5007 }
5008 | ParameterOp::ReplaceAll {
5009 pattern,
5010 replacement,
5011 replacement_word_ast,
5012 } => {
5013 Self::rebase_pattern(pattern, base);
5014 replacement.rebased(base);
5015 Self::rebase_word(replacement_word_ast, base);
5016 }
5017 ParameterOp::UseDefault
5018 | ParameterOp::AssignDefault
5019 | ParameterOp::UseReplacement
5020 | ParameterOp::Error
5021 | ParameterOp::UpperFirst
5022 | ParameterOp::UpperAll
5023 | ParameterOp::LowerFirst
5024 | ParameterOp::LowerAll => {}
5025 }
5026 }
5027
5028 fn rebase_conditional_expr(expr: &mut ConditionalExpr, base: Position) {
5029 match expr {
5030 ConditionalExpr::Binary(binary) => {
5031 binary.op_span = binary.op_span.rebased(base);
5032 Self::rebase_conditional_expr(&mut binary.left, base);
5033 Self::rebase_conditional_expr(&mut binary.right, base);
5034 }
5035 ConditionalExpr::Unary(unary) => {
5036 unary.op_span = unary.op_span.rebased(base);
5037 Self::rebase_conditional_expr(&mut unary.expr, base);
5038 }
5039 ConditionalExpr::Parenthesized(paren) => {
5040 paren.left_paren_span = paren.left_paren_span.rebased(base);
5041 paren.right_paren_span = paren.right_paren_span.rebased(base);
5042 Self::rebase_conditional_expr(&mut paren.expr, base);
5043 }
5044 ConditionalExpr::Word(word) | ConditionalExpr::Regex(word) => {
5045 Self::rebase_word(word, base);
5046 }
5047 ConditionalExpr::Pattern(pattern) => Self::rebase_pattern(pattern, base),
5048 ConditionalExpr::VarRef(var_ref) => Self::rebase_var_ref(var_ref, base),
5049 }
5050 }
5051
5052 fn rebase_arithmetic_expr(expr: &mut ArithmeticExprNode, base: Position) {
5053 expr.span = expr.span.rebased(base);
5054 match &mut expr.kind {
5055 ArithmeticExpr::Number(text) => text.rebased(base),
5056 ArithmeticExpr::Variable(_) => {}
5057 ArithmeticExpr::Indexed { index, .. } => Self::rebase_arithmetic_expr(index, base),
5058 ArithmeticExpr::ShellWord(word) => Self::rebase_word(word, base),
5059 ArithmeticExpr::Parenthesized { expression } => {
5060 Self::rebase_arithmetic_expr(expression, base)
5061 }
5062 ArithmeticExpr::Unary { expr, .. } | ArithmeticExpr::Postfix { expr, .. } => {
5063 Self::rebase_arithmetic_expr(expr, base)
5064 }
5065 ArithmeticExpr::Binary { left, right, .. } => {
5066 Self::rebase_arithmetic_expr(left, base);
5067 Self::rebase_arithmetic_expr(right, base);
5068 }
5069 ArithmeticExpr::Conditional {
5070 condition,
5071 then_expr,
5072 else_expr,
5073 } => {
5074 Self::rebase_arithmetic_expr(condition, base);
5075 Self::rebase_arithmetic_expr(then_expr, base);
5076 Self::rebase_arithmetic_expr(else_expr, base);
5077 }
5078 ArithmeticExpr::Assignment { target, value, .. } => {
5079 Self::rebase_arithmetic_lvalue(target, base);
5080 Self::rebase_arithmetic_expr(value, base);
5081 }
5082 }
5083 }
5084
5085 fn rebase_arithmetic_lvalue(target: &mut ArithmeticLvalue, base: Position) {
5086 match target {
5087 ArithmeticLvalue::Variable(_) => {}
5088 ArithmeticLvalue::Indexed { index, .. } => Self::rebase_arithmetic_expr(index, base),
5089 }
5090 }
5091
5092 fn push_word_part(
5093 parts: &mut Vec<WordPartNode>,
5094 part: WordPart,
5095 start: Position,
5096 end: Position,
5097 ) {
5098 parts.push(WordPartNode::new(part, Span::from_positions(start, end)));
5099 }
5100
5101 fn flush_literal_part(
5102 &self,
5103 parts: &mut Vec<WordPartNode>,
5104 current: &mut String,
5105 current_start: Position,
5106 end: Position,
5107 source_backed: bool,
5108 ) {
5109 if !current.is_empty() {
5110 Self::push_word_part(
5111 parts,
5112 WordPart::Literal(self.literal_text(
5113 std::mem::take(current),
5114 current_start,
5115 end,
5116 source_backed,
5117 )),
5118 current_start,
5119 end,
5120 );
5121 }
5122 }
5123
5124 fn literal_text(
5125 &self,
5126 text: String,
5127 start: Position,
5128 end: Position,
5129 source_backed: bool,
5130 ) -> LiteralText {
5131 let span = Span::from_positions(start, end);
5132 if self.source_matches(span, &text) {
5133 LiteralText::source()
5134 } else if source_backed {
5135 LiteralText::cooked_source(text)
5136 } else {
5137 LiteralText::owned(text)
5138 }
5139 }
5140
5141 fn literal_text_from_str(
5142 &self,
5143 text: &str,
5144 start: Position,
5145 end: Position,
5146 source_backed: bool,
5147 ) -> LiteralText {
5148 self.literal_text_impl(text, None, start, end, source_backed)
5149 }
5150
5151 fn literal_text_impl(
5152 &self,
5153 text: &str,
5154 owned: Option<String>,
5155 start: Position,
5156 end: Position,
5157 source_backed: bool,
5158 ) -> LiteralText {
5159 let span = Span::from_positions(start, end);
5160 if self.source_matches(span, text) {
5161 LiteralText::source()
5162 } else if source_backed {
5163 LiteralText::cooked_source(owned.unwrap_or_else(|| text.to_owned()))
5164 } else {
5165 LiteralText::owned(owned.unwrap_or_else(|| text.to_owned()))
5166 }
5167 }
5168
5169 fn source_text(&self, text: String, start: Position, end: Position) -> SourceText {
5170 let span = Span::from_positions(start, end);
5171 if self.source_matches(span, &text) {
5172 SourceText::source(span)
5173 } else {
5174 SourceText::cooked(span, text)
5175 }
5176 }
5177
5178 fn source_text_from_str(&self, text: &str, start: Position, end: Position) -> SourceText {
5179 self.source_text_impl(text, None, start, end)
5180 }
5181
5182 fn source_text_impl(
5183 &self,
5184 text: &str,
5185 owned: Option<String>,
5186 start: Position,
5187 end: Position,
5188 ) -> SourceText {
5189 let span = Span::from_positions(start, end);
5190 if self.source_matches(span, text) {
5191 SourceText::source(span)
5192 } else {
5193 SourceText::cooked(span, owned.unwrap_or_else(|| text.to_owned()))
5194 }
5195 }
5196
5197 fn empty_source_text(&self, pos: Position) -> SourceText {
5198 SourceText::source(Span::from_positions(pos, pos))
5199 }
5200
5201 fn input_prefix_ends_with(&self, end_offset: usize, ch: char) -> bool {
5202 self.input
5203 .get(..end_offset)
5204 .is_some_and(|prefix| prefix.ends_with(ch))
5205 }
5206
5207 fn input_span_ends_with(&self, start: Position, end: Position, ch: char) -> bool {
5208 self.input
5209 .get(start.offset..end.offset)
5210 .is_some_and(|slice| slice.ends_with(ch))
5211 }
5212
5213 fn input_suffix_starts_with(&self, start_offset: usize, ch: char) -> bool {
5214 self.input
5215 .get(start_offset..)
5216 .is_some_and(|suffix| suffix.starts_with(ch))
5217 }
5218
5219 fn subscript_source_text(&self, raw: &str, span: Span) -> (SourceText, Option<SourceText>) {
5220 if raw.len() >= 2
5221 && ((raw.starts_with('"') && raw.ends_with('"'))
5222 || (raw.starts_with('\'') && raw.ends_with('\'')))
5223 {
5224 let raw_text = raw.to_string();
5225 let raw = if self.source_matches(span, raw) {
5226 SourceText::source(span)
5227 } else {
5228 SourceText::cooked(span, raw_text.clone())
5229 };
5230 let cooked = raw_text[1..raw_text.len() - 1].to_string();
5231 return (self.source_text(cooked, span.start, span.end), Some(raw));
5232 }
5233
5234 let text = if self.source_matches(span, raw) {
5235 SourceText::source(span)
5236 } else {
5237 SourceText::cooked(span, raw.to_string())
5238 };
5239 (text, None)
5240 }
5241
5242 fn subscript_from_source_text(
5243 &self,
5244 text: SourceText,
5245 raw: Option<SourceText>,
5246 interpretation: SubscriptInterpretation,
5247 ) -> Subscript {
5248 let kind = match text.slice(self.input).trim() {
5249 "@" => SubscriptKind::Selector(SubscriptSelector::At),
5250 "*" => SubscriptKind::Selector(SubscriptSelector::Star),
5251 _ => SubscriptKind::Ordinary,
5252 };
5253 let word_ast = if matches!(kind, SubscriptKind::Ordinary) {
5254 Some(self.parse_source_text_as_word(raw.as_ref().unwrap_or(&text)))
5255 } else {
5256 None
5257 };
5258 let arithmetic_ast = if matches!(kind, SubscriptKind::Ordinary) {
5259 self.simple_subscript_arithmetic_ast(&text)
5260 .or_else(|| self.maybe_parse_source_text_as_arithmetic(&text))
5261 } else {
5262 None
5263 };
5264 Subscript {
5265 text,
5266 raw,
5267 kind,
5268 interpretation,
5269 word_ast,
5270 arithmetic_ast,
5271 }
5272 }
5273
5274 fn simple_subscript_arithmetic_ast(&self, text: &SourceText) -> Option<ArithmeticExprNode> {
5275 if !text.is_source_backed() {
5276 return None;
5277 }
5278
5279 let raw = text.slice(self.input);
5280 if raw.is_empty() || raw.trim() != raw {
5281 return None;
5282 }
5283
5284 let span = text.span();
5285 if raw.bytes().all(|byte| byte.is_ascii_digit()) {
5286 return Some(ArithmeticExprNode::new(
5287 ArithmeticExpr::Number(SourceText::source(span)),
5288 span,
5289 ));
5290 }
5291
5292 if Self::is_valid_identifier(raw) {
5293 return Some(ArithmeticExprNode::new(
5294 ArithmeticExpr::Variable(Name::from(raw)),
5295 span,
5296 ));
5297 }
5298
5299 None
5300 }
5301
5302 fn subscript_from_text(
5303 &self,
5304 raw: &str,
5305 span: Span,
5306 interpretation: SubscriptInterpretation,
5307 ) -> Subscript {
5308 let (text, raw) = self.subscript_source_text(raw, span);
5309 self.subscript_from_source_text(text, raw, interpretation)
5310 }
5311
5312 fn var_ref(
5313 &self,
5314 name: impl Into<Name>,
5315 name_span: Span,
5316 subscript: Option<Subscript>,
5317 span: Span,
5318 ) -> VarRef {
5319 VarRef {
5320 name: name.into(),
5321 name_span,
5322 subscript,
5323 span,
5324 }
5325 }
5326
5327 fn parameter_var_ref(
5328 &self,
5329 part_start: Position,
5330 prefix: &str,
5331 name: &str,
5332 subscript: Option<Subscript>,
5333 part_end: Position,
5334 ) -> VarRef {
5335 let name_start = part_start.advanced_by(prefix);
5336 let name_span = Span::from_positions(name_start, name_start.advanced_by(name));
5337 self.var_ref(
5338 Name::from(name),
5339 name_span,
5340 subscript,
5341 Span::from_positions(part_start, part_end),
5342 )
5343 }
5344
5345 fn parameter_word_part_from_legacy(
5346 &self,
5347 part: WordPart,
5348 part_start: Position,
5349 part_end: Position,
5350 source_backed: bool,
5351 ) -> WordPart {
5352 let span = Span::from_positions(part_start, part_end);
5353 let raw_body = self.parameter_raw_body_from_legacy(&part, span, source_backed);
5354 let raw_body_text = raw_body.slice(self.input).to_string();
5355
5356 let syntax = match part {
5357 WordPart::ParameterExpansion {
5358 reference,
5359 operator,
5360 operand,
5361 operand_word_ast,
5362 colon_variant,
5363 } => Some(BourneParameterExpansion::Operation {
5364 reference,
5365 operator: self.enrich_parameter_operator(operator),
5366 operand,
5367 operand_word_ast,
5368 colon_variant,
5369 }),
5370 WordPart::Length(reference) | WordPart::ArrayLength(reference) => {
5371 Some(BourneParameterExpansion::Length { reference })
5372 }
5373 WordPart::ArrayAccess(reference) => {
5374 Some(BourneParameterExpansion::Access { reference })
5375 }
5376 WordPart::ArrayIndices(reference) => {
5377 Some(BourneParameterExpansion::Indices { reference })
5378 }
5379 WordPart::Substring {
5380 reference,
5381 offset,
5382 offset_ast,
5383 offset_word_ast,
5384 length,
5385 length_ast,
5386 length_word_ast,
5387 }
5388 | WordPart::ArraySlice {
5389 reference,
5390 offset,
5391 offset_ast,
5392 offset_word_ast,
5393 length,
5394 length_ast,
5395 length_word_ast,
5396 } => Some(BourneParameterExpansion::Slice {
5397 reference,
5398 offset,
5399 offset_ast,
5400 offset_word_ast,
5401 length,
5402 length_ast,
5403 length_word_ast,
5404 }),
5405 WordPart::IndirectExpansion {
5406 reference,
5407 operator,
5408 operand,
5409 operand_word_ast,
5410 colon_variant,
5411 } => Some(BourneParameterExpansion::Indirect {
5412 reference,
5413 operator: operator.map(|operator| self.enrich_parameter_operator(operator)),
5414 operand,
5415 operand_word_ast,
5416 colon_variant,
5417 }),
5418 WordPart::PrefixMatch { prefix, kind } => {
5419 Some(BourneParameterExpansion::PrefixMatch { prefix, kind })
5420 }
5421 WordPart::Transformation {
5422 reference,
5423 operator,
5424 } => Some(BourneParameterExpansion::Transformation {
5425 reference,
5426 operator,
5427 }),
5428 WordPart::Variable(name) if raw_body_text == name.as_str() => {
5429 Some(BourneParameterExpansion::Access {
5430 reference: self.parameter_var_ref(
5431 part_start,
5432 "${",
5433 name.as_str(),
5434 None,
5435 part_end,
5436 ),
5437 })
5438 }
5439 other => return other,
5440 };
5441
5442 WordPart::Parameter(ParameterExpansion {
5443 syntax: ParameterExpansionSyntax::Bourne(syntax.expect("matched Some above")),
5444 span,
5445 raw_body,
5446 })
5447 }
5448
5449 fn enrich_parameter_operator(&self, operator: ParameterOp) -> ParameterOp {
5450 match operator {
5451 ParameterOp::ReplaceFirst {
5452 pattern,
5453 replacement,
5454 ..
5455 } => ParameterOp::ReplaceFirst {
5456 pattern,
5457 replacement_word_ast: self.parse_source_text_as_word(&replacement),
5458 replacement,
5459 },
5460 ParameterOp::ReplaceAll {
5461 pattern,
5462 replacement,
5463 ..
5464 } => ParameterOp::ReplaceAll {
5465 pattern,
5466 replacement_word_ast: self.parse_source_text_as_word(&replacement),
5467 replacement,
5468 },
5469 ParameterOp::UseDefault
5470 | ParameterOp::AssignDefault
5471 | ParameterOp::UseReplacement
5472 | ParameterOp::Error
5473 | ParameterOp::RemovePrefixShort { .. }
5474 | ParameterOp::RemovePrefixLong { .. }
5475 | ParameterOp::RemoveSuffixShort { .. }
5476 | ParameterOp::RemoveSuffixLong { .. }
5477 | ParameterOp::UpperFirst
5478 | ParameterOp::UpperAll
5479 | ParameterOp::LowerFirst
5480 | ParameterOp::LowerAll => operator,
5481 }
5482 }
5483
5484 fn parameter_raw_body_from_legacy(
5485 &self,
5486 part: &WordPart,
5487 span: Span,
5488 source_backed: bool,
5489 ) -> SourceText {
5490 if source_backed && span.end.offset <= self.input.len() {
5491 let syntax = span.slice(self.input);
5492 if let Some(body) = syntax
5493 .strip_prefix("${")
5494 .and_then(|syntax| syntax.strip_suffix('}'))
5495 {
5496 let start = span.start.advanced_by("${");
5497 let end = start.advanced_by(body);
5498 return SourceText::source(Span::from_positions(start, end));
5499 }
5500 }
5501
5502 let mut syntax = String::new();
5503 self.push_word_part_syntax(&mut syntax, part, span);
5504 let body = syntax
5505 .strip_prefix("${")
5506 .and_then(|syntax| syntax.strip_suffix('}'))
5507 .unwrap_or(syntax.as_str())
5508 .to_string();
5509 SourceText::from(body)
5510 }
5511
5512 fn zsh_parameter_word_part(
5513 &mut self,
5514 raw_body: SourceText,
5515 part_start: Position,
5516 part_end: Position,
5517 ) -> WordPart {
5518 let syntax = self.parse_zsh_parameter_syntax(&raw_body, raw_body.span().start);
5519 WordPart::Parameter(ParameterExpansion {
5520 syntax: ParameterExpansionSyntax::Zsh(syntax),
5521 span: Span::from_positions(part_start, part_end),
5522 raw_body,
5523 })
5524 }
5525
5526 fn parse_zsh_modifier_group(
5527 &self,
5528 text: &str,
5529 base: Position,
5530 start: usize,
5531 ) -> Option<(usize, Vec<ZshModifier>)> {
5532 let rest = text.get(start..)?;
5533 if !rest.starts_with('(') {
5534 return None;
5535 }
5536
5537 let close_rel = rest[1..].find(')')?;
5538 let close = start + 1 + close_rel;
5539 let group_text = &text[start..=close];
5540 let inner = &text[start + 1..close];
5541 let group_start = base.advanced_by(&text[..start]);
5542 let group_span = Span::from_positions(group_start, group_start.advanced_by(group_text));
5543 let mut modifiers = Vec::new();
5544 let mut index = 0usize;
5545
5546 while index < inner.len() {
5547 let name = inner[index..].chars().next()?;
5548 index += name.len_utf8();
5549
5550 let mut argument_delimiter = None;
5551 let mut argument = None;
5552 if matches!(name, 's' | 'j')
5553 && let Some(delimiter) = inner[index..].chars().next()
5554 {
5555 index += delimiter.len_utf8();
5556 let argument_start = index;
5557 while index < inner.len() {
5558 let ch = inner[index..].chars().next()?;
5559 if ch == delimiter {
5560 let argument_text = &inner[argument_start..index];
5561 let argument_base =
5562 group_start.advanced_by(&group_text[..1 + argument_start]);
5563 let argument_end = argument_base.advanced_by(argument_text);
5564 argument_delimiter = Some(delimiter);
5565 argument = Some(self.source_text(
5566 argument_text.to_string(),
5567 argument_base,
5568 argument_end,
5569 ));
5570 index += delimiter.len_utf8();
5571 break;
5572 }
5573 index += ch.len_utf8();
5574 }
5575 }
5576
5577 let argument_word_ast = argument
5578 .as_ref()
5579 .map(|argument| self.parse_source_text_as_word(argument));
5580
5581 modifiers.push(ZshModifier {
5582 name,
5583 argument,
5584 argument_word_ast,
5585 argument_delimiter,
5586 span: group_span,
5587 });
5588 }
5589
5590 Some((close + 1, modifiers))
5591 }
5592
5593 fn parse_zsh_parameter_syntax(
5594 &mut self,
5595 raw_body: &SourceText,
5596 base: Position,
5597 ) -> ZshParameterExpansion {
5598 let text = raw_body.slice(self.input);
5599 let mut index = 0;
5600 let mut modifiers = Vec::new();
5601 let mut length_prefix = None;
5602 let source_backed = raw_body.is_source_backed();
5603
5604 while text[index..].starts_with('(')
5605 && let Some((next_index, group_modifiers)) =
5606 self.parse_zsh_modifier_group(text, base, index)
5607 {
5608 modifiers.extend(group_modifiers);
5609 index = next_index;
5610 }
5611
5612 while index < text.len() {
5613 let Some(flag) = text[index..].chars().next() else {
5614 break;
5615 };
5616 match flag {
5617 '=' | '~' | '^' => {
5618 let modifier_start = base.advanced_by(&text[..index]);
5619 let modifier_end =
5620 modifier_start.advanced_by(&text[index..index + flag.len_utf8()]);
5621 modifiers.push(ZshModifier {
5622 name: flag,
5623 argument: None,
5624 argument_word_ast: None,
5625 argument_delimiter: None,
5626 span: Span::from_positions(modifier_start, modifier_end),
5627 });
5628 index += flag.len_utf8();
5629 }
5630 '#' if length_prefix.is_none() => {
5631 let prefix_start = base.advanced_by(&text[..index]);
5632 let prefix_end = prefix_start.advanced_by("#");
5633 length_prefix = Some(Span::from_positions(prefix_start, prefix_end));
5634 index += '#'.len_utf8();
5635 }
5636 _ => break,
5637 }
5638 }
5639
5640 let (target, operation_index) = if text[index..].starts_with("${") {
5641 let end = self
5642 .find_matching_parameter_end(&text[index..])
5643 .unwrap_or(text.len() - index);
5644 let nested_text = &text[index..index + end];
5645 let target =
5646 self.parse_nested_parameter_target(nested_text, base.advanced_by(&text[..index]));
5647 (target, index + end)
5648 } else if text[index..].starts_with(':') || text[index..].is_empty() {
5649 (ZshExpansionTarget::Empty, index)
5650 } else {
5651 let end = self
5652 .find_zsh_operation_start(&text[index..])
5653 .map(|offset| index + offset)
5654 .unwrap_or(text.len());
5655 let raw_target = &text[index..end];
5656 let trimmed = raw_target.trim();
5657 let target = if trimmed.is_empty() {
5658 ZshExpansionTarget::Empty
5659 } else {
5660 let leading = raw_target
5661 .len()
5662 .saturating_sub(raw_target.trim_start().len());
5663 let target_base = base.advanced_by(&text[..index + leading]);
5664 self.parse_zsh_target_from_text(
5665 trimmed,
5666 target_base,
5667 source_backed && leading == 0 && trimmed.len() == raw_target.len(),
5668 )
5669 };
5670 (target, end)
5671 };
5672
5673 let operation = (operation_index < text.len()).then(|| {
5674 self.parse_zsh_parameter_operation(
5675 &text[operation_index..],
5676 base.advanced_by(&text[..operation_index]),
5677 )
5678 });
5679
5680 ZshParameterExpansion {
5681 target,
5682 modifiers,
5683 length_prefix,
5684 operation,
5685 }
5686 }
5687
5688 fn parse_zsh_target_from_text(
5689 &mut self,
5690 text: &str,
5691 base: Position,
5692 source_backed: bool,
5693 ) -> ZshExpansionTarget {
5694 let trimmed = text.trim();
5695 if trimmed.is_empty() {
5696 return ZshExpansionTarget::Empty;
5697 }
5698
5699 if trimmed.starts_with("${") && trimmed.ends_with('}') {
5700 return self.parse_nested_parameter_target(trimmed, base);
5701 }
5702
5703 if let Some(reference) = self.maybe_parse_loose_var_ref_target(trimmed) {
5704 return ZshExpansionTarget::Reference(reference);
5705 }
5706
5707 let span = Span::from_positions(base, base.advanced_by(trimmed));
5708 let word = self.parse_word_with_context(trimmed, span, base, source_backed);
5709 if let Some(reference) =
5710 self.parse_var_ref_from_word(&word, SubscriptInterpretation::Contextual)
5711 {
5712 ZshExpansionTarget::Reference(reference)
5713 } else {
5714 ZshExpansionTarget::Word(word)
5715 }
5716 }
5717
5718 fn maybe_parse_loose_var_ref_target(&self, text: &str) -> Option<VarRef> {
5719 let trimmed = text.trim();
5720 Self::looks_like_plain_parameter_access(trimmed).then(|| self.parse_loose_var_ref(trimmed))
5721 }
5722
5723 fn is_plain_special_parameter_name(name: &str) -> bool {
5724 matches!(name, "#" | "$" | "!" | "*" | "@" | "?" | "-") || name == "0"
5725 }
5726
5727 fn looks_like_plain_parameter_access(text: &str) -> bool {
5728 let trimmed = text.trim();
5729 if trimmed.is_empty() {
5730 return false;
5731 }
5732
5733 let name = if let Some(open) = trimmed.find('[') {
5734 if !trimmed.ends_with(']') {
5735 return false;
5736 }
5737 &trimmed[..open]
5738 } else {
5739 trimmed
5740 };
5741
5742 Self::is_valid_identifier(name)
5743 || name.bytes().all(|byte| byte.is_ascii_digit())
5744 || Self::is_plain_special_parameter_name(name)
5745 }
5746
5747 fn parse_nested_parameter_target(&mut self, text: &str, base: Position) -> ZshExpansionTarget {
5748 if !(text.starts_with("${") && text.ends_with('}')) {
5749 return self.parse_zsh_target_from_text(text, base, false);
5750 }
5751
5752 let raw_body_start = base.advanced_by("${");
5753 let raw_body = self.source_text(
5754 text[2..text.len() - 1].to_string(),
5755 raw_body_start,
5756 base.advanced_by(&text[..text.len() - 1]),
5757 );
5758 let raw_body_text = raw_body.slice(self.input);
5759 let has_operation = self.find_zsh_operation_start(raw_body_text).is_some();
5760 let syntax = if Self::looks_like_plain_parameter_access(raw_body_text) && !has_operation {
5761 ParameterExpansionSyntax::Bourne(BourneParameterExpansion::Access {
5762 reference: self.parse_loose_var_ref(raw_body_text),
5763 })
5764 } else if raw_body_text.starts_with('(')
5765 || raw_body_text.starts_with(':')
5766 || raw_body_text.starts_with('=')
5767 || raw_body_text.starts_with('^')
5768 || raw_body_text.starts_with('~')
5769 || raw_body_text.starts_with('.')
5770 || raw_body_text.starts_with('#')
5771 || raw_body_text.starts_with('"')
5772 || raw_body_text.starts_with('\'')
5773 || raw_body_text.starts_with('$')
5774 || has_operation
5775 {
5776 ParameterExpansionSyntax::Zsh(
5777 self.parse_zsh_parameter_syntax(&raw_body, raw_body_start),
5778 )
5779 } else {
5780 ParameterExpansionSyntax::Bourne(BourneParameterExpansion::Access {
5781 reference: self.parse_loose_var_ref(raw_body_text),
5782 })
5783 };
5784
5785 ZshExpansionTarget::Nested(Box::new(ParameterExpansion {
5786 syntax,
5787 span: Span::from_positions(base, base.advanced_by(text)),
5788 raw_body,
5789 }))
5790 }
5791
5792 fn parse_loose_var_ref(&self, text: &str) -> VarRef {
5793 let trimmed = text.trim();
5794 if let Some(open) = trimmed.find('[')
5795 && trimmed.ends_with(']')
5796 {
5797 let name = &trimmed[..open];
5798 let subscript_text = &trimmed[open + 1..trimmed.len() - 1];
5799 let subscript = self.subscript_from_source_text(
5800 SourceText::from(subscript_text.to_string()),
5801 None,
5802 SubscriptInterpretation::Contextual,
5803 );
5804 return VarRef {
5805 name: Name::from(name),
5806 name_span: Span::new(),
5807 subscript: Some(subscript),
5808 span: Span::new(),
5809 };
5810 }
5811
5812 VarRef {
5813 name: Name::from(trimmed),
5814 name_span: Span::new(),
5815 subscript: None,
5816 span: Span::new(),
5817 }
5818 }
5819
5820 fn find_matching_parameter_end(&self, text: &str) -> Option<usize> {
5821 let mut depth = 0_i32;
5822 let mut chars = text.char_indices().peekable();
5823
5824 while let Some((index, ch)) = chars.next() {
5825 match ch {
5826 '$' if chars.peek().is_some_and(|(_, next)| *next == '{') => {
5827 depth += 1;
5828 }
5829 '}' => {
5830 depth -= 1;
5831 if depth == 0 {
5832 return Some(index + ch.len_utf8());
5833 }
5834 }
5835 _ => {}
5836 }
5837 }
5838
5839 None
5840 }
5841
5842 fn find_zsh_operation_start(&self, text: &str) -> Option<usize> {
5843 let mut bracket_depth = 0_usize;
5844 let mut in_single = false;
5845 let mut in_double = false;
5846 let mut escaped = false;
5847
5848 for (index, ch) in text.char_indices() {
5849 if escaped {
5850 escaped = false;
5851 continue;
5852 }
5853
5854 match ch {
5855 '\\' if !in_single => escaped = true,
5856 '\'' if !in_double => in_single = !in_single,
5857 '"' if !in_single => in_double = !in_double,
5858 '[' if !in_single && !in_double => bracket_depth += 1,
5859 ']' if !in_single && !in_double && bracket_depth > 0 => bracket_depth -= 1,
5860 ':' if !in_single && !in_double && bracket_depth == 0 => return Some(index),
5861 '#' | '%' | '/' | '^' | ',' | '~'
5862 if !in_single && !in_double && bracket_depth == 0 && index > 0 =>
5863 {
5864 return Some(index);
5865 }
5866 _ => {}
5867 }
5868 }
5869
5870 None
5871 }
5872
5873 fn zsh_operation_source_text(
5874 &self,
5875 text: &str,
5876 base: Position,
5877 start: usize,
5878 end: usize,
5879 ) -> SourceText {
5880 self.source_text(
5881 text[start..end].to_string(),
5882 base.advanced_by(&text[..start]),
5883 base.advanced_by(&text[..end]),
5884 )
5885 }
5886
5887 fn find_zsh_top_level_delimiter(&self, text: &str, delimiter: char) -> Option<usize> {
5888 let mut chars = text.char_indices().peekable();
5889 let mut in_single = false;
5890 let mut in_double = false;
5891 let mut escaped = false;
5892 let mut brace_depth = 0_usize;
5893 let mut paren_depth = 0_usize;
5894
5895 while let Some((index, ch)) = chars.next() {
5896 if escaped {
5897 escaped = false;
5898 continue;
5899 }
5900
5901 match ch {
5902 '\\' if !in_single => escaped = true,
5903 '\'' if !in_double => in_single = !in_single,
5904 '"' if !in_single => in_double = !in_double,
5905 '$' if !in_single => {
5906 if let Some((_, next)) = chars.peek() {
5907 if *next == '{' {
5908 brace_depth += 1;
5909 chars.next();
5910 } else if *next == '(' {
5911 paren_depth += 1;
5912 chars.next();
5913 if let Some((_, after)) = chars.peek()
5914 && *after == '('
5915 {
5916 paren_depth += 1;
5917 chars.next();
5918 }
5919 }
5920 }
5921 }
5922 '}' if !in_single && !in_double && brace_depth > 0 => brace_depth -= 1,
5923 ')' if !in_single && !in_double && paren_depth > 0 => paren_depth -= 1,
5924 _ if ch == delimiter
5925 && !in_single
5926 && !in_double
5927 && brace_depth == 0
5928 && paren_depth == 0 =>
5929 {
5930 return Some(index);
5931 }
5932 _ => {}
5933 }
5934 }
5935
5936 None
5937 }
5938
5939 fn zsh_simple_modifier_suffix_segment(segment: &str) -> bool {
5940 let mut chars = segment.chars();
5941 let Some(first) = chars.next() else {
5942 return false;
5943 };
5944
5945 match first {
5946 'a' | 'A' | 'c' | 'e' | 'l' | 'P' | 'q' | 'Q' | 'r' | 'u' => chars.next().is_none(),
5947 'h' | 't' => chars.all(|ch| ch.is_ascii_digit()),
5948 _ => false,
5949 }
5950 }
5951
5952 fn zsh_modifier_suffix_candidate(rest: &str) -> bool {
5953 if rest.is_empty() {
5954 return false;
5955 }
5956
5957 let Some(first) = rest.chars().next() else {
5958 return false;
5959 };
5960 if first.is_ascii_digit()
5961 || first.is_ascii_whitespace()
5962 || matches!(first, '$' | '\'' | '"' | '(' | '{')
5963 {
5964 return false;
5965 }
5966
5967 rest.split(':')
5968 .all(Self::zsh_simple_modifier_suffix_segment)
5969 }
5970
5971 fn zsh_slice_candidate(rest: &str) -> bool {
5972 let Some(first) = rest.chars().next() else {
5973 return false;
5974 };
5975
5976 !Self::zsh_modifier_suffix_candidate(rest)
5977 && (first.is_ascii_alphanumeric()
5978 || first == '_'
5979 || first.is_ascii_whitespace()
5980 || matches!(first, '$' | '\'' | '"' | '(' | '{'))
5981 }
5982
5983 fn parse_zsh_parameter_operation(&self, text: &str, base: Position) -> ZshExpansionOperation {
5984 if let Some(operand) = text.strip_prefix(":#") {
5985 let operand = self.source_text(
5986 operand.to_string(),
5987 base.advanced_by(":#"),
5988 base.advanced_by(text),
5989 );
5990 return ZshExpansionOperation::PatternOperation {
5991 kind: ZshPatternOp::Filter,
5992 operand_word_ast: self.parse_source_text_as_word(&operand),
5993 operand,
5994 };
5995 }
5996
5997 if let Some((kind, operand)) = text
5998 .strip_prefix(":-")
5999 .map(|operand| (ZshDefaultingOp::UseDefault, operand))
6000 .or_else(|| {
6001 text.strip_prefix(":=")
6002 .map(|operand| (ZshDefaultingOp::AssignDefault, operand))
6003 })
6004 .or_else(|| {
6005 text.strip_prefix(":+")
6006 .map(|operand| (ZshDefaultingOp::UseReplacement, operand))
6007 })
6008 .or_else(|| {
6009 text.strip_prefix(":?")
6010 .map(|operand| (ZshDefaultingOp::Error, operand))
6011 })
6012 {
6013 let operand = self.source_text(
6014 operand.to_string(),
6015 base.advanced_by(&text[..2]),
6016 base.advanced_by(text),
6017 );
6018 return ZshExpansionOperation::Defaulting {
6019 kind,
6020 operand_word_ast: self.parse_source_text_as_word(&operand),
6021 operand,
6022 colon_variant: true,
6023 };
6024 }
6025
6026 if let Some((kind, prefix_len)) = [
6027 ("##", ZshTrimOp::RemovePrefixLong),
6028 ("#", ZshTrimOp::RemovePrefixShort),
6029 ("%%", ZshTrimOp::RemoveSuffixLong),
6030 ("%", ZshTrimOp::RemoveSuffixShort),
6031 ]
6032 .into_iter()
6033 .find_map(|(prefix, kind)| text.starts_with(prefix).then_some((kind, prefix.len())))
6034 {
6035 let operand = self.zsh_operation_source_text(text, base, prefix_len, text.len());
6036 return ZshExpansionOperation::TrimOperation {
6037 kind,
6038 operand_word_ast: self.parse_source_text_as_word(&operand),
6039 operand,
6040 };
6041 }
6042
6043 if let Some((kind, prefix_len)) = [
6044 ("//", ZshReplacementOp::ReplaceAll),
6045 ("/#", ZshReplacementOp::ReplacePrefix),
6046 ("/%", ZshReplacementOp::ReplaceSuffix),
6047 ("/", ZshReplacementOp::ReplaceFirst),
6048 ]
6049 .into_iter()
6050 .find_map(|(prefix, kind)| text.starts_with(prefix).then_some((kind, prefix.len())))
6051 {
6052 let rest = &text[prefix_len..];
6053 let separator = self.find_zsh_top_level_delimiter(rest, '/');
6054 let pattern_end = separator.unwrap_or(rest.len());
6055 let pattern =
6056 self.zsh_operation_source_text(text, base, prefix_len, prefix_len + pattern_end);
6057 let replacement = separator.map(|separator| {
6058 self.zsh_operation_source_text(text, base, prefix_len + separator + 1, text.len())
6059 });
6060 return ZshExpansionOperation::ReplacementOperation {
6061 kind,
6062 pattern_word_ast: self.parse_source_text_as_word(&pattern),
6063 replacement_word_ast: self.parse_optional_source_text_as_word(replacement.as_ref()),
6064 pattern,
6065 replacement,
6066 };
6067 }
6068
6069 if let Some(rest) = text.strip_prefix(':') {
6070 if Self::zsh_modifier_suffix_candidate(rest) {
6071 let text = self.source_text(text.to_string(), base, base.advanced_by(text));
6072 return ZshExpansionOperation::Unknown {
6073 word_ast: self.parse_source_text_as_word(&text),
6074 text,
6075 };
6076 }
6077
6078 if Self::zsh_slice_candidate(rest) {
6079 let separator = self.find_zsh_top_level_delimiter(rest, ':');
6080 let offset_end = separator.unwrap_or(rest.len());
6081 let offset = self.zsh_operation_source_text(text, base, 1, 1 + offset_end);
6082 let length = separator.map(|separator| {
6083 self.zsh_operation_source_text(text, base, 1 + separator + 1, text.len())
6084 });
6085 return ZshExpansionOperation::Slice {
6086 offset_word_ast: self.parse_source_text_as_word(&offset),
6087 length_word_ast: self.parse_optional_source_text_as_word(length.as_ref()),
6088 offset,
6089 length,
6090 };
6091 }
6092 }
6093
6094 let text = self.source_text(text.to_string(), base, base.advanced_by(text));
6095 ZshExpansionOperation::Unknown {
6096 word_ast: self.parse_source_text_as_word(&text),
6097 text,
6098 }
6099 }
6100
6101 fn parse_explicit_arithmetic_span(
6102 &self,
6103 span: Option<Span>,
6104 context: &'static str,
6105 ) -> Result<Option<ArithmeticExprNode>> {
6106 let Some(span) = span else {
6107 return Ok(None);
6108 };
6109 if span.slice(self.input).trim().is_empty() {
6110 return Ok(None);
6111 }
6112 arithmetic::parse_expression(
6113 span.slice(self.input),
6114 span,
6115 self.dialect,
6116 self.max_depth.saturating_sub(self.current_depth),
6117 self.fuel,
6118 )
6119 .map(Some)
6120 .map_err(|error| match error {
6121 Error::Parse { message, .. } => self.error(format!("{context}: {message}")),
6122 })
6123 }
6124
6125 fn parse_source_text_as_arithmetic(&self, text: &SourceText) -> Result<ArithmeticExprNode> {
6126 arithmetic::parse_expression(
6127 text.slice(self.input),
6128 text.span(),
6129 self.dialect,
6130 self.max_depth.saturating_sub(self.current_depth),
6131 self.fuel,
6132 )
6133 }
6134
6135 fn maybe_parse_source_text_as_arithmetic(
6136 &self,
6137 text: &SourceText,
6138 ) -> Option<ArithmeticExprNode> {
6139 if !text.is_source_backed() {
6140 return None;
6141 }
6142 self.parse_source_text_as_arithmetic(text).ok()
6143 }
6144
6145 fn parse_source_text_as_word(&self, text: &SourceText) -> Word {
6146 Self::parse_word_fragment(self.input, text.slice(self.input), text.span())
6147 }
6148
6149 fn parse_optional_source_text_as_word(&self, text: Option<&SourceText>) -> Option<Word> {
6150 text.map(|text| self.parse_source_text_as_word(text))
6151 }
6152
6153 fn source_matches(&self, span: Span, text: &str) -> bool {
6154 span.start.offset <= span.end.offset
6155 && self
6156 .input
6157 .get(span.start.offset..span.end.offset)
6158 .is_some_and(|slice| slice == text)
6159 }
6160
6161 fn checkpoint(&self) -> ParserCheckpoint<'a> {
6162 ParserCheckpoint {
6163 lexer: self.lexer.clone(),
6164 synthetic_tokens: self.synthetic_tokens.clone(),
6165 alias_replays: self.alias_replays.clone(),
6166 current_token: self.current_token.clone(),
6167 current_word_cache: self.current_word_cache.clone(),
6168 current_token_kind: self.current_token_kind,
6169 current_keyword: self.current_keyword,
6170 current_span: self.current_span,
6171 peeked_token: self.peeked_token.clone(),
6172 current_depth: self.current_depth,
6173 fuel: self.fuel,
6174 comments: self.comments.clone(),
6175 expand_next_word: self.expand_next_word,
6176 brace_group_depth: self.brace_group_depth,
6177 brace_body_stack: self.brace_body_stack.clone(),
6178 syntax_facts: self.syntax_facts.clone(),
6179 #[cfg(feature = "benchmarking")]
6180 benchmark_counters: self.benchmark_counters,
6181 }
6182 }
6183
6184 fn restore(&mut self, checkpoint: ParserCheckpoint<'a>) {
6185 self.lexer = checkpoint.lexer;
6186 self.synthetic_tokens = checkpoint.synthetic_tokens;
6187 self.alias_replays = checkpoint.alias_replays;
6188 self.current_token = checkpoint.current_token;
6189 self.current_word_cache = checkpoint.current_word_cache;
6190 self.current_token_kind = checkpoint.current_token_kind;
6191 self.current_keyword = checkpoint.current_keyword;
6192 self.current_span = checkpoint.current_span;
6193 self.peeked_token = checkpoint.peeked_token;
6194 self.current_depth = checkpoint.current_depth;
6195 self.fuel = checkpoint.fuel;
6196 self.comments = checkpoint.comments;
6197 self.expand_next_word = checkpoint.expand_next_word;
6198 self.brace_group_depth = checkpoint.brace_group_depth;
6199 self.brace_body_stack = checkpoint.brace_body_stack;
6200 self.syntax_facts = checkpoint.syntax_facts;
6201 #[cfg(feature = "benchmarking")]
6202 {
6203 self.benchmark_counters = checkpoint.benchmark_counters;
6204 }
6205 }
6206
6207 fn set_current_spanned(&mut self, token: LexedToken<'a>) {
6208 #[cfg(feature = "benchmarking")]
6209 self.maybe_record_set_current_spanned_call();
6210 let span = token.span;
6211 self.current_token_kind = Some(token.kind);
6212 self.current_keyword = Self::keyword_from_token(&token);
6213 self.current_token = Some(token);
6214 self.current_word_cache = None;
6215 self.current_span = span;
6216 }
6217
6218 fn set_current_kind(&mut self, kind: TokenKind, span: Span) {
6219 self.current_token_kind = Some(kind);
6220 self.current_keyword = None;
6221 self.current_token = Some(LexedToken::punctuation(kind).with_span(span));
6222 self.current_word_cache = None;
6223 self.current_span = span;
6224 }
6225
6226 fn clear_current_token(&mut self) {
6227 self.current_token = None;
6228 self.current_word_cache = None;
6229 self.current_token_kind = None;
6230 self.current_keyword = None;
6231 }
6232
6233 fn next_pending_token(&mut self) -> Option<LexedToken<'a>> {
6234 if let Some(token) = self.synthetic_tokens.pop_front() {
6235 return Some(token.materialize());
6236 }
6237
6238 loop {
6239 let replay = self.alias_replays.last_mut()?;
6240 if let Some(token) = replay.next_token() {
6241 return Some(token);
6242 }
6243 self.alias_replays.pop();
6244 }
6245 }
6246
6247 fn next_spanned_token_with_comments(&mut self) -> Option<LexedToken<'a>> {
6248 self.next_pending_token()
6249 .or_else(|| self.lexer.next_lexed_token_with_comments())
6250 }
6251
6252 fn compile_alias_definition(&self, value: &str) -> AliasDefinition {
6253 let source = Arc::<str>::from(value.to_string());
6254 let mut lexer = Lexer::with_max_subst_depth(source.as_ref(), self.max_depth);
6255 let mut tokens = Vec::new();
6256
6257 while let Some(token) = lexer.next_lexed_token_with_comments() {
6258 tokens.push(token.into_shared(&source));
6259 }
6260
6261 AliasDefinition {
6262 tokens: tokens.into(),
6263 expands_next_word: value.chars().last().is_some_and(char::is_whitespace),
6264 }
6265 }
6266
6267 fn maybe_expand_current_alias_chain(&mut self) {
6268 if !self.expand_aliases {
6269 self.expand_next_word = false;
6270 return;
6271 }
6272
6273 let mut seen = HashSet::new();
6274 let mut expands_next_word = false;
6275
6276 loop {
6277 if self.current_token_kind != Some(TokenKind::Word) {
6278 break;
6279 }
6280 let Some(name) = self.current_token.as_ref().and_then(LexedToken::word_text) else {
6281 break;
6282 };
6283 let Some(alias) = self.aliases.get(name).cloned() else {
6284 break;
6285 };
6286 if !seen.insert(name.to_string()) {
6287 break;
6288 }
6289
6290 expands_next_word = alias.expands_next_word;
6291 self.peeked_token = None;
6292 self.alias_replays
6293 .push(AliasReplay::new(&alias, self.current_span.start));
6294 self.advance_raw();
6295 }
6296
6297 self.expand_next_word = expands_next_word;
6298 }
6299
6300 fn next_word_char(
6301 chars: &mut std::iter::Peekable<std::str::Chars<'_>>,
6302 cursor: &mut Position,
6303 ) -> Option<char> {
6304 let ch = chars.next()?;
6305 cursor.advance(ch);
6306 Some(ch)
6307 }
6308
6309 fn next_word_char_unwrap(
6310 chars: &mut std::iter::Peekable<std::str::Chars<'_>>,
6311 cursor: &mut Position,
6312 ) -> char {
6313 Self::next_word_char(chars, cursor)
6314 .expect("word parser should only consume characters that were already peeked")
6315 }
6316
6317 fn consume_word_char_if(
6318 chars: &mut std::iter::Peekable<std::str::Chars<'_>>,
6319 cursor: &mut Position,
6320 expected: char,
6321 ) -> bool {
6322 if chars.peek() == Some(&expected) {
6323 Self::next_word_char_unwrap(chars, cursor);
6324 true
6325 } else {
6326 false
6327 }
6328 }
6329
6330 fn read_word_while<F>(
6331 chars: &mut std::iter::Peekable<std::str::Chars<'_>>,
6332 cursor: &mut Position,
6333 mut predicate: F,
6334 ) -> String
6335 where
6336 F: FnMut(char) -> bool,
6337 {
6338 let mut text = String::new();
6339 while let Some(&ch) = chars.peek() {
6340 if !predicate(ch) {
6341 break;
6342 }
6343 text.push(Self::next_word_char_unwrap(chars, cursor));
6344 }
6345 text
6346 }
6347
6348 fn rebase_redirects(redirects: &mut [Redirect], base: Position) {
6349 for redirect in redirects {
6350 redirect.span = redirect.span.rebased(base);
6351 redirect.fd_var_span = redirect.fd_var_span.map(|span| span.rebased(base));
6352 match &mut redirect.target {
6353 RedirectTarget::Word(word) => Self::rebase_word(word, base),
6354 RedirectTarget::Heredoc(heredoc) => {
6355 heredoc.delimiter.span = heredoc.delimiter.span.rebased(base);
6356 Self::rebase_word(&mut heredoc.delimiter.raw, base);
6357 Self::rebase_heredoc_body(&mut heredoc.body, base);
6358 }
6359 }
6360 }
6361 }
6362
6363 fn rebase_assignments(assignments: &mut [Assignment], base: Position) {
6364 for assignment in assignments {
6365 assignment.span = assignment.span.rebased(base);
6366 Self::rebase_var_ref(&mut assignment.target, base);
6367 match &mut assignment.value {
6368 AssignmentValue::Scalar(word) => Self::rebase_word(word, base),
6369 AssignmentValue::Compound(array) => Self::rebase_array_expr(array, base),
6370 }
6371 }
6372 }
6373
6374 fn error(&self, message: impl Into<String>) -> Error {
6376 Error::parse_at(
6377 message,
6378 self.current_span.start.line,
6379 self.current_span.start.column,
6380 )
6381 }
6382
6383 fn ensure_feature(
6384 &self,
6385 enabled: bool,
6386 feature: &str,
6387 unsupported_message: &str,
6388 ) -> Result<()> {
6389 if enabled {
6390 Ok(())
6391 } else {
6392 Err(self.error(format!("{feature} {unsupported_message}")))
6393 }
6394 }
6395
6396 fn ensure_double_bracket(&self) -> Result<()> {
6397 self.ensure_feature(
6398 self.dialect.features().double_bracket,
6399 "[[ ]] conditionals",
6400 "are not available in this shell mode",
6401 )
6402 }
6403
6404 fn ensure_arithmetic_for(&self) -> Result<()> {
6405 self.ensure_feature(
6406 self.dialect.features().arithmetic_for,
6407 "c-style for loops",
6408 "are not available in this shell mode",
6409 )
6410 }
6411
6412 fn ensure_coproc(&self) -> Result<()> {
6413 self.ensure_feature(
6414 self.dialect.features().coproc_keyword,
6415 "coprocess commands",
6416 "are not available in this shell mode",
6417 )
6418 }
6419
6420 fn ensure_arithmetic_command(&self) -> Result<()> {
6421 self.ensure_feature(
6422 self.dialect.features().arithmetic_command,
6423 "arithmetic commands",
6424 "are not available in this shell mode",
6425 )
6426 }
6427
6428 fn ensure_select_loop(&self) -> Result<()> {
6429 self.ensure_feature(
6430 self.dialect.features().select_loop,
6431 "select loops",
6432 "are not available in this shell mode",
6433 )
6434 }
6435
6436 fn ensure_repeat_loop(&self) -> Result<()> {
6437 self.ensure_feature(
6438 self.zsh_short_repeat_enabled(),
6439 "repeat loops",
6440 "are not available in this shell mode",
6441 )
6442 }
6443
6444 fn ensure_foreach_loop(&self) -> Result<()> {
6445 self.ensure_feature(
6446 self.zsh_short_loops_enabled(),
6447 "foreach loops",
6448 "are not available in this shell mode",
6449 )
6450 }
6451
6452 fn ensure_function_keyword(&self) -> Result<()> {
6453 self.ensure_feature(
6454 self.dialect.features().function_keyword,
6455 "function keyword definitions",
6456 "are not available in this shell mode",
6457 )
6458 }
6459
6460 fn tick(&mut self) -> Result<()> {
6462 if self.fuel == 0 {
6463 let used = self.max_fuel - self.fuel;
6464 return Err(Error::parse(format!(
6465 "parser fuel exhausted ({} operations, max {})",
6466 used, self.max_fuel
6467 )));
6468 }
6469 self.fuel -= 1;
6470 Ok(())
6471 }
6472
6473 fn push_depth(&mut self) -> Result<()> {
6475 self.current_depth += 1;
6476 if self.current_depth > self.max_depth {
6477 return Err(Error::parse(format!(
6478 "AST nesting too deep ({} levels, max {})",
6479 self.current_depth, self.max_depth
6480 )));
6481 }
6482 Ok(())
6483 }
6484
6485 fn pop_depth(&mut self) {
6487 if self.current_depth > 0 {
6488 self.current_depth -= 1;
6489 }
6490 }
6491
6492 fn check_error_token(&self) -> Result<()> {
6494 if self.current_token_kind == Some(TokenKind::Error) {
6495 let msg = self
6496 .current_token
6497 .as_ref()
6498 .and_then(LexedToken::error_kind)
6499 .map(|kind| kind.message())
6500 .unwrap_or("unknown lexer error");
6501 return Err(self.error(format!("syntax error: {}", msg)));
6502 }
6503 Ok(())
6504 }
6505
6506 fn parse_diagnostic_from_error(&self, error: Error) -> ParseDiagnostic {
6507 let Error::Parse { message, .. } = error;
6508 ParseDiagnostic {
6509 message,
6510 span: self.current_span,
6511 }
6512 }
6513
6514 fn compound_span(compound: &CompoundCommand) -> Span {
6515 match compound {
6516 CompoundCommand::If(command) => command.span,
6517 CompoundCommand::For(command) => command.span,
6518 CompoundCommand::Repeat(command) => command.span,
6519 CompoundCommand::Foreach(command) => command.span,
6520 CompoundCommand::ArithmeticFor(command) => command.span,
6521 CompoundCommand::While(command) => command.span,
6522 CompoundCommand::Until(command) => command.span,
6523 CompoundCommand::Case(command) => command.span,
6524 CompoundCommand::Select(command) => command.span,
6525 CompoundCommand::Subshell(body) | CompoundCommand::BraceGroup(body) => body.span,
6526 CompoundCommand::Arithmetic(command) => command.span,
6527 CompoundCommand::Time(command) => command.span,
6528 CompoundCommand::Conditional(command) => command.span,
6529 CompoundCommand::Coproc(command) => command.span,
6530 CompoundCommand::Always(command) => command.span,
6531 }
6532 }
6533
6534 fn stmt_seq_with_span(span: Span, stmts: Vec<Stmt>) -> StmtSeq {
6535 StmtSeq {
6536 leading_comments: Vec::new(),
6537 stmts,
6538 trailing_comments: Vec::new(),
6539 span,
6540 }
6541 }
6542
6543 fn binary_stmt(left: Stmt, op: BinaryOp, op_span: Span, right: Stmt) -> Stmt {
6544 let span = left.span.merge(right.span);
6545 Stmt {
6546 leading_comments: Vec::new(),
6547 command: AstCommand::Binary(BinaryCommand {
6548 left: Box::new(left),
6549 op,
6550 op_span,
6551 right: Box::new(right),
6552 span,
6553 }),
6554 negated: false,
6555 redirects: Vec::new(),
6556 terminator: None,
6557 terminator_span: None,
6558 inline_comment: None,
6559 span,
6560 }
6561 }
6562
6563 fn lower_builtin_command(
6564 builtin: BuiltinCommand,
6565 ) -> (AstBuiltinCommand, SmallVec<[Redirect; 1]>, Span) {
6566 match builtin {
6567 BuiltinCommand::Break(command) => {
6568 let span = command.span;
6569 let redirects = command.redirects;
6570 (
6571 AstBuiltinCommand::Break(AstBreakCommand {
6572 depth: command.depth,
6573 extra_args: command.extra_args.into_vec(),
6574 assignments: command.assignments.into_vec(),
6575 span,
6576 }),
6577 redirects,
6578 span,
6579 )
6580 }
6581 BuiltinCommand::Continue(command) => {
6582 let span = command.span;
6583 let redirects = command.redirects;
6584 (
6585 AstBuiltinCommand::Continue(AstContinueCommand {
6586 depth: command.depth,
6587 extra_args: command.extra_args.into_vec(),
6588 assignments: command.assignments.into_vec(),
6589 span,
6590 }),
6591 redirects,
6592 span,
6593 )
6594 }
6595 BuiltinCommand::Return(command) => {
6596 let span = command.span;
6597 let redirects = command.redirects;
6598 (
6599 AstBuiltinCommand::Return(AstReturnCommand {
6600 code: command.code,
6601 extra_args: command.extra_args.into_vec(),
6602 assignments: command.assignments.into_vec(),
6603 span,
6604 }),
6605 redirects,
6606 span,
6607 )
6608 }
6609 BuiltinCommand::Exit(command) => {
6610 let span = command.span;
6611 let redirects = command.redirects;
6612 (
6613 AstBuiltinCommand::Exit(AstExitCommand {
6614 code: command.code,
6615 extra_args: command.extra_args.into_vec(),
6616 assignments: command.assignments.into_vec(),
6617 span,
6618 }),
6619 redirects,
6620 span,
6621 )
6622 }
6623 }
6624 }
6625
6626 fn lower_non_sequence_command_to_stmt(command: Command) -> Stmt {
6627 match command {
6628 Command::Simple(command) => Stmt {
6629 leading_comments: Vec::new(),
6630 command: AstCommand::Simple(AstSimpleCommand {
6631 name: command.name,
6632 args: command.args.into_vec(),
6633 assignments: command.assignments.into_vec(),
6634 span: command.span,
6635 }),
6636 negated: false,
6637 redirects: command.redirects.into_vec(),
6638 terminator: None,
6639 terminator_span: None,
6640 inline_comment: None,
6641 span: command.span,
6642 },
6643 Command::Builtin(command) => {
6644 let (command, redirects, span) = Self::lower_builtin_command(command);
6645 Stmt {
6646 leading_comments: Vec::new(),
6647 command: AstCommand::Builtin(command),
6648 negated: false,
6649 redirects: redirects.into_vec(),
6650 terminator: None,
6651 terminator_span: None,
6652 inline_comment: None,
6653 span,
6654 }
6655 }
6656 Command::Decl(command) => {
6657 let command = *command;
6658 Stmt {
6659 leading_comments: Vec::new(),
6660 command: AstCommand::Decl(AstDeclClause {
6661 variant: command.variant,
6662 variant_span: command.variant_span,
6663 operands: command.operands.into_vec(),
6664 assignments: command.assignments.into_vec(),
6665 span: command.span,
6666 }),
6667 negated: false,
6668 redirects: command.redirects.into_vec(),
6669 terminator: None,
6670 terminator_span: None,
6671 inline_comment: None,
6672 span: command.span,
6673 }
6674 }
6675 Command::Compound(compound, redirects) => {
6676 let span = Self::compound_span(&compound);
6677 Stmt {
6678 leading_comments: Vec::new(),
6679 command: AstCommand::Compound(*compound),
6680 negated: false,
6681 redirects: redirects.into_vec(),
6682 terminator: None,
6683 terminator_span: None,
6684 inline_comment: None,
6685 span,
6686 }
6687 }
6688 Command::Function(function) => Stmt {
6689 leading_comments: Vec::new(),
6690 span: function.span,
6691 command: AstCommand::Function(function),
6692 negated: false,
6693 redirects: Vec::new(),
6694 terminator: None,
6695 terminator_span: None,
6696 inline_comment: None,
6697 },
6698 Command::AnonymousFunction(function, redirects) => Stmt {
6699 leading_comments: Vec::new(),
6700 span: function.span,
6701 command: AstCommand::AnonymousFunction(function),
6702 negated: false,
6703 redirects: redirects.into_vec(),
6704 terminator: None,
6705 terminator_span: None,
6706 inline_comment: None,
6707 },
6708 }
6709 }
6710
6711 fn comment_start(comment: Comment) -> usize {
6712 usize::from(comment.range.start())
6713 }
6714
6715 fn is_inline_comment(source: &str, stmt: &Stmt, comment: Comment) -> bool {
6716 let comment_start = Self::comment_start(comment);
6717 if comment_start < stmt.span.end.offset {
6718 return false;
6719 }
6720 source
6721 .get(stmt.span.end.offset..comment_start)
6722 .is_some_and(|gap| !gap.contains('\n'))
6723 }
6724
6725 fn take_comments_before(
6726 comments: &mut VecDeque<Comment>,
6727 end_offset: usize,
6728 ) -> VecDeque<Comment> {
6729 let mut taken = VecDeque::new();
6730 while comments
6731 .front()
6732 .is_some_and(|comment| Self::comment_start(*comment) < end_offset)
6733 {
6734 taken.push_back(
6735 comments
6736 .pop_front()
6737 .expect("front comment should exist while draining"),
6738 );
6739 }
6740 taken
6741 }
6742
6743 fn attach_comments_to_file(&self, file: &mut File) {
6744 let mut comments = self.comments.iter().copied().collect::<VecDeque<_>>();
6745 Self::attach_comments_to_stmt_seq_with_source(self.input, &mut file.body, &mut comments);
6746 file.body.trailing_comments.extend(comments);
6747 }
6748
6749 fn attach_comments_to_stmt_seq_with_source(
6750 source: &str,
6751 sequence: &mut StmtSeq,
6752 comments: &mut VecDeque<Comment>,
6753 ) {
6754 if sequence.stmts.is_empty() {
6755 sequence
6756 .trailing_comments
6757 .extend(Self::take_comments_before(
6758 comments,
6759 sequence.span.end.offset,
6760 ));
6761 return;
6762 }
6763
6764 for (index, stmt) in sequence.stmts.iter_mut().enumerate() {
6765 let leading = Self::take_comments_before(comments, stmt.span.start.offset);
6766 if index == 0 {
6767 sequence.leading_comments.extend(leading);
6768 } else {
6769 stmt.leading_comments.extend(leading);
6770 }
6771
6772 let mut nested = Self::take_comments_before(comments, stmt.span.end.offset);
6773 Self::attach_comments_to_stmt_with_source(source, stmt, &mut nested);
6774 if !nested.is_empty() {
6775 stmt.leading_comments.extend(nested);
6776 }
6777
6778 if stmt.inline_comment.is_none()
6779 && comments
6780 .front()
6781 .is_some_and(|comment| Self::is_inline_comment(source, stmt, *comment))
6782 {
6783 stmt.inline_comment = comments.pop_front();
6784 }
6785 }
6786
6787 sequence
6788 .trailing_comments
6789 .extend(Self::take_comments_before(
6790 comments,
6791 sequence.span.end.offset,
6792 ));
6793 }
6794
6795 fn attach_comments_to_stmt_with_source(
6796 source: &str,
6797 stmt: &mut Stmt,
6798 comments: &mut VecDeque<Comment>,
6799 ) {
6800 match &mut stmt.command {
6801 AstCommand::Binary(binary) => {
6802 let mut left_comments =
6803 Self::take_comments_before(comments, binary.left.span.end.offset);
6804 Self::attach_comments_to_stmt_with_source(
6805 source,
6806 binary.left.as_mut(),
6807 &mut left_comments,
6808 );
6809 if !left_comments.is_empty() {
6810 binary.left.leading_comments.extend(left_comments);
6811 }
6812
6813 let mut right_comments = std::mem::take(comments);
6814 Self::attach_comments_to_stmt_with_source(
6815 source,
6816 binary.right.as_mut(),
6817 &mut right_comments,
6818 );
6819 if !right_comments.is_empty() {
6820 binary.right.leading_comments.extend(right_comments);
6821 }
6822 }
6823 AstCommand::Compound(compound) => {
6824 Self::attach_comments_to_compound_with_source(source, compound, comments);
6825 }
6826 AstCommand::Function(function) => {
6827 let mut body_comments = std::mem::take(comments);
6828 Self::attach_comments_to_stmt_with_source(
6829 source,
6830 function.body.as_mut(),
6831 &mut body_comments,
6832 );
6833 if !body_comments.is_empty() {
6834 function.body.leading_comments.extend(body_comments);
6835 }
6836 }
6837 AstCommand::AnonymousFunction(function) => {
6838 let mut body_comments = std::mem::take(comments);
6839 Self::attach_comments_to_stmt_with_source(
6840 source,
6841 function.body.as_mut(),
6842 &mut body_comments,
6843 );
6844 if !body_comments.is_empty() {
6845 function.body.leading_comments.extend(body_comments);
6846 }
6847 }
6848 AstCommand::Simple(_) | AstCommand::Builtin(_) | AstCommand::Decl(_) => {}
6849 }
6850 }
6851
6852 fn attach_comments_to_compound_with_source(
6853 source: &str,
6854 command: &mut CompoundCommand,
6855 comments: &mut VecDeque<Comment>,
6856 ) {
6857 match command {
6858 CompoundCommand::If(command) => {
6859 let mut condition =
6860 Self::take_comments_before(comments, command.condition.span.end.offset);
6861 Self::attach_comments_to_stmt_seq_with_source(
6862 source,
6863 &mut command.condition,
6864 &mut condition,
6865 );
6866 command.condition.trailing_comments.extend(condition);
6867
6868 let mut then_branch =
6869 Self::take_comments_before(comments, command.then_branch.span.end.offset);
6870 Self::attach_comments_to_stmt_seq_with_source(
6871 source,
6872 &mut command.then_branch,
6873 &mut then_branch,
6874 );
6875 command.then_branch.trailing_comments.extend(then_branch);
6876
6877 for (condition_seq, body_seq) in &mut command.elif_branches {
6878 let mut elif_condition =
6879 Self::take_comments_before(comments, condition_seq.span.end.offset);
6880 Self::attach_comments_to_stmt_seq_with_source(
6881 source,
6882 condition_seq,
6883 &mut elif_condition,
6884 );
6885 condition_seq.trailing_comments.extend(elif_condition);
6886
6887 let mut elif_body =
6888 Self::take_comments_before(comments, body_seq.span.end.offset);
6889 Self::attach_comments_to_stmt_seq_with_source(source, body_seq, &mut elif_body);
6890 body_seq.trailing_comments.extend(elif_body);
6891 }
6892
6893 if let Some(else_branch) = &mut command.else_branch {
6894 let mut else_comments = std::mem::take(comments);
6895 Self::attach_comments_to_stmt_seq_with_source(
6896 source,
6897 else_branch,
6898 &mut else_comments,
6899 );
6900 else_branch.trailing_comments.extend(else_comments);
6901 }
6902 }
6903 CompoundCommand::For(command) => {
6904 let mut body_comments = std::mem::take(comments);
6905 Self::attach_comments_to_stmt_seq_with_source(
6906 source,
6907 &mut command.body,
6908 &mut body_comments,
6909 );
6910 command.body.trailing_comments.extend(body_comments);
6911 }
6912 CompoundCommand::Repeat(command) => {
6913 let mut body_comments = std::mem::take(comments);
6914 Self::attach_comments_to_stmt_seq_with_source(
6915 source,
6916 &mut command.body,
6917 &mut body_comments,
6918 );
6919 command.body.trailing_comments.extend(body_comments);
6920 }
6921 CompoundCommand::Foreach(command) => {
6922 let mut body_comments = std::mem::take(comments);
6923 Self::attach_comments_to_stmt_seq_with_source(
6924 source,
6925 &mut command.body,
6926 &mut body_comments,
6927 );
6928 command.body.trailing_comments.extend(body_comments);
6929 }
6930 CompoundCommand::ArithmeticFor(command) => {
6931 let mut body_comments = std::mem::take(comments);
6932 Self::attach_comments_to_stmt_seq_with_source(
6933 source,
6934 &mut command.body,
6935 &mut body_comments,
6936 );
6937 command.body.trailing_comments.extend(body_comments);
6938 }
6939 CompoundCommand::While(command) => {
6940 let mut condition =
6941 Self::take_comments_before(comments, command.condition.span.end.offset);
6942 Self::attach_comments_to_stmt_seq_with_source(
6943 source,
6944 &mut command.condition,
6945 &mut condition,
6946 );
6947 command.condition.trailing_comments.extend(condition);
6948
6949 let mut body_comments = std::mem::take(comments);
6950 Self::attach_comments_to_stmt_seq_with_source(
6951 source,
6952 &mut command.body,
6953 &mut body_comments,
6954 );
6955 command.body.trailing_comments.extend(body_comments);
6956 }
6957 CompoundCommand::Until(command) => {
6958 let mut condition =
6959 Self::take_comments_before(comments, command.condition.span.end.offset);
6960 Self::attach_comments_to_stmt_seq_with_source(
6961 source,
6962 &mut command.condition,
6963 &mut condition,
6964 );
6965 command.condition.trailing_comments.extend(condition);
6966
6967 let mut body_comments = std::mem::take(comments);
6968 Self::attach_comments_to_stmt_seq_with_source(
6969 source,
6970 &mut command.body,
6971 &mut body_comments,
6972 );
6973 command.body.trailing_comments.extend(body_comments);
6974 }
6975 CompoundCommand::Case(command) => {
6976 for case in &mut command.cases {
6977 let mut body_comments =
6978 Self::take_comments_before(comments, case.body.span.end.offset);
6979 Self::attach_comments_to_stmt_seq_with_source(
6980 source,
6981 &mut case.body,
6982 &mut body_comments,
6983 );
6984 case.body.trailing_comments.extend(body_comments);
6985 }
6986 }
6987 CompoundCommand::Select(command) => {
6988 let mut body_comments = std::mem::take(comments);
6989 Self::attach_comments_to_stmt_seq_with_source(
6990 source,
6991 &mut command.body,
6992 &mut body_comments,
6993 );
6994 command.body.trailing_comments.extend(body_comments);
6995 }
6996 CompoundCommand::Subshell(body) | CompoundCommand::BraceGroup(body) => {
6997 let mut body_comments = std::mem::take(comments);
6998 Self::attach_comments_to_stmt_seq_with_source(source, body, &mut body_comments);
6999 body.trailing_comments.extend(body_comments);
7000 }
7001 CompoundCommand::Always(command) => {
7002 let mut body_comments =
7003 Self::take_comments_before(comments, command.body.span.end.offset);
7004 Self::attach_comments_to_stmt_seq_with_source(
7005 source,
7006 &mut command.body,
7007 &mut body_comments,
7008 );
7009 command.body.trailing_comments.extend(body_comments);
7010
7011 let mut always_comments = std::mem::take(comments);
7012 Self::attach_comments_to_stmt_seq_with_source(
7013 source,
7014 &mut command.always_body,
7015 &mut always_comments,
7016 );
7017 command
7018 .always_body
7019 .trailing_comments
7020 .extend(always_comments);
7021 }
7022 CompoundCommand::Time(command) => {
7023 if let Some(inner) = &mut command.command {
7024 let mut inner_comments = std::mem::take(comments);
7025 Self::attach_comments_to_stmt_with_source(
7026 source,
7027 inner.as_mut(),
7028 &mut inner_comments,
7029 );
7030 if !inner_comments.is_empty() {
7031 inner.leading_comments.extend(inner_comments);
7032 }
7033 }
7034 }
7035 CompoundCommand::Coproc(command) => {
7036 let mut body_comments = std::mem::take(comments);
7037 Self::attach_comments_to_stmt_with_source(
7038 source,
7039 command.body.as_mut(),
7040 &mut body_comments,
7041 );
7042 if !body_comments.is_empty() {
7043 command.body.leading_comments.extend(body_comments);
7044 }
7045 }
7046 CompoundCommand::Arithmetic(_) | CompoundCommand::Conditional(_) => {}
7047 }
7048 }
7049
7050 fn advance_raw(&mut self) {
7051 #[cfg(feature = "benchmarking")]
7052 self.maybe_record_advance_raw_call();
7053 if let Some(peeked) = self.peeked_token.take() {
7054 self.set_current_spanned(peeked);
7055 } else {
7056 loop {
7057 match self.next_spanned_token_with_comments() {
7058 Some(st) if st.kind == TokenKind::Comment => {
7059 self.maybe_record_comment(&st);
7060 }
7061 Some(st) => {
7062 self.set_current_spanned(st);
7063 break;
7064 }
7065 None => {
7066 self.clear_current_token();
7067 break;
7069 }
7070 }
7071 }
7072 }
7073 }
7074
7075 #[cfg(feature = "benchmarking")]
7076 fn maybe_record_set_current_spanned_call(&mut self) {
7077 if let Some(counters) = &mut self.benchmark_counters {
7078 counters.parser_set_current_spanned_calls += 1;
7079 }
7080 }
7081
7082 #[cfg(feature = "benchmarking")]
7083 fn maybe_record_advance_raw_call(&mut self) {
7084 if let Some(counters) = &mut self.benchmark_counters {
7085 counters.parser_advance_raw_calls += 1;
7086 }
7087 }
7088
7089 #[cfg(feature = "benchmarking")]
7090 fn finish_benchmark_counters(&self) -> ParserBenchmarkCounters {
7091 let mut counters = self.benchmark_counters.unwrap_or_default();
7092 counters.lexer_current_position_calls =
7093 self.lexer.benchmark_counters().current_position_calls;
7094 counters
7095 }
7096
7097 fn advance(&mut self) {
7098 let should_expand = std::mem::take(&mut self.expand_next_word);
7099 self.advance_raw();
7100 if should_expand {
7101 if self
7102 .current_token
7103 .as_ref()
7104 .is_some_and(|token| token.flags.is_synthetic())
7105 {
7106 self.expand_next_word = true;
7107 } else {
7108 self.maybe_expand_current_alias_chain();
7109 }
7110 }
7111 }
7112
7113 fn peek_next(&mut self) -> Option<&LexedToken<'a>> {
7115 if self.peeked_token.is_none() {
7116 loop {
7117 match self.next_spanned_token_with_comments() {
7118 Some(st) if st.kind == TokenKind::Comment => {
7119 self.maybe_record_comment(&st);
7120 }
7121 other => {
7122 self.peeked_token = other;
7123 break;
7124 }
7125 }
7126 }
7127 }
7128 self.peeked_token.as_ref()
7129 }
7130
7131 fn peek_next_kind(&mut self) -> Option<TokenKind> {
7132 self.peek_next()?;
7133 self.peeked_token.as_ref().map(|st| st.kind)
7134 }
7135
7136 fn peek_next_is(&mut self, kind: TokenKind) -> bool {
7137 self.peek_next_kind() == Some(kind)
7138 }
7139
7140 fn at(&self, kind: TokenKind) -> bool {
7141 self.current_token_kind == Some(kind)
7142 }
7143
7144 fn current_token_has_leading_whitespace(&self) -> bool {
7145 self.current_span.start.offset > 0
7146 && self.input[..self.current_span.start.offset]
7147 .chars()
7148 .next_back()
7149 .is_some_and(|ch| matches!(ch, ' ' | '\t' | '\n'))
7150 }
7151
7152 fn current_token_is_tight_to_next_token(&mut self) -> bool {
7153 let current_end = self.current_span.end.offset;
7154 self.peek_next()
7155 .is_some_and(|token| token.span.start.offset == current_end)
7156 }
7157
7158 fn at_in_set(&self, set: TokenSet) -> bool {
7159 self.current_token_kind
7160 .is_some_and(|kind| set.contains(kind))
7161 }
7162
7163 fn at_word_like(&self) -> bool {
7164 self.current_token_kind.is_some_and(TokenKind::is_word_like)
7165 }
7166
7167 fn current_word_str(&self) -> Option<&str> {
7168 self.current_token_kind
7169 .filter(|kind| kind.is_word_like())
7170 .and(self.current_token.as_ref())
7171 .and_then(LexedToken::word_text)
7172 }
7173
7174 fn classify_keyword(word: &str) -> Option<Keyword> {
7175 match word.as_bytes() {
7176 b"if" => Some(Keyword::If),
7177 b"for" => Some(Keyword::For),
7178 b"repeat" => Some(Keyword::Repeat),
7179 b"foreach" => Some(Keyword::Foreach),
7180 b"while" => Some(Keyword::While),
7181 b"until" => Some(Keyword::Until),
7182 b"case" => Some(Keyword::Case),
7183 b"select" => Some(Keyword::Select),
7184 b"time" => Some(Keyword::Time),
7185 b"coproc" => Some(Keyword::Coproc),
7186 b"function" => Some(Keyword::Function),
7187 b"always" => Some(Keyword::Always),
7188 b"then" => Some(Keyword::Then),
7189 b"else" => Some(Keyword::Else),
7190 b"elif" => Some(Keyword::Elif),
7191 b"fi" => Some(Keyword::Fi),
7192 b"do" => Some(Keyword::Do),
7193 b"done" => Some(Keyword::Done),
7194 b"esac" => Some(Keyword::Esac),
7195 b"in" => Some(Keyword::In),
7196 _ => None,
7197 }
7198 }
7199
7200 fn current_keyword(&self) -> Option<Keyword> {
7201 self.current_keyword
7202 }
7203
7204 fn looks_like_disabled_repeat_loop(&mut self) -> Result<bool> {
7205 if self.current_keyword() != Some(Keyword::Repeat) {
7206 return Ok(false);
7207 }
7208
7209 let checkpoint = self.checkpoint();
7210 self.advance();
7211 if !self.at_word_like() {
7212 self.restore(checkpoint);
7213 return Ok(false);
7214 }
7215 self.advance();
7216
7217 let result = match self.current_token_kind {
7218 Some(TokenKind::LeftBrace) => Ok(true),
7219 Some(TokenKind::Semicolon) => {
7220 self.advance();
7221 if let Err(error) = self.skip_newlines() {
7222 self.restore(checkpoint);
7223 return Err(error);
7224 }
7225 Ok(self.current_keyword() == Some(Keyword::Do))
7226 }
7227 Some(TokenKind::Newline) => {
7228 if let Err(error) = self.skip_newlines() {
7229 self.restore(checkpoint);
7230 return Err(error);
7231 }
7232 Ok(self.current_keyword() == Some(Keyword::Do))
7233 }
7234 _ => Ok(false),
7235 };
7236 self.restore(checkpoint);
7237 result
7238 }
7239
7240 fn looks_like_disabled_foreach_loop(&mut self) -> Result<bool> {
7241 if self.current_keyword() != Some(Keyword::Foreach) {
7242 return Ok(false);
7243 }
7244
7245 let checkpoint = self.checkpoint();
7246 self.advance();
7247 if self.current_name_token().is_none() {
7248 self.restore(checkpoint);
7249 return Ok(false);
7250 }
7251 self.advance();
7252
7253 let result = if self.at(TokenKind::LeftParen) {
7254 self.advance();
7255 let mut saw_word = false;
7256 while !self.at(TokenKind::RightParen) {
7257 if !self.at_word_like() {
7258 self.restore(checkpoint);
7259 return Ok(false);
7260 }
7261 saw_word = true;
7262 self.advance();
7263 }
7264 if !saw_word {
7265 self.restore(checkpoint);
7266 return Ok(false);
7267 }
7268 self.advance();
7269 Ok(self.at(TokenKind::LeftBrace))
7270 } else {
7271 if self.current_keyword() != Some(Keyword::In) {
7272 self.restore(checkpoint);
7273 return Ok(false);
7274 }
7275 self.advance();
7276
7277 let mut saw_word = false;
7278 let saw_separator = loop {
7279 if self.current_keyword() == Some(Keyword::Do) {
7280 break false;
7281 }
7282
7283 match self.current_token_kind {
7284 Some(kind) if kind.is_word_like() => {
7285 saw_word = true;
7286 self.advance();
7287 }
7288 Some(TokenKind::Semicolon) => {
7289 self.advance();
7290 break true;
7291 }
7292 Some(TokenKind::Newline) => {
7293 if let Err(error) = self.skip_newlines() {
7294 self.restore(checkpoint);
7295 return Err(error);
7296 }
7297 break true;
7298 }
7299 _ => break false,
7300 }
7301 };
7302
7303 Ok(saw_word && saw_separator && self.current_keyword() == Some(Keyword::Do))
7304 };
7305 self.restore(checkpoint);
7306 result
7307 }
7308
7309 fn skip_newlines_with_flag(&mut self) -> Result<bool> {
7310 let mut skipped = false;
7311 while self.at(TokenKind::Newline) {
7312 self.tick()?;
7313 self.advance();
7314 skipped = true;
7315 }
7316 Ok(skipped)
7317 }
7318
7319 fn skip_newlines(&mut self) -> Result<()> {
7320 self.skip_newlines_with_flag().map(|_| ())
7321 }
7322}
7323#[cfg(test)]
7324mod tests;