glyph_parser/
indent_tracker.rs

1//! Track indentation in Python-like code
2
3use std::collections::VecDeque;
4
5pub struct IndentTracker {
6    indent_stack: Vec<usize>,
7    pending_tokens: VecDeque<IndentToken>,
8    at_line_start: bool,
9}
10
11#[derive(Debug, Clone, Copy, PartialEq)]
12pub enum IndentToken {
13    Indent,
14    Dedent,
15}
16
17impl Default for IndentTracker {
18    fn default() -> Self {
19        Self {
20            indent_stack: vec![0],
21            pending_tokens: VecDeque::new(),
22            at_line_start: true,
23        }
24    }
25}
26
27impl IndentTracker {
28    pub fn new() -> Self {
29        Self::default()
30    }
31
32    pub fn handle_newline(&mut self) {
33        self.at_line_start = true;
34    }
35
36    pub fn handle_whitespace(&mut self, spaces: usize) -> Option<VecDeque<IndentToken>> {
37        if !self.at_line_start {
38            return None;
39        }
40
41        self.at_line_start = false;
42        let current_indent = *self.indent_stack.last().unwrap_or(&0);
43
44        if spaces > current_indent {
45            // Indent
46            self.indent_stack.push(spaces);
47            self.pending_tokens.push_back(IndentToken::Indent);
48        } else if spaces < current_indent {
49            // Dedent - might be multiple levels
50            while let Some(&level) = self.indent_stack.last() {
51                if level <= spaces {
52                    break;
53                }
54                self.indent_stack.pop();
55                self.pending_tokens.push_back(IndentToken::Dedent);
56            }
57        }
58        // If spaces == current_indent, no indent/dedent token
59
60        if self.pending_tokens.is_empty() {
61            None
62        } else {
63            Some(self.pending_tokens.drain(..).collect())
64        }
65    }
66
67    pub fn handle_non_whitespace(&mut self) -> Option<VecDeque<IndentToken>> {
68        if self.at_line_start {
69            self.at_line_start = false;
70            // No indentation at line start means column 0
71            self.handle_whitespace(0)
72        } else {
73            None
74        }
75    }
76
77    pub fn finish(&mut self) -> VecDeque<IndentToken> {
78        // Emit remaining dedents
79        while self.indent_stack.len() > 1 {
80            self.indent_stack.pop();
81            self.pending_tokens.push_back(IndentToken::Dedent);
82        }
83        self.pending_tokens.drain(..).collect()
84    }
85}