kbvm 0.1.5

An implementation of the XKB specification
Documentation
use {
    crate::xkb::{
        code::Code,
        span::{Span, SpanUnit},
    },
    hashbrown::HashMap,
    std::{path::PathBuf, sync::Arc},
};

#[derive(Default)]
pub(crate) struct CodeMap {
    ranges: Vec<CodeRange>,
    canonical_idx: HashMap<*const u8, usize>,
}

struct CodeRange {
    span: Span,
    code: Code,
    file: Option<Arc<PathBuf>>,
    canonical_idx: Option<usize>,
    lines: Option<Vec<SpanUnit>>,
    include_span: Option<Span>,
}

pub(crate) struct CodeInfo<'a> {
    pub(crate) code: &'a Code,
    pub(crate) span: Span,
    pub(crate) file: Option<&'a Arc<PathBuf>>,
    pub(crate) lines: &'a [SpanUnit],
    pub(crate) lines_offset: SpanUnit,
    pub(crate) include_span: Option<Span>,
}

impl CodeRange {
    fn create_lines(&mut self) {
        if self.lines.is_some() {
            return;
        }
        let mut lines = vec![];
        let mut lo = self.span.lo;
        lines.push(lo);
        let mut code = self.code.as_bytes();
        while code.len() > 0 {
            let Some(pos) = code.iter().position(|&c| c == b'\n') else {
                break;
            };
            code = &code[pos + 1..];
            lo = lo.saturating_add((pos + 1) as SpanUnit);
            lines.push(lo);
        }
        self.lines = Some(lines);
    }
}

impl CodeMap {
    pub(crate) fn add(
        &mut self,
        file: Option<&Arc<PathBuf>>,
        include_span: Option<Span>,
        code: &Code,
    ) -> Span {
        let lo = self
            .ranges
            .last()
            .map(|l| l.span.hi.saturating_add(1))
            .unwrap_or_default();
        let span = Span {
            lo,
            hi: lo.saturating_add(code.len() as SpanUnit),
        };
        let canonical_idx = self
            .canonical_idx
            .try_insert(code.as_ptr(), self.ranges.len())
            .err()
            .map(|e| *e.entry.get());
        self.ranges.push(CodeRange {
            span,
            code: code.clone(),
            file: file.cloned(),
            canonical_idx,
            lines: None,
            include_span,
        });
        span
    }

    pub(crate) fn get(&mut self, span: Span) -> CodeInfo<'_> {
        let range = self.ranges.binary_search_by_key(&span.lo, |r| r.span.hi);
        let mut idx = range.unwrap_or_else(|i| i);
        if idx == self.ranges.len() {
            idx -= 1;
        }
        let (lo, hi) = self.ranges.split_at_mut(idx);
        let range = &mut hi[0];
        let (lines, lines_offset) = match range.canonical_idx {
            Some(idx) => {
                let crange = &mut lo[idx];
                crange.create_lines();
                (
                    crange.lines.as_ref().unwrap(),
                    range.span.lo - crange.span.lo,
                )
            }
            _ => {
                range.create_lines();
                (range.lines.as_ref().unwrap(), 0)
            }
        };
        CodeInfo {
            code: &range.code,
            span: range.span,
            file: range.file.as_ref(),
            lines,
            lines_offset,
            include_span: range.include_span,
        }
    }
}