expression_engine 0.7.0

An expression engine written in pure rust
Documentation
use crate::keyword;
use core::clone::Clone;
use rust_decimal::Decimal;
use std::fmt;

#[derive(Clone, PartialEq, Debug, Copy)]
pub enum DelimTokenType {
    // "("
    OpenParen,
    // ")"
    CloseParen,
    // "["
    OpenBracket,
    // "]"
    CloseBracket,
    // "{"
    OpenBrace,
    // "}"
    CloseBrace,

    Unknown,
}

impl From<char> for DelimTokenType {
    fn from(value: char) -> Self {
        use DelimTokenType::*;
        match value {
            '(' => OpenParen,
            ')' => CloseParen,
            '[' => OpenBracket,
            ']' => CloseBracket,
            '{' => OpenBrace,
            '}' => CloseBrace,
            _ => Unknown,
        }
    }
}

impl From<&str> for DelimTokenType {
    fn from(value: &str) -> Self {
        use DelimTokenType::*;
        match value {
            "(" => OpenParen,
            ")" => CloseParen,
            "[" => OpenBracket,
            "]" => CloseBracket,
            "{" => OpenBrace,
            "}" => CloseBrace,
            _ => Unknown,
        }
    }
}

impl DelimTokenType {
    pub fn string(&self) -> String {
        use DelimTokenType::*;
        match self {
            OpenParen => "(".to_string(),
            CloseParen => ")".to_string(),
            OpenBracket => "[".to_string(),
            CloseBracket => "]".to_string(),
            OpenBrace => "{".to_string(),
            CloseBrace => "}".to_string(),
            Unknown => "??".to_string(),
        }
    }
}

#[derive(Clone, PartialEq, Debug, Copy)]
pub struct Span(pub usize, pub usize);

