use super::*;
impl<'a> Parser<'a> {
pub(super) fn simple_word_from_token(
&mut self,
token: &LexedToken<'_>,
span: Span,
) -> Option<Word> {
let word = token.word()?;
let source_backed = !token.flags.is_synthetic();
if self.zsh_glob_word_parsing_enabled_at(span.start.offset)
&& let Some(segment) = word.single_segment()
&& segment.kind() == LexedWordSegmentKind::Plain
&& let Some(word) = self.maybe_parse_zsh_qualified_glob_word(
segment.as_str(),
span,
segment.span().is_some() && source_backed && segment.text_is_source_backed(),
)
{
return Some(word);
}
let mut parts = Self::word_part_buffer_with_capacity(word.segments().size_hint().0);
for segment in word.segments() {
let source_backed = segment.span().is_some() && !token.flags.is_synthetic();
let content_span = Self::segment_content_span(segment, span);
let raw_text = segment.as_str();
let use_source_slice = source_backed
&& match segment.kind() {
LexedWordSegmentKind::Plain => {
segment.text_is_source_backed()
|| raw_text.contains("${") && raw_text.contains('/')
|| !raw_text.contains("$(")
}
_ => segment.text_is_source_backed(),
};
let text = if use_source_slice {
content_span.slice(self.input)
} else {
raw_text
};
match segment.kind() {
LexedWordSegmentKind::Plain
| LexedWordSegmentKind::DoubleQuoted
| LexedWordSegmentKind::DollarDoubleQuoted
if Self::word_text_needs_parse(text) =>
{
return None;
}
LexedWordSegmentKind::Plain
| LexedWordSegmentKind::SingleQuoted
| LexedWordSegmentKind::DollarSingleQuoted
| LexedWordSegmentKind::DoubleQuoted
| LexedWordSegmentKind::DollarDoubleQuoted => {}
LexedWordSegmentKind::Composite => return None,
}
let wrapper_span = Self::segment_wrapper_span(segment, span);
let part = match segment.kind() {
LexedWordSegmentKind::Plain => {
self.literal_part_from_text(text, content_span, source_backed)
}
LexedWordSegmentKind::SingleQuoted => {
self.single_quoted_part_from_text(text, content_span, wrapper_span, false)
}
LexedWordSegmentKind::DollarSingleQuoted => {
self.single_quoted_part_from_text(text, content_span, wrapper_span, true)
}
LexedWordSegmentKind::DoubleQuoted => self.double_quoted_literal_part_from_text(
text,
content_span,
wrapper_span,
source_backed,
false,
),
LexedWordSegmentKind::DollarDoubleQuoted => self
.double_quoted_literal_part_from_text(
text,
content_span,
wrapper_span,
source_backed,
true,
),
LexedWordSegmentKind::Composite => unreachable!(),
};
Self::push_word_part_node(&mut parts, part);
}
Some(self.word_with_part_buffer(parts, span))
}
pub(super) fn segment_content_span(segment: &LexedWordSegment<'_>, fallback: Span) -> Span {
segment
.span()
.or_else(|| segment.wrapper_span())
.unwrap_or(fallback)
}
pub(super) fn segment_wrapper_span(segment: &LexedWordSegment<'_>, fallback: Span) -> Span {
segment
.wrapper_span()
.or_else(|| segment.span())
.unwrap_or(fallback)
}
pub(super) fn literal_part_from_text(
&self,
text: &str,
span: Span,
source_backed: bool,
) -> WordPartNode {
WordPartNode::new(
WordPart::Literal(self.literal_text_from_str(
text,
span.start,
span.end,
source_backed,
)),
span,
)
}
pub(super) fn single_quoted_part_from_text(
&self,
text: &str,
content_span: Span,
wrapper_span: Span,
dollar: bool,
) -> WordPartNode {
WordPartNode::new(
WordPart::SingleQuoted {
value: self.source_text_from_str(text, content_span.start, content_span.end),
dollar,
},
wrapper_span,
)
}
pub(super) fn double_quoted_literal_part_from_text(
&self,
text: &str,
content_span: Span,
wrapper_span: Span,
source_backed: bool,
dollar: bool,
) -> WordPartNode {
WordPartNode::new(
WordPart::DoubleQuoted {
parts: vec![self.literal_part_from_text(text, content_span, source_backed)],
dollar,
},
wrapper_span,
)
}
pub(super) fn decode_word_from_token(
&mut self,
token: &LexedToken<'_>,
span: Span,
) -> Option<Word> {
let word = token.word()?;
if word.single_segment().is_none()
&& !token.flags.is_synthetic()
&& let Some(source_text) = token.source_slice(self.input)
{
return Some(self.parse_word_with_context(source_text, span, span.start, true));
}
if let Some(segment) = word.single_segment() {
let content_span = Self::segment_content_span(segment, span);
let wrapper_span = Self::segment_wrapper_span(segment, span);
let source_backed = segment.span().is_some() && !token.flags.is_synthetic();
let raw_text = segment.as_str();
let use_source_slice = source_backed
&& match segment.kind() {
LexedWordSegmentKind::Plain => {
segment.text_is_source_backed()
|| raw_text.contains("${") && raw_text.contains('/')
|| !raw_text.contains("$(")
}
_ => segment.text_is_source_backed(),
};
let text = if use_source_slice {
content_span.slice(self.input)
} else {
raw_text
};
let decode_text = if source_backed
&& !self.source_matches(content_span, text)
&& matches!(
segment.kind(),
LexedWordSegmentKind::DoubleQuoted | LexedWordSegmentKind::DollarDoubleQuoted
)
&& (!text.contains("$(") || text.contains("$(("))
{
content_span.slice(self.input)
} else {
text
};
let preserve_escaped_expansion_literals =
source_backed && self.source_matches(content_span, decode_text);
return match segment.kind() {
LexedWordSegmentKind::SingleQuoted => Some(self.word_with_single_part(
self.single_quoted_part_from_text(text, content_span, wrapper_span, false),
span,
)),
LexedWordSegmentKind::DollarSingleQuoted => Some(self.word_with_single_part(
self.single_quoted_part_from_text(text, content_span, wrapper_span, true),
span,
)),
LexedWordSegmentKind::Plain if Self::word_text_needs_parse(text) => Some(
self.decode_word_text_preserving_quotes_if_needed_with_escape_mode(
text,
span,
content_span.start,
source_backed,
preserve_escaped_expansion_literals,
),
),
LexedWordSegmentKind::DoubleQuoted | LexedWordSegmentKind::DollarDoubleQuoted
if Self::word_text_needs_parse(text) =>
{
let inner = self.decode_quoted_segment_text(
decode_text,
content_span,
content_span.start,
source_backed,
);
Some(self.word_with_single_part(
WordPartNode::new(
WordPart::DoubleQuoted {
parts: inner.parts,
dollar: matches!(
segment.kind(),
LexedWordSegmentKind::DollarDoubleQuoted
),
},
wrapper_span,
),
span,
))
}
LexedWordSegmentKind::Plain => Some(self.word_with_single_part(
self.literal_part_from_text(text, content_span, source_backed),
span,
)),
LexedWordSegmentKind::DoubleQuoted => Some(self.word_with_single_part(
self.double_quoted_literal_part_from_text(
text,
content_span,
wrapper_span,
source_backed,
false,
),
span,
)),
LexedWordSegmentKind::DollarDoubleQuoted => Some(self.word_with_single_part(
self.double_quoted_literal_part_from_text(
text,
content_span,
wrapper_span,
source_backed,
true,
),
span,
)),
LexedWordSegmentKind::Composite => None,
};
}
let mut parts = Self::word_part_buffer_with_capacity(word.segments().size_hint().0);
let mut cursor = span.start;
for segment in word.segments() {
let raw_text = segment.as_str();
let content_span = if let Some(segment_span) = segment.span() {
cursor = segment_span.end;
segment_span
} else {
let start = cursor;
let end = start.advanced_by(raw_text);
cursor = end;
Span::from_positions(start, end)
};
let wrapper_span = segment.wrapper_span().unwrap_or(content_span);
let source_backed = segment.span().is_some() && !token.flags.is_synthetic();
let use_source_slice = source_backed
&& match segment.kind() {
LexedWordSegmentKind::Plain => {
segment.text_is_source_backed()
|| raw_text.contains("${") && raw_text.contains('/')
|| !raw_text.contains("$(")
}
_ => segment.text_is_source_backed(),
};
let text = if use_source_slice {
content_span.slice(self.input)
} else {
raw_text
};
let preserve_escaped_expansion_literals = source_backed;
match segment.kind() {
LexedWordSegmentKind::SingleQuoted => Self::push_word_part_node(
&mut parts,
self.single_quoted_part_from_text(text, content_span, wrapper_span, false),
),
LexedWordSegmentKind::DollarSingleQuoted => Self::push_word_part_node(
&mut parts,
self.single_quoted_part_from_text(text, content_span, wrapper_span, true),
),
LexedWordSegmentKind::Plain => {
if Self::word_text_needs_parse(text) {
parts.extend(
self.decode_word_text_preserving_quotes_if_needed_with_escape_mode(
text,
content_span,
content_span.start,
source_backed,
preserve_escaped_expansion_literals,
)
.parts,
);
} else {
Self::push_word_part_node(
&mut parts,
self.literal_part_from_text(text, content_span, source_backed),
);
}
}
LexedWordSegmentKind::DoubleQuoted | LexedWordSegmentKind::DollarDoubleQuoted => {
if Self::word_text_needs_parse(text) {
let inner = self.decode_quoted_segment_text(
text,
content_span,
content_span.start,
source_backed,
);
Self::push_word_part_node(
&mut parts,
WordPartNode::new(
WordPart::DoubleQuoted {
parts: inner.parts,
dollar: matches!(
segment.kind(),
LexedWordSegmentKind::DollarDoubleQuoted
),
},
wrapper_span,
),
);
} else {
Self::push_word_part_node(
&mut parts,
self.double_quoted_literal_part_from_text(
text,
content_span,
wrapper_span,
source_backed,
matches!(segment.kind(), LexedWordSegmentKind::DollarDoubleQuoted),
),
);
}
}
LexedWordSegmentKind::Composite => return None,
}
}
Some(self.word_with_part_buffer(parts, span))
}
pub(super) fn current_word_ref(&mut self) -> Option<&Word> {
if self.current_word_cache.is_none() {
self.current_word_cache = self.current_word();
}
self.current_word_cache.as_ref()
}
pub(super) fn current_word(&mut self) -> Option<Word> {
if let Some(word) = self.current_word_cache.as_ref() {
return Some(word.clone());
}
if let Some(word) = self.current_zsh_glob_word_from_source() {
self.current_word_cache = Some(word.clone());
return Some(word);
}
let span = self.current_span;
if let Some(token) = self.current_token.clone()
&& let Some(word) = self.simple_word_from_token(&token, span)
{
return Some(word);
}
let token = self.current_token.take()?;
let word = self.decode_word_from_token(&token, span);
self.current_token = Some(token);
if let Some(word) = word.as_ref() {
self.current_word_cache = Some(word.clone());
}
word
}
pub(super) fn take_current_word(&mut self) -> Option<Word> {
if let Some(word) = self.current_word_cache.take() {
return Some(word);
}
if let Some(word) = self.current_zsh_glob_word_from_source() {
return Some(word);
}
let span = self.current_span;
if let Some(token) = self.current_token.clone()
&& let Some(word) = self.simple_word_from_token(&token, span)
{
return Some(word);
}
let token = self.current_token.take()?;
let word = self.decode_word_from_token(&token, span);
self.current_token = Some(token);
word
}
pub(super) fn take_current_word_and_advance(&mut self) -> Option<Word> {
let word = self.take_current_word()?;
self.advance_past_word(&word);
Some(word)
}
pub(super) fn advance_past_word(&mut self, word: &Word) {
let stop_after_synthetic = self
.current_token
.as_ref()
.is_some_and(|token| token.flags.is_synthetic());
while self.current_token.is_some() && self.current_span.start.offset < word.span.end.offset
{
self.advance();
if stop_after_synthetic
&& self
.current_token
.as_ref()
.is_none_or(|token| !token.flags.is_synthetic())
{
break;
}
}
}
pub(super) fn keyword_from_token(token: &LexedToken<'_>) -> Option<Keyword> {
(token.kind == TokenKind::Word)
.then(|| token.word_text())
.flatten()
.and_then(Self::classify_keyword)
}
pub(super) fn current_conditional_literal_word(&self) -> Option<Word> {
match self.current_token_kind? {
TokenKind::LeftBrace | TokenKind::RightBrace => Some(Word::literal_with_span(
self.input[self.current_span.start.offset..self.current_span.end.offset]
.to_string(),
self.current_span,
)),
_ => None,
}
}
pub(super) fn current_name_token(&self) -> Option<(Name, Span)> {
self.current_token_kind
.filter(|kind| kind.is_word_like())
.and_then(|_| self.current_word_str())
.map(|word| (Name::from(word), self.current_span))
}
pub(super) fn current_static_token_text(&self) -> Option<(String, bool)> {
let token = self.current_token.as_ref()?;
let raw_text = token.word_string()?;
let text_had_escape_markers = raw_text.contains('\x00');
let text = if text_had_escape_markers {
raw_text.replace('\x00', "")
} else {
raw_text
};
match token.kind {
TokenKind::LiteralWord => Some((text, true)),
TokenKind::QuotedWord if !Self::word_text_needs_parse(&text) => Some((text, true)),
TokenKind::Word if !Self::word_text_needs_parse(&text) => Some((
text,
token.flags.has_cooked_text() || text_had_escape_markers,
)),
_ => None,
}
}
}