tla-checker 0.3.9

A TLA+ model checker written in Rust
Documentation
use std::sync::Arc;

use crate::span::Span;

#[derive(Clone)]
pub struct Source {
    text: Arc<str>,
    name: Arc<str>,
    line_starts: Vec<u32>,
}

impl Source {
    pub fn new(name: impl Into<Arc<str>>, text: impl Into<Arc<str>>) -> Self {
        let text: Arc<str> = text.into();
        let line_starts = compute_line_starts(&text);
        Self {
            text,
            name: name.into(),
            line_starts,
        }
    }

    pub fn name(&self) -> &str {
        &self.name
    }

    pub fn text(&self) -> &str {
        &self.text
    }

    pub fn line_col(&self, offset: u32) -> (usize, usize) {
        let offset = offset as usize;
        let line = self
            .line_starts
            .partition_point(|&start| (start as usize) <= offset)
            .saturating_sub(1);
        let line_start = self.line_starts.get(line).copied().unwrap_or(0) as usize;
        let col = offset.saturating_sub(line_start);
        (line + 1, col + 1)
    }

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

    pub fn line_count(&self) -> usize {
        self.line_starts.len()
    }

    pub fn slice(&self, span: Span) -> &str {
        let start = span.start as usize;
        let end = (span.end as usize).min(self.text.len());
        if start <= end && end <= self.text.len() {
            &self.text[start..end]
        } else {
            ""
        }
    }
}

fn compute_line_starts(text: &str) -> Vec<u32> {
    let mut starts = vec![0];
    for (i, c) in text.char_indices() {
        if c == '\n' {
            starts.push((i + 1) as u32);
        }
    }
    starts
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn line_col_single_line() {
        let src = Source::new("test.tla", "x = 42");
        assert_eq!(src.line_col(0), (1, 1));
        assert_eq!(src.line_col(2), (1, 3));
        assert_eq!(src.line_col(5), (1, 6));
    }

    #[test]
    fn line_col_multi_line() {
        let src = Source::new("test.tla", "line1\nline2\nline3");
        assert_eq!(src.line_col(0), (1, 1));
        assert_eq!(src.line_col(5), (1, 6));
        assert_eq!(src.line_col(6), (2, 1));
        assert_eq!(src.line_col(12), (3, 1));
    }

    #[test]
    fn line_text_retrieval() {
        let src = Source::new("test.tla", "first\nsecond\nthird");
        assert_eq!(src.line_text(1), "first");
        assert_eq!(src.line_text(2), "second");
        assert_eq!(src.line_text(3), "third");
    }

    #[test]
    fn slice_extraction() {
        let src = Source::new("test.tla", "hello world");
        assert_eq!(src.slice(Span::new(0, 5)), "hello");
        assert_eq!(src.slice(Span::new(6, 11)), "world");
    }
}