asciimath 0.8.8

Mathematical expression parser and evaluator
Documentation
use crate::{
    ast::{Args, Evaluate, EvaluationResult, Node, Root, Scope},
    error::Error,
    lexer::tokenize,
    tokens::{Operator, Token, TokenList},
};
use std::{collections::VecDeque, string::ToString};

type NodeList = Vec<Node>;

pub fn eval(expr: &str, scope: &Scope) -> EvaluationResult {
    parse_tokens(tokenize(expr, scope)?, scope)?.eval()
}

pub fn compile<'a>(expr: &'a str, scope: &'a Scope) -> Result<Root<'a>, Error> {
    parse_tokens(tokenize(expr, scope)?, scope)
}

fn encounter_func(f: String, operands: &mut NodeList) -> Result<(), Error> {
    let mut args = Args::with_capacity(2);

    // ASSUMPTION: at least one argument per function
    args.push_front(
        operands
            .pop()
            .ok_or(Error::NotEnoughFunctionParams(f.clone()))?,
    );

    while let Some(last) = operands.pop() {
        if last.token != Token::Comma {
            operands.push(last);
            break;
        }
        else {
            args.push_front(
                operands
                    .pop()
                    .ok_or(Error::FunctionSyntaxError(f.clone()))?,
            );
        }
    }

    operands.push(Node::new(Token::Function(f), Some(args)));
    Ok(())
}

fn right_paren(
    operators: &mut TokenList,
    operands: &mut NodeList,
) -> Result<(), Error> {
    while let Some(top) = operators.pop() {
        match top {
            Token::LeftParenthesis => match operators.last() {
                Some(Token::Function(_)) => {},
                _ => break,
            },
            Token::Function(f) => encounter_func(f, operands)?,
            Token::Operator(op) => add_operator(op, operands)?,
            _ => {},
        }
    }
    Ok(())
}

fn add_operator(
    operator: Operator,
    operands: &mut NodeList,
) -> Result<(), Error> {
    let num_operands = operator.num_operands();

    let mut args: VecDeque<Node> =
        VecDeque::with_capacity(num_operands as usize);

    for _ in 0..num_operands {
        args.push_front(
            operands
                .pop()
                .ok_or(Error::MissingOperands(operator.to_string()))?,
        );
    }
    operands.push(Node::new(Token::Operator(operator), Some(args)));
    Ok(())
}

fn encounter_operator(
    cur_operator: Operator,
    operators: &mut TokenList,
    operands: &mut NodeList,
) -> Result<(), Error> {
    while let Some(top) = operators.pop() {
        match top {
            Token::Operator(top_operator) => {
                if top_operator > cur_operator
                    || (top_operator == cur_operator
                        && !cur_operator.is_right_associative())
                {
                    add_operator(top_operator, operands)?
                }
                else {
                    operators.push(Token::Operator(top_operator));
                    break;
                }
            },
            Token::Function(f) => encounter_func(f, operands)?,
            _ => {
                operators.push(top);
                break;
            },
        }
    }

    operators.push(Token::Operator(cur_operator));
    Ok(())
}

fn parse_tokens(tokens: TokenList, scope: &Scope) -> Result<Root, Error> {
    let mut operators: TokenList = Vec::new();
    let mut operands: NodeList = Vec::new();

    for token in tokens {
        match token {
            Token::Number(num) => operands.push(Node {
                token: Token::Number(num),
                args: None,
            }),
            Token::Variable(var) => operands.push(Node {
                token: Token::Variable(var),
                args: None,
            }),
            Token::RightParenthesis => {
                right_paren(&mut operators, &mut operands)?
            },
            Token::LeftParenthesis => operators.push(token),
            Token::Operator(op1) => {
                encounter_operator(op1, &mut operators, &mut operands)?;
            },
            Token::Function(f) => operators.push(Token::Function(f)),
            Token::Comma => operands.push(Node::new(token, None)),
        };
    }

    while let Some(Token::Operator(operator)) = operators.pop() {
        add_operator(operator, &mut operands)?
    }

    // TODO: revisit this when the final output can also be a string
    operands.pop().map_or_else(
        || Err(Error::EmptyExpression),
        |node| Ok(Root { node, scope }),
    )
}