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