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