Skip to main content

oxilean_parse/indent_tracker/
types.rs

1//! Auto-generated module
2//!
3//! 🤖 Generated with [SplitRS](https://github.com/cool-japan/splitrs)
4
5use super::functions::*;
6
7#[derive(Debug, Clone, Default)]
8pub struct IndentValidator {
9    pub expect_spaces: bool,
10    pub violations: Vec<(usize, char)>,
11}
12impl IndentValidator {
13    pub fn expect_spaces() -> Self {
14        Self {
15            expect_spaces: true,
16            violations: Vec::new(),
17        }
18    }
19    pub fn expect_tabs() -> Self {
20        Self {
21            expect_spaces: false,
22            violations: Vec::new(),
23        }
24    }
25    pub fn validate(&mut self, src: &str) {
26        for (i, line) in src.lines().enumerate() {
27            for ch in line.chars().take_while(|c| c.is_whitespace()) {
28                let violation = if self.expect_spaces {
29                    ch == '\t'
30                } else {
31                    ch == ' '
32                };
33                if violation {
34                    self.violations.push((i + 1, ch));
35                    break;
36                }
37            }
38        }
39    }
40    pub fn is_valid(&self) -> bool {
41        self.violations.is_empty()
42    }
43}
44/// A contiguous region of source lines that share the same base indentation.
45#[derive(Debug, Clone)]
46pub struct IndentRegion {
47    /// 1-based start line.
48    pub start_line: usize,
49    /// 1-based end line (inclusive).
50    pub end_line: usize,
51    /// Base indentation width for this region.
52    pub indent_width: usize,
53}
54impl IndentRegion {
55    /// Create a new region.
56    pub fn new(start_line: usize, end_line: usize, indent_width: usize) -> Self {
57        Self {
58            start_line,
59            end_line,
60            indent_width,
61        }
62    }
63    /// Number of lines in this region.
64    pub fn line_count(&self) -> usize {
65        self.end_line.saturating_sub(self.start_line) + 1
66    }
67    /// True if this region contains `line`.
68    pub fn contains_line(&self, line: usize) -> bool {
69        line >= self.start_line && line <= self.end_line
70    }
71}
72#[derive(Debug, Default)]
73pub struct SourceSplitter {
74    pub tab_width: usize,
75}
76impl SourceSplitter {
77    pub fn new(tab_width: usize) -> Self {
78        Self { tab_width }
79    }
80    pub fn split<'a>(&self, src: &'a str) -> Vec<(usize, &'a str)> {
81        let lines: Vec<&str> = src.lines().collect();
82        let mut result = Vec::new();
83        let mut block_start: Option<usize> = None;
84        let mut block_start_byte: usize = 0;
85        let mut byte_offset = 0usize;
86        for (i, &line) in lines.iter().enumerate() {
87            let class = LineClass::classify(line);
88            if class.starts_decl() {
89                if let Some(start_line) = block_start {
90                    result.push((start_line, &src[block_start_byte..byte_offset]));
91                }
92                block_start = Some(i);
93                block_start_byte = byte_offset;
94            }
95            byte_offset += line.len();
96            if i + 1 < lines.len() {
97                byte_offset += 1;
98            }
99        }
100        if let Some(start_line) = block_start {
101            result.push((start_line, &src[block_start_byte..]));
102        }
103        result
104    }
105}
106#[derive(Debug, Clone)]
107pub struct DoBlockTracker {
108    block_columns: Vec<usize>,
109    pub tab_width: usize,
110}
111impl DoBlockTracker {
112    pub fn new(tab_width: usize) -> Self {
113        Self {
114            block_columns: Vec::new(),
115            tab_width,
116        }
117    }
118    pub fn enter(&mut self, col: usize) {
119        self.block_columns.push(col);
120    }
121    pub fn exit(&mut self) -> Option<usize> {
122        self.block_columns.pop()
123    }
124    pub fn current_col(&self) -> Option<usize> {
125        self.block_columns.last().copied()
126    }
127    pub fn is_statement(&self, line: &str) -> bool {
128        let col = LayoutContext::indent_col(line, self.tab_width);
129        self.current_col() == Some(col)
130    }
131    pub fn is_deeper(&self, line: &str) -> bool {
132        let col = LayoutContext::indent_col(line, self.tab_width);
133        self.current_col().is_some_and(|e| col > e)
134    }
135    pub fn is_closer(&self, line: &str) -> bool {
136        let col = LayoutContext::indent_col(line, self.tab_width);
137        self.current_col().is_some_and(|e| col < e)
138    }
139    pub fn depth(&self) -> usize {
140        self.block_columns.len()
141    }
142}
143/// A layout rule describes how indentation affects block structure in OxiLean.
144#[derive(Debug, Clone, PartialEq, Eq)]
145pub enum LayoutRule {
146    /// Lines at exactly this column continue the same block.
147    SameBlock(usize),
148    /// Lines strictly to the right of this column are continuations.
149    Continuation(usize),
150    /// Lines strictly to the left of this column close the block.
151    CloseBlock(usize),
152}
153impl LayoutRule {
154    /// Test whether a given column satisfies this layout rule.
155    pub fn matches(&self, col: usize) -> bool {
156        match self {
157            LayoutRule::SameBlock(c) => col == *c,
158            LayoutRule::Continuation(c) => col > *c,
159            LayoutRule::CloseBlock(c) => col < *c,
160        }
161    }
162    /// The pivot column for this rule.
163    pub fn pivot(&self) -> usize {
164        match self {
165            LayoutRule::SameBlock(c) | LayoutRule::Continuation(c) | LayoutRule::CloseBlock(c) => {
166                *c
167            }
168        }
169    }
170}
171#[derive(Debug, Clone, PartialEq, Eq)]
172pub struct IndentMismatchError {
173    pub line_number: usize,
174    pub expected: usize,
175    pub actual: usize,
176    pub context: String,
177}
178impl IndentMismatchError {
179    pub fn new(
180        line_number: usize,
181        expected: usize,
182        actual: usize,
183        context: impl Into<String>,
184    ) -> Self {
185        Self {
186            line_number,
187            expected,
188            actual,
189            context: context.into(),
190        }
191    }
192    pub fn message(&self) -> String {
193        format!(
194            "indentation error at line {}: expected {} spaces, found {} ({})",
195            self.line_number, self.expected, self.actual, self.context
196        )
197    }
198}
199/// Records a history of indent levels for undo/redo-style editing support.
200#[derive(Debug, Clone, Default)]
201pub struct IndentLevelHistory {
202    past: Vec<Vec<IndentLevel>>,
203    present: Vec<IndentLevel>,
204    future: Vec<Vec<IndentLevel>>,
205}
206impl IndentLevelHistory {
207    /// Create an empty history.
208    pub fn new() -> Self {
209        Self::default()
210    }
211    /// Record the current state before making a change.
212    pub fn snapshot(&mut self) {
213        self.past.push(self.present.clone());
214        self.future.clear();
215    }
216    /// Set the current indent levels.
217    pub fn set_levels(&mut self, levels: Vec<IndentLevel>) {
218        self.present = levels;
219    }
220    /// Current indent levels.
221    pub fn current(&self) -> &[IndentLevel] {
222        &self.present
223    }
224    /// Undo: restore the previous state.
225    pub fn undo(&mut self) -> bool {
226        if let Some(prev) = self.past.pop() {
227            self.future.push(self.present.clone());
228            self.present = prev;
229            true
230        } else {
231            false
232        }
233    }
234    /// Redo: reapply the previously undone change.
235    pub fn redo(&mut self) -> bool {
236        if let Some(next) = self.future.pop() {
237            self.past.push(self.present.clone());
238            self.present = next;
239            true
240        } else {
241            false
242        }
243    }
244    /// Number of undoable steps.
245    pub fn undo_depth(&self) -> usize {
246        self.past.len()
247    }
248    /// Number of redoable steps.
249    pub fn redo_depth(&self) -> usize {
250        self.future.len()
251    }
252}
253#[derive(Debug, Clone)]
254pub struct Scope {
255    pub kind: String,
256    pub base_indent: IndentLevel,
257    pub bindings: Vec<String>,
258}
259impl Scope {
260    pub fn new(kind: impl Into<String>, base_indent: IndentLevel) -> Self {
261        Self {
262            kind: kind.into(),
263            base_indent,
264            bindings: Vec::new(),
265        }
266    }
267    pub fn add_binding(&mut self, name: impl Into<String>) {
268        self.bindings.push(name.into());
269    }
270    pub fn is_bound(&self, name: &str) -> bool {
271        self.bindings.iter().any(|b| b == name)
272    }
273}
274/// Context passed around during layout-sensitive parsing.
275#[derive(Debug, Clone)]
276pub struct LayoutContext {
277    rules: Vec<LayoutRule>,
278    pub tab_width: usize,
279}
280impl LayoutContext {
281    pub fn new(tab_width: usize) -> Self {
282        Self {
283            rules: Vec::new(),
284            tab_width,
285        }
286    }
287    pub fn push_rule(&mut self, rule: LayoutRule) {
288        self.rules.push(rule);
289    }
290    pub fn pop_rule(&mut self) -> Option<LayoutRule> {
291        self.rules.pop()
292    }
293    pub fn current_rule(&self) -> Option<&LayoutRule> {
294        self.rules.last()
295    }
296    pub fn indent_col(line: &str, tab_width: usize) -> usize {
297        let mut col = 0usize;
298        for ch in line.chars() {
299            match ch {
300                ' ' => col += 1,
301                '\t' => {
302                    col = (col / tab_width + 1) * tab_width;
303                }
304                _ => break,
305            }
306        }
307        col
308    }
309    pub fn line_continues_block(&self, line: &str) -> bool {
310        let col = Self::indent_col(line, self.tab_width);
311        match self.current_rule() {
312            Some(rule) => rule.matches(col),
313            None => true,
314        }
315    }
316    pub fn depth(&self) -> usize {
317        self.rules.len()
318    }
319    pub fn clear(&mut self) {
320        self.rules.clear();
321    }
322}
323#[derive(Debug, Clone, Copy, PartialEq, Eq)]
324pub enum IndentDiff {
325    Indent,
326    Same,
327    Dedent,
328}
329/// A stack of indentation levels used during parsing.
330pub struct IndentStack {
331    pub stack: Vec<IndentLevel>,
332    pub tab_width: usize,
333}
334impl IndentStack {
335    pub fn new(tab_width: usize) -> Self {
336        Self {
337            stack: Vec::new(),
338            tab_width,
339        }
340    }
341    pub fn push(&mut self, level: IndentLevel) {
342        self.stack.push(level);
343    }
344    pub fn pop(&mut self) -> Option<IndentLevel> {
345        self.stack.pop()
346    }
347    /// Return a reference to the current (top) indentation level.
348    pub fn current(&self) -> Option<&IndentLevel> {
349        self.stack.last()
350    }
351    /// Pop levels from the stack until the top is at or below `level`.
352    /// Returns the number of levels popped.
353    pub fn dedent_to(&mut self, level: &IndentLevel) -> usize {
354        let tw = self.tab_width;
355        let mut count = 0;
356        while let Some(top) = self.stack.last() {
357            if top.total_width(tw) > level.total_width(tw) {
358                self.stack.pop();
359                count += 1;
360            } else {
361                break;
362            }
363        }
364        count
365    }
366}
367/// Represents an indentation level as a mix of spaces and tabs.
368#[derive(Debug, Clone, PartialEq, Eq)]
369pub struct IndentLevel {
370    pub spaces: usize,
371    pub tabs: usize,
372}
373impl IndentLevel {
374    pub fn new(spaces: usize, tabs: usize) -> Self {
375        Self { spaces, tabs }
376    }
377    /// Total width in columns, using the given tab width.
378    pub fn total_width(&self, tab_width: usize) -> usize {
379        self.tabs * tab_width + self.spaces
380    }
381    /// Returns true if this level is strictly deeper than `other`.
382    pub fn is_deeper_than(&self, other: &IndentLevel, tab_width: usize) -> bool {
383        self.total_width(tab_width) > other.total_width(tab_width)
384    }
385}
386/// An indent "fence" that marks the minimum indentation for a scope.
387#[allow(dead_code)]
388#[allow(missing_docs)]
389pub struct IndentFence {
390    pub min_indent: usize,
391    pub active: bool,
392}
393impl IndentFence {
394    #[allow(dead_code)]
395    #[allow(missing_docs)]
396    pub fn new(min_indent: usize) -> Self {
397        Self {
398            min_indent,
399            active: true,
400        }
401    }
402    #[allow(dead_code)]
403    #[allow(missing_docs)]
404    pub fn deactivate(&mut self) {
405        self.active = false;
406    }
407    #[allow(dead_code)]
408    #[allow(missing_docs)]
409    pub fn allows(&self, indent: usize) -> bool {
410        !self.active || indent >= self.min_indent
411    }
412}
413#[derive(Debug, Clone)]
414pub struct IndentRewriter {
415    pub from_step: usize,
416    pub to_step: usize,
417}
418impl IndentRewriter {
419    pub fn new(from_step: usize, to_step: usize) -> Self {
420        Self { from_step, to_step }
421    }
422    pub fn rewrite_line(&self, line: &str) -> String {
423        let spaces = line.chars().take_while(|&c| c == ' ').count();
424        if spaces == 0 || self.from_step == 0 {
425            return line.to_string();
426        }
427        let levels = spaces / self.from_step;
428        let remainder = spaces % self.from_step;
429        " ".repeat(levels * self.to_step + remainder) + &line[spaces..]
430    }
431    pub fn rewrite(&self, src: &str) -> String {
432        src.lines()
433            .map(|l| self.rewrite_line(l))
434            .collect::<Vec<_>>()
435            .join("\n")
436    }
437}
438#[derive(Debug, Clone, Default)]
439pub struct MultilineStringTracker {
440    pub in_string: bool,
441    pub open_col: usize,
442    pub line_count: usize,
443}
444impl MultilineStringTracker {
445    pub fn new() -> Self {
446        Self::default()
447    }
448    pub fn open(&mut self, col: usize) {
449        self.in_string = true;
450        self.open_col = col;
451        self.line_count = 0;
452    }
453    pub fn close(&mut self) {
454        self.in_string = false;
455        self.open_col = 0;
456        self.line_count = 0;
457    }
458    pub fn next_line(&mut self) {
459        if self.in_string {
460            self.line_count += 1;
461        }
462    }
463    pub fn has_unescaped_quote(line: &str) -> bool {
464        let mut escaped = false;
465        for ch in line.chars() {
466            if escaped {
467                escaped = false;
468            } else if ch == '\\' {
469                escaped = true;
470            } else if ch == '"' {
471                return true;
472            }
473        }
474        false
475    }
476}
477#[derive(Debug, Clone, Default)]
478pub struct IndentConsistencyReport {
479    pub step: usize,
480    pub uses_tabs: bool,
481    pub uses_spaces: bool,
482    pub is_mixed: bool,
483    pub total_indented: usize,
484}
485impl IndentConsistencyReport {
486    pub fn from_source(src: &str) -> Self {
487        let stats = IndentStats::analyse(src);
488        let deltas = IndentDelta::compute_all(src, 4);
489        let steps: Vec<usize> = deltas
490            .iter()
491            .filter(|d| d.is_increase())
492            .map(|d| d.after - d.before)
493            .collect();
494        let step = steps.iter().copied().fold(0usize, gcd);
495        IndentConsistencyReport {
496            step,
497            uses_tabs: stats.tab_lines > 0 || stats.mixed_lines > 0,
498            uses_spaces: stats.space_lines > 0 || stats.mixed_lines > 0,
499            is_mixed: stats.mixed_lines > 0,
500            total_indented: stats.code_lines(),
501        }
502    }
503}
504#[derive(Debug, Clone, Copy, PartialEq, Eq)]
505pub enum WhitespaceKind {
506    None,
507    Space,
508    MultiSpace(usize),
509    Newline,
510    MultiNewline(usize),
511    Mixed,
512}
513impl WhitespaceKind {
514    pub fn classify(src: &str, start: usize, end: usize) -> WhitespaceKind {
515        let slice = &src[start..end];
516        if slice.is_empty() {
517            return WhitespaceKind::None;
518        }
519        let newline_count = slice.chars().filter(|&c| c == '\n').count();
520        let space_count = slice.chars().filter(|&c| c == ' ').count();
521        if newline_count == 0 {
522            if space_count == 1 {
523                WhitespaceKind::Space
524            } else {
525                WhitespaceKind::MultiSpace(space_count)
526            }
527        } else if space_count == 0 {
528            if newline_count == 1 {
529                WhitespaceKind::Newline
530            } else {
531                WhitespaceKind::MultiNewline(newline_count)
532            }
533        } else {
534            WhitespaceKind::Mixed
535        }
536    }
537    pub fn contains_newline(self) -> bool {
538        matches!(
539            self,
540            WhitespaceKind::Newline | WhitespaceKind::MultiNewline(_) | WhitespaceKind::Mixed
541        )
542    }
543}
544#[derive(Debug)]
545pub struct BlockParser {
546    pub tab_width: usize,
547    block_stack: Vec<usize>,
548}
549impl BlockParser {
550    pub fn new(tab_width: usize) -> Self {
551        Self {
552            tab_width,
553            block_stack: Vec::new(),
554        }
555    }
556    pub fn parse_blocks<'a>(&mut self, lines: &[&'a str]) -> Vec<Vec<&'a str>> {
557        let mut result: Vec<Vec<&'a str>> = Vec::new();
558        let mut current: Vec<&'a str> = Vec::new();
559        for &line in lines {
560            if line.trim().is_empty() {
561                current.push(line);
562                continue;
563            }
564            let col = LayoutContext::indent_col(line, self.tab_width);
565            match self.block_stack.last().copied() {
566                None => {
567                    self.block_stack.push(col);
568                    current.push(line);
569                }
570                Some(base) => {
571                    if col >= base {
572                        current.push(line);
573                    } else {
574                        if !current.is_empty() {
575                            result.push(current.clone());
576                            current.clear();
577                        }
578                        self.block_stack.pop();
579                        self.block_stack.push(col);
580                        current.push(line);
581                    }
582                }
583            }
584        }
585        if !current.is_empty() {
586            result.push(current);
587        }
588        result
589    }
590    pub fn reset(&mut self) {
591        self.block_stack.clear();
592    }
593    pub fn depth(&self) -> usize {
594        self.block_stack.len()
595    }
596}
597#[derive(Debug, Clone, Default)]
598pub struct LetBindingTracker {
599    bindings: Vec<LetBinding>,
600}
601impl LetBindingTracker {
602    pub fn new() -> Self {
603        Self {
604            bindings: Vec::new(),
605        }
606    }
607    pub fn push(&mut self, binding: LetBinding) {
608        self.bindings.push(binding);
609    }
610    pub fn pop(&mut self) -> Option<LetBinding> {
611        self.bindings.pop()
612    }
613    pub fn resolve(&self, name: &str) -> Option<&LetBinding> {
614        self.bindings.iter().rev().find(|b| b.name == name)
615    }
616    pub fn all(&self) -> &[LetBinding] {
617        &self.bindings
618    }
619    pub fn clear(&mut self) {
620        self.bindings.clear();
621    }
622    pub fn len(&self) -> usize {
623        self.bindings.len()
624    }
625    pub fn is_empty(&self) -> bool {
626        self.bindings.is_empty()
627    }
628}
629#[derive(Debug, Clone, Default)]
630pub struct OpenBraceTracker {
631    stack: Vec<(char, usize, usize)>,
632}
633impl OpenBraceTracker {
634    pub fn new() -> Self {
635        Self::default()
636    }
637    pub fn push(&mut self, ch: char, line: usize, col: usize) {
638        self.stack.push((ch, line, col));
639    }
640    pub fn pop(
641        &mut self,
642        ch: char,
643        line: usize,
644        col: usize,
645    ) -> Result<(), (char, char, usize, usize)> {
646        let expected = Self::matching_open(ch);
647        match self.stack.last() {
648            Some(&(top, _, _)) if top == expected => {
649                self.stack.pop();
650                Ok(())
651            }
652            Some(&(top, open_line, open_col)) => Err((top, ch, open_line, open_col)),
653            None => Err(('?', ch, line, col)),
654        }
655    }
656    pub fn matching_open(close: char) -> char {
657        match close {
658            ')' => '(',
659            ']' => '[',
660            '}' => '{',
661            _ => '\0',
662        }
663    }
664    pub fn is_balanced(&self) -> bool {
665        self.stack.is_empty()
666    }
667    pub fn unmatched(&self) -> &[(char, usize, usize)] {
668        &self.stack
669    }
670    pub fn clear(&mut self) {
671        self.stack.clear();
672    }
673}
674/// Tracks `where` blocks and their item indentation.
675pub struct WhereBlockTracker {
676    pub indent_stack: IndentStack,
677    pub in_where_block: bool,
678    /// Each entry is (item_name, indent_width).
679    pub where_items: Vec<(String, usize)>,
680}
681impl WhereBlockTracker {
682    pub fn new() -> Self {
683        Self {
684            indent_stack: IndentStack::new(4),
685            in_where_block: false,
686            where_items: Vec::new(),
687        }
688    }
689    /// Enter a `where` block at the given base indentation.
690    pub fn enter_where(&mut self, base_indent: IndentLevel) {
691        self.in_where_block = true;
692        self.indent_stack.push(base_indent);
693    }
694    /// Exit the current `where` block.
695    pub fn exit_where(&mut self) {
696        self.in_where_block = false;
697        self.indent_stack.pop();
698        self.where_items.clear();
699    }
700    /// Register a named item inside the where block at the given indent.
701    pub fn add_where_item(&mut self, name: &str, indent: IndentLevel) {
702        let tw = self.indent_stack.tab_width;
703        self.where_items
704            .push((name.to_string(), indent.total_width(tw)));
705    }
706    /// Check whether `line` at `indent` is a valid where-block item.
707    pub fn is_where_item(&self, _line: &str, indent: IndentLevel) -> bool {
708        if !self.in_where_block {
709            return false;
710        }
711        let tw = self.indent_stack.tab_width;
712        match self.indent_stack.current() {
713            Some(base) => indent.is_deeper_than(base, tw),
714            None => false,
715        }
716    }
717    /// Parse the leading whitespace of a line into an `IndentLevel`.
718    pub fn parse_leading_whitespace(line: &str) -> IndentLevel {
719        let mut spaces = 0usize;
720        let mut tabs = 0usize;
721        for ch in line.chars() {
722            match ch {
723                ' ' => spaces += 1,
724                '\t' => tabs += 1,
725                _ => break,
726            }
727        }
728        IndentLevel::new(spaces, tabs)
729    }
730}
731/// Classifies sequences of tokens based on their indentation patterns.
732/// Used to detect common OxiLean syntax patterns like `where` clauses.
733#[derive(Debug, Clone, PartialEq, Eq)]
734pub enum TokenSequenceClass {
735    /// A top-level definition with a `where` clause.
736    DefWithWhere,
737    /// A standalone definition without `where`.
738    PlainDef,
739    /// A `let` expression block.
740    LetBlock,
741    /// A `do`-notation block.
742    DoBlock,
743    /// Unknown/unclassified sequence.
744    Unknown,
745}
746/// Indentation mode: spaces vs tabs.
747#[allow(dead_code)]
748#[allow(missing_docs)]
749#[derive(Clone, Copy, Debug, PartialEq, Eq)]
750pub enum IndentMode {
751    Spaces(usize),
752    Tabs(usize),
753}
754impl IndentMode {
755    #[allow(dead_code)]
756    #[allow(missing_docs)]
757    pub fn unit_width(&self) -> usize {
758        match self {
759            Self::Spaces(n) | Self::Tabs(n) => *n,
760        }
761    }
762    #[allow(dead_code)]
763    #[allow(missing_docs)]
764    pub fn indent_str(&self, level: usize) -> String {
765        match self {
766            Self::Spaces(n) => " ".repeat(n * level),
767            Self::Tabs(_) => "\t".repeat(level),
768        }
769    }
770}
771#[derive(Debug, Clone, Default)]
772pub struct IndentHistory {
773    pub entries: Vec<(usize, usize)>,
774    pub tab_width: usize,
775}
776impl IndentHistory {
777    pub fn new(tab_width: usize) -> Self {
778        Self {
779            entries: Vec::new(),
780            tab_width,
781        }
782    }
783    pub fn record(&mut self, line_number: usize, line: &str) {
784        let col = LayoutContext::indent_col(line, self.tab_width);
785        self.entries.push((line_number, col));
786    }
787    pub fn from_source(src: &str, tab_width: usize) -> Self {
788        let mut hist = Self::new(tab_width);
789        for (i, line) in src.lines().enumerate() {
790            if !line.trim().is_empty() {
791                hist.record(i + 1, line);
792            }
793        }
794        hist
795    }
796    pub fn max_indent(&self) -> usize {
797        self.entries.iter().map(|&(_, w)| w).max().unwrap_or(0)
798    }
799    pub fn min_nonzero_indent(&self) -> Option<usize> {
800        self.entries
801            .iter()
802            .map(|&(_, w)| w)
803            .filter(|&w| w > 0)
804            .min()
805    }
806}
807/// Represents a "virtual" column position in a layout engine.
808#[allow(dead_code)]
809#[allow(missing_docs)]
810pub struct VirtualColumn {
811    pub column: usize,
812    pub tab_width: usize,
813}
814impl VirtualColumn {
815    #[allow(dead_code)]
816    #[allow(missing_docs)]
817    pub fn new(column: usize, tab_width: usize) -> Self {
818        Self { column, tab_width }
819    }
820    #[allow(dead_code)]
821    #[allow(missing_docs)]
822    pub fn advance_by(&self, n: usize) -> Self {
823        Self {
824            column: self.column + n,
825            ..*self
826        }
827    }
828    #[allow(dead_code)]
829    #[allow(missing_docs)]
830    pub fn advance_tab(&self) -> Self {
831        let next = ((self.column / self.tab_width) + 1) * self.tab_width;
832        Self {
833            column: next,
834            ..*self
835        }
836    }
837    #[allow(dead_code)]
838    #[allow(missing_docs)]
839    pub fn is_aligned_to(&self, n: usize) -> bool {
840        n > 0 && self.column % n == 0
841    }
842}
843#[derive(Debug, Clone, Default)]
844pub struct CommentTracker {
845    pub block_depth: usize,
846    pub in_line_comment: bool,
847}
848impl CommentTracker {
849    pub fn new() -> Self {
850        Self::default()
851    }
852    pub fn process_pair(&mut self, ch: char, next: char) {
853        if self.in_line_comment {
854            return;
855        }
856        if self.block_depth == 0 && ch == '-' && next == '-' {
857            self.in_line_comment = true;
858        } else if ch == '/' && next == '-' {
859            self.block_depth += 1;
860        } else if self.block_depth > 0 && ch == '-' && next == '/' {
861            self.block_depth -= 1;
862        }
863    }
864    pub fn end_of_line(&mut self) {
865        self.in_line_comment = false;
866    }
867    pub fn in_comment(&self) -> bool {
868        self.in_line_comment || self.block_depth > 0
869    }
870}
871#[derive(Debug, Clone, PartialEq, Eq)]
872pub enum LineClass {
873    Blank,
874    Comment,
875    DeclOpener,
876    WhereKeyword,
877    LetBinding,
878    Continuation,
879    Other,
880}
881impl LineClass {
882    pub fn classify(line: &str) -> LineClass {
883        let trimmed = line.trim_start();
884        if trimmed.is_empty() {
885            return LineClass::Blank;
886        }
887        if trimmed.starts_with("--") {
888            return LineClass::Comment;
889        }
890        let decl_openers = [
891            "def ",
892            "theorem ",
893            "lemma ",
894            "axiom ",
895            "structure ",
896            "class ",
897            "instance ",
898            "definition ",
899            "noncomputable ",
900            "private ",
901            "protected ",
902            "abbrev ",
903        ];
904        for opener in &decl_openers {
905            if trimmed.starts_with(opener) {
906                return LineClass::DeclOpener;
907            }
908        }
909        if let Some(after) = trimmed.strip_prefix("where") {
910            if after.is_empty()
911                || !after
912                    .chars()
913                    .next()
914                    .is_some_and(|c| c.is_alphanumeric() || c == '_')
915            {
916                return LineClass::WhereKeyword;
917            }
918        }
919        if trimmed.starts_with("let ") {
920            return LineClass::LetBinding;
921        }
922        LineClass::Other
923    }
924    pub fn is_ignorable(&self) -> bool {
925        matches!(self, LineClass::Blank | LineClass::Comment)
926    }
927    pub fn starts_decl(&self) -> bool {
928        matches!(self, LineClass::DeclOpener)
929    }
930}
931#[derive(Debug, Clone, Copy, PartialEq, Eq)]
932pub struct LineSpan {
933    pub start: usize,
934    pub end: usize,
935}
936impl LineSpan {
937    pub fn new(start: usize, end: usize) -> Self {
938        assert!(start <= end);
939        Self { start, end }
940    }
941    pub fn single(line: usize) -> Self {
942        Self {
943            start: line,
944            end: line,
945        }
946    }
947    pub fn len(&self) -> usize {
948        self.end - self.start + 1
949    }
950    /// Whether this span covers no lines.
951    pub fn is_empty(&self) -> bool {
952        self.end < self.start
953    }
954    pub fn is_single_line(&self) -> bool {
955        self.start == self.end
956    }
957    pub fn contains(&self, line: usize) -> bool {
958        line >= self.start && line <= self.end
959    }
960    pub fn merge(self, other: LineSpan) -> LineSpan {
961        LineSpan::new(self.start.min(other.start), self.end.max(other.end))
962    }
963}
964#[derive(Debug, Clone, PartialEq, Eq)]
965pub struct IndentGuide {
966    pub column: usize,
967    pub is_active: bool,
968}
969impl IndentGuide {
970    pub fn new(column: usize, is_active: bool) -> Self {
971        Self { column, is_active }
972    }
973}
974#[derive(Debug, Clone)]
975pub struct IndentFixer {
976    pub target_step: usize,
977    pub tab_width: usize,
978}
979impl IndentFixer {
980    pub fn new(target_step: usize, tab_width: usize) -> Self {
981        Self {
982            target_step,
983            tab_width,
984        }
985    }
986    pub fn fix(&self, src: &str) -> String {
987        let report = IndentConsistencyReport::from_source(src);
988        let from_step = if report.step > 0 {
989            report.step
990        } else {
991            self.target_step
992        };
993        let norm = IndentNormaliser::new(self.tab_width);
994        let rw = IndentRewriter::new(from_step, self.target_step);
995        rw.rewrite(&norm.normalise(src))
996    }
997}
998#[derive(Debug, Clone, PartialEq, Eq)]
999pub struct HangingIndent {
1000    pub first_col: usize,
1001    pub cont_col: usize,
1002}
1003impl HangingIndent {
1004    pub fn new(first_col: usize, cont_col: usize) -> Self {
1005        Self {
1006            first_col,
1007            cont_col,
1008        }
1009    }
1010    pub fn is_continuation(&self, col: usize) -> bool {
1011        col == self.cont_col
1012    }
1013    pub fn is_first(&self, col: usize) -> bool {
1014        col == self.first_col
1015    }
1016    pub fn overhang(&self) -> usize {
1017        self.cont_col.saturating_sub(self.first_col)
1018    }
1019}
1020#[derive(Debug, Clone)]
1021pub struct IndentNormaliser {
1022    pub tab_width: usize,
1023}
1024impl IndentNormaliser {
1025    pub fn new(tab_width: usize) -> Self {
1026        Self { tab_width }
1027    }
1028    pub fn normalise_line(&self, line: &str) -> String {
1029        let mut col = 0usize;
1030        let mut rest_start = 0;
1031        for (byte_idx, ch) in line.char_indices() {
1032            match ch {
1033                ' ' => {
1034                    col += 1;
1035                    rest_start = byte_idx + 1;
1036                }
1037                '\t' => {
1038                    let next_stop = (col / self.tab_width + 1) * self.tab_width;
1039                    col = next_stop;
1040                    rest_start = byte_idx + 1;
1041                }
1042                _ => break,
1043            }
1044        }
1045        " ".repeat(col) + &line[rest_start..]
1046    }
1047    pub fn normalise(&self, src: &str) -> String {
1048        src.lines()
1049            .map(|l| self.normalise_line(l))
1050            .collect::<Vec<_>>()
1051            .join("\n")
1052    }
1053}
1054/// A registry of construct indent rules.
1055#[allow(dead_code)]
1056#[allow(missing_docs)]
1057pub struct ConstructRuleRegistry {
1058    rules: std::collections::HashMap<String, ConstructIndentRule>,
1059}
1060impl ConstructRuleRegistry {
1061    #[allow(dead_code)]
1062    #[allow(missing_docs)]
1063    pub fn new() -> Self {
1064        let mut reg = Self {
1065            rules: std::collections::HashMap::new(),
1066        };
1067        reg.add(ConstructIndentRule::standard("def"));
1068        reg.add(ConstructIndentRule::standard("theorem"));
1069        reg.add(ConstructIndentRule::standard("match"));
1070        reg.add(ConstructIndentRule::standard("do"));
1071        reg.add(ConstructIndentRule::standard("where"));
1072        reg
1073    }
1074    #[allow(dead_code)]
1075    #[allow(missing_docs)]
1076    pub fn add(&mut self, rule: ConstructIndentRule) {
1077        self.rules.insert(rule.construct_name.clone(), rule);
1078    }
1079    #[allow(dead_code)]
1080    #[allow(missing_docs)]
1081    pub fn lookup(&self, name: &str) -> Option<&ConstructIndentRule> {
1082        self.rules.get(name)
1083    }
1084    #[allow(dead_code)]
1085    #[allow(missing_docs)]
1086    pub fn body_indent(&self, name: &str) -> usize {
1087        self.rules.get(name).map_or(2, |r| r.body_indent)
1088    }
1089}
1090/// A "column oracle" that can predict alignment positions.
1091#[allow(dead_code)]
1092pub struct ColumnOracle {
1093    tab_width: usize,
1094    reference_columns: Vec<usize>,
1095}
1096impl ColumnOracle {
1097    #[allow(dead_code)]
1098    #[allow(missing_docs)]
1099    pub fn new(tab_width: usize) -> Self {
1100        Self {
1101            tab_width,
1102            reference_columns: Vec::new(),
1103        }
1104    }
1105    #[allow(dead_code)]
1106    #[allow(missing_docs)]
1107    pub fn add_reference(&mut self, col: usize) {
1108        self.reference_columns.push(col);
1109    }
1110    #[allow(dead_code)]
1111    #[allow(missing_docs)]
1112    pub fn next_alignment(&self, current: usize) -> usize {
1113        self.reference_columns
1114            .iter()
1115            .copied()
1116            .filter(|&c| c > current)
1117            .min()
1118            .unwrap_or(((current / self.tab_width) + 1) * self.tab_width)
1119    }
1120    #[allow(dead_code)]
1121    #[allow(missing_docs)]
1122    pub fn is_at_reference(&self, col: usize) -> bool {
1123        self.reference_columns.contains(&col)
1124    }
1125}
1126#[derive(Debug, Clone)]
1127pub struct LetBinding {
1128    pub name: String,
1129    pub col: usize,
1130    pub has_type: bool,
1131}
1132impl LetBinding {
1133    pub fn new(name: impl Into<String>, col: usize, has_type: bool) -> Self {
1134        Self {
1135            name: name.into(),
1136            col,
1137            has_type,
1138        }
1139    }
1140}
1141#[derive(Debug, Clone)]
1142pub struct TabStopIterator {
1143    pub tab_width: usize,
1144    pub(super) current: usize,
1145}
1146impl TabStopIterator {
1147    pub fn new(tab_width: usize) -> Self {
1148        Self {
1149            tab_width,
1150            current: 0,
1151        }
1152    }
1153    pub fn from_col(tab_width: usize, start_col: usize) -> Self {
1154        let next_stop = if start_col % tab_width == 0 {
1155            start_col
1156        } else {
1157            (start_col / tab_width + 1) * tab_width
1158        };
1159        Self {
1160            tab_width,
1161            current: next_stop,
1162        }
1163    }
1164}
1165#[derive(Debug, Clone, Default)]
1166pub struct ColumnAligner {
1167    pub target_col: usize,
1168}
1169impl ColumnAligner {
1170    pub fn new(target_col: usize) -> Self {
1171        Self { target_col }
1172    }
1173    pub fn pad(&self, s: &str, current_col: usize) -> String {
1174        if current_col >= self.target_col {
1175            format!("{} ", s)
1176        } else {
1177            format!("{}{}", s, " ".repeat(self.target_col - current_col))
1178        }
1179    }
1180    pub fn align_all(items: &[String]) -> Vec<String> {
1181        let max_len = items.iter().map(|s| s.len()).max().unwrap_or(0);
1182        items
1183            .iter()
1184            .map(|s| format!("{}{}", s, " ".repeat(max_len - s.len())))
1185            .collect()
1186    }
1187}
1188/// An "indent block" that groups lines by their indent level.
1189#[allow(dead_code)]
1190#[allow(missing_docs)]
1191pub struct IndentBlock {
1192    pub level: usize,
1193    pub lines: Vec<String>,
1194}
1195impl IndentBlock {
1196    #[allow(dead_code)]
1197    #[allow(missing_docs)]
1198    pub fn new(level: usize) -> Self {
1199        Self {
1200            level,
1201            lines: Vec::new(),
1202        }
1203    }
1204    #[allow(dead_code)]
1205    #[allow(missing_docs)]
1206    pub fn add_line(&mut self, line: impl Into<String>) {
1207        self.lines.push(line.into());
1208    }
1209    #[allow(dead_code)]
1210    #[allow(missing_docs)]
1211    pub fn line_count(&self) -> usize {
1212        self.lines.len()
1213    }
1214    #[allow(dead_code)]
1215    #[allow(missing_docs)]
1216    pub fn render(&self) -> String {
1217        self.lines.join("\n")
1218    }
1219}
1220#[derive(Debug, Clone, Default)]
1221pub struct AlignedPrinter {
1222    entries: Vec<(usize, String, String)>,
1223    pub separator: String,
1224}
1225impl AlignedPrinter {
1226    pub fn new(separator: impl Into<String>) -> Self {
1227        Self {
1228            entries: Vec::new(),
1229            separator: separator.into(),
1230        }
1231    }
1232    pub fn add(&mut self, indent: usize, lhs: impl Into<String>, rhs: impl Into<String>) {
1233        self.entries.push((indent, lhs.into(), rhs.into()));
1234    }
1235    pub fn format(&self) -> String {
1236        let max_lhs = self
1237            .entries
1238            .iter()
1239            .map(|(_, lhs, _)| lhs.len())
1240            .max()
1241            .unwrap_or(0);
1242        let mut lines = Vec::new();
1243        for (indent, lhs, rhs) in &self.entries {
1244            let padding = " ".repeat(max_lhs - lhs.len());
1245            let indent_str = " ".repeat(*indent);
1246            lines.push(format!(
1247                "{}{}{} {} {}",
1248                indent_str, lhs, padding, self.separator, rhs
1249            ));
1250        }
1251        lines.join("\n")
1252    }
1253    pub fn clear(&mut self) {
1254        self.entries.clear();
1255    }
1256    pub fn len(&self) -> usize {
1257        self.entries.len()
1258    }
1259    pub fn is_empty(&self) -> bool {
1260        self.entries.is_empty()
1261    }
1262}
1263/// A position tracker that records all indent-change events.
1264#[allow(dead_code)]
1265#[allow(missing_docs)]
1266pub struct IndentChangeLog {
1267    events: Vec<(usize, usize, usize)>,
1268}
1269impl IndentChangeLog {
1270    #[allow(dead_code)]
1271    #[allow(missing_docs)]
1272    pub fn new() -> Self {
1273        Self { events: Vec::new() }
1274    }
1275    #[allow(dead_code)]
1276    #[allow(missing_docs)]
1277    pub fn record(&mut self, line: usize, old: usize, new: usize) {
1278        if old != new {
1279            self.events.push((line, old, new));
1280        }
1281    }
1282    #[allow(dead_code)]
1283    #[allow(missing_docs)]
1284    pub fn count(&self) -> usize {
1285        self.events.len()
1286    }
1287    #[allow(dead_code)]
1288    #[allow(missing_docs)]
1289    pub fn increases(&self) -> usize {
1290        self.events.iter().filter(|(_, o, n)| n > o).count()
1291    }
1292    #[allow(dead_code)]
1293    #[allow(missing_docs)]
1294    pub fn decreases(&self) -> usize {
1295        self.events.iter().filter(|(_, o, n)| n < o).count()
1296    }
1297}
1298#[derive(Debug, Clone, PartialEq, Eq)]
1299pub struct IndentDelta {
1300    pub line: usize,
1301    pub before: usize,
1302    pub after: usize,
1303}
1304impl IndentDelta {
1305    pub fn compute_all(src: &str, tab_width: usize) -> Vec<IndentDelta> {
1306        let mut deltas = Vec::new();
1307        let mut prev: Option<usize> = None;
1308        for (i, line) in src.lines().enumerate() {
1309            if line.trim().is_empty() {
1310                continue;
1311            }
1312            let col = LayoutContext::indent_col(line, tab_width);
1313            if let Some(p) = prev {
1314                if col != p {
1315                    deltas.push(IndentDelta {
1316                        line: i + 1,
1317                        before: p,
1318                        after: col,
1319                    });
1320                }
1321            }
1322            prev = Some(col);
1323        }
1324        deltas
1325    }
1326    pub fn is_increase(&self) -> bool {
1327        self.after > self.before
1328    }
1329    pub fn is_decrease(&self) -> bool {
1330        self.after < self.before
1331    }
1332    pub fn change(&self) -> i64 {
1333        self.after as i64 - self.before as i64
1334    }
1335}
1336#[derive(Debug, Clone, Default)]
1337pub struct IndentStats {
1338    pub space_lines: usize,
1339    pub tab_lines: usize,
1340    pub mixed_lines: usize,
1341    pub blank_lines: usize,
1342    pub most_common_step: usize,
1343}
1344impl IndentStats {
1345    pub fn analyse(src: &str) -> Self {
1346        let mut stats = IndentStats::default();
1347        let mut step_counts: std::collections::HashMap<usize, usize> =
1348            std::collections::HashMap::new();
1349        let mut prev_width: Option<usize> = None;
1350        for line in src.lines() {
1351            let trimmed = line.trim_start();
1352            if trimmed.is_empty() {
1353                stats.blank_lines += 1;
1354                continue;
1355            }
1356            let leading: String = line.chars().take_while(|c| c.is_whitespace()).collect();
1357            let has_tab = leading.contains('\t');
1358            let has_space = leading.contains(' ');
1359            if has_tab && has_space {
1360                stats.mixed_lines += 1;
1361            } else if has_tab {
1362                stats.tab_lines += 1;
1363            } else if has_space {
1364                stats.space_lines += 1;
1365            }
1366            let width: usize = leading.chars().map(|c| if c == '\t' { 4 } else { 1 }).sum();
1367            if let Some(pw) = prev_width {
1368                if width > pw {
1369                    *step_counts.entry(width - pw).or_insert(0) += 1;
1370                }
1371            }
1372            prev_width = Some(width);
1373        }
1374        if let Some((&step, _)) = step_counts.iter().max_by_key(|(_, &c)| c) {
1375            stats.most_common_step = step;
1376        }
1377        stats
1378    }
1379    pub fn code_lines(&self) -> usize {
1380        self.space_lines + self.tab_lines + self.mixed_lines
1381    }
1382    pub fn is_spaces_only(&self) -> bool {
1383        self.tab_lines == 0 && self.mixed_lines == 0
1384    }
1385    pub fn is_tabs_only(&self) -> bool {
1386        self.space_lines == 0 && self.mixed_lines == 0
1387    }
1388}
1389/// A "hanging indent" state: tracks when a line is a continuation of the previous.
1390#[allow(dead_code)]
1391#[allow(missing_docs)]
1392pub struct HangingIndentState {
1393    pub base_indent: usize,
1394    pub hanging: bool,
1395    pub hanging_indent: usize,
1396}
1397impl HangingIndentState {
1398    #[allow(dead_code)]
1399    #[allow(missing_docs)]
1400    pub fn new(base: usize) -> Self {
1401        Self {
1402            base_indent: base,
1403            hanging: false,
1404            hanging_indent: base + 4,
1405        }
1406    }
1407    #[allow(dead_code)]
1408    #[allow(missing_docs)]
1409    pub fn enter_hanging(&mut self) {
1410        self.hanging = true;
1411    }
1412    #[allow(dead_code)]
1413    #[allow(missing_docs)]
1414    pub fn exit_hanging(&mut self) {
1415        self.hanging = false;
1416    }
1417    #[allow(dead_code)]
1418    #[allow(missing_docs)]
1419    pub fn current_indent(&self) -> usize {
1420        if self.hanging {
1421            self.hanging_indent
1422        } else {
1423            self.base_indent
1424        }
1425    }
1426}
1427#[derive(Debug, Default)]
1428pub struct IndentationChecker {
1429    pub tab_width: usize,
1430    pub errors: Vec<IndentMismatchError>,
1431}
1432impl IndentationChecker {
1433    pub fn new(tab_width: usize) -> Self {
1434        Self {
1435            tab_width,
1436            errors: Vec::new(),
1437        }
1438    }
1439    pub fn check_transition(
1440        &mut self,
1441        line_number: usize,
1442        prev: &IndentLevel,
1443        next: &IndentLevel,
1444        context: &str,
1445    ) {
1446        let tw = self.tab_width;
1447        let pw = prev.total_width(tw);
1448        let nw = next.total_width(tw);
1449        if nw > pw && (nw - pw) % tw != 0 && tw > 0 {
1450            self.errors
1451                .push(IndentMismatchError::new(line_number, pw + tw, nw, context));
1452        }
1453    }
1454    pub fn is_clean(&self) -> bool {
1455        self.errors.is_empty()
1456    }
1457    pub fn clear(&mut self) {
1458        self.errors.clear();
1459    }
1460}
1461/// An indent "zipper": allows navigating up and down the indent hierarchy.
1462#[allow(dead_code)]
1463pub struct IndentZipper {
1464    above: Vec<(usize, String)>,
1465    focus: (usize, String),
1466    below: Vec<(usize, String)>,
1467}
1468impl IndentZipper {
1469    #[allow(dead_code)]
1470    #[allow(missing_docs)]
1471    pub fn from_source(source: &str) -> Option<Self> {
1472        let lines: Vec<_> = source
1473            .lines()
1474            .enumerate()
1475            .filter(|(_, l)| !l.trim().is_empty())
1476            .map(|(_, l)| {
1477                let indent = l.len() - l.trim_start().len();
1478                (indent, l.to_string())
1479            })
1480            .collect();
1481        if lines.is_empty() {
1482            return None;
1483        }
1484        let mut above = lines;
1485        let focus = above.remove(0);
1486        Some(Self {
1487            above: Vec::new(),
1488            focus,
1489            below: above,
1490        })
1491    }
1492    #[allow(dead_code)]
1493    #[allow(missing_docs)]
1494    pub fn move_down(&mut self) -> bool {
1495        if self.below.is_empty() {
1496            return false;
1497        }
1498        let next = self.below.remove(0);
1499        self.above.push(std::mem::replace(&mut self.focus, next));
1500        true
1501    }
1502    #[allow(dead_code)]
1503    #[allow(missing_docs)]
1504    pub fn move_up(&mut self) -> bool {
1505        if self.above.is_empty() {
1506            return false;
1507        }
1508        let prev = self
1509            .above
1510            .pop()
1511            .expect("above is non-empty per is_empty check above");
1512        self.below
1513            .insert(0, std::mem::replace(&mut self.focus, prev));
1514        true
1515    }
1516    #[allow(dead_code)]
1517    #[allow(missing_docs)]
1518    pub fn current_indent(&self) -> usize {
1519        self.focus.0
1520    }
1521    #[allow(dead_code)]
1522    #[allow(missing_docs)]
1523    pub fn current_content(&self) -> &str {
1524        &self.focus.1
1525    }
1526    #[allow(dead_code)]
1527    #[allow(missing_docs)]
1528    pub fn position(&self) -> usize {
1529        self.above.len()
1530    }
1531}
1532/// Indent rule for a specific construct.
1533#[allow(dead_code)]
1534#[allow(missing_docs)]
1535#[derive(Clone, Debug)]
1536pub struct ConstructIndentRule {
1537    pub construct_name: String,
1538    pub body_indent: usize,
1539    pub continuation_indent: usize,
1540    pub hanging_indent: usize,
1541}
1542impl ConstructIndentRule {
1543    #[allow(dead_code)]
1544    #[allow(missing_docs)]
1545    pub fn new(name: impl Into<String>, body: usize, cont: usize, hang: usize) -> Self {
1546        Self {
1547            construct_name: name.into(),
1548            body_indent: body,
1549            continuation_indent: cont,
1550            hanging_indent: hang,
1551        }
1552    }
1553    #[allow(dead_code)]
1554    #[allow(missing_docs)]
1555    pub fn standard(name: impl Into<String>) -> Self {
1556        Self::new(name, 2, 4, 4)
1557    }
1558}
1559#[derive(Debug, Clone, Default)]
1560pub struct ScopeTracker {
1561    scopes: Vec<Scope>,
1562}
1563impl ScopeTracker {
1564    pub fn new() -> Self {
1565        Self { scopes: Vec::new() }
1566    }
1567    pub fn enter(&mut self, kind: impl Into<String>, indent: IndentLevel) {
1568        self.scopes.push(Scope::new(kind, indent));
1569    }
1570    pub fn exit(&mut self) -> Option<Scope> {
1571        self.scopes.pop()
1572    }
1573    pub fn current(&self) -> Option<&Scope> {
1574        self.scopes.last()
1575    }
1576    pub fn current_mut(&mut self) -> Option<&mut Scope> {
1577        self.scopes.last_mut()
1578    }
1579    pub fn resolve(&self, name: &str) -> Option<&Scope> {
1580        self.scopes.iter().rev().find(|s| s.is_bound(name))
1581    }
1582    pub fn depth(&self) -> usize {
1583        self.scopes.len()
1584    }
1585    pub fn clear(&mut self) {
1586        self.scopes.clear();
1587    }
1588    pub fn bind(&mut self, name: impl Into<String>) {
1589        if let Some(scope) = self.current_mut() {
1590            scope.add_binding(name);
1591        }
1592    }
1593}