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