easy_config 0.5.3

A language, parser, and lexer designed to make easy to read and write configuration files.
Documentation
use std::fmt::Display;
use crate::lexical_span::LexicalSpan;
use crate::parser::parser_error::{end_of_input, ParserError};

#[derive(Debug, Clone, Copy, Ord, PartialOrd, Eq, PartialEq, Hash)]
pub enum Kind {
    LParen,
    RParen,
    Text,
    Number,
    Equals,
    Eoi
}

impl Display for Kind {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "{:?}", match self {
            Kind::LParen => "Left Parenthesis",
            Kind::RParen => "Right Parenthesis",
            Kind::Text => "Text",
            Kind::Number => "Number",
            Kind::Equals => "Equals",
            Kind::Eoi => "End of Input"
        })
    }
}

#[derive(Debug, Clone, Eq, PartialEq, Hash)]
pub struct Token {
    kind: Kind,
    span: LexicalSpan,
    lexeme: String
}

fn valid_identifier_first_letter(letter: char) -> bool {
    letter.is_alphabetic() || letter == '_'
}

impl Token {
    pub fn new(kind: Kind, start: usize, lexeme: impl AsRef<str>) -> Self {
        let lexeme = lexeme.as_ref().to_owned();
        Self { kind, span: LexicalSpan::new(start, start + lexeme.len()), lexeme }
    }

    pub fn kind(&self) -> Kind {
        self.kind
    }
    pub fn span(&self) -> LexicalSpan {
        self.span
    }

    pub fn lexeme(&self) -> &str {
        &self.lexeme
    }

    pub fn invalid_identifier_char_index(&self) -> Option<usize> {
        if self.kind != Kind::Text {
            return Some(0)
        }

        let mut chars = self.lexeme.chars().enumerate();
        if let Some((index, c)) = chars.next() {
            if !valid_identifier_first_letter(c) {
                return Some(index)
            }
        }

        for (index, c) in chars {
            if !(c.is_alphanumeric() || c == '_') {
                return Some(index)
            }
        }

        None
    }

    pub fn new_eoi(at: usize) -> Self {
        Self {
            kind: Kind::Eoi,
            span: LexicalSpan::new(at, at + 1),
            lexeme: "End Of Input".to_string(),
        }
    }

    pub fn eoi_check(self, source_text: impl AsRef<str>) -> Result<Self, ParserError> {
        if self.kind == Kind::Eoi {
            Err(end_of_input(source_text))
        } else {
            Ok(self)
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn valid_identifier() {
       assert!(Token::new(Kind::Text, 0, "some_name").invalid_identifier_char_index().is_none());
    }

    #[test]
    fn invalid_identifier() {
        assert_eq!(Token::new(Kind::Text, 0, "some-name").invalid_identifier_char_index(), Some(4));
    }
}