use std::collections::VecDeque;
pub struct IndentTracker {
indent_stack: Vec<usize>,
pending_tokens: VecDeque<IndentToken>,
at_line_start: bool,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum IndentToken {
Indent,
Dedent,
}
impl Default for IndentTracker {
fn default() -> Self {
Self {
indent_stack: vec![0],
pending_tokens: VecDeque::new(),
at_line_start: true,
}
}
}
impl IndentTracker {
pub fn new() -> Self {
Self::default()
}
pub fn handle_newline(&mut self) {
self.at_line_start = true;
}
pub fn handle_whitespace(&mut self, spaces: usize) -> Option<VecDeque<IndentToken>> {
if !self.at_line_start {
return None;
}
self.at_line_start = false;
let current_indent = *self.indent_stack.last().unwrap_or(&0);
if spaces > current_indent {
self.indent_stack.push(spaces);
self.pending_tokens.push_back(IndentToken::Indent);
} else if spaces < current_indent {
while let Some(&level) = self.indent_stack.last() {
if level <= spaces {
break;
}
self.indent_stack.pop();
self.pending_tokens.push_back(IndentToken::Dedent);
}
}
if self.pending_tokens.is_empty() {
None
} else {
Some(self.pending_tokens.drain(..).collect())
}
}
pub fn handle_non_whitespace(&mut self) -> Option<VecDeque<IndentToken>> {
if self.at_line_start {
self.at_line_start = false;
self.handle_whitespace(0)
} else {
None
}
}
pub fn finish(&mut self) -> VecDeque<IndentToken> {
while self.indent_stack.len() > 1 {
self.indent_stack.pop();
self.pending_tokens.push_back(IndentToken::Dedent);
}
self.pending_tokens.drain(..).collect()
}
}