chompy 0.2.0

A series of utilities used to create parsers
Documentation
use crate::{
    diagnostics::Result,
    lex::{CharStream, Lex, Tok, TokenKind, UnexpectedChar},
    utils::{Location, Span},
};

/// Test macro for token lexing.
#[macro_export]
macro_rules! test_tok_match {
    ($name:ident: $src:expr => $($should_be:expr), * $(,)?) => {
        #[cfg(test)]
        #[test]
        fn $name() {
            use $crate::lex::Lex;
            let expected = vec![$($should_be, )*];
            let mut lexer = $crate::tests::utils::Lexer::new($src);
            let mut outputed = vec![];
            while let Some(tok) = lexer.lex().unwrap() {
                outputed.push(tok.kind().clone());
            }
            pretty_assertions::assert_eq!(expected, *outputed);
        }
    };
}

/// Test macro for token mismatches.
#[macro_export]
macro_rules! test_tok_mismatch {
    ($name:ident: $src:expr => $($should_be:expr), * $(,)?) => {
        #[cfg(test)]
        #[test]
        #[should_panic]
        fn $name() {
            use $crate::lex::Lex;
            let expected = vec![$($should_be, )*];
            let mut lexer = $crate::tests::utils::Lexer::new($src);
            let mut outputed = vec![];
            while let Some(tok) = lexer.lex().unwrap() {
                outputed.push(tok.kind().clone());
            }
            pretty_assertions::assert_eq!(expected, *outputed);
        }
    };
}

/// Macro test for specific lex errors.
#[macro_export]
macro_rules! test_lex_err {
    ($name:ident: $src:expr => $should_be:expr) => {
        #[cfg(test)]
        #[test]
        fn $name() {
            use $crate::lex::Lex;
            let mut lexer = $crate::tests::utils::Lexer::new($src);
            pretty_assertions::assert_eq!(Err($should_be), lexer.lex());
        }
    };
}

pub struct Lexer {
    source: &'static str,
    char_stream: CharStream,
    file_id: usize,
}

impl Lexer {
    pub fn new(source: &'static str) -> Self {
        Self {
            source,
            char_stream: CharStream::new(source),
            file_id: 0,
        }
    }
}

impl Lex<Tok<TokKind>, TokKind> for Lexer {
    fn source(&self) -> &'static str {
        self.source
    }

    fn char_stream(&mut self) -> &mut CharStream {
        &mut self.char_stream
    }

    fn lex(&mut self) -> Result<Option<Tok<TokKind>>> {
        let start_pos = self.char_stream.position();
        if self.char_stream.match_chomp_with(|c| c.is_whitespace()) {
            return self.lex();
        }
        let kind = if let Some(hex) = self.construct_hex("0x") {
            TokKind::Hex(hex?)
        } else if let Some(float) = self.construct_float(true) {
            TokKind::Float(float)
        } else if let Some(int) = self.construct_integer(true) {
            TokKind::Int(int)
        } else if let Some(string) = self.construct_string(&['"', '\''], &['\\']) {
            TokKind::String(string?)
        } else if let Some(string) = self.construct_comment(&["//"]) {
            TokKind::Comment(string)
        } else if let Some(ident) = self.construct_ident() {
            match ident {
                "let" => TokKind::Let,
                "const" => TokKind::Const,
                ident => TokKind::Ident(ident),
            }
        } else if let Some(chr) = self.chomp() {
            match chr {
                '=' => {
                    if self.match_chomp('=') {
                        TokKind::DoubleEqual
                    } else {
                        TokKind::Equal
                    }
                }
                ';' => TokKind::SemiColon,
                _ => {
                    return Err(UnexpectedChar(Location::new(
                        self.file_id,
                        Span::new(start_pos, self.char_stream.position()),
                    ))
                    .into());
                }
            }
        } else {
            return Ok(None);
        };

        Ok(Some(Tok::new(
            kind,
            Location::new(0, start_pos..self.char_stream.position()),
        )))
    }

    fn file_id(&self) -> crate::utils::FileId {
        self.file_id
    }
}

#[derive(PartialEq, Clone, Debug)]
pub enum TokKind {
    Let,
    Const,
    Equal,
    DoubleEqual,
    SemiColon,
    Ident(&'static str),
    Int(i64),
    Float(f64),
    String(&'static str),
    Hex(&'static str),
    Comment(&'static str),
}

impl TokenKind for TokKind {}

impl std::fmt::Display for TokKind {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            TokKind::Let => f.pad("let"),
            TokKind::Const => f.pad("const"),
            TokKind::Equal => f.pad("="),
            TokKind::DoubleEqual => f.pad("=="),
            TokKind::SemiColon => f.pad(";"),
            TokKind::Ident(iden) => f.pad(iden),
            TokKind::Int(r) => f.pad(&r.to_string()),
            TokKind::Float(r) => f.pad(&r.to_string()),
            TokKind::String(s) => f.pad(&format!("\"{s}\"")),
            TokKind::Hex(hex) => f.pad(hex),
            TokKind::Comment(s) => f.pad(s),
        }
    }
}