precise_calc/
context.rs

1//! Contains the [Context] struct used to store functions and values currently defined.
2
3use std::collections::HashMap;
4
5use astro_float::BigFloat;
6use lazy_static::lazy_static;
7use serde::{Deserialize, Serialize};
8
9use crate::ast::{BuiltinFunc, CalcFuncRef, UserFunc};
10use crate::builtins;
11use crate::{CalcError, CalcResult, Number, PREC, RM};
12
13lazy_static! {
14    /// HashMap of all builtin functions (name mapped to function).
15    pub static ref BUILTINS: HashMap<String, BuiltinFunc> = {
16        let mut m = HashMap::new();
17
18        // Square root function
19        m.insert(
20            "sqrt".to_string(),
21            BuiltinFunc::new(1, builtins::sqrt)
22        );
23
24        // Natural logarithm
25        m.insert(
26            "ln".to_string(),
27            BuiltinFunc::new(1, builtins::ln)
28        );
29
30        // Logarithm of any base
31        m.insert(
32            "log".to_string(),
33            BuiltinFunc::new(1, builtins::log)
34        );
35
36        // Trig functions
37        m.insert(
38            "sin".to_string(),
39            BuiltinFunc::new(1, builtins::sin)
40        );
41
42        m.insert(
43            "cos".to_string(),
44            BuiltinFunc::new(1, builtins::cos)
45        );
46
47        m.insert(
48            "tan".to_string(),
49            BuiltinFunc::new(1, builtins::tan)
50        );
51
52        m
53    };
54
55    /// Builtin values such as e or pi.
56    pub static ref BUILTIN_VALUES: HashMap<String, BigFloat> = {
57        let mut m = HashMap::new();
58        let mut consts = astro_float::Consts::new().unwrap();
59        m.insert("pi".to_string(), consts.pi(PREC, RM));
60        m.insert("e".to_string(), consts.e(PREC, RM));
61        m
62    };
63}
64
65/// The context for evaluating expressions and statements in.
66///
67/// Maps names to functions and values.
68#[derive(Clone, Serialize, Deserialize)]
69pub struct Context {
70    functions: Vec<HashMap<String, UserFunc>>,
71    values: Vec<HashMap<String, Number>>,
72}
73
74impl Context {
75    /// Create a new empty context.
76    pub fn new() -> Context {
77        Context {
78            functions: vec![HashMap::new()],
79            values: vec![HashMap::new()],
80        }
81    }
82
83    /// Add a new scope of values to the context.
84    pub fn add_scope(&mut self, values: HashMap<String, Number>) {
85        self.values.push(values);
86    }
87
88    /// Lookup a value in the context. Returns the [CalcError::NameNotFound] if the name is not
89    /// mapped in the context (in any scope). If the name is in multiple scopes it returns the
90    /// value from the last scope.
91    pub fn lookup_value(&self, name: &str) -> CalcResult {
92        if let Some(value) = self
93            .values
94            .iter()
95            .rev()
96            .find(|s| s.contains_key(name))
97            .and_then(|s| s.get(name).cloned())
98        {
99            Ok(value)
100        } else if let Some(value) = BUILTIN_VALUES.get(name) {
101            Ok(value.clone())
102        } else {
103            Err(CalcError::NameNotFound(name.to_owned()))
104        }
105    }
106
107    /// Lookup a function in the context. Returns the [CalcError::NameNotFound] if the name is not
108    /// mapped in the context (in any scope). If the name is in multiple scopes it returns the
109    /// value from the last scope.
110    pub fn lookup_fn(&self, name: &str) -> Result<CalcFuncRef, CalcError> {
111        if let Some(func) = self
112            .functions
113            .iter()
114            .rev()
115            .find(|s| s.contains_key(name))
116            .and_then(|s| s.get(name))
117        {
118            Ok(CalcFuncRef::UserDef(func))
119        } else if let Some(func) = BUILTINS.get(name) {
120            Ok(CalcFuncRef::Builtin(func))
121        } else {
122            Err(CalcError::NameNotFound(name.to_owned()))
123        }
124    }
125
126    /// Bind `value` to `name` in the top scope. If `name` is the name of a builtin value nothing
127    /// will be bound and the function will return [CalcError::NameAlreadyBound].
128    pub fn bind_value(&mut self, name: String, value: Number) -> CalcResult {
129        // Make sure you don't overwrite a builtin
130        if BUILTIN_VALUES.contains_key(&name) {
131            Err(CalcError::NameAlreadyBound(name))
132        } else {
133            self.values.last_mut().unwrap().insert(name, value.clone());
134            Ok(value)
135        }
136    }
137
138    /// Bind `func` to `name` in the top scope. If `name` is the name of a builtin function nothing
139    /// will be bound and the function will return [CalcError::NameAlreadyBound].
140    pub fn bind_fn(&mut self, name: String, func: UserFunc) -> Result<(), CalcError> {
141        // Make sure you don't overwrite a builtin
142        if BUILTINS.contains_key(&name) {
143            Err(CalcError::NameAlreadyBound(name))
144        } else {
145            self.functions.last_mut().unwrap().insert(name, func);
146            Ok(())
147        }
148    }
149}