math-parser-rs 0.1.0

A simple handwritten dsl for interpreting math.
Documentation
use super::types::*;

pub fn tokenize(function: String) -> LexResult {
    let mut rhs: Vec<Token> = vec![];
    let chars = function.to_lowercase().chars().collect::<Vec<_>>();
    let mut i = 0;

    let simple_token = [
        ('+', TokenType::Operator),
        ('-', TokenType::Operator),
        ('/', TokenType::Operator),
        ('*', TokenType::Operator),
        ('(', TokenType::ParaOpen),
        (')', TokenType::ParaClose),
        (',', TokenType::Comma),
    ];

    while i < chars.len() {
        if chars[i].is_whitespace() {
            i += 1;
            continue;
        }

        if let Some((token, n)) = try_number(&chars, i) {
            rhs.push(token);
            i += n;
            continue;
        }

        if let Some((token, n)) = try_identifiers(&chars, i) {
            rhs.push(token);
            i += n;
            continue;
        }

        if let Some(t_type) = simple_token
            .iter()
            .find(|(ch, _)| *ch == chars[i])
            .map(|p| p.1)
        {
            rhs.push(Token::new(t_type, chars[i].to_string(), i));
            i += 1;
            continue;
        }

        rhs.push(Token::new(
            TokenType::Unimplemented,
            chars[i].to_string(),
            i,
        ));
        i += 1;
    }

    LexResult { rhs }
}

fn try_number(chars: &[char], mut i: usize) -> Option<(Token, usize)> {
    let start = i;
    if !chars[i].is_ascii_digit() {
        return None;
    }
    while i < chars.len() && (chars[i].is_ascii_digit() || chars[i] == '.') {
        i += 1;
    }
    let token = Token {
        token_type: TokenType::Number,
        text: chars[start..i].iter().collect(),
        position: start,
    };
    Some((token, i - start))
}

fn try_identifiers(chars: &[char], mut i: usize) -> Option<(Token, usize)> {
    let start = i;
    if !chars[i].is_ascii_alphabetic() {
        return None;
    }
    while i < chars.len() && chars[i].is_ascii_alphanumeric() {
        i += 1;
    }
    let token = Token {
        token_type: TokenType::Identifier,
        text: chars[start..i].iter().collect(),
        position: start,
    };
    Some((token, i - start))
}

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

    #[test]
    fn test_try_number() {
        assert_eq!(
            try_number(&['1', '2'], 0),
            Some((Token::new(TokenType::Number, "12".to_string(), 0), 2))
        );
        assert_eq!(
            try_number(&['1', '.', '2'], 0),
            Some((Token::new(TokenType::Number, "1.2".to_string(), 0), 3))
        );
        assert_eq!(
            try_number(&['1', '1', '.', '2'], 0),
            Some((Token::new(TokenType::Number, "11.2".to_string(), 0), 4))
        );
        assert_eq!(
            try_number(&['1', '.', '2'], 2),
            Some((Token::new(TokenType::Number, "2".to_string(), 2), 1))
        );
        assert_eq!(
            try_number(&['1', '+', '2'], 0),
            Some((Token::new(TokenType::Number, "1".to_string(), 0), 1))
        );
        assert_eq!(
            try_number(&['1', ' ', '2'], 0),
            Some((Token::new(TokenType::Number, "1".to_string(), 0), 1))
        );
    }
}