work_tock2 0.1.0

CLI time logging software
use crate::err::*;
pub type TokenRes<'a> = Result<Token<'a>, ParseErr>;

use std::str::FromStr;
#[derive(Debug)]
pub struct Token<'a> {
    pub s: &'a str,
    pub line: usize,
    pub col: usize,
    pub tt: TokenType,
}

impl<'a> Token<'a> {
    pub fn as_err(&self, etype: ErrType) -> ParseErr {
        ParseErr {
            line: self.line,
            col: self.col,
            etype,
        }
    }

    pub fn num_val(&self) -> Result<u32, ParseErr> {
        u32::from_str(self.s).map_err(|_| self.as_err(ErrType::NotANumber))
    }
}

#[derive(PartialEq, Debug)]
pub enum TokenType {
    Sep,
    Time,
    Ident,
    Dollar,
    Number,
    Colon,
    Slash,
    Minus,
    Equals,
    SquareOpen,
    SquareClose,
    Tag,
    ClearTag,
    Percent,
    EOF,
}

pub struct Tokenizer<'a> {
    s: &'a str,
    t_start: usize,
    line: usize,
    col: usize,
}

impl<'a> Tokenizer<'a> {
    pub fn new(s: &'a str) -> Self {
        Tokenizer {
            s,
            t_start: 0,
            line: 1,
            col: 0,
        }
    }

    pub fn chars(&mut self) -> std::str::CharIndices<'a> {
        self.s[self.t_start..].char_indices()
    }

    fn whitespace(&mut self) {
        let mut tmp = self.s[self.t_start..].char_indices();
        while let Some((i, c)) = tmp.next() {
            self.col += 1;
            match c {
                ' ' | '\t' | '\r' => {}
                _ => {
                    self.t_start += i;
                    return;
                }
            }
        }
        self.t_start = self.s.len();
    }

    pub fn next_token(&mut self) -> TokenRes<'a> {
        self.whitespace();
        match self.s[self.t_start..].chars().next() {
            Some('\n') => {
                self.line += 1;
                self.col = 0;
                Ok(self.make_token(1, TokenType::Sep))
            }

            Some('#') => {
                let mut tmp = self.s[self.t_start..].char_indices();
                while let Some((i, c)) = tmp.next() {
                    if c == '\n' {
                        self.col = 0;
                        self.line += 1;
                        return Ok(self.make_token(i + 1, TokenType::Sep));
                    }
                }
                Ok(self.make_token(self.s[self.t_start..].len(), TokenType::EOF))
            }
            Some(',') => Ok(self.make_token(1, TokenType::Sep)),
            Some('_') => self.make_tag(),

            Some('$') => Ok(self.make_token(1, TokenType::Dollar)),
            Some(':') => Ok(self.make_token(1, TokenType::Colon)),
            Some('/') => Ok(self.make_token(1, TokenType::Slash)),
            Some('[') => Ok(self.make_token(1, TokenType::SquareOpen)),
            Some(']') => Ok(self.make_token(1, TokenType::SquareClose)),
            Some('-') => Ok(self.make_token(1, TokenType::Minus)),
            Some('%') => Ok(self.make_token(1,TokenType::Percent)),
            Some('=') => Ok(self.make_token(1, TokenType::Equals)),
            Some(c) if c >= '0' && c <= '9' => self.number(),
            Some(c) if c.is_alphabetic() => Ok(self.ident()),
            Some(_) => self.make_err(ErrType::NoToken),
            None => Ok(self.make_token(0, TokenType::EOF)),
        }
    }

    pub fn make_token(&mut self, len: usize, tt: TokenType) -> Token<'a> {
        let res = Token {
            s: &self.s[self.t_start..self.t_start + len],
            line: self.line,
            col: self.col + 1 - len,
            tt,
        };
        self.t_start += len;
        res
    }

    pub fn make_err(&mut self, etype: ErrType) -> TokenRes<'a> {
        Err(ParseErr {
            line: self.line,
            col: self.col,
            etype,
        })
    }

    pub fn number(&mut self) -> TokenRes<'a> {
        let mut tmp = self.chars();
        while let Some((i, c)) = tmp.next() {
            if c < '0' || c > '9' {
                return Ok(self.make_token(i, TokenType::Number));
            }
            self.col += 1;
        }
        Ok(self.make_token(self.s.len() - self.t_start, TokenType::Number))
    }

    pub fn ident(&mut self) -> Token<'a> {
        let mut tmp = self.chars();
        while let Some((i, c)) = tmp.next() {
            if !c.is_alphabetic() && c != '_' && (c < '0' || c > '9') {
                return self.make_token(i, TokenType::Ident);
            }
            self.col += 1;
        }
        self.make_token(self.s.len() - self.t_start, TokenType::Ident)
    }

    pub fn make_tag(&mut self) -> TokenRes<'a> {
        match self.s[self.t_start + 1..].chars().next() {
            Some('_') => Ok(self.make_token(2, TokenType::ClearTag)),
            Some(_) | None => Ok(self.make_token(1, TokenType::Tag)),
        }
    }
}