1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109

use crate::{CalcError, CalcErrorType};

mod builtins;

#[cfg(test)]
mod tests;

/// Object passed to the calculate() function to track state of the calculator, 
/// including defined variables, functions, and equation history
pub struct Context {
	var_table: Vec<VarTableEntry>,
	/// Stores the value of the last successful calculation result, used when
	/// evaluating the `ans` builtin variable;
	pub prev_ans: Option<f64>,
	function_table: Vec<Function>
}

impl Context {
	pub fn new() -> Self {
		Self {
			var_table: builtins::get_consts(),
			function_table: builtins::get_functions(),
			prev_ans: None,
		}
	}

	/// Takes in a query string and returns an Option that is none if the variable
	/// doesn't exist in the var table. The option contains a result that will be
	/// Ok with the var's value if the var can be read from, otherwise an error.
	pub fn lookup_var(&self, query: &String) -> Option<Result<f64, CalcError>> {
		// answer variable
		if query.eq("ans") {
			if let Some(ans) = self.prev_ans {
				return Some(Ok(ans));
			} else {
				return Some(Err(CalcError {
					error_type: CalcErrorType::CalculationError,
					msg: "Cannot use \'ans\' without a previous evaluated equation".to_string(),
				}));
			}
		}

		// looking up var in table
		for entry in &self.var_table {
			if entry.name.eq(query) {
				return Some(Ok(entry.value));
			}
		}
		return None;
	}

	/// This function looks up a function with the specified name, returning a None
	/// Option if the function doesn't exist, an Err inside the Option if there's
	/// an issue with the arguments, otherwise it executes the function with the given
	/// arguments and returns the answer
	pub fn try_function(&self, name: &String, args: Vec<f64>) -> Option<Result<f64, CalcError>> {
		for f in self.function_table.iter() {
			if !f.name.eq(name) { continue; }
			if f.num_args != 0 && f.num_args != args.len() {
				return Some(Err(CalcError {
					error_type: CalcErrorType::ArgumentError,
					msg: "Invalid number of arguments".to_string(),
				}));
			}
			return Some((f.closure)(args));
		}
		return None;
	}

	/// This function triest to assign a value to variable, returning an empty Ok
	/// if successful, otherwise an Err
	pub fn assign_var(&mut self, query: &String, val: f64) -> Result<(), CalcError> {
		for entry in &mut self.var_table {
			if entry.name.eq(query) {
				if entry.constant {
					return Err(CalcError {
						error_type: CalcErrorType::AssignmentError,
						msg: format!("Can't assign value to constant \'{}\'", entry.name).to_string()
					});
				}
				entry.value = val;
				return Ok(());
			}
		}
		self.var_table.push(VarTableEntry {
			name: query.clone(),
			value: val,
			constant: false,
		});
		return Ok(());

	}
}

/// Represents a variable, whether builtin constant or user-defined
struct VarTableEntry {
	pub name: String,
	pub value: f64,
	pub constant: bool,
}

/// Represents a builtin function, contains the name, number of args, and a closure
/// that performs the function's operation
struct Function {
	name: String,
	num_args: usize,
	closure: Box<dyn Fn(Vec<f64>) -> Result<f64, CalcError>>
}