calc 0.5.0

CLI calculator app
Documentation
use crate::ast::{
    AnnotatedExpr,
    Constant,
    Expr,
    Function,
    HistoryIndexKind,
    InfixOperator,
    ParseError as UserParseError,
    PrefixOperator,
    Term,
};
use lalrpop_util::ParseError;

grammar;

extern {
    type Error = UserParseError;
}

match {
    // numeric terminals get top priority
    r"[_0-9]+(\.[_0-9]+)?",
    r"\.[_0-9]+",
    r"0b[_01]+",
    r"0o[_0-7]+",
    r"0d[_0-9]+",
    r"0x[_0-9a-fA-F]+",
} else {
    // everything else
    _,
}

FuncName: Function = {
    "abs" => Function::Abs,
    "ceil" => Function::Ceil,
    "floor" => Function::Floor,
    "round" => Function::Round,
    "sin" => Function::Sin,
    "cos" => Function::Cos,
    "tan" => Function::Tan,
    "sinh" => Function::Sinh,
    "cosh" => Function::Cosh,
    "tanh" => Function::Tanh,
    "asin" => Function::Asin,
    "acos" => Function::Acos,
    "atan" => Function::Atan,
    "asinh" => Function::Asinh,
    "acosh" => Function::Acosh,
    "atanh" => Function::Atanh,
    "rad" => Function::Rad,
    "deg" => Function::Deg,
    "sqrt" => Function::Sqrt,
    "cbrt" => Function::Cbrt,
    "log" => Function::Log,
    "lg" => Function::Lg,
    "ln" => Function::Ln,
    "exp" => Function::Exp,
};

Constant: Constant = {
    "e" => Constant::E,
    "pi" => Constant::Pi,
    "π" => Constant::Pi,
};

Term: Term<'input> = {
    r"[_0-9]+(\.[_0-9]+)?" => Term::Literal(<>),
    r"\.[_0-9]+" => Term::Literal(<>),
    r"0b[_01]+" => Term::BinLiteral(<>),
    r"0o[_0-7]+" => Term::OctLiteral(<>),
    r"0d[_0-9]+" => Term::Literal(<>),
    r"0x[_0-9a-fA-F]+" => Term::HexLiteral(<>),
    <Constant> => Term::Constant(<>),
    <h:r"@\[([0-9]+)\]"> =>? Ok(Term::History(
        HistoryIndexKind::Absolute,
        h[2..h.len()-1].parse().map_err(|err| ParseError::User { error: UserParseError::Index(err) })?
    )),
    <h:r"@\{([0-9]+)\}"> =>? Ok(Term::History(
        HistoryIndexKind::Relative,
        h[2..h.len()-1].parse().map_err(|err| ParseError::User { error: UserParseError::Index(err) })?
    )),
    <h:r"@+"> => Term::History(HistoryIndexKind::Relative, <>.len()),
};

// Expressions need to evolve from low precedence to high.
// This ensures that when we recursively evaluate them, we end up with the correct results.
//
// This parses the lowest level of precedence: addition and subtraction.
pub Expr: Expr<'input> = {
    <l:Expr> "+" <r:Factor> => Expr::Infix(Box::new(l), InfixOperator::Add, Box::new(r)),
    <l:Expr> "-" <r:Factor> => Expr::Infix(Box::new(l), InfixOperator::Sub, Box::new(r)),
    Factor,
};

