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