#[derive(Clone, PartialEq, Debug, Copy)]
pub enum Token<'input> {
    Operator(&'input str, Span),
    Delim(DelimTokenType, Span),
    Number(Decimal, Span),
    Comma(&'input str, Span),
    Bool(bool, Span),
    String(&'input str, Span),
    Reference(&'input str, Span),
    Function(&'input str, Span),
    Semicolon(&'input str, Span),
    EOF,
}

pub fn check_op(token: Token, expected: &str) -> bool {
    match token {
        Token::Delim(op, _) => {
            if op.string() == expected {
                return true;
            }
        }
        Token::Operator(op, _) => {
            if op == expected {
                return true;
            }
        }
        _ => return false,
    }
    return false;
}

impl<'input> Token<'input> {
    pub fn is_open_paren(self) -> bool {
        check_op(self, "(")
    }

    pub fn is_close_paren(self) -> bool {
        check_op(self, ")")
    }

    pub fn is_open_bracket(self) -> bool {
        check_op(self, "[")
    }

    pub fn is_close_bracket(self) -> bool {
        check_op(self, "]")
    }

    pub fn is_open_brace(self) -> bool {
        check_op(self, "{")
    }

    pub fn is_close_brace(self) -> bool {
        check_op(self, "}")
    }

    pub fn is_question_mark(self) -> bool {
        check_op(self, "?")
    }

    pub fn is_colon(self) -> bool {
        check_op(self, ":")
    }

    pub fn is_eof(self) -> bool {
        match self {
            Self::EOF => true,
            _ => false,
        }
    }

    pub fn is_op_token(&self) -> bool {
        match self {
            Self::Operator(_, _) => true,
            _ => false,
        }
    }

    pub fn is_postfix_op_token(&self) -> bool {
        match self {
            Self::Operator(op, _) => keyword::is_postfix_op(op),
            _ => false,
        }
    }

    pub fn is_binop_token(&self) -> bool {
        match self {
            Self::Operator(op, _) => keyword::is_infix_op(op),
            _ => false,
        }
    }

    pub fn is_not_token(&self) -> bool {
        match self {
            Self::Operator(op, _) => keyword::is_not(op),
            _ => false,
        }
    }

    pub fn is_semicolon(&self) -> bool {
        match self {
            Self::Semicolon(..) => true,
            _ => false,
        }
    }

    #[cfg(not(tarpaulin_include))]
    pub fn string(self) -> String {
        use Token::*;
        match self {
            Operator(op, _) => op.to_string(),
            Number(val, _) => val.to_string(),
            Comma(val, _) => val.to_string(),
            Bool(val, _) => val.to_string(),
            String(val, _) => val.to_string(),
            Reference(val, _) => val.to_string(),
            Function(val, _) => val.to_string(),
            Semicolon(val, _) => val.to_string(),
            Delim(ty, _) => ty.string(),
            EOF => "EOF".to_string(),
        }
    }
}

#[cfg(not(tarpaulin_include))]
impl fmt::Display for Span {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "[{}..={})", self.0, self.1)
    }
}

#[cfg(not(tarpaulin_include))]
impl<'input> fmt::Display for Token<'input> {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        use Token::*;
        match self {
            Bool(val, span) => write!(f, "Bool Token: {}, {}", val, span),
            Comma(val, span) => write!(f, "Comma Token: {}, {}", val, span),
            Number(val, span) => write!(f, "Number Token: {}, {}", val, span),
            Operator(val, span) => write!(f, "Operator Token: {}, {}", val, span),
            Reference(val, span) => write!(f, "Reference Token: {}, {}", val, span),
            Function(val, span) => write!(f, "Function Token: {}, {}", val, span),
            String(val, span) => write!(f, "String Token: {}, {}", val, span),
            Semicolon(val, span) => write!(f, "Semicolon Token: {}, {}", val, span),
            Delim(ty, span) => write!(f, "Delim Token: {}, {}", ty.string(), span),
            EOF => write!(f, "EOF"),
        }
    }
}

#[cfg(test)]
mod tests {
    use super::{DelimTokenType, Span, Token};
    use rstest::rstest;

    #[rstest]
    #[case("(", DelimTokenType::OpenParen)]
    #[case(")", DelimTokenType::CloseParen)]
    #[case("[", DelimTokenType::OpenBracket)]
    #[case("]", DelimTokenType::CloseBracket)]
    #[case("{", DelimTokenType::OpenBrace)]
    #[case("}", DelimTokenType::CloseBrace)]
    #[case("f", DelimTokenType::Unknown)]
    fn test_delim_token_type_from_str(#[case] input: &str, #[case] output: DelimTokenType) {
        assert_eq!(DelimTokenType::from(input), output)
    }

    #[rstest]
    #[case('(', DelimTokenType::OpenParen)]
    #[case(')', DelimTokenType::CloseParen)]
    #[case('[', DelimTokenType::OpenBracket)]
    #[case(']', DelimTokenType::CloseBracket)]
    #[case('{', DelimTokenType::OpenBrace)]
    #[case('}', DelimTokenType::CloseBrace)]
    #[case('b', DelimTokenType::Unknown)]
    fn test_delim_token_type_from_char(#[case] input: char, #[case] output: DelimTokenType) {
        assert_eq!(DelimTokenType::from(input), output)
    }

    #[rstest]
    #[case(Token::Delim(DelimTokenType::OpenParen, Span(0, 0)), true)]
    #[case(Token::Delim(DelimTokenType::CloseParen, Span(0, 0)), false)]
    #[case(Token::Bool(false, Span(0, 0)), false)]
    fn test_is_open_paren(#[case] input: Token, #[case] output: bool) {
        assert_eq!(input.is_open_paren(), output)
    }

    #[rstest]
    #[case(Token::Delim(DelimTokenType::OpenBrace, Span(0, 0)), true)]
    #[case(Token::Delim(DelimTokenType::CloseBrace, Span(0, 0)), false)]
    #[case(Token::Bool(false, Span(0, 0)), false)]
    fn test_is_open_brace(#[case] input: Token, #[case] output: bool) {
        assert_eq!(input.is_open_brace(), output)
    }

    #[rstest]
    #[case(Token::Delim(DelimTokenType::OpenBracket, Span(0, 0)), true)]
    #[case(Token::Delim(DelimTokenType::CloseBracket, Span(0, 0)), false)]
    #[case(Token::Bool(false, Span(0, 0)), false)]
    fn test_is_open_bracket(#[case] input: Token, #[case] output: bool) {
        assert_eq!(input.is_open_bracket(), output)
    }
}