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