diff options
Diffstat (limited to 'src/lex/mod.rs')
-rw-r--r-- | src/lex/mod.rs | 283 |
1 files changed, 0 insertions, 283 deletions
diff --git a/src/lex/mod.rs b/src/lex/mod.rs deleted file mode 100644 index 3222c12..0000000 --- a/src/lex/mod.rs +++ /dev/null | |||
@@ -1,283 +0,0 @@ | |||
1 | /* Copyright (C) 2019 Akshay Oppiliappan <[email protected]> | ||
2 | * Refer to LICENCE for more information. | ||
3 | * */ | ||
4 | |||
5 | use lazy_static::lazy_static; | ||
6 | use std::collections::HashMap; | ||
7 | |||
8 | use crate::CONFIGURATION; | ||
9 | |||
10 | use crate::error::{CalcError, Math}; | ||
11 | |||
12 | #[derive(Debug, Copy, Clone, PartialEq)] | ||
13 | pub struct Operator { | ||
14 | token: char, | ||
15 | pub operation: fn(f64, f64) -> f64, | ||
16 | pub precedence: u8, | ||
17 | pub is_left_associative: bool, | ||
18 | } | ||
19 | |||
20 | impl Operator { | ||
21 | fn token_from_op( | ||
22 | token: char, | ||
23 | operation: fn(f64, f64) -> f64, | ||
24 | precedence: u8, | ||
25 | is_left_associative: bool, | ||
26 | ) -> Token { | ||
27 | Token::Operator(Operator { | ||
28 | token, | ||
29 | operation, | ||
30 | precedence, | ||
31 | is_left_associative, | ||
32 | }) | ||
33 | } | ||
34 | pub fn operate(self, x: f64, y: f64) -> Result<f64, CalcError> { | ||
35 | if self.token == '/' && y == 0. { | ||
36 | Err(CalcError::Math(Math::DivideByZero)) | ||
37 | } else { | ||
38 | Ok((self.operation)(x, y)) | ||
39 | } | ||
40 | } | ||
41 | } | ||
42 | |||
43 | #[derive(Debug, Clone, PartialEq)] | ||
44 | pub struct Function { | ||
45 | token: String, | ||
46 | relation: fn(f64) -> f64, | ||
47 | } | ||
48 | |||
49 | impl Function { | ||
50 | fn token_from_fn(token: String, relation: fn(f64) -> f64) -> Token { | ||
51 | Token::Function(Function { token, relation }) | ||
52 | } | ||
53 | pub fn apply(self, arg: f64) -> Result<f64, CalcError> { | ||
54 | let result = (self.relation)(arg); | ||
55 | if !result.is_finite() { | ||
56 | Err(CalcError::Math(Math::OutOfBounds)) | ||
57 | } else { | ||
58 | Ok(result) | ||
59 | } | ||
60 | } | ||
61 | } | ||
62 | |||
63 | #[derive(Debug, Clone, PartialEq)] | ||
64 | pub enum Token { | ||
65 | Operator(Operator), | ||
66 | Num(f64), | ||
67 | Function(Function), | ||
68 | LParen, | ||
69 | RParen, | ||
70 | } | ||
71 | |||
72 | lazy_static! { | ||
73 | static ref CONSTANTS: HashMap<&'static str, Token> = { | ||
74 | let mut m = HashMap::new(); | ||
75 | m.insert("e", Token::Num(std::f64::consts::E)); | ||
76 | m.insert("pi", Token::Num(std::f64::consts::PI)); | ||
77 | m | ||
78 | }; | ||
79 | |||
80 | static ref FUNCTIONS: HashMap<&'static str, Token> = { | ||
81 | let mut m = HashMap::new(); | ||
82 | m.insert("sin", Function::token_from_fn("sin".into(), |x| is_radian_mode(x, CONFIGURATION.radian_mode).sin())); | ||
83 | m.insert("cos", Function::token_from_fn("cos".into(), |x| is_radian_mode(x, CONFIGURATION.radian_mode).cos())); | ||
84 | m.insert("tan", Function::token_from_fn("tan".into(), |x| is_radian_mode(x, CONFIGURATION.radian_mode).tan())); | ||
85 | m.insert("csc", Function::token_from_fn("csc".into(), |x| is_radian_mode(x, CONFIGURATION.radian_mode).sin().recip())); | ||
86 | m.insert("sec", Function::token_from_fn("sec".into(), |x| is_radian_mode(x, CONFIGURATION.radian_mode).cos().recip())); | ||
87 | m.insert("cot", Function::token_from_fn("cot".into(), |x| is_radian_mode(x, CONFIGURATION.radian_mode).tan().recip())); | ||
88 | m.insert("sinh", Function::token_from_fn("sinh".into(), |x| x.sinh())); | ||
89 | m.insert("cosh", Function::token_from_fn("cosh".into(), |x| x.cosh())); | ||
90 | m.insert("tanh", Function::token_from_fn("tanh".into(), |x| x.tanh())); | ||
91 | m.insert("ln", Function::token_from_fn("ln".into(), |x| x.ln())); | ||
92 | m.insert("log", Function::token_from_fn("log".into(), |x| x.log10())); | ||
93 | m.insert("sqrt", Function::token_from_fn("sqrt".into(), |x| x.sqrt())); | ||
94 | m.insert("ceil", Function::token_from_fn("ceil".into(), |x| x.ceil())); | ||
95 | m.insert("floor", Function::token_from_fn("floor".into(), |x| x.floor())); | ||
96 | m.insert("rad", Function::token_from_fn("rad".into(), |x| x.to_radians())); | ||
97 | m.insert("deg", Function::token_from_fn("deg".into(), |x| x.to_degrees())); | ||
98 | m.insert("abs", Function::token_from_fn("abs".into(), |x| x.abs())); | ||
99 | m.insert("asin", Function::token_from_fn("asin".into(), |x| x.asin())); | ||
100 | m.insert("acos", Function::token_from_fn("acos".into(), |x| x.acos())); | ||
101 | m.insert("atan", Function::token_from_fn("atan".into(), |x| x.atan())); | ||
102 | m.insert("acsc", Function::token_from_fn("acsc".into(), |x| (1./x).asin())); | ||
103 | m.insert("asec", Function::token_from_fn("asec".into(), |x| (1./x).acos())); | ||
104 | m.insert("acot", Function::token_from_fn("acot".into(), |x| (1./x).atan())); | ||
105 | // single arg function s can be added here | ||
106 | m | ||
107 | }; | ||
108 | |||
109 | static ref OPERATORS: HashMap<char, Token> = { | ||
110 | let mut m = HashMap::new(); | ||
111 | m.insert('+', Operator::token_from_op('+', |x, y| x + y, 2, true)); | ||
112 | m.insert('-', Operator::token_from_op('-', |x, y| x - y, 2, true)); | ||
113 | m.insert('*', Operator::token_from_op('*', |x, y| x * y, 3, true)); | ||
114 | m.insert('/', Operator::token_from_op('/', |x, y| x / y, 3, true)); | ||
115 | m.insert('%', Operator::token_from_op('%', |x, y| x % y, 3, true)); | ||
116 | m.insert('^', Operator::token_from_op('^', |x, y| x.powf(y) , 4, false)); | ||
117 | m.insert('!', Operator::token_from_op('!', |x, _| factorial(x) , 4, true)); | ||
118 | m | ||
119 | }; | ||
120 | } | ||
121 | |||
122 | fn factorial(n: f64) -> f64 { | ||
123 | n.signum() * (1..=n.abs() as u64).fold(1, |p, n| p * n) as f64 | ||
124 | } | ||
125 | |||
126 | pub fn lexer(input: &str, prev_ans: Option<f64>) -> Result<Vec<Token>, CalcError> { | ||
127 | let mut num_vec: String = String::new(); | ||
128 | let mut char_vec: String = String::new(); | ||
129 | let mut result: Vec<Token> = vec![]; | ||
130 | let mut last_char_is_op = true; | ||
131 | |||
132 | let mut chars = input.chars().peekable(); | ||
133 | while let Some(mut letter) = chars.next() { | ||
134 | match letter { | ||
135 | '0'..='9' | '.' => { | ||
136 | if !char_vec.is_empty() { | ||
137 | if FUNCTIONS.get(&char_vec[..]).is_some() { | ||
138 | return Err(CalcError::Syntax(format!( | ||
139 | "Function '{}' expected parentheses", | ||
140 | char_vec | ||
141 | ))); | ||
142 | } else { | ||
143 | return Err(CalcError::Syntax(format!( | ||
144 | "Unexpected character '{}'", | ||
145 | char_vec | ||
146 | ))); | ||
147 | } | ||
148 | } | ||
149 | num_vec.push(letter); | ||
150 | last_char_is_op = false; | ||
151 | } | ||
152 | '_' => { | ||
153 | if prev_ans.is_none() { | ||
154 | return Err(CalcError::Syntax("No previous answer!".into())); | ||
155 | } | ||
156 | if !char_vec.is_empty() { | ||
157 | if FUNCTIONS.get(&char_vec[..]).is_some() { | ||
158 | return Err(CalcError::Syntax(format!( | ||
159 | "Function '{}' expected parentheses", | ||
160 | char_vec | ||
161 | ))); | ||
162 | } else { | ||
163 | return Err(CalcError::Syntax(format!( | ||
164 | "Unexpected character '{}'", | ||
165 | char_vec | ||
166 | ))); | ||
167 | } | ||
168 | } | ||
169 | let parse_num = num_vec.parse::<f64>().ok(); | ||
170 | if let Some(x) = parse_num { | ||
171 | result.push(Token::Num(x)); | ||
172 | result.push(OPERATORS.get(&'*').unwrap().clone()); | ||
173 | num_vec.clear(); | ||
174 | } | ||
175 | last_char_is_op = false; | ||
176 | result.push(Token::Num(prev_ans.unwrap())); | ||
177 | } | ||
178 | 'a'..='z' | 'A'..='Z' => { | ||
179 | let parse_num = num_vec.parse::<f64>().ok(); | ||
180 | if let Some(x) = parse_num { | ||
181 | result.push(Token::Num(x)); | ||
182 | result.push(OPERATORS.get(&'*').unwrap().clone()); | ||
183 | num_vec.clear(); | ||
184 | } | ||
185 | char_vec.push(letter); | ||
186 | last_char_is_op = false; | ||
187 | } | ||
188 | '+' | '-' => { | ||
189 | let op_token = OPERATORS.get(&letter).unwrap().clone(); | ||
190 | let parse_num = num_vec.parse::<f64>().ok(); | ||
191 | if !last_char_is_op { | ||
192 | if let Some(x) = parse_num { | ||
193 | result.push(Token::Num(x)); | ||
194 | num_vec.clear(); | ||
195 | last_char_is_op = true; | ||
196 | } else if let Some(token) = CONSTANTS.get(&char_vec[..]) { | ||
197 | result.push(token.clone()); | ||
198 | char_vec.clear(); | ||
199 | last_char_is_op = true; | ||
200 | } | ||
201 | result.push(op_token); | ||
202 | } else if last_char_is_op { | ||
203 | result.push(Token::LParen); | ||
204 | result.push(Token::Num( | ||
205 | (letter.to_string() + "1").parse::<f64>().unwrap(), | ||
206 | )); | ||
207 | result.push(Token::RParen); | ||
208 | result.push(Operator::token_from_op('*', |x, y| x * y, 10, true)); | ||
209 | } | ||
210 | } | ||
211 | '/' | '*' | '%' | '^' | '!' => { | ||
212 | drain_stack(&mut num_vec, &mut char_vec, &mut result); | ||
213 | if letter == '*' && chars.peek() == Some(&'*') { | ||
214 | // Accept `**` operator as meaning `^` (exponentation). | ||
215 | let _ = chars.next(); | ||
216 | letter = '^'; | ||
217 | } | ||
218 | let operator_token: Token = OPERATORS.get(&letter).unwrap().clone(); | ||
219 | result.push(operator_token); | ||
220 | last_char_is_op = true; | ||
221 | if letter == '!' { | ||
222 | result.push(Token::Num(1.)); | ||
223 | last_char_is_op = false; | ||
224 | } | ||
225 | } | ||
226 | '(' => { | ||
227 | if !char_vec.is_empty() { | ||
228 | if let Some(res) = FUNCTIONS.get(&char_vec[..]) { | ||
229 | result.push(res.clone()); | ||
230 | } else { | ||
231 | return Err(CalcError::Syntax(format!( | ||
232 | "Unknown function '{}'", | ||
233 | char_vec | ||
234 | ))); | ||
235 | } | ||
236 | char_vec.clear(); | ||
237 | } else { | ||
238 | let parse_num = num_vec.parse::<f64>().ok(); | ||
239 | if let Some(x) = parse_num { | ||
240 | result.push(Token::Num(x)); | ||
241 | result.push(OPERATORS.get(&'*').unwrap().clone()); | ||
242 | num_vec.clear(); | ||
243 | } | ||
244 | } | ||
245 | |||
246 | if let Some(Token::RParen) = result.last() { | ||
247 | result.push(OPERATORS.get(&'*').unwrap().clone()); | ||
248 | } | ||
249 | result.push(Token::LParen); | ||
250 | last_char_is_op = true; | ||
251 | } | ||
252 | ')' => { | ||
253 | drain_stack(&mut num_vec, &mut char_vec, &mut result); | ||
254 | result.push(Token::RParen); | ||
255 | last_char_is_op = false; | ||
256 | } | ||
257 | ' ' => {} | ||
258 | _ => return Err(CalcError::Syntax(format!("Unexpected token: '{}'", letter))), | ||
259 | } | ||
260 | } | ||
261 | // println!("{:?}", result); | ||
262 | drain_stack(&mut num_vec, &mut char_vec, &mut result); | ||
263 | Ok(result) | ||
264 | } | ||
265 | |||
266 | fn drain_stack(num_vec: &mut String, char_vec: &mut String, result: &mut Vec<Token>) { | ||
267 | let parse_num = num_vec.parse::<f64>().ok(); | ||
268 | if let Some(x) = parse_num { | ||
269 | result.push(Token::Num(x)); | ||
270 | num_vec.clear(); | ||
271 | } else if let Some(token) = CONSTANTS.get(&char_vec[..]) { | ||
272 | result.push(token.clone()); | ||
273 | char_vec.clear(); | ||
274 | } | ||
275 | } | ||
276 | |||
277 | fn is_radian_mode(x: f64, is_radian: bool) -> f64 { | ||
278 | if is_radian { | ||
279 | x | ||
280 | } else { | ||
281 | x.to_radians() | ||
282 | } | ||
283 | } | ||