rantlr-core 0.1.0

Rantlr core: .gr lexer, parser, linter, and in-process runtime
Documentation
//! Source locations used by every stage (lexer → parser → linter → LSP).

/// Zero-based UTF-8 byte offset into the source file.
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
pub struct BytePos(pub u32);

impl BytePos {
    pub const fn new(offset: u32) -> Self {
        Self(offset)
    }

    pub const fn as_usize(self) -> usize {
        self.0 as usize
    }
}

/// Half-open byte range `[start, end)` in the original source.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub struct Span {
    pub start: BytePos,
    pub end: BytePos,
}

impl Span {
    pub const fn new(start: BytePos, end: BytePos) -> Self {
        Self { start, end }
    }

    pub fn from_offsets(start: usize, end: usize) -> Self {
        Self {
            start: BytePos(start as u32),
            end: BytePos(end as u32),
        }
    }

    pub const fn len(self) -> u32 {
        self.end.0.saturating_sub(self.start.0)
    }

    pub fn merge(self, other: Span) -> Span {
        Span {
            start: self.start.min(other.start),
            end: self.end.max(other.end),
        }
    }

    /// 1-based line/column for human diagnostics.
    pub fn line_col(self, source: &str) -> (usize, usize) {
        let mut line = 1usize;
        let mut col = 1usize;
        for (i, ch) in source.char_indices() {
            if i >= self.start.as_usize() {
                break;
            }
            if ch == '\n' {
                line += 1;
                col = 1;
            } else {
                col += 1;
            }
        }
        (line, col)
    }
}