// This parses the next level of precedence: multiplication and division.
Factor: Expr<'input> = {
    <l:Factor> "*" <r:Bitwise> => Expr::Infix(Box::new(l), InfixOperator::Mul, Box::new(r)), // asterisk
    <l:Factor> "x" <r:Bitwise> => Expr::Infix(Box::new(l), InfixOperator::Mul, Box::new(r)), // lowercase x
    <l:Factor> "×" <r:Bitwise> => Expr::Infix(Box::new(l), InfixOperator::Mul, Box::new(r)), // u+00d7 multiplication sign
    <l:Factor> "·" <r:Bitwise> => Expr::Infix(Box::new(l), InfixOperator::Mul, Box::new(r)), // u+00b7 middle dot
    <l:Factor> "⋅" <r:Bitwise> => Expr::Infix(Box::new(l), InfixOperator::Mul, Box::new(r)), // u+22c5 dot operator
    <l:Factor> "✕" <r:Bitwise> => Expr::Infix(Box::new(l), InfixOperator::Mul, Box::new(r)), // u+2715 multiplication x
    <l:Factor> "✖" <r:Bitwise> => Expr::Infix(Box::new(l), InfixOperator::Mul, Box::new(r)), // u+2716 heavy multiplication x
    <l:Factor> "/" <r:Bitwise> => Expr::Infix(Box::new(l), InfixOperator::Div, Box::new(r)), // slash
    <l:Factor> "÷" <r:Bitwise> => Expr::Infix(Box::new(l), InfixOperator::Div, Box::new(r)), // u+00f7 division sign
    <l:Factor> "%" <r:Bitwise> => Expr::Infix(Box::new(l), InfixOperator::Rem, Box::new(r)),
    <l:Factor> "//" <r:Bitwise> => Expr::Infix(Box::new(l), InfixOperator::TruncDiv, Box::new(r)),
    Bitwise,
};

// This parses the next level of precedence: bitwise operations
Bitwise: Expr<'input> = {
    <l:Bitwise> "&" <r:ShiftExp> => Expr::Infix(Box::new(l), InfixOperator::BitAnd, Box::new(r)),
    <l:Bitwise> "|" <r:ShiftExp> => Expr::Infix(Box::new(l), InfixOperator::BitOr, Box::new(r)),
    <l:Bitwise> "^" <r:ShiftExp> => Expr::Infix(Box::new(l), InfixOperator::BitXor, Box::new(r)),
    ShiftExp,
};

// This parses the next level of precedence: bit shifts and exponentiaton
ShiftExp: Expr<'input> = {
    <l:ShiftExp> "<<" <r:Unary> => Expr::Infix(Box::new(l), InfixOperator::Lshift, Box::new(r)),
    <l:ShiftExp> ">>" <r:Unary> => Expr::Infix(Box::new(l), InfixOperator::Rshift, Box::new(r)),
    <l:ShiftExp> "<<<" <r:Unary> => Expr::Infix(Box::new(l), InfixOperator::RotateL, Box::new(r)),
    <l:ShiftExp> ">>>" <r:Unary> => Expr::Infix(Box::new(l), InfixOperator::RotateR, Box::new(r)),
    <l:ShiftExp> "**" <r:Unary> => Expr::Infix(Box::new(l), InfixOperator::Pow, Box::new(r)),
    Unary,
};

// This parses the next level of precedence: unary operations
Unary: Expr<'input> = {
    "!" <r:Unary> => Expr::Prefix(PrefixOperator::Not, Box::new(r)),
    "-" <r:Unary> => Expr::Prefix(PrefixOperator::Negation, Box::new(r)),
    ExprTerm,
};

// This parses the final level of precedence: terms, functions, and parentheses
ExprTerm: Expr<'input> = {
    <Term> => Expr::Term(<>),
    <f:FuncName> "(" <e:Expr> ")" => Expr::Func(f, Box::new(e)),
    "(" <Expr> ")" => Expr::Group(Box::new(<>)),
    "⌈" <Expr> "⌉" => Expr::Func(Function::Ceil, Box::new(<>)),
    "⌊" <Expr> "⌋" => Expr::Func(Function::Floor, Box::new(<>)),
};

pub AnnotatedExpr: AnnotatedExpr<'input> = {
    <expr:Expr> <fmt:r":.*"> =>? Ok(AnnotatedExpr {
        expr,
        format: fmt[1..].parse().map_err(|err| ParseError::User { error: UserParseError::Format(err) })?,
    }),
    <expr:Expr> => AnnotatedExpr { expr, format: Default::default() },
};