sci_calc/context/
mod.rs

1
2use crate::{CalcError, CalcErrorType};
3
4mod builtins;
5
6#[cfg(test)]
7mod tests;
8
9/// Object passed to the calculate() function to track state of the calculator, 
10/// including defined variables, functions, and equation history
11pub struct Context {
12	var_table: Vec<VarTableEntry>,
13	/// Stores the value of the last successful calculation result, used when
14	/// evaluating the `ans` builtin variable;
15	pub prev_ans: Option<f64>,
16	function_table: Vec<Function>
17}
18
19impl Context {
20	pub fn new() -> Self {
21		Self {
22			var_table: builtins::get_consts(),
23			function_table: builtins::get_functions(),
24			prev_ans: None,
25		}
26	}
27
28	/// Takes in a query string and returns an Option that is none if the variable
29	/// doesn't exist in the var table. The option contains a result that will be
30	/// Ok with the var's value if the var can be read from, otherwise an error.
31	pub fn lookup_var(&self, query: &String) -> Option<Result<f64, CalcError>> {
32		// answer variable
33		if query.eq("ans") {
34			if let Some(ans) = self.prev_ans {
35				return Some(Ok(ans));
36			} else {
37				return Some(Err(CalcError {
38					error_type: CalcErrorType::CalculationError,
39					msg: "Cannot use \'ans\' without a previous evaluated equation".to_string(),
40				}));
41			}
42		}
43
44		// looking up var in table
45		for entry in &self.var_table {
46			if entry.name.eq(query) {
47				return Some(Ok(entry.value));
48			}
49		}
50		return None;
51	}
52
53	/// This function looks up a function with the specified name, returning a None
54	/// Option if the function doesn't exist, an Err inside the Option if there's
55	/// an issue with the arguments, otherwise it executes the function with the given
56	/// arguments and returns the answer
57	pub fn try_function(&self, name: &String, args: Vec<f64>) -> Option<Result<f64, CalcError>> {
58		for f in self.function_table.iter() {
59			if !f.name.eq(name) { continue; }
60			if f.num_args != 0 && f.num_args != args.len() {
61				return Some(Err(CalcError {
62					error_type: CalcErrorType::ArgumentError,
63					msg: "Invalid number of arguments".to_string(),
64				}));
65			}
66			return Some((f.closure)(args));
67		}
68		return None;
69	}
70
71	/// This function triest to assign a value to variable, returning an empty Ok
72	/// if successful, otherwise an Err
73	pub fn assign_var(&mut self, query: &String, val: f64) -> Result<(), CalcError> {
74		for entry in &mut self.var_table {
75			if entry.name.eq(query) {
76				if entry.constant {
77					return Err(CalcError {
78						error_type: CalcErrorType::AssignmentError,
79						msg: format!("Can't assign value to constant \'{}\'", entry.name).to_string()
80					});
81				}
82				entry.value = val;
83				return Ok(());
84			}
85		}
86		self.var_table.push(VarTableEntry {
87			name: query.clone(),
88			value: val,
89			constant: false,
90		});
91		return Ok(());
92
93	}
94}
95
96/// Represents a variable, whether builtin constant or user-defined
97struct VarTableEntry {
98	pub name: String,
99	pub value: f64,
100	pub constant: bool,
101}
102
103/// Represents a builtin function, contains the name, number of args, and a closure
104/// that performs the function's operation
105struct Function {
106	name: String,
107	num_args: usize,
108	closure: Box<dyn Fn(Vec<f64>) -> Result<f64, CalcError>>
109}