weavatrix-rust 2.14.3

Protocol-independent Rust repository intelligence: typed evidence graphs for impact, architecture, APIs, Git, search, and memory
Documentation
pub(super) struct Scanner<'a> {
    text: &'a str,
    origin: usize,
    pos: usize,
}

impl<'a> Scanner<'a> {
    pub(super) fn new(text: &'a str, origin: usize) -> Self {
        Self {
            text,
            origin,
            pos: 0,
        }
    }

    pub(super) fn eof(&self) -> bool {
        self.pos >= self.text.len()
    }

    pub(super) fn origin_at(&self, local: usize) -> usize {
        self.origin.saturating_add(local)
    }

    pub(super) fn position(&self) -> usize {
        self.pos
    }

    pub(super) fn absolute(&self) -> usize {
        self.origin_at(self.pos)
    }

    pub(super) fn skip_trivia(&mut self) {
        loop {
            self.skip_ws();
            if self.starts_with("%%{") {
                self.skip_until("}%%");
                continue;
            }
            if self.starts_with("%%") {
                self.skip_line();
                continue;
            }
            break;
        }
    }

    pub(super) fn skip_ws(&mut self) {
        while let Some(character) = self.peek_char() {
            if character == '\n' || !character.is_whitespace() {
                break;
            }
            self.bump(character.len_utf8());
        }
    }

    pub(super) fn skip_line(&mut self) {
        if let Some(index) = self.rest().find('\n') {
            self.pos += index + 1;
        } else {
            self.pos = self.text.len();
        }
    }

    pub(super) fn skip_newline(&mut self) {
        if self.peek_char() == Some('\r') {
            self.bump(1);
        }
        if self.peek_char() == Some('\n') {
            self.bump(1);
        }
    }

    pub(super) fn take_ident(&mut self) -> Option<String> {
        self.skip_ws();
        if self.peek_char() == Some('"') || self.peek_char() == Some('\'') {
            return self.take_quoted();
        }
        let rest = self.rest();
        let mut chars = rest.chars();
        let first = chars.next()?;
        if !first.is_ascii_alphabetic() && first != '_' {
            return None;
        }
        let mut length = first.len_utf8();
        for character in chars {
            if character.is_ascii_alphanumeric() || matches!(character, '_' | '-') {
                length += character.len_utf8();
            } else {
                break;
            }
        }
        let ident = rest[..length].to_owned();
        self.pos += length;
        Some(ident)
    }

    pub(super) fn take_quoted(&mut self) -> Option<String> {
        let quote = self
            .peek_char()
            .filter(|character| matches!(character, '"' | '\''))?;
        self.bump(1);
        let rest = self.rest();
        let mut escaped = false;
        let mut length = 0;
        for character in rest.chars() {
            length += character.len_utf8();
            if escaped {
                escaped = false;
                continue;
            }
            if character == '\\' {
                escaped = true;
                continue;
            }
            if character == quote {
                let inner = rest[..length.saturating_sub(1)].to_owned();
                self.pos += length;
                return Some(inner);
            }
        }
        None
    }

    pub(super) fn keyword(&mut self, name: &str) -> bool {
        self.skip_ws();
        if !self.starts_with(name) {
            return false;
        }
        let after = self.pos + name.len();
        let next = self.text[after..].chars().next();
        if next.is_some_and(|character| character.is_ascii_alphanumeric() || character == '_') {
            return false;
        }
        self.pos = after;
        true
    }

    pub(super) fn eat(&mut self, token: &str) -> bool {
        self.skip_ws();
        if self.starts_with(token) {
            self.pos += token.len();
            true
        } else {
            false
        }
    }

    pub(super) fn peek_char(&self) -> Option<char> {
        self.rest().chars().next()
    }

    pub(super) fn starts_with(&self, token: &str) -> bool {
        self.rest().starts_with(token)
    }

    pub(super) fn rest(&self) -> &'a str {
        &self.text[self.pos..]
    }

    pub(super) fn slice(&self, start: usize, end: usize) -> &'a str {
        let start = start.min(self.text.len());
        let end = end.min(self.text.len()).max(start);
        &self.text[start..end]
    }

    pub(super) fn bump(&mut self, amount: usize) {
        self.pos = (self.pos + amount).min(self.text.len());
    }

    fn skip_until(&mut self, token: &str) {
        if let Some(index) = self.rest().find(token) {
            self.pos += index + token.len();
        } else {
            self.skip_line();
        }
    }
}