Skip to main content

oxilean_parse/span_util/
types.rs

1//! Auto-generated module
2//!
3//! 🤖 Generated with [SplitRS](https://github.com/cool-japan/splitrs)
4
5use super::functions::*;
6use crate::tokens::Span;
7
8/// A registry mapping `FileId` to file paths and source strings.
9#[allow(dead_code)]
10#[allow(missing_docs)]
11#[derive(Clone, Debug, Default)]
12pub struct FileRegistry {
13    entries: Vec<(FileId, String, String)>,
14    next_id: u32,
15}
16impl FileRegistry {
17    /// Create an empty registry.
18    #[allow(dead_code)]
19    #[allow(missing_docs)]
20    pub fn new() -> Self {
21        Self {
22            entries: Vec::new(),
23            next_id: 1,
24        }
25    }
26    /// Register a new file, returning its `FileId`.
27    #[allow(dead_code)]
28    #[allow(missing_docs)]
29    pub fn register(&mut self, path: impl Into<String>, source: impl Into<String>) -> FileId {
30        let id = FileId(self.next_id);
31        self.next_id += 1;
32        self.entries.push((id, path.into(), source.into()));
33        id
34    }
35    /// Look up the source string for a file.
36    #[allow(dead_code)]
37    #[allow(missing_docs)]
38    pub fn source(&self, id: FileId) -> Option<&str> {
39        self.entries
40            .iter()
41            .find(|(fid, _, _)| *fid == id)
42            .map(|(_, _, src)| src.as_str())
43    }
44    /// Look up the path for a file.
45    #[allow(dead_code)]
46    #[allow(missing_docs)]
47    pub fn path(&self, id: FileId) -> Option<&str> {
48        self.entries
49            .iter()
50            .find(|(fid, _, _)| *fid == id)
51            .map(|(_, p, _)| p.as_str())
52    }
53    /// Number of registered files.
54    #[allow(dead_code)]
55    #[allow(missing_docs)]
56    pub fn len(&self) -> usize {
57        self.entries.len()
58    }
59    /// `true` if no files are registered.
60    #[allow(dead_code)]
61    #[allow(missing_docs)]
62    pub fn is_empty(&self) -> bool {
63        self.entries.is_empty()
64    }
65    /// Extract text at a `FileSpan`.
66    #[allow(dead_code)]
67    #[allow(missing_docs)]
68    pub fn extract(&self, fspan: &FileSpan) -> &str {
69        self.source(fspan.file)
70            .and_then(|src| src.get(fspan.span.start..fspan.span.end))
71            .unwrap_or("")
72    }
73}
74/// A diagnostic span: a source span with severity and message.
75#[allow(dead_code)]
76#[allow(missing_docs)]
77#[derive(Clone, Debug)]
78pub struct DiagnosticSpan {
79    /// The source span.
80    pub span: Span,
81    /// Severity level.
82    pub severity: SpanSeverity,
83    /// Diagnostic message.
84    #[allow(missing_docs)]
85    pub message: String,
86}
87impl DiagnosticSpan {
88    /// Create a new diagnostic span.
89    #[allow(dead_code)]
90    #[allow(missing_docs)]
91    pub fn new(span: Span, severity: SpanSeverity, message: impl Into<String>) -> Self {
92        Self {
93            span,
94            severity,
95            message: message.into(),
96        }
97    }
98    /// Create an error diagnostic.
99    #[allow(dead_code)]
100    #[allow(missing_docs)]
101    pub fn error(span: Span, message: impl Into<String>) -> Self {
102        Self::new(span, SpanSeverity::Error, message)
103    }
104    /// Create a warning diagnostic.
105    #[allow(dead_code)]
106    #[allow(missing_docs)]
107    pub fn warning(span: Span, message: impl Into<String>) -> Self {
108        Self::new(span, SpanSeverity::Warning, message)
109    }
110    /// Create an info diagnostic.
111    #[allow(dead_code)]
112    #[allow(missing_docs)]
113    pub fn info(span: Span, message: impl Into<String>) -> Self {
114        Self::new(span, SpanSeverity::Info, message)
115    }
116    /// Format as a short string for display.
117    #[allow(dead_code)]
118    #[allow(missing_docs)]
119    pub fn format_short(&self) -> String {
120        format!(
121            "[{}] {}: {}",
122            self.severity.label(),
123            span_short(&self.span),
124            self.message
125        )
126    }
127}
128/// A span paired with its provenance information.
129#[allow(dead_code)]
130#[allow(missing_docs)]
131#[derive(Clone, Debug)]
132pub struct ProvenanceSpan {
133    pub span: Span,
134    pub origin: SpanOrigin,
135}
136impl ProvenanceSpan {
137    #[allow(dead_code)]
138    #[allow(missing_docs)]
139    pub fn new(span: Span, origin: SpanOrigin) -> Self {
140        Self { span, origin }
141    }
142    #[allow(dead_code)]
143    #[allow(missing_docs)]
144    pub fn user(span: Span) -> Self {
145        Self::new(span, SpanOrigin::UserSource)
146    }
147    #[allow(dead_code)]
148    #[allow(missing_docs)]
149    pub fn synthetic() -> Self {
150        Self::new(dummy_span(), SpanOrigin::Synthetic)
151    }
152    #[allow(dead_code)]
153    #[allow(missing_docs)]
154    pub fn macro_expanded(span: Span, macro_name: impl Into<String>) -> Self {
155        Self::new(
156            span,
157            SpanOrigin::MacroExpanded {
158                macro_name: macro_name.into(),
159            },
160        )
161    }
162}
163/// A collection of annotated spans.
164#[allow(dead_code)]
165#[allow(missing_docs)]
166#[derive(Clone, Debug, Default)]
167pub struct SpanAnnotations {
168    items: Vec<AnnotatedSpan>,
169}
170impl SpanAnnotations {
171    #[allow(dead_code)]
172    #[allow(missing_docs)]
173    pub fn new() -> Self {
174        Self::default()
175    }
176    #[allow(dead_code)]
177    #[allow(missing_docs)]
178    pub fn add(&mut self, a: AnnotatedSpan) {
179        self.items.push(a);
180    }
181    #[allow(dead_code)]
182    #[allow(missing_docs)]
183    pub fn annotate(&mut self, span: Span, text: impl Into<String>) {
184        self.items.push(AnnotatedSpan::new(span, text));
185    }
186    #[allow(dead_code)]
187    #[allow(missing_docs)]
188    pub fn at_offset(&self, offset: usize) -> Vec<&AnnotatedSpan> {
189        self.items
190            .iter()
191            .filter(|a| span_contains(&a.span, offset))
192            .collect()
193    }
194    #[allow(dead_code)]
195    #[allow(missing_docs)]
196    pub fn with_tag<'a>(&'a self, tag: &str) -> Vec<&'a AnnotatedSpan> {
197        self.items
198            .iter()
199            .filter(|a| a.tag.as_deref() == Some(tag))
200            .collect()
201    }
202    #[allow(dead_code)]
203    #[allow(missing_docs)]
204    pub fn len(&self) -> usize {
205        self.items.len()
206    }
207    #[allow(dead_code)]
208    #[allow(missing_docs)]
209    pub fn is_empty(&self) -> bool {
210        self.items.is_empty()
211    }
212    #[allow(dead_code)]
213    #[allow(missing_docs)]
214    pub fn sort_by_start(&mut self) {
215        self.items.sort_by_key(|a| a.span.start);
216    }
217    #[allow(dead_code)]
218    #[allow(missing_docs)]
219    pub fn iter(&self) -> impl Iterator<Item = &AnnotatedSpan> {
220        self.items.iter()
221    }
222    #[allow(dead_code)]
223    #[allow(missing_docs)]
224    pub fn clear(&mut self) {
225        self.items.clear();
226    }
227    #[allow(dead_code)]
228    #[allow(missing_docs)]
229    pub fn merge(&mut self, other: SpanAnnotations) {
230        self.items.extend(other.items);
231    }
232}
233/// Incrementally build a `Span` from individual character positions.
234#[derive(Clone, Debug)]
235#[allow(missing_docs)]
236pub struct SpanBuilder {
237    start: usize,
238    line: usize,
239    column: usize,
240}
241impl SpanBuilder {
242    /// Begin a new span at `start` (byte offset, 1-indexed line/col).
243    #[allow(missing_docs)]
244    pub fn new(start: usize, line: usize, column: usize) -> Self {
245        Self {
246            start,
247            line,
248            column,
249        }
250    }
251    /// Finish the span at `end` (byte offset).
252    #[allow(missing_docs)]
253    pub fn finish(&self, end: usize) -> Span {
254        Span::new(self.start, end, self.line, self.column)
255    }
256    /// Return the start byte offset.
257    #[allow(missing_docs)]
258    pub fn start(&self) -> usize {
259        self.start
260    }
261    /// Return the start `SourcePos`.
262    #[allow(missing_docs)]
263    pub fn pos(&self) -> SourcePos {
264        SourcePos::new(self.line, self.column)
265    }
266}
267/// Convert a UTF-8 byte span to a UTF-16 code-unit span.
268///
269/// UTF-16 spans are used by LSP (Language Server Protocol).
270#[allow(dead_code)]
271#[allow(missing_docs)]
272pub struct Utf16Span {
273    /// Start UTF-16 code-unit index on the line.
274    pub start_utf16: usize,
275    /// End UTF-16 code-unit index on the line.
276    pub end_utf16: usize,
277    /// Line number (0-indexed for LSP).
278    #[allow(missing_docs)]
279    pub line: usize,
280}
281/// Statistics about a collection of spans.
282#[allow(dead_code)]
283#[allow(missing_docs)]
284#[derive(Clone, Debug, Default)]
285pub struct SpanStats {
286    pub count: usize,
287    pub total_len: usize,
288    pub min_len: usize,
289    pub max_len: usize,
290}
291impl SpanStats {
292    #[allow(dead_code)]
293    #[allow(missing_docs)]
294    pub fn compute(spans: &[Span]) -> Self {
295        if spans.is_empty() {
296            return Self::default();
297        }
298        let count = spans.len();
299        let lengths: Vec<usize> = spans.iter().map(span_len).collect();
300        let total_len: usize = lengths.iter().sum();
301        let min_len = *lengths.iter().min().unwrap_or(&0);
302        let max_len = *lengths.iter().max().unwrap_or(&0);
303        Self {
304            count,
305            total_len,
306            min_len,
307            max_len,
308        }
309    }
310    #[allow(dead_code)]
311    #[allow(missing_docs)]
312    pub fn avg_len(&self) -> usize {
313        self.total_len.checked_div(self.count).unwrap_or(0)
314    }
315}
316/// Identifies a source file by an integer ID.
317#[allow(dead_code)]
318#[allow(missing_docs)]
319#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
320pub struct FileId(pub u32);
321impl FileId {
322    /// The "unknown" / dummy file ID.
323    #[allow(dead_code)]
324    #[allow(missing_docs)]
325    pub const UNKNOWN: FileId = FileId(0);
326    /// Create a new file ID.
327    #[allow(dead_code)]
328    #[allow(missing_docs)]
329    pub fn new(id: u32) -> Self {
330        FileId(id)
331    }
332    /// Returns the raw integer ID.
333    #[allow(dead_code)]
334    #[allow(missing_docs)]
335    pub fn raw(self) -> u32 {
336        self.0
337    }
338}
339/// A linear chain of spans.
340#[allow(dead_code)]
341#[allow(missing_docs)]
342#[derive(Clone, Debug, Default)]
343pub struct SpanChain {
344    spans: Vec<Span>,
345}
346impl SpanChain {
347    #[allow(dead_code)]
348    #[allow(missing_docs)]
349    pub fn new() -> Self {
350        Self::default()
351    }
352    #[allow(dead_code)]
353    #[allow(missing_docs)]
354    pub fn push(&mut self, span: Span) {
355        self.spans.push(span);
356    }
357    #[allow(dead_code)]
358    #[allow(missing_docs)]
359    pub fn total_span(&self) -> Option<Span> {
360        merge_spans(&self.spans)
361    }
362    #[allow(dead_code)]
363    #[allow(missing_docs)]
364    pub fn len(&self) -> usize {
365        self.spans.len()
366    }
367    #[allow(dead_code)]
368    #[allow(missing_docs)]
369    pub fn is_empty(&self) -> bool {
370        self.spans.is_empty()
371    }
372    #[allow(dead_code)]
373    #[allow(missing_docs)]
374    pub fn iter(&self) -> impl Iterator<Item = &Span> {
375        self.spans.iter()
376    }
377    #[allow(dead_code)]
378    #[allow(missing_docs)]
379    pub fn first(&self) -> Option<&Span> {
380        self.spans.first()
381    }
382    #[allow(dead_code)]
383    #[allow(missing_docs)]
384    pub fn last(&self) -> Option<&Span> {
385        self.spans.last()
386    }
387    #[allow(dead_code)]
388    #[allow(missing_docs)]
389    pub fn clear(&mut self) {
390        self.spans.clear();
391    }
392}
393/// A registry that maps string keys to `Span` values.
394///
395/// Useful for tracking where named items were defined in a source file.
396#[derive(Clone, Debug, Default)]
397#[allow(missing_docs)]
398pub struct SpanRegistry {
399    entries: std::collections::HashMap<String, Span>,
400}
401impl SpanRegistry {
402    /// Create an empty registry.
403    #[allow(missing_docs)]
404    pub fn new() -> Self {
405        Self {
406            entries: std::collections::HashMap::new(),
407        }
408    }
409    /// Register a span for `key`.
410    #[allow(missing_docs)]
411    pub fn register(&mut self, key: impl Into<String>, span: Span) {
412        self.entries.insert(key.into(), span);
413    }
414    /// Look up the span for `key`.
415    #[allow(missing_docs)]
416    pub fn get(&self, key: &str) -> Option<&Span> {
417        self.entries.get(key)
418    }
419    /// `true` if `key` is registered.
420    #[allow(missing_docs)]
421    pub fn contains(&self, key: &str) -> bool {
422        self.entries.contains_key(key)
423    }
424    /// Number of registered entries.
425    #[allow(missing_docs)]
426    pub fn len(&self) -> usize {
427        self.entries.len()
428    }
429    /// `true` if empty.
430    #[allow(missing_docs)]
431    pub fn is_empty(&self) -> bool {
432        self.entries.is_empty()
433    }
434    /// Iterate over all `(key, span)` pairs.
435    #[allow(missing_docs)]
436    pub fn iter(&self) -> impl Iterator<Item = (&String, &Span)> {
437        self.entries.iter()
438    }
439    /// Remove an entry, returning its span.
440    #[allow(missing_docs)]
441    pub fn remove(&mut self, key: &str) -> Option<Span> {
442        self.entries.remove(key)
443    }
444    /// Merge another registry into this one (other wins on conflict).
445    #[allow(missing_docs)]
446    pub fn merge(&mut self, other: SpanRegistry) {
447        for (k, v) in other.entries {
448            self.entries.insert(k, v);
449        }
450    }
451}
452/// A span together with a priority level.
453#[allow(dead_code)]
454#[allow(missing_docs)]
455#[derive(Clone, Debug)]
456pub struct PrioritizedSpan {
457    pub span: Span,
458    pub priority: u32,
459}
460impl PrioritizedSpan {
461    #[allow(dead_code)]
462    #[allow(missing_docs)]
463    pub fn new(span: Span, priority: u32) -> Self {
464        Self { span, priority }
465    }
466}
467/// A named source range: a label plus a span.
468///
469/// Used in diagnostics to annotate specific regions of source text.
470#[derive(Clone, Debug, PartialEq)]
471#[allow(missing_docs)]
472pub struct LabeledSpan {
473    /// Human-readable label for this region.
474    pub label: String,
475    /// The source span.
476    pub span: Span,
477}
478impl LabeledSpan {
479    /// Construct a new labeled span.
480    #[allow(missing_docs)]
481    pub fn new(label: impl Into<String>, span: Span) -> Self {
482        Self {
483            label: label.into(),
484            span,
485        }
486    }
487    /// Return the length of the underlying span.
488    #[allow(missing_docs)]
489    pub fn len(&self) -> usize {
490        span_len(&self.span)
491    }
492    /// `true` if the span is zero-length.
493    #[allow(missing_docs)]
494    pub fn is_empty(&self) -> bool {
495        self.len() == 0
496    }
497}
498/// A cursor that tracks the current byte position, line, and column while
499/// scanning a source string.
500#[derive(Clone, Debug)]
501#[allow(missing_docs)]
502pub struct SourceCursor<'a> {
503    source: &'a str,
504    pos: usize,
505    line: usize,
506    col: usize,
507}
508impl<'a> SourceCursor<'a> {
509    /// Create a new cursor at the beginning of `source`.
510    #[allow(missing_docs)]
511    pub fn new(source: &'a str) -> Self {
512        Self {
513            source,
514            pos: 0,
515            line: 1,
516            col: 1,
517        }
518    }
519    /// Return the current byte position.
520    #[allow(missing_docs)]
521    pub fn pos(&self) -> usize {
522        self.pos
523    }
524    /// Return the current `SourcePos` (1-indexed).
525    #[allow(missing_docs)]
526    pub fn source_pos(&self) -> SourcePos {
527        SourcePos::new(self.line, self.col)
528    }
529    /// Return the current `Span` (zero-length, at the current position).
530    #[allow(missing_docs)]
531    pub fn current_span(&self) -> Span {
532        Span::new(self.pos, self.pos, self.line, self.col)
533    }
534    /// `true` if the cursor is at the end of the source.
535    #[allow(missing_docs)]
536    pub fn is_eof(&self) -> bool {
537        self.pos >= self.source.len()
538    }
539    /// Peek at the next character without advancing.
540    #[allow(missing_docs)]
541    pub fn peek(&self) -> Option<char> {
542        self.source[self.pos..].chars().next()
543    }
544    /// Peek at the character `n` UTF-8 code points ahead.
545    #[allow(missing_docs)]
546    pub fn peek_ahead(&self, n: usize) -> Option<char> {
547        self.source[self.pos..].chars().nth(n)
548    }
549    /// Advance by one character and return it.
550    #[allow(missing_docs)]
551    pub fn advance(&mut self) -> Option<char> {
552        let ch = self.source[self.pos..].chars().next()?;
553        self.pos += ch.len_utf8();
554        if ch == '\n' {
555            self.line += 1;
556            self.col = 1;
557        } else {
558            self.col += 1;
559        }
560        Some(ch)
561    }
562    /// Advance while the predicate is true, returning the consumed substring.
563    #[allow(missing_docs)]
564    pub fn advance_while<F: Fn(char) -> bool>(&mut self, pred: F) -> &'a str {
565        let start = self.pos;
566        while let Some(ch) = self.peek() {
567            if pred(ch) {
568                self.advance();
569            } else {
570                break;
571            }
572        }
573        &self.source[start..self.pos]
574    }
575    /// Consume exactly `n` bytes (assumes ASCII for simplicity).
576    #[allow(missing_docs)]
577    pub fn advance_bytes(&mut self, n: usize) {
578        for _ in 0..n {
579            self.advance();
580        }
581    }
582    /// Create a `Span` starting at `start_pos` and ending at the current position.
583    #[allow(missing_docs)]
584    pub fn span_from(&self, start: usize, start_line: usize, start_col: usize) -> Span {
585        Span::new(start, self.pos, start_line, start_col)
586    }
587    /// Return the remaining (unconsumed) source text.
588    #[allow(missing_docs)]
589    pub fn rest(&self) -> &'a str {
590        &self.source[self.pos..]
591    }
592    /// Return the consumed source text.
593    #[allow(missing_docs)]
594    pub fn consumed(&self) -> &'a str {
595        &self.source[..self.pos]
596    }
597}
598/// A collection of diagnostics associated with a source file.
599#[allow(dead_code)]
600#[allow(missing_docs)]
601#[derive(Clone, Debug, Default)]
602pub struct DiagnosticSet {
603    diagnostics: Vec<DiagnosticSpan>,
604}
605impl DiagnosticSet {
606    /// Create an empty set.
607    #[allow(dead_code)]
608    #[allow(missing_docs)]
609    pub fn new() -> Self {
610        Self::default()
611    }
612    /// Add a diagnostic.
613    #[allow(dead_code)]
614    #[allow(missing_docs)]
615    pub fn add(&mut self, d: DiagnosticSpan) {
616        self.diagnostics.push(d);
617    }
618    /// Add an error.
619    #[allow(dead_code)]
620    #[allow(missing_docs)]
621    pub fn add_error(&mut self, span: Span, msg: impl Into<String>) {
622        self.add(DiagnosticSpan::error(span, msg));
623    }
624    /// Add a warning.
625    #[allow(dead_code)]
626    #[allow(missing_docs)]
627    pub fn add_warning(&mut self, span: Span, msg: impl Into<String>) {
628        self.add(DiagnosticSpan::warning(span, msg));
629    }
630    /// Add an info.
631    #[allow(dead_code)]
632    #[allow(missing_docs)]
633    pub fn add_info(&mut self, span: Span, msg: impl Into<String>) {
634        self.add(DiagnosticSpan::info(span, msg));
635    }
636    /// Count diagnostics of a given severity.
637    #[allow(dead_code)]
638    #[allow(missing_docs)]
639    pub fn count_severity(&self, sev: &SpanSeverity) -> usize {
640        self.diagnostics
641            .iter()
642            .filter(|d| &d.severity == sev)
643            .count()
644    }
645    /// Total number of diagnostics.
646    #[allow(dead_code)]
647    #[allow(missing_docs)]
648    pub fn len(&self) -> usize {
649        self.diagnostics.len()
650    }
651    /// Check if empty.
652    #[allow(dead_code)]
653    #[allow(missing_docs)]
654    pub fn is_empty(&self) -> bool {
655        self.diagnostics.is_empty()
656    }
657    /// Check if there are any errors.
658    #[allow(dead_code)]
659    #[allow(missing_docs)]
660    pub fn has_errors(&self) -> bool {
661        self.diagnostics.iter().any(|d| d.severity.is_error())
662    }
663    /// Get all errors.
664    #[allow(dead_code)]
665    #[allow(missing_docs)]
666    pub fn errors(&self) -> Vec<&DiagnosticSpan> {
667        self.diagnostics
668            .iter()
669            .filter(|d| d.severity.is_error())
670            .collect()
671    }
672    /// Get all warnings.
673    #[allow(dead_code)]
674    #[allow(missing_docs)]
675    pub fn warnings(&self) -> Vec<&DiagnosticSpan> {
676        self.diagnostics
677            .iter()
678            .filter(|d| matches!(d.severity, SpanSeverity::Warning))
679            .collect()
680    }
681    /// Sort diagnostics by span start offset.
682    #[allow(dead_code)]
683    #[allow(missing_docs)]
684    pub fn sort_by_position(&mut self) {
685        self.diagnostics.sort_by_key(|d| d.span.start);
686    }
687    /// Iterate over all diagnostics.
688    #[allow(dead_code)]
689    #[allow(missing_docs)]
690    pub fn iter(&self) -> impl Iterator<Item = &DiagnosticSpan> {
691        self.diagnostics.iter()
692    }
693    /// Clear all diagnostics.
694    #[allow(dead_code)]
695    #[allow(missing_docs)]
696    pub fn clear(&mut self) {
697        self.diagnostics.clear();
698    }
699    /// Merge another set into this one.
700    #[allow(dead_code)]
701    #[allow(missing_docs)]
702    pub fn merge(&mut self, other: DiagnosticSet) {
703        self.diagnostics.extend(other.diagnostics);
704    }
705}
706/// A span diff: describes changes between two spans.
707#[allow(dead_code)]
708#[allow(missing_docs)]
709#[derive(Debug, Clone)]
710pub struct SpanDiff {
711    /// The old span.
712    pub old: Span,
713    /// The new span.
714    pub new: Span,
715    /// The byte delta (positive = grew, negative = shrank).
716    #[allow(missing_docs)]
717    pub byte_delta: i64,
718}
719impl SpanDiff {
720    /// Compute the diff between two spans.
721    #[allow(dead_code)]
722    #[allow(missing_docs)]
723    pub fn compute(old: Span, new: Span) -> Self {
724        let byte_delta = new.end as i64 - old.end as i64;
725        Self {
726            old,
727            new,
728            byte_delta,
729        }
730    }
731    /// Whether the span grew.
732    #[allow(dead_code)]
733    #[allow(missing_docs)]
734    pub fn grew(&self) -> bool {
735        self.byte_delta > 0
736    }
737    /// Whether the span shrank.
738    #[allow(dead_code)]
739    #[allow(missing_docs)]
740    pub fn shrank(&self) -> bool {
741        self.byte_delta < 0
742    }
743    /// Whether the span is unchanged.
744    #[allow(dead_code)]
745    #[allow(missing_docs)]
746    pub fn unchanged(&self) -> bool {
747        self.byte_delta == 0
748    }
749}
750/// Tracks how a set of spans evolve as edits are applied.
751#[allow(dead_code)]
752#[allow(missing_docs)]
753#[derive(Clone, Debug, Default)]
754pub struct IncrementalSpanTracker {
755    spans: Vec<Span>,
756    edit_count: usize,
757}
758impl IncrementalSpanTracker {
759    #[allow(dead_code)]
760    #[allow(missing_docs)]
761    pub fn new() -> Self {
762        Self::default()
763    }
764    #[allow(dead_code)]
765    #[allow(missing_docs)]
766    pub fn track(&mut self, span: Span) {
767        self.spans.push(span);
768    }
769    #[allow(dead_code)]
770    #[allow(missing_docs)]
771    pub fn apply_edit(&mut self, edit_start: usize, delta: i64) {
772        shift_spans(&mut self.spans, edit_start, delta);
773        self.edit_count += 1;
774    }
775    #[allow(dead_code)]
776    #[allow(missing_docs)]
777    pub fn spans(&self) -> &[Span] {
778        &self.spans
779    }
780    #[allow(dead_code)]
781    #[allow(missing_docs)]
782    pub fn edit_count(&self) -> usize {
783        self.edit_count
784    }
785    #[allow(dead_code)]
786    #[allow(missing_docs)]
787    pub fn reset(&mut self) {
788        self.spans.clear();
789        self.edit_count = 0;
790    }
791}
792/// A value paired with its source `Span`.
793#[derive(Clone, Debug, PartialEq)]
794#[allow(missing_docs)]
795pub struct Spanned<T> {
796    /// The value.
797    pub value: T,
798    /// The source span.
799    pub span: Span,
800}
801impl<T> Spanned<T> {
802    /// Wrap `value` at `span`.
803    #[allow(missing_docs)]
804    pub fn new(value: T, span: Span) -> Self {
805        Self { value, span }
806    }
807    /// Map over the value, keeping the span.
808    #[allow(missing_docs)]
809    pub fn map<U, F: FnOnce(T) -> U>(self, f: F) -> Spanned<U> {
810        Spanned {
811            value: f(self.value),
812            span: self.span,
813        }
814    }
815    /// Borrow the inner value.
816    #[allow(clippy::should_implement_trait)]
817    #[allow(missing_docs)]
818    pub fn as_ref(&self) -> &T {
819        &self.value
820    }
821    /// Consume and return the value, discarding the span.
822    #[allow(missing_docs)]
823    pub fn into_value(self) -> T {
824        self.value
825    }
826}
827/// The origin of a span.
828#[allow(dead_code)]
829#[allow(missing_docs)]
830#[derive(Clone, Debug, PartialEq)]
831pub enum SpanOrigin {
832    UserSource,
833    MacroExpanded { macro_name: String },
834    Elaborated,
835    Synthetic,
836}
837impl SpanOrigin {
838    #[allow(dead_code)]
839    #[allow(missing_docs)]
840    pub fn is_user_source(&self) -> bool {
841        matches!(self, SpanOrigin::UserSource)
842    }
843    #[allow(dead_code)]
844    #[allow(missing_docs)]
845    pub fn is_synthetic(&self) -> bool {
846        matches!(self, SpanOrigin::Synthetic)
847    }
848    #[allow(dead_code)]
849    #[allow(missing_docs)]
850    pub fn kind_str(&self) -> &'static str {
851        match self {
852            SpanOrigin::UserSource => "user",
853            SpanOrigin::MacroExpanded { .. } => "macro",
854            SpanOrigin::Elaborated => "elab",
855            SpanOrigin::Synthetic => "synthetic",
856        }
857    }
858}
859/// A span paired with a severity level, for diagnostics.
860#[allow(dead_code)]
861#[allow(missing_docs)]
862#[derive(Clone, Debug, PartialEq)]
863pub enum SpanSeverity {
864    /// Informational annotation.
865    Info,
866    /// A warning annotation.
867    Warning,
868    /// An error annotation.
869    Error,
870}
871impl SpanSeverity {
872    /// Returns `true` if this is an error severity.
873    #[allow(dead_code)]
874    #[allow(missing_docs)]
875    pub fn is_error(&self) -> bool {
876        matches!(self, SpanSeverity::Error)
877    }
878    /// Short label string for this severity.
879    #[allow(dead_code)]
880    #[allow(missing_docs)]
881    pub fn label(&self) -> &'static str {
882        match self {
883            SpanSeverity::Info => "info",
884            SpanSeverity::Warning => "warning",
885            SpanSeverity::Error => "error",
886        }
887    }
888}
889/// A span paired with annotation text and optional tag.
890#[allow(dead_code)]
891#[allow(missing_docs)]
892#[derive(Clone, Debug, PartialEq)]
893pub struct AnnotatedSpan {
894    pub span: Span,
895    pub annotation: String,
896    pub tag: Option<String>,
897}
898impl AnnotatedSpan {
899    #[allow(dead_code)]
900    #[allow(missing_docs)]
901    pub fn new(span: Span, annotation: impl Into<String>) -> Self {
902        Self {
903            span,
904            annotation: annotation.into(),
905            tag: None,
906        }
907    }
908    #[allow(dead_code)]
909    #[allow(missing_docs)]
910    pub fn with_tag(span: Span, annotation: impl Into<String>, tag: impl Into<String>) -> Self {
911        Self {
912            span,
913            annotation: annotation.into(),
914            tag: Some(tag.into()),
915        }
916    }
917    #[allow(dead_code)]
918    #[allow(missing_docs)]
919    pub fn len(&self) -> usize {
920        span_len(&self.span)
921    }
922    #[allow(dead_code)]
923    #[allow(missing_docs)]
924    pub fn is_empty(&self) -> bool {
925        self.len() == 0
926    }
927}
928/// A half-open interval \[start, end) over span indices (for range operations).
929#[allow(dead_code)]
930#[allow(missing_docs)]
931#[derive(Clone, Copy, Debug, PartialEq, Eq)]
932pub struct SpanRange {
933    pub start_idx: usize,
934    pub end_idx: usize,
935}
936impl SpanRange {
937    #[allow(dead_code)]
938    #[allow(missing_docs)]
939    pub fn new(start_idx: usize, end_idx: usize) -> Self {
940        Self { start_idx, end_idx }
941    }
942    #[allow(dead_code)]
943    #[allow(missing_docs)]
944    pub fn len(&self) -> usize {
945        self.end_idx.saturating_sub(self.start_idx)
946    }
947    #[allow(dead_code)]
948    #[allow(missing_docs)]
949    pub fn is_empty(&self) -> bool {
950        self.start_idx >= self.end_idx
951    }
952    #[allow(dead_code)]
953    #[allow(missing_docs)]
954    pub fn contains(&self, idx: usize) -> bool {
955        idx >= self.start_idx && idx < self.end_idx
956    }
957}
958/// A span with a byte-level padding on each side.
959#[allow(dead_code)]
960#[allow(missing_docs)]
961#[derive(Clone, Debug)]
962pub struct PaddedSpan {
963    pub inner: Span,
964    pub left_pad: usize,
965    pub right_pad: usize,
966}
967impl PaddedSpan {
968    #[allow(dead_code)]
969    #[allow(missing_docs)]
970    pub fn new(inner: Span, left_pad: usize, right_pad: usize) -> Self {
971        Self {
972            inner,
973            left_pad,
974            right_pad,
975        }
976    }
977    /// Expand the inner span by the padding amounts, clamped to source bounds.
978    #[allow(dead_code)]
979    #[allow(missing_docs)]
980    pub fn expanded(&self, source_len: usize) -> Span {
981        let start = self.inner.start.saturating_sub(self.left_pad);
982        let end = (self.inner.end + self.right_pad).min(source_len);
983        Span::new(start, end, self.inner.line, self.inner.column)
984    }
985}
986/// A flat map from span start offsets to values.
987#[allow(dead_code)]
988#[allow(missing_docs)]
989#[derive(Clone, Debug, Default)]
990pub struct SpanMap<V> {
991    entries: Vec<(usize, V)>,
992}
993impl<V> SpanMap<V> {
994    /// Create an empty map.
995    #[allow(dead_code)]
996    #[allow(missing_docs)]
997    pub fn new() -> Self {
998        Self {
999            entries: Vec::new(),
1000        }
1001    }
1002    /// Insert a value at `offset`.
1003    #[allow(dead_code)]
1004    #[allow(missing_docs)]
1005    pub fn insert(&mut self, offset: usize, value: V) {
1006        self.entries.push((offset, value));
1007    }
1008    /// Look up the value at `offset`.
1009    #[allow(dead_code)]
1010    #[allow(missing_docs)]
1011    pub fn get(&self, offset: usize) -> Option<&V> {
1012        self.entries
1013            .iter()
1014            .find(|(o, _)| *o == offset)
1015            .map(|(_, v)| v)
1016    }
1017    /// Number of entries.
1018    #[allow(dead_code)]
1019    #[allow(missing_docs)]
1020    pub fn len(&self) -> usize {
1021        self.entries.len()
1022    }
1023    /// Check if empty.
1024    #[allow(dead_code)]
1025    #[allow(missing_docs)]
1026    pub fn is_empty(&self) -> bool {
1027        self.entries.is_empty()
1028    }
1029    /// Iterate over all entries.
1030    #[allow(dead_code)]
1031    #[allow(missing_docs)]
1032    pub fn iter(&self) -> impl Iterator<Item = &(usize, V)> {
1033        self.entries.iter()
1034    }
1035}
1036/// A span that also carries a `FileId`, enabling multi-file diagnostics.
1037#[allow(dead_code)]
1038#[allow(missing_docs)]
1039#[derive(Clone, Debug, PartialEq)]
1040pub struct FileSpan {
1041    /// Which file this span belongs to.
1042    pub file: FileId,
1043    /// The span within that file.
1044    pub span: Span,
1045}
1046impl FileSpan {
1047    /// Create a new file span.
1048    #[allow(dead_code)]
1049    #[allow(missing_docs)]
1050    pub fn new(file: FileId, span: Span) -> Self {
1051        Self { file, span }
1052    }
1053    /// Return the length in bytes.
1054    #[allow(dead_code)]
1055    #[allow(missing_docs)]
1056    pub fn len(&self) -> usize {
1057        span_len(&self.span)
1058    }
1059    /// `true` if this is a zero-length span.
1060    #[allow(dead_code)]
1061    #[allow(missing_docs)]
1062    pub fn is_empty(&self) -> bool {
1063        self.len() == 0
1064    }
1065    /// Merge two `FileSpan`s (panics if they come from different files).
1066    #[allow(dead_code)]
1067    #[allow(missing_docs)]
1068    pub fn merge_with(&self, other: &FileSpan) -> FileSpan {
1069        assert_eq!(
1070            self.file, other.file,
1071            "cannot merge spans from different files"
1072        );
1073        FileSpan {
1074            file: self.file,
1075            span: self.span.merge(&other.span),
1076        }
1077    }
1078}
1079/// A line/column position in source text (1-indexed).
1080#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
1081#[allow(missing_docs)]
1082pub struct SourcePos {
1083    /// 1-indexed line number.
1084    pub line: usize,
1085    /// 1-indexed column number (byte offset within the line).
1086    pub col: usize,
1087}
1088impl SourcePos {
1089    /// Construct a new `SourcePos`.
1090    #[allow(missing_docs)]
1091    pub fn new(line: usize, col: usize) -> Self {
1092        Self { line, col }
1093    }
1094    /// The "beginning of file" position.
1095    #[allow(missing_docs)]
1096    pub fn start() -> Self {
1097        Self { line: 1, col: 1 }
1098    }
1099    /// Advance by one ASCII character (no newline).
1100    #[allow(missing_docs)]
1101    pub fn advance_col(&self) -> Self {
1102        Self {
1103            line: self.line,
1104            col: self.col + 1,
1105        }
1106    }
1107    /// Advance to the next line (column resets to 1).
1108    #[allow(missing_docs)]
1109    pub fn advance_line(&self) -> Self {
1110        Self {
1111            line: self.line + 1,
1112            col: 1,
1113        }
1114    }
1115    /// `true` if `other` is on the same line as `self`.
1116    #[allow(missing_docs)]
1117    pub fn same_line(&self, other: &SourcePos) -> bool {
1118        self.line == other.line
1119    }
1120}