precise_calc/
eval.rs

1//! Contains the functions used to evaluate an AST.
2
3use std::fmt::Display;
4
5use astro_float::Consts;
6
7use crate::ast::{Atom, BinaryOp, Expr, Stmt, UnaryOp, UserFunc};
8use crate::context::Context;
9use crate::formatting::float_to_string;
10use crate::{CalcError, CalcResult, Number, PREC, RM};
11
12/// Evaluate an atom in `ctx`. If `atom` is a number, return that number. If `atom` is a symbol,
13/// return the value bound to the symbol. If the symbol is not bound in `ctx`, return
14/// [CalcError::NameNotFound].
15pub fn eval_atom(atom: &Atom, ctx: &Context) -> CalcResult {
16    match atom {
17        Atom::Num(num) => Ok(num.clone()),
18        Atom::Symbol(name) => ctx.lookup_value(name),
19    }
20}
21
22/// Evaluate an expression in `ctx`.
23pub fn eval_expr(expr: &Expr, ctx: &Context) -> CalcResult {
24    match expr {
25        Expr::AtomExpr(atom) => eval_atom(atom, ctx),
26        Expr::UnaryExpr { op, data } => {
27            let data = eval_expr(data, ctx)?;
28            match op {
29                UnaryOp::Negate => Ok(-data),
30            }
31        }
32        Expr::BinaryExpr { lhs, rhs, op } => {
33            let lhs = eval_expr(lhs, ctx)?;
34            let rhs = eval_expr(rhs, ctx)?;
35            Ok(match op {
36                BinaryOp::Plus => lhs.add(&rhs, PREC, RM),
37                BinaryOp::Minus => lhs.sub(&rhs, PREC, RM),
38                BinaryOp::Times => lhs.mul(&rhs, PREC, RM),
39                BinaryOp::Divide => lhs.div(&rhs, PREC, RM),
40                BinaryOp::Power => {
41                    // TODO: Don't unwrap
42                    let mut consts = Consts::new().unwrap();
43                    lhs.pow(&rhs, PREC, RM, &mut consts)
44                }
45            })
46        }
47        Expr::FunctionCall { function, args } => {
48            let function = ctx.lookup_fn(function)?;
49            let args = args
50                .into_iter()
51                .map(|arg| eval_expr(arg, ctx))
52                .collect::<Result<Vec<Number>, CalcError>>()?;
53
54            function.call(&args, ctx)
55        }
56    }
57}
58
59/// Values that can be returned when a statement is evaluated.
60pub enum CalcValue {
61    /// The value returned when functions are defined.
62    Ok,
63
64    /// The value returned by an expression or variable definition statement.
65    Value(Number),
66}
67
68impl Display for CalcValue {
69    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
70        match self {
71            CalcValue::Ok => write!(f, "ok"),
72            CalcValue::Value(v) => write!(f, "{}", float_to_string(v)),
73        }
74    }
75}
76
77/// Evaluate a statement in `ctx`.
78pub fn eval_stmt(stmt: &Stmt, ctx: &mut Context) -> Result<CalcValue, CalcError> {
79    match stmt {
80        Stmt::FuncDef { name, params, body } => {
81            let func = UserFunc::new(params.clone(), body.clone());
82            ctx.bind_fn(name.clone(), func)?;
83            Ok(CalcValue::Ok)
84        }
85        Stmt::Assignment { name, value } => {
86            let value = eval_expr(&value, ctx)?;
87            ctx.bind_value(name.clone(), value)
88                .map(|v| CalcValue::Value(v))
89        }
90        Stmt::ExprStmt(expr) => eval_expr(expr, ctx).map(|v| CalcValue::Value(v)),
91    }
92}