ziyy-core 0.1.4

Terminal Styling using HTML-like syntax.
Documentation
use super::token::Token;
use crate::common::Span;
use crate::scanner::{GenericScanner, Source};
use crate::splitter::fragment::Fragment;

pub struct Scanner {
    source: Vec<char>,
    tokens: Vec<Token>,
    start: usize,
    current: usize,
    span: Span,
}

impl Scanner {
    pub fn new(mut source: Fragment) -> Self {
        source.span.tie_start();

        Self {
            source: source.lexeme.chars().collect(),
            tokens: vec![],
            start: 0,
            current: 0,
            span: source.span,
        }
    }

    fn escape(&mut self) {
        if self.is_at_end() {
            return;
        }
        let c = self.advance();

        let mut scan_until = |limit: u8, tester: fn(c: char) -> bool| {
            let mut i = 0;
            while i < limit && tester(self.peek()) {
                self.advance();
                i += 1;
            }
        };

        fn is_hexdigit(c: char) -> bool {
            c.is_ascii_hexdigit()
        }

        fn is_octdigit(c: char) -> bool {
            matches!(c, '0'..'8')
        }

        match c {
            'a' => self.add_token('\x07'),
            'b' => self.add_token('\x08'),
            'e' => self.add_token('\x1b'),
            'f' => self.add_token('\x0c'),
            'n' => self.add_token('\x0a'),
            'r' => self.add_token('\x0d'),
            't' => self.add_token('\t'),
            'v' => self.add_token('\x0b'),
            '\\' => self.add_token('\\'),
            '<' => self.add_token('<'),
            '>' => self.add_token('>'),
            '0' => {
                scan_until(3, is_octdigit);
                let num = u32::from_str_radix(&self.text()[2..], 8).unwrap();
                self.add_token(char::from_u32(num).unwrap_or(char::REPLACEMENT_CHARACTER));
            }
            'x' => {
                scan_until(2, is_hexdigit);
                let num = u32::from_str_radix(&self.text()[2..], 16).unwrap();
                self.add_token(char::from_u32(num).unwrap_or(char::REPLACEMENT_CHARACTER));
            }
            'u' => {
                scan_until(4, is_hexdigit);
                let num = u32::from_str_radix(&self.text()[2..], 16).unwrap();
                self.add_token(char::from_u32(num).unwrap_or(char::REPLACEMENT_CHARACTER));
            }
            'U' => {
                scan_until(8, is_hexdigit);
                let num = u32::from_str_radix(&self.text()[2..], 16).unwrap();
                self.add_token(char::from_u32(num).unwrap_or(char::REPLACEMENT_CHARACTER));
            }
            _ => {
                self.add_token('\\');
                self.add_token(c);
            }
        };
    }

    fn text(&self) -> String {
        self.source[self.start..self.current].to_string()
    }

    fn add_token(&mut self, literal: char) {
        self.tokens.push(Token::new(literal, self.span));
        self.span.tie_end();
    }
}

impl_generic_scanner!(|s: &mut Scanner| {
    let c = s.advance();
    match c {
        '\\' => s.escape(),
        '\x1b' => {
            s.add_token('\x1b');
        }
        _ => s.add_token(c),
    }
});

trait ToString {
    fn to_string(&self) -> String;
}

impl ToString for [char] {
    fn to_string(&self) -> String {
        let mut text = String::with_capacity(self.len());

        for ch in self {
            text.push(*ch)
        }

        text
    }
}