weavatrix-rust 2.16.1

Protocol-independent Rust repository intelligence: typed evidence graphs for impact, architecture, APIs, Git, search, and memory
Documentation
use crate::model::Diagnostic;

pub(super) const MAX_DEPTH: usize = 64;
pub(super) const MAX_NODES: usize = 20_000;

pub(super) struct Parser<'a> {
    pub raw: &'a str,
    pub pos: usize,
    pub nodes: usize,
}

impl Parser<'_> {
    pub(super) fn next_content_indent(&self) -> Option<(usize, usize)> {
        let mut index = self.pos;
        if index < self.raw.len() && !at_line_start(self.raw, index) {
            let line_end = line_end(self.raw, index);
            let rest = &self.raw[index..line_end];
            if !rest.trim().is_empty() && !rest.trim_start().starts_with('#') {
                return Some((column_at(self.raw, index), index));
            }
            index = if line_end < self.raw.len() {
                line_end + 1
            } else {
                return None;
            };
        }
        while index < self.raw.len() {
            let line_end = line_end(self.raw, index);
            let line = &self.raw[index..line_end];
            let trimmed = line.trim_start();
            if !trimmed.is_empty() && !trimmed.starts_with('#') {
                return Some((line.len() - trimmed.len(), index));
            }
            index = if line_end < self.raw.len() {
                line_end + 1
            } else {
                return None;
            };
        }
        None
    }

    pub(super) fn skip_separators(&mut self) {
        self.skip_blank();
        if self.starts_with("---") {
            self.pos += 3;
            self.skip_to_eol();
            self.consume('\n');
        }
    }

    pub(super) fn skip_blank(&mut self) {
        while let Some((indent, start)) = self.next_content_indent() {
            if start > self.pos {
                self.pos = start + indent;
                return;
            }
            if start == self.pos {
                if at_line_start(self.raw, start) {
                    self.pos = start + indent;
                }
                return;
            }
            self.pos = start + 1;
        }
        self.pos = self.raw.len();
    }

    pub(super) fn skip_to_content(&mut self) {
        if let Some((indent, start)) = self.next_content_indent() {
            self.pos = if at_line_start(self.raw, start) {
                start + indent
            } else {
                start
            };
        }
    }

    pub(super) fn skip_to_eol(&mut self) {
        if let Some(offset) = self.raw[self.pos..].find('\n') {
            self.pos += offset;
        } else {
            self.pos = self.raw.len();
        }
    }

    pub(super) fn skip_spaces(&mut self) {
        while self.peek() == Some(' ') || self.peek() == Some('\t') {
            self.pos += 1;
        }
    }

    pub(super) fn skip_bom(&mut self) {
        if self.raw.starts_with('\u{feff}') {
            self.pos = '\u{feff}'.len_utf8();
        }
    }

    pub(super) fn consume(&mut self, expected: char) -> bool {
        if self.peek() == Some(expected) {
            self.pos += expected.len_utf8();
            true
        } else {
            false
        }
    }

    pub(super) fn starts_with(&self, prefix: &str) -> bool {
        self.raw[self.pos..].starts_with(prefix)
    }

    pub(super) fn peek(&self) -> Option<char> {
        self.raw[self.pos..].chars().next()
    }

    pub(super) fn at_line_end(&self) -> bool {
        matches!(self.peek(), None | Some('\n' | '#'))
    }

    pub(super) fn line_tail_is_item(&self) -> bool {
        self.raw[self.pos..].starts_with("-\n") || self.raw[self.pos..] == *"-"
    }

    pub(super) fn looks_like_mapping_start(&self) -> bool {
        let line = self.raw[self.pos..].split('\n').next().unwrap_or("");
        let trimmed = line.trim_end();
        if trimmed.is_empty() {
            return false;
        }
        if trimmed.starts_with('\'') || trimmed.starts_with('"') {
            return trimmed.contains(':');
        }
        let Some((key, rest)) = trimmed.split_once(':') else {
            return false;
        };
        !key.is_empty() && (rest.is_empty() || rest.starts_with([' ', '\t']))
    }

    pub(super) fn budget(&mut self, depth: usize) -> Result<(), Diagnostic> {
        self.nodes += 1;
        if depth > MAX_DEPTH || self.nodes > MAX_NODES {
            return Err(self.error("", "YAML nesting or node budget exceeded"));
        }
        Ok(())
    }

    #[allow(clippy::unused_self)]
    pub(super) fn error(&self, path: &str, message: impl Into<String>) -> Diagnostic {
        Diagnostic {
            code: "yaml.limit".into(),
            message: if path.is_empty() {
                message.into()
            } else {
                format!("{path}: {}", message.into())
            },
            span: None,
        }
    }
}

fn at_line_start(raw: &str, index: usize) -> bool {
    index == 0 || raw.as_bytes().get(index.saturating_sub(1)) == Some(&b'\n')
}

fn line_end(raw: &str, index: usize) -> usize {
    raw[index..]
        .find('\n')
        .map_or(raw.len(), |offset| index + offset)
}

fn column_at(raw: &str, index: usize) -> usize {
    let prefix = &raw[..index];
    prefix
        .rsplit_once('\n')
        .map_or(prefix, |(_, last)| last)
        .chars()
        .count()
}