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        if self.count == 0 {
314            0
315        } else {
316            self.total_len / self.count
317        }
318    }
319}
320/// Identifies a source file by an integer ID.
321#[allow(dead_code)]
322#[allow(missing_docs)]
323#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
324pub struct FileId(pub u32);
325impl FileId {
326    /// The "unknown" / dummy file ID.
327    #[allow(dead_code)]
328    #[allow(missing_docs)]
329    pub const UNKNOWN: FileId = FileId(0);
330    /// Create a new file ID.
331    #[allow(dead_code)]
332    #[allow(missing_docs)]
333    pub fn new(id: u32) -> Self {
334        FileId(id)
335    }
336    /// Returns the raw integer ID.
337    #[allow(dead_code)]
338    #[allow(missing_docs)]
339    pub fn raw(self) -> u32 {
340        self.0
341    }
342}
343/// A linear chain of spans.
344#[allow(dead_code)]
345#[allow(missing_docs)]
346#[derive(Clone, Debug, Default)]
347pub struct SpanChain {
348    spans: Vec<Span>,
349}
350impl SpanChain {
351    #[allow(dead_code)]
352    #[allow(missing_docs)]
353    pub fn new() -> Self {
354        Self::default()
355    }
356    #[allow(dead_code)]
357    #[allow(missing_docs)]
358    pub fn push(&mut self, span: Span) {
359        self.spans.push(span);
360    }
361    #[allow(dead_code)]
362    #[allow(missing_docs)]
363    pub fn total_span(&self) -> Option<Span> {
364        merge_spans(&self.spans)
365    }
366    #[allow(dead_code)]
367    #[allow(missing_docs)]
368    pub fn len(&self) -> usize {
369        self.spans.len()
370    }
371    #[allow(dead_code)]
372    #[allow(missing_docs)]
373    pub fn is_empty(&self) -> bool {
374        self.spans.is_empty()
375    }
376    #[allow(dead_code)]
377    #[allow(missing_docs)]
378    pub fn iter(&self) -> impl Iterator<Item = &Span> {
379        self.spans.iter()
380    }
381    #[allow(dead_code)]
382    #[allow(missing_docs)]
383    pub fn first(&self) -> Option<&Span> {
384        self.spans.first()
385    }
386    #[allow(dead_code)]
387    #[allow(missing_docs)]
388    pub fn last(&self) -> Option<&Span> {
389        self.spans.last()
390    }
391    #[allow(dead_code)]
392    #[allow(missing_docs)]
393    pub fn clear(&mut self) {
394        self.spans.clear();
395    }
396}
397/// A registry that maps string keys to `Span` values.
398///
399/// Useful for tracking where named items were defined in a source file.
400#[derive(Clone, Debug, Default)]
401#[allow(missing_docs)]
402pub struct SpanRegistry {
403    entries: std::collections::HashMap<String, Span>,
404}
405impl SpanRegistry {
406    /// Create an empty registry.
407    #[allow(missing_docs)]
408    pub fn new() -> Self {
409        Self {
410            entries: std::collections::HashMap::new(),
411        }
412    }
413    /// Register a span for `key`.
414    #[allow(missing_docs)]
415    pub fn register(&mut self, key: impl Into<String>, span: Span) {
416        self.entries.insert(key.into(), span);
417    }
418    /// Look up the span for `key`.
419    #[allow(missing_docs)]
420    pub fn get(&self, key: &str) -> Option<&Span> {
421        self.entries.get(key)
422    }
423    /// `true` if `key` is registered.
424    #[allow(missing_docs)]
425    pub fn contains(&self, key: &str) -> bool {
426        self.entries.contains_key(key)
427    }
428    /// Number of registered entries.
429    #[allow(missing_docs)]
430    pub fn len(&self) -> usize {
431        self.entries.len()
432    }
433    /// `true` if empty.
434    #[allow(missing_docs)]
435    pub fn is_empty(&self) -> bool {
436        self.entries.is_empty()
437    }
438    /// Iterate over all `(key, span)` pairs.
439    #[allow(missing_docs)]
440    pub fn iter(&self) -> impl Iterator<Item = (&String, &Span)> {
441        self.entries.iter()
442    }
443    /// Remove an entry, returning its span.
444    #[allow(missing_docs)]
445    pub fn remove(&mut self, key: &str) -> Option<Span> {
446        self.entries.remove(key)
447    }
448    /// Merge another registry into this one (other wins on conflict).
449    #[allow(missing_docs)]
450    pub fn merge(&mut self, other: SpanRegistry) {
451        for (k, v) in other.entries {
452            self.entries.insert(k, v);
453        }
454    }
455}
456/// A span together with a priority level.
457#[allow(dead_code)]
458#[allow(missing_docs)]
459#[derive(Clone, Debug)]
460pub struct PrioritizedSpan {
461    pub span: Span,
462    pub priority: u32,
463}
464impl PrioritizedSpan {
465    #[allow(dead_code)]
466    #[allow(missing_docs)]
467    pub fn new(span: Span, priority: u32) -> Self {
468        Self { span, priority }
469    }
470}
471/// A named source range: a label plus a span.
472///
473/// Used in diagnostics to annotate specific regions of source text.
474#[derive(Clone, Debug, PartialEq)]
475#[allow(missing_docs)]
476pub struct LabeledSpan {
477    /// Human-readable label for this region.
478    pub label: String,
479    /// The source span.
480    pub span: Span,
481}
482impl LabeledSpan {
483    /// Construct a new labeled span.
484    #[allow(missing_docs)]
485    pub fn new(label: impl Into<String>, span: Span) -> Self {
486        Self {
487            label: label.into(),
488            span,
489        }
490    }
491    /// Return the length of the underlying span.
492    #[allow(missing_docs)]
493    pub fn len(&self) -> usize {
494        span_len(&self.span)
495    }
496    /// `true` if the span is zero-length.
497    #[allow(missing_docs)]
498    pub fn is_empty(&self) -> bool {
499        self.len() == 0
500    }
501}
502/// A cursor that tracks the current byte position, line, and column while
503/// scanning a source string.
504#[derive(Clone, Debug)]
505#[allow(missing_docs)]
506pub struct SourceCursor<'a> {
507    source: &'a str,
508    pos: usize,
509    line: usize,
510    col: usize,
511}
512impl<'a> SourceCursor<'a> {
513    /// Create a new cursor at the beginning of `source`.
514    #[allow(missing_docs)]
515    pub fn new(source: &'a str) -> Self {
516        Self {
517            source,
518            pos: 0,
519            line: 1,
520            col: 1,
521        }
522    }
523    /// Return the current byte position.
524    #[allow(missing_docs)]
525    pub fn pos(&self) -> usize {
526        self.pos
527    }
528    /// Return the current `SourcePos` (1-indexed).
529    #[allow(missing_docs)]
530    pub fn source_pos(&self) -> SourcePos {
531        SourcePos::new(self.line, self.col)
532    }
533    /// Return the current `Span` (zero-length, at the current position).
534    #[allow(missing_docs)]
535    pub fn current_span(&self) -> Span {
536        Span::new(self.pos, self.pos, self.line, self.col)
537    }
538    /// `true` if the cursor is at the end of the source.
539    #[allow(missing_docs)]
540    pub fn is_eof(&self) -> bool {
541        self.pos >= self.source.len()
542    }
543    /// Peek at the next character without advancing.
544    #[allow(missing_docs)]
545    pub fn peek(&self) -> Option<char> {
546        self.source[self.pos..].chars().next()
547    }
548    /// Peek at the character `n` UTF-8 code points ahead.
549    #[allow(missing_docs)]
550    pub fn peek_ahead(&self, n: usize) -> Option<char> {
551        self.source[self.pos..].chars().nth(n)
552    }
553    /// Advance by one character and return it.
554    #[allow(missing_docs)]
555    pub fn advance(&mut self) -> Option<char> {
556        let ch = self.source[self.pos..].chars().next()?;
557        self.pos += ch.len_utf8();
558        if ch == '\n' {
559            self.line += 1;
560            self.col = 1;
561        } else {
562            self.col += 1;
563        }
564        Some(ch)
565    }
566    /// Advance while the predicate is true, returning the consumed substring.
567    #[allow(missing_docs)]
568    pub fn advance_while<F: Fn(char) -> bool>(&mut self, pred: F) -> &'a str {
569        let start = self.pos;
570        while let Some(ch) = self.peek() {
571            if pred(ch) {
572                self.advance();
573            } else {
574                break;
575            }
576        }
577        &self.source[start..self.pos]
578    }
579    /// Consume exactly `n` bytes (assumes ASCII for simplicity).
580    #[allow(missing_docs)]
581    pub fn advance_bytes(&mut self, n: usize) {
582        for _ in 0..n {
583            self.advance();
584        }
585    }
586    /// Create a `Span` starting at `start_pos` and ending at the current position.
587    #[allow(missing_docs)]
588    pub fn span_from(&self, start: usize, start_line: usize, start_col: usize) -> Span {
589        Span::new(start, self.pos, start_line, start_col)
590    }
591    /// Return the remaining (unconsumed) source text.
592    #[allow(missing_docs)]
593    pub fn rest(&self) -> &'a str {
594        &self.source[self.pos..]
595    }
596    /// Return the consumed source text.
597    #[allow(missing_docs)]
598    pub fn consumed(&self) -> &'a str {
599        &self.source[..self.pos]
600    }
601}
602/// A collection of diagnostics associated with a source file.
603#[allow(dead_code)]
604#[allow(missing_docs)]
605#[derive(Clone, Debug, Default)]
606pub struct DiagnosticSet {
607    diagnostics: Vec<DiagnosticSpan>,
608}
609impl DiagnosticSet {
610    /// Create an empty set.
611    #[allow(dead_code)]
612    #[allow(missing_docs)]
613    pub fn new() -> Self {
614        Self::default()
615    }
616    /// Add a diagnostic.
617    #[allow(dead_code)]
618    #[allow(missing_docs)]
619    pub fn add(&mut self, d: DiagnosticSpan) {
620        self.diagnostics.push(d);
621    }
622    /// Add an error.
623    #[allow(dead_code)]
624    #[allow(missing_docs)]
625    pub fn add_error(&mut self, span: Span, msg: impl Into<String>) {
626        self.add(DiagnosticSpan::error(span, msg));
627    }
628    /// Add a warning.
629    #[allow(dead_code)]
630    #[allow(missing_docs)]
631    pub fn add_warning(&mut self, span: Span, msg: impl Into<String>) {
632        self.add(DiagnosticSpan::warning(span, msg));
633    }
634    /// Add an info.
635    #[allow(dead_code)]
636    #[allow(missing_docs)]
637    pub fn add_info(&mut self, span: Span, msg: impl Into<String>) {
638        self.add(DiagnosticSpan::info(span, msg));
639    }
640    /// Count diagnostics of a given severity.
641    #[allow(dead_code)]
642    #[allow(missing_docs)]
643    pub fn count_severity(&self, sev: &SpanSeverity) -> usize {
644        self.diagnostics
645            .iter()
646            .filter(|d| &d.severity == sev)
647            .count()
648    }
649    /// Total number of diagnostics.
650    #[allow(dead_code)]
651    #[allow(missing_docs)]
652    pub fn len(&self) -> usize {
653        self.diagnostics.len()
654    }
655    /// Check if empty.
656    #[allow(dead_code)]
657    #[allow(missing_docs)]
658    pub fn is_empty(&self) -> bool {
659        self.diagnostics.is_empty()
660    }
661    /// Check if there are any errors.
662    #[allow(dead_code)]
663    #[allow(missing_docs)]
664    pub fn has_errors(&self) -> bool {
665        self.diagnostics.iter().any(|d| d.severity.is_error())
666    }
667    /// Get all errors.
668    #[allow(dead_code)]
669    #[allow(missing_docs)]
670    pub fn errors(&self) -> Vec<&DiagnosticSpan> {
671        self.diagnostics
672            .iter()
673            .filter(|d| d.severity.is_error())
674            .collect()
675    }
676    /// Get all warnings.
677    #[allow(dead_code)]
678    #[allow(missing_docs)]
679    pub fn warnings(&self) -> Vec<&DiagnosticSpan> {
680        self.diagnostics
681            .iter()
682            .filter(|d| matches!(d.severity, SpanSeverity::Warning))
683            .collect()
684    }
685    /// Sort diagnostics by span start offset.
686    #[allow(dead_code)]
687    #[allow(missing_docs)]
688    pub fn sort_by_position(&mut self) {
689        self.diagnostics.sort_by_key(|d| d.span.start);
690    }
691    /// Iterate over all diagnostics.
692    #[allow(dead_code)]
693    #[allow(missing_docs)]
694    pub fn iter(&self) -> impl Iterator<Item = &DiagnosticSpan> {
695        self.diagnostics.iter()
696    }
697    /// Clear all diagnostics.
698    #[allow(dead_code)]
699    #[allow(missing_docs)]
700    pub fn clear(&mut self) {
701        self.diagnostics.clear();
702    }
703    /// Merge another set into this one.
704    #[allow(dead_code)]
705    #[allow(missing_docs)]
706    pub fn merge(&mut self, other: DiagnosticSet) {
707        self.diagnostics.extend(other.diagnostics);
708    }
709}
710/// A span diff: describes changes between two spans.
711#[allow(dead_code)]
712#[allow(missing_docs)]
713#[derive(Debug, Clone)]
714pub struct SpanDiff {
715    /// The old span.
716    pub old: Span,
717    /// The new span.
718    pub new: Span,
719    /// The byte delta (positive = grew, negative = shrank).
720    #[allow(missing_docs)]
721    pub byte_delta: i64,
722}
723impl SpanDiff {
724    /// Compute the diff between two spans.
725    #[allow(dead_code)]
726    #[allow(missing_docs)]
727    pub fn compute(old: Span, new: Span) -> Self {
728        let byte_delta = new.end as i64 - old.end as i64;
729        Self {
730            old,
731            new,
732            byte_delta,
733        }
734    }
735    /// Whether the span grew.
736    #[allow(dead_code)]
737    #[allow(missing_docs)]
738    pub fn grew(&self) -> bool {
739        self.byte_delta > 0
740    }
741    /// Whether the span shrank.
742    #[allow(dead_code)]
743    #[allow(missing_docs)]
744    pub fn shrank(&self) -> bool {
745        self.byte_delta < 0
746    }
747    /// Whether the span is unchanged.
748    #[allow(dead_code)]
749    #[allow(missing_docs)]
750    pub fn unchanged(&self) -> bool {
751        self.byte_delta == 0
752    }
753}
754/// Tracks how a set of spans evolve as edits are applied.
755#[allow(dead_code)]
756#[allow(missing_docs)]
757#[derive(Clone, Debug, Default)]
758pub struct IncrementalSpanTracker {
759    spans: Vec<Span>,
760    edit_count: usize,
761}
762impl IncrementalSpanTracker {
763    #[allow(dead_code)]
764    #[allow(missing_docs)]
765    pub fn new() -> Self {
766        Self::default()
767    }
768    #[allow(dead_code)]
769    #[allow(missing_docs)]
770    pub fn track(&mut self, span: Span) {
771        self.spans.push(span);
772    }
773    #[allow(dead_code)]
774    #[allow(missing_docs)]
775    pub fn apply_edit(&mut self, edit_start: usize, delta: i64) {
776        shift_spans(&mut self.spans, edit_start, delta);
777        self.edit_count += 1;
778    }
779    #[allow(dead_code)]
780    #[allow(missing_docs)]
781    pub fn spans(&self) -> &[Span] {
782        &self.spans
783    }
784    #[allow(dead_code)]
785    #[allow(missing_docs)]
786    pub fn edit_count(&self) -> usize {
787        self.edit_count
788    }
789    #[allow(dead_code)]
790    #[allow(missing_docs)]
791    pub fn reset(&mut self) {
792        self.spans.clear();
793        self.edit_count = 0;
794    }
795}
796/// A value paired with its source `Span`.
797#[derive(Clone, Debug, PartialEq)]
798#[allow(missing_docs)]
799pub struct Spanned<T> {
800    /// The value.
801    pub value: T,
802    /// The source span.
803    pub span: Span,
804}
805impl<T> Spanned<T> {
806    /// Wrap `value` at `span`.
807    #[allow(missing_docs)]
808    pub fn new(value: T, span: Span) -> Self {
809        Self { value, span }
810    }
811    /// Map over the value, keeping the span.
812    #[allow(missing_docs)]
813    pub fn map<U, F: FnOnce(T) -> U>(self, f: F) -> Spanned<U> {
814        Spanned {
815            value: f(self.value),
816            span: self.span,
817        }
818    }
819    /// Borrow the inner value.
820    #[allow(clippy::should_implement_trait)]
821    #[allow(missing_docs)]
822    pub fn as_ref(&self) -> &T {
823        &self.value
824    }
825    /// Consume and return the value, discarding the span.
826    #[allow(missing_docs)]
827    pub fn into_value(self) -> T {
828        self.value
829    }
830}
831/// The origin of a span.
832#[allow(dead_code)]
833#[allow(missing_docs)]
834#[derive(Clone, Debug, PartialEq)]
835pub enum SpanOrigin {
836    UserSource,
837    MacroExpanded { macro_name: String },
838    Elaborated,
839    Synthetic,
840}
841impl SpanOrigin {
842    #[allow(dead_code)]
843    #[allow(missing_docs)]
844    pub fn is_user_source(&self) -> bool {
845        matches!(self, SpanOrigin::UserSource)
846    }
847    #[allow(dead_code)]
848    #[allow(missing_docs)]
849    pub fn is_synthetic(&self) -> bool {
850        matches!(self, SpanOrigin::Synthetic)
851    }
852    #[allow(dead_code)]
853    #[allow(missing_docs)]
854    pub fn kind_str(&self) -> &'static str {
855        match self {
856            SpanOrigin::UserSource => "user",
857            SpanOrigin::MacroExpanded { .. } => "macro",
858            SpanOrigin::Elaborated => "elab",
859            SpanOrigin::Synthetic => "synthetic",
860        }
861    }
862}
863/// A span paired with a severity level, for diagnostics.
864#[allow(dead_code)]
865#[allow(missing_docs)]
866#[derive(Clone, Debug, PartialEq)]
867pub enum SpanSeverity {
868    /// Informational annotation.
869    Info,
870    /// A warning annotation.
871    Warning,
872    /// An error annotation.
873    Error,
874}
875impl SpanSeverity {
876    /// Returns `true` if this is an error severity.
877    #[allow(dead_code)]
878    #[allow(missing_docs)]
879    pub fn is_error(&self) -> bool {
880        matches!(self, SpanSeverity::Error)
881    }
882    /// Short label string for this severity.
883    #[allow(dead_code)]
884    #[allow(missing_docs)]
885    pub fn label(&self) -> &'static str {
886        match self {
887            SpanSeverity::Info => "info",
888            SpanSeverity::Warning => "warning",
889            SpanSeverity::Error => "error",
890        }
891    }
892}
893/// A span paired with annotation text and optional tag.
894#[allow(dead_code)]
895#[allow(missing_docs)]
896#[derive(Clone, Debug, PartialEq)]
897pub struct AnnotatedSpan {
898    pub span: Span,
899    pub annotation: String,
900    pub tag: Option<String>,
901}
902impl AnnotatedSpan {
903    #[allow(dead_code)]
904    #[allow(missing_docs)]
905    pub fn new(span: Span, annotation: impl Into<String>) -> Self {
906        Self {
907            span,
908            annotation: annotation.into(),
909            tag: None,
910        }
911    }
912    #[allow(dead_code)]
913    #[allow(missing_docs)]
914    pub fn with_tag(span: Span, annotation: impl Into<String>, tag: impl Into<String>) -> Self {
915        Self {
916            span,
917            annotation: annotation.into(),
918            tag: Some(tag.into()),
919        }
920    }
921    #[allow(dead_code)]
922    #[allow(missing_docs)]
923    pub fn len(&self) -> usize {
924        span_len(&self.span)
925    }
926    #[allow(dead_code)]
927    #[allow(missing_docs)]
928    pub fn is_empty(&self) -> bool {
929        self.len() == 0
930    }
931}
932/// A half-open interval \[start, end) over span indices (for range operations).
933#[allow(dead_code)]
934#[allow(missing_docs)]
935#[derive(Clone, Copy, Debug, PartialEq, Eq)]
936pub struct SpanRange {
937    pub start_idx: usize,
938    pub end_idx: usize,
939}
940impl SpanRange {
941    #[allow(dead_code)]
942    #[allow(missing_docs)]
943    pub fn new(start_idx: usize, end_idx: usize) -> Self {
944        Self { start_idx, end_idx }
945    }
946    #[allow(dead_code)]
947    #[allow(missing_docs)]
948    pub fn len(&self) -> usize {
949        self.end_idx.saturating_sub(self.start_idx)
950    }
951    #[allow(dead_code)]
952    #[allow(missing_docs)]
953    pub fn is_empty(&self) -> bool {
954        self.start_idx >= self.end_idx
955    }
956    #[allow(dead_code)]
957    #[allow(missing_docs)]
958    pub fn contains(&self, idx: usize) -> bool {
959        idx >= self.start_idx && idx < self.end_idx
960    }
961}
962/// A span with a byte-level padding on each side.
963#[allow(dead_code)]
964#[allow(missing_docs)]
965#[derive(Clone, Debug)]
966pub struct PaddedSpan {
967    pub inner: Span,
968    pub left_pad: usize,
969    pub right_pad: usize,
970}
971impl PaddedSpan {
972    #[allow(dead_code)]
973    #[allow(missing_docs)]
974    pub fn new(inner: Span, left_pad: usize, right_pad: usize) -> Self {
975        Self {
976            inner,
977            left_pad,
978            right_pad,
979        }
980    }
981    /// Expand the inner span by the padding amounts, clamped to source bounds.
982    #[allow(dead_code)]
983    #[allow(missing_docs)]
984    pub fn expanded(&self, source_len: usize) -> Span {
985        let start = self.inner.start.saturating_sub(self.left_pad);
986        let end = (self.inner.end + self.right_pad).min(source_len);
987        Span::new(start, end, self.inner.line, self.inner.column)
988    }
989}
990/// A flat map from span start offsets to values.
991#[allow(dead_code)]
992#[allow(missing_docs)]
993#[derive(Clone, Debug, Default)]
994pub struct SpanMap<V> {
995    entries: Vec<(usize, V)>,
996}
997impl<V> SpanMap<V> {
998    /// Create an empty map.
999    #[allow(dead_code)]
1000    #[allow(missing_docs)]
1001    pub fn new() -> Self {
1002        Self {
1003            entries: Vec::new(),
1004        }
1005    }
1006    /// Insert a value at `offset`.
1007    #[allow(dead_code)]
1008    #[allow(missing_docs)]
1009    pub fn insert(&mut self, offset: usize, value: V) {
1010        self.entries.push((offset, value));
1011    }
1012    /// Look up the value at `offset`.
1013    #[allow(dead_code)]
1014    #[allow(missing_docs)]
1015    pub fn get(&self, offset: usize) -> Option<&V> {
1016        self.entries
1017            .iter()
1018            .find(|(o, _)| *o == offset)
1019            .map(|(_, v)| v)
1020    }
1021    /// Number of entries.
1022    #[allow(dead_code)]
1023    #[allow(missing_docs)]
1024    pub fn len(&self) -> usize {
1025        self.entries.len()
1026    }
1027    /// Check if empty.
1028    #[allow(dead_code)]
1029    #[allow(missing_docs)]
1030    pub fn is_empty(&self) -> bool {
1031        self.entries.is_empty()
1032    }
1033    /// Iterate over all entries.
1034    #[allow(dead_code)]
1035    #[allow(missing_docs)]
1036    pub fn iter(&self) -> impl Iterator<Item = &(usize, V)> {
1037        self.entries.iter()
1038    }
1039}
1040/// A span that also carries a `FileId`, enabling multi-file diagnostics.
1041#[allow(dead_code)]
1042#[allow(missing_docs)]
1043#[derive(Clone, Debug, PartialEq)]
1044pub struct FileSpan {
1045    /// Which file this span belongs to.
1046    pub file: FileId,
1047    /// The span within that file.
1048    pub span: Span,
1049}
1050impl FileSpan {
1051    /// Create a new file span.
1052    #[allow(dead_code)]
1053    #[allow(missing_docs)]
1054    pub fn new(file: FileId, span: Span) -> Self {
1055        Self { file, span }
1056    }
1057    /// Return the length in bytes.
1058    #[allow(dead_code)]
1059    #[allow(missing_docs)]
1060    pub fn len(&self) -> usize {
1061        span_len(&self.span)
1062    }
1063    /// `true` if this is a zero-length span.
1064    #[allow(dead_code)]
1065    #[allow(missing_docs)]
1066    pub fn is_empty(&self) -> bool {
1067        self.len() == 0
1068    }
1069    /// Merge two `FileSpan`s (panics if they come from different files).
1070    #[allow(dead_code)]
1071    #[allow(missing_docs)]
1072    pub fn merge_with(&self, other: &FileSpan) -> FileSpan {
1073        assert_eq!(
1074            self.file, other.file,
1075            "cannot merge spans from different files"
1076        );
1077        FileSpan {
1078            file: self.file,
1079            span: self.span.merge(&other.span),
1080        }
1081    }
1082}
1083/// A line/column position in source text (1-indexed).
1084#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
1085#[allow(missing_docs)]
1086pub struct SourcePos {
1087    /// 1-indexed line number.
1088    pub line: usize,
1089    /// 1-indexed column number (byte offset within the line).
1090    pub col: usize,
1091}
1092impl SourcePos {
1093    /// Construct a new `SourcePos`.
1094    #[allow(missing_docs)]
1095    pub fn new(line: usize, col: usize) -> Self {
1096        Self { line, col }
1097    }
1098    /// The "beginning of file" position.
1099    #[allow(missing_docs)]
1100    pub fn start() -> Self {
1101        Self { line: 1, col: 1 }
1102    }
1103    /// Advance by one ASCII character (no newline).
1104    #[allow(missing_docs)]
1105    pub fn advance_col(&self) -> Self {
1106        Self {
1107            line: self.line,
1108            col: self.col + 1,
1109        }
1110    }
1111    /// Advance to the next line (column resets to 1).
1112    #[allow(missing_docs)]
1113    pub fn advance_line(&self) -> Self {
1114        Self {
1115            line: self.line + 1,
1116            col: 1,
1117        }
1118    }
1119    /// `true` if `other` is on the same line as `self`.
1120    #[allow(missing_docs)]
1121    pub fn same_line(&self, other: &SourcePos) -> bool {
1122        self.line == other.line
1123    }
1124}