jayce 12.1.0

jayce is a tokenizer 🌌
Documentation
pub mod internal;
use regex::Regex;

pub struct Duo<T> {
    pub kind: T,
    pub regex: Regex,
    pub preserve: bool,
}

impl<T> Duo<T> {
    pub fn new(kind: T, regex: &str, preserve: bool) -> Self {
        Self {
            kind,
            regex: Regex::new(regex).unwrap(),
            preserve,
        }
    }
}

pub struct Tokenizer<'a, T> {
    source: &'a str,
    duos: &'a [Duo<T>],
    pub cursor: usize,
    pub line: usize,
    pub column: usize,
    next: Option<Token<'a, T>>,
}

#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Token<'a, T> {
    pub kind: &'a T,
    pub value: &'a str,
    pub pos: (usize, usize),
}

impl<'a, T> Tokenizer<'a, T> {
    #[inline]
    pub fn new(source: &'a str, duos: &'a [Duo<T>]) -> Self {
        Self {
            source,
            duos,
            cursor: 0,
            line: 1,
            column: 1,
            next: None,
        }
    }

    fn advance(&mut self) -> Result<Option<Token<'a, T>>, Box<dyn std::error::Error>> {
        while self.cursor < self.source.len() {
            let mut matched = false;

            for duo in self.duos.iter() {
                if let Some(result) = duo.regex.find(&self.source[self.cursor..]) {
                    let value: &str = result.as_str();
                    let token_pos = (self.line, self.column);
                    let len = result.len();
                    self.cursor += len;
                    let newlines_count = bytecount::count(value.as_bytes(), b'\n');
                    if newlines_count > 0 {
                        self.line += newlines_count;
                        self.column = len - value.rfind('\n').unwrap_or(1);
                    } else {
                        self.column += len;
                    }

                    if duo.preserve {
                        return Ok(Some(Token {
                            kind: &duo.kind,
                            value,
                            pos: token_pos,
                        }));
                    } else {
                        matched = true;
                        break;
                    }
                }
            }

            if !matched {
                return Err(format!(
                    "Failed to match at line {}, column {}.",
                    self.line, self.column
                ))?;
            }
        }

        Ok(None)
    }

    pub fn peek(&mut self) -> Result<Option<Token<'a, T>>, Box<dyn std::error::Error>>
    where
        T: Clone,
    {
        if self.next.is_none() {
            self.next = self.advance()?;
        }

        Ok(self.next.clone())
    }

    pub fn consume(&mut self) -> Result<Option<Token<'a, T>>, Box<dyn std::error::Error>> {
        if self.next.is_none() {
            self.next = self.advance()?;
        }

        let result = Ok(self.next.take());
        self.next = self.advance()?;
        result
    }

    pub fn consume_all(&mut self) -> Result<Vec<Token<'a, T>>, Box<dyn std::error::Error>> {
        let mut tokens: Vec<Token<'_, T>> = Vec::new();
        while let Some(token) = self.consume()? {
            tokens.push(token);
        }
        Ok(tokens)
    }
}