neyuki 0.6.1

A small Lua-like scripting language implemented in Rust
//! The character cursor and position tracking.

#[derive(Clone, Debug, PartialEq, Eq)]
pub struct SourcePos {
    pub offset: usize,
    pub line: usize,
    pub col: usize,
}

impl SourcePos {
    pub fn new(offset: usize, line: usize, col: usize) -> Self {
        Self { offset, line, col }
    }
}

pub struct Scanner<'a> {
    src: &'a str,
    pos: usize,
    line: usize,
    col: usize,
    len: usize,
}

impl<'a> Scanner<'a> {
    pub fn new(source: &'a str) -> Self {
        Self {
            src: source,
            pos: 0,
            line: 1,
            col: 1,
            len: source.len(),
        }
    }

    #[inline(always)]
    pub fn is_at_end(&self) -> bool {
        self.pos >= self.len
    }

    #[inline(always)]
    pub fn pos(&self) -> usize {
        self.pos
    }

    #[inline(always)]
    pub fn line(&self) -> usize {
        self.line
    }

    #[inline(always)]
    pub fn col(&self) -> usize {
        self.col
    }

    #[inline(always)]
    pub fn current_pos(&self) -> SourcePos {
        SourcePos::new(self.pos, self.line, self.col)
    }

    #[inline(always)]
    pub fn remaining(&self) -> &'a str {
        if self.pos < self.len {
            &self.src[self.pos..]
        } else {
            ""
        }
    }

    #[inline(always)]
    pub fn slice_from(&self, start: usize) -> &'a str {
        &self.src[start..self.pos]
    }

    #[inline(always)]
    pub fn peek(&self) -> char {
        self.remaining().chars().next().unwrap_or('\0')
    }

    #[inline(always)]
    pub fn peek_byte(&self) -> Option<u8> {
        if self.pos < self.len {
            Some(self.src.as_bytes()[self.pos])
        } else {
            None
        }
    }

    pub fn peek_next(&self) -> Option<char> {
        let mut chars = self.remaining().chars();
        let _ = chars.next()?;
        chars.next()
    }

    pub fn advance(&mut self) -> char {
        let ch = self.peek();
        let ch_len = ch.len_utf8();
        self.pos += ch_len;
        if ch == '\n' {
            self.line += 1;
            self.col = 1;
        } else {
            self.col += 1;
        }
        ch
    }

    pub fn advance_bytes(&mut self, count: usize) {
        for _ in 0..count {
            if self.is_at_end() {
                break;
            }
            self.advance();
        }
    }

    pub fn starts_with(&self, prefix: &str) -> bool {
        self.remaining().starts_with(prefix)
    }

    pub fn skip_whitespace(&mut self) {
        while !self.is_at_end() {
            match self.peek() {
                ' ' | '\t' | '\r' => {
                    self.advance();
                }
                '\n' => {
                    self.advance();
                }
                _ => break,
            }
        }
    }

    pub fn restore(&mut self, pos: SourcePos) {
        self.pos = pos.offset;
        self.line = pos.line;
        self.col = pos.col;
    }
}