use super::{LexError, Lexer, Token, Word};
pub(super) struct Pending {
pub delimiter: Option<String>,
strip_tabs: bool,
pub literal: bool,
token: usize,
}
impl Lexer {
pub(super) fn start_heredoc(&mut self, strip_tabs: bool) {
self.pending_heredocs.push(Pending {
delimiter: None,
strip_tabs,
literal: false,
token: self.tokens.len(),
});
self.tokens.push(Token::HereDoc {
body: Word::default(),
literal: false,
});
}
pub(super) fn awaiting_delimiter(&mut self) -> Option<&mut Pending> {
self.pending_heredocs
.last_mut()
.filter(|h| h.delimiter.is_none())
}
pub(super) fn consume_heredoc_bodies(&mut self) -> Result<(), LexError> {
let pending = std::mem::take(&mut self.pending_heredocs);
for heredoc in pending {
let Some(delimiter) = heredoc.delimiter else {
continue;
};
let mut body = String::new();
loop {
if self.pos >= self.chars.len() {
return Err(LexError::UnterminatedHereDoc { delimiter });
}
let line_start = self.pos;
while self.peek().is_some_and(|c| c != '\n') {
self.bump();
}
let line: String = self.chars[line_start..self.pos].iter().collect();
self.bump(); let candidate = if heredoc.strip_tabs {
line.trim_start_matches('\t')
} else {
&line
};
if candidate == delimiter {
break;
}
body.push_str(candidate);
body.push('\n');
}
let body = if heredoc.literal {
Word {
text: body,
..Word::default()
}
} else {
let mut expander = Lexer::new(&body);
expander.expanding(None)?;
expander.current.unwrap_or_default()
};
self.tokens[heredoc.token] = Token::HereDoc {
body,
literal: heredoc.literal,
};
}
Ok(())
}
}