asciimath 0.8.8

Mathematical expression parser and evaluator
Documentation
use crate::{
    ast::{resolve_fn, resolve_var, NumericLiteral, Scope},
    error::Error,
    tokens::{Operator, Token, TokenList},
    util::consume_while,
};

fn resolve_vars(expr: &str, scope: &Scope, mut tokens: &mut Vec<Token>) {
    let mut chars = expr.chars();
    let mut var = String::new();
    let mut is_valid_var = false;

    let new_var = |name, t: &mut Vec<Token>| {
        t.push(Token::Variable(name));
        t.push(Token::Operator(Operator::Multiply));
    };

    loop {
        if let Some(c) = chars.next() {
            var.push(c);
            is_valid_var = resolve_var(&var, scope).is_ok();
            if !is_valid_var {
                continue;
            }
        }

        if !var.is_empty() {
            if is_valid_var {
                new_var(var.clone(), &mut tokens);
            }
            else {
                for c in var.chars() {
                    new_var(c.to_string(), &mut tokens);
                }
            }

            var.clear();
            continue;
        }
        break;
    }
}

fn parse_implicit(
    expr: &str,
    scope: &Scope,
    tokens: &mut TokenList,
) -> Result<(), Error> {
    let mut chars = expr.chars().peekable();

    if tokens.last() == Some(&Token::RightParenthesis) {
        tokens.push(Token::Operator(Operator::Multiply));
    }

    while let Some(&ch) = chars.peek() {
        match ch {
            '0'...'9' => {
                let num = consume_while(chars.by_ref(), |n| {
                    n.is_digit(10) || n == '.'
                });
                let n = num
                    .parse::<NumericLiteral>()
                    .map_err(|_e| Error::InvalidToken(num))?;
                tokens.push(Token::Number(n));
                tokens.push(Token::Operator(Operator::Multiply));
            },
            'a'...'z' | 'A'...'Z' => {
                let vars = consume_while(&mut chars, |c| c.is_alphabetic());
                resolve_vars(&vars, scope, tokens);
                chars.by_ref().next();
            },
            _ => {},
        }
    }
    Ok(())
}

fn get_token(ch: Option<&char>, t: &mut Vec<Token>) -> Option<Token> {
    if let Some(ch) = ch {
        match ch {
            '+' => Some(Token::Operator(Operator::Add)),
            '-' => match t.last() {
                Some(Token::Comma)
                | Some(Token::LeftParenthesis)
                | Some(Token::Function(_))
                | Some(Token::Operator(_))
                | None => {
                    t.push(Token::Number(-1.0));
                    t.push(Token::Operator(Operator::Multiply));
                    None
                },
                _ => Some(Token::Operator(Operator::Substract)),
            },
            '*' => Some(Token::Operator(Operator::Multiply)),
            '/' => Some(Token::Operator(Operator::Divide)),
            '^' => Some(Token::Operator(Operator::Exponentiate)),
            '>' => Some(Token::Operator(Operator::IsGreaterThan)),
            '<' => Some(Token::Operator(Operator::IsLessThan)),
            '=' => match t.last() {
                Some(Token::Operator(Operator::Not)) => {
                    t.pop();
                    Some(Token::Operator(Operator::IsNotEqualTo))
                },
                Some(Token::Operator(Operator::IsGreaterThan)) => {
                    t.pop();
                    Some(Token::Operator(Operator::IsGreaterThanOrEqualTo))
                },
                Some(Token::Operator(Operator::IsLessThan)) => {
                    t.pop();
                    Some(Token::Operator(Operator::IsLessThanOrEqualTo))
                },
                Some(Token::Operator(Operator::IsEqualTo)) => None,
                _ => Some(Token::Operator(Operator::IsEqualTo)),
            },
            '(' => Some(Token::LeftParenthesis),
            ')' => Some(Token::RightParenthesis),
            ',' => Some(Token::Comma),
            '!' => Some(Token::Operator(Operator::Not)),
            _ => None,
        }
    }
    else {
        None
    }
}

pub fn tokenize<'a>(expr: &str, scope: &'a Scope) -> Result<TokenList, Error> {
    dbg!(expr);
    let mut chars = expr.chars().peekable();
    let mut tokens = Vec::with_capacity(expr.len());

    while let Some(&_c) = chars.peek() {
        let temp = consume_while(&mut chars.by_ref(), |c| {
            c.is_alphanumeric() || c == '_' || c == '.'
        });

        if !temp.is_empty() {
            if chars.peek() == Some(&'(') && resolve_fn(&temp, scope).is_ok() {
                tokens.push(Token::Function(temp));
                continue;
            }
            else {
                parse_implicit(&temp, scope, &mut tokens)?;
                if chars.peek() != Some(&'(') {
                    tokens.pop();
                }
            }
        }
        if let Some(token) = get_token(chars.peek(), &mut tokens) {
            tokens.push(token);
        }
        chars.next();
    }

    Ok(tokens)
}
mod tests;