brief-core 0.3.0

Compiler library for the Brief markup language: lexer, parser, AST, HTML/LLM emitters, formatter, and Markdown-to-Brief converter.
Documentation
use std::ops::Range;

#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Span {
    pub start: u32,
    pub len: u32,
}

impl Span {
    pub const DUMMY: Span = Span { start: 0, len: 0 };

    pub fn new(start: usize, len: usize) -> Self {
        Span {
            start: start as u32,
            len: len as u32,
        }
    }

    pub fn end(&self) -> usize {
        (self.start + self.len) as usize
    }
    pub fn range(&self) -> Range<usize> {
        self.start as usize..self.end()
    }

    pub fn join(self, other: Span) -> Span {
        let start = self.start.min(other.start);
        let end = (self.start + self.len).max(other.start + other.len);
        Span {
            start,
            len: end - start,
        }
    }
}

#[derive(Debug, Clone)]
pub struct SourceMap {
    pub path: String,
    pub source: String,
    line_starts: Vec<u32>,
}

impl SourceMap {
    pub fn new(path: impl Into<String>, source: impl Into<String>) -> Self {
        let source = source.into();
        let mut line_starts = vec![0u32];
        for (i, b) in source.bytes().enumerate() {
            if b == b'\n' {
                line_starts.push((i + 1) as u32);
            }
        }
        SourceMap {
            path: path.into(),
            source,
            line_starts,
        }
    }

    pub fn line_col(&self, offset: u32) -> (usize, usize) {
        let idx = match self.line_starts.binary_search(&offset) {
            Ok(i) => i,
            Err(i) => i.saturating_sub(1),
        };
        let line_start = self.line_starts[idx];
        let prefix = &self.source.as_bytes()[line_start as usize..offset as usize];
        let col = std::str::from_utf8(prefix)
            .map(|s| s.chars().count())
            .unwrap_or(0)
            + 1;
        (idx + 1, col)
    }

    pub fn line_text(&self, line: usize) -> &str {
        let start = self.line_starts[line - 1] as usize;
        let end = self
            .line_starts
            .get(line)
            .map(|n| *n as usize)
            .unwrap_or(self.source.len());
        let s = &self.source[start..end];
        s.trim_end_matches('\n').trim_end_matches('\r')
    }
}