diff options
author | Ivan Tham <[email protected]> | 2022-01-05 04:01:48 +0000 |
---|---|---|
committer | GitHub <[email protected]> | 2022-01-05 04:01:48 +0000 |
commit | 9fa2823b3916184dbfbb9c704ab34ae79c0c1038 (patch) | |
tree | 597a3ac1bad860fa62737faa58aeb71184c249be /src | |
parent | 3f52f6f2ce8408350fcdc180d416e63dd627d93f (diff) | |
parent | 3f0f8d9f0f910b4c9477297049d4a508eacc3734 (diff) |
Merge pull request #51 from Ma27/exp-and-round
Add functions `exp()`, `exp2()` & `round()`; fix minor readline issues
Diffstat (limited to 'src')
-rw-r--r-- | src/lex.rs | 25 | ||||
-rw-r--r-- | src/main.rs | 31 | ||||
-rw-r--r-- | src/readline.rs | 42 |
3 files changed, 81 insertions, 17 deletions
@@ -101,6 +101,9 @@ lazy_static! { | |||
101 | m.insert("acsc", Function::token_from_fn("acsc".into(), |x| (1./x).asin())); | 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())); | 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())); | 103 | m.insert("acot", Function::token_from_fn("acot".into(), |x| (1./x).atan())); |
104 | m.insert("exp", Function::token_from_fn("exp".into(), |x| x.exp())); | ||
105 | m.insert("exp2", Function::token_from_fn("exp2".into(), |x| x.exp2())); | ||
106 | m.insert("round", Function::token_from_fn("round".into(), |x| x.round())); | ||
104 | // single arg function s can be added here | 107 | // single arg function s can be added here |
105 | m | 108 | m |
106 | }; | 109 | }; |
@@ -134,19 +137,29 @@ pub fn lexer(input: &str, prev_ans: Option<f64>) -> Result<Vec<Token>, CalcError | |||
134 | '0'..='9' | '.' => { | 137 | '0'..='9' | '.' => { |
135 | if !char_vec.is_empty() { | 138 | if !char_vec.is_empty() { |
136 | if FUNCTIONS.get(&char_vec[..]).is_some() { | 139 | if FUNCTIONS.get(&char_vec[..]).is_some() { |
137 | return Err(CalcError::Syntax(format!( | 140 | char_vec.push(letter); |
138 | "Function '{}' expected parentheses", | 141 | if !FUNCTIONS.get(&char_vec[..]).is_some() { |
139 | char_vec | 142 | return Err(CalcError::Syntax(format!( |
140 | ))); | 143 | "Function '{}' expected parentheses", |
144 | &char_vec[..char_vec.chars().count()-1] | ||
145 | ))); | ||
146 | } | ||
147 | } else if CONSTANTS.get(&char_vec[..]).is_some() { | ||
148 | result.push(CONSTANTS.get(&char_vec[..]).unwrap().clone()); | ||
149 | result.push(OPERATORS.get(&'*').unwrap().clone()); | ||
150 | char_vec.clear(); | ||
151 | num_vec.push(letter); | ||
152 | last_char_is_op = false; | ||
141 | } else { | 153 | } else { |
142 | return Err(CalcError::Syntax(format!( | 154 | return Err(CalcError::Syntax(format!( |
143 | "Unexpected character '{}'", | 155 | "Unexpected character '{}'", |
144 | char_vec | 156 | char_vec |
145 | ))); | 157 | ))); |
146 | } | 158 | } |
159 | } else { | ||
160 | num_vec.push(letter); | ||
161 | last_char_is_op = false; | ||
147 | } | 162 | } |
148 | num_vec.push(letter); | ||
149 | last_char_is_op = false; | ||
150 | } | 163 | } |
151 | '_' => { | 164 | '_' => { |
152 | if prev_ans.is_none() { | 165 | if prev_ans.is_none() { |
diff --git a/src/main.rs b/src/main.rs index 75c33bb..e6a865e 100644 --- a/src/main.rs +++ b/src/main.rs | |||
@@ -259,4 +259,35 @@ mod tests { | |||
259 | let evaled = eval_math_expression("9 + _ ", Some(0f64)).unwrap(); | 259 | let evaled = eval_math_expression("9 + _ ", Some(0f64)).unwrap(); |
260 | assert_eq!(9., evaled); | 260 | assert_eq!(9., evaled); |
261 | } | 261 | } |
262 | #[test] | ||
263 | fn eval_const_multiplication() { | ||
264 | let evaled = eval_math_expression("e2", None).unwrap(); | ||
265 | assert_eq!(5.4365636569, evaled); | ||
266 | } | ||
267 | #[test] | ||
268 | fn eval_round() { | ||
269 | let evaled = eval_math_expression("round(0.5)+round(2.4)", None).unwrap(); | ||
270 | assert_eq!(3., evaled); | ||
271 | } | ||
272 | #[test] | ||
273 | fn eval_exp2() { | ||
274 | assert_eq!( | ||
275 | 256., | ||
276 | eval_math_expression("exp2(8)", None).unwrap() | ||
277 | ); | ||
278 | } | ||
279 | #[test] | ||
280 | fn eval_exp() { | ||
281 | assert_eq!( | ||
282 | 20.0855369232 as f64, | ||
283 | eval_math_expression("exp(3)", None).unwrap() | ||
284 | ); | ||
285 | } | ||
286 | #[test] | ||
287 | fn eval_e_times_n() { | ||
288 | assert_eq!( | ||
289 | 0. as f64, | ||
290 | eval_math_expression("e0", None).unwrap() | ||
291 | ); | ||
292 | } | ||
262 | } | 293 | } |
diff --git a/src/readline.rs b/src/readline.rs index d689f95..8906a4f 100644 --- a/src/readline.rs +++ b/src/readline.rs | |||
@@ -7,6 +7,7 @@ use rustyline::error::ReadlineError; | |||
7 | use rustyline::highlight::Highlighter; | 7 | use rustyline::highlight::Highlighter; |
8 | use rustyline::hint::{Hinter, HistoryHinter}; | 8 | use rustyline::hint::{Hinter, HistoryHinter}; |
9 | use rustyline::{Context, Editor, Helper}; | 9 | use rustyline::{Context, Editor, Helper}; |
10 | use rustyline::validate::Validator; | ||
10 | 11 | ||
11 | use directories::ProjectDirs; | 12 | use directories::ProjectDirs; |
12 | 13 | ||
@@ -14,6 +15,7 @@ use regex::Regex; | |||
14 | 15 | ||
15 | use crate::error::CalcError; | 16 | use crate::error::CalcError; |
16 | use crate::eval_math_expression; | 17 | use crate::eval_math_expression; |
18 | use crate::lex::{CONSTANTS, FUNCTIONS}; | ||
17 | 19 | ||
18 | pub struct RLHelper { | 20 | pub struct RLHelper { |
19 | completer: FilenameCompleter, | 21 | completer: FilenameCompleter, |
@@ -51,20 +53,34 @@ impl Highlighter for LineHighlighter { | |||
51 | let op = eval_math_expression(line, prev_ans); | 53 | let op = eval_math_expression(line, prev_ans); |
52 | match op { | 54 | match op { |
53 | Ok(_) => { | 55 | Ok(_) => { |
54 | let constants = ["e", "pi"]; | 56 | let constants = CONSTANTS.keys(); |
55 | let functions = [ | 57 | let functions = FUNCTIONS.keys(); |
56 | "sin", "cos", "tan", "csc", "sec", "cot", "sinh", "cosh", "tanh", "ln", "log", | ||
57 | "sqrt", "ceil", "floor", "rad", "deg", "abs", "asin", "acos", "atan", "acsc", | ||
58 | "asec", "acot", | ||
59 | ]; | ||
60 | let ops = Regex::new(r"(?P<o>[\+-/\*%\^!])").unwrap(); | 58 | let ops = Regex::new(r"(?P<o>[\+-/\*%\^!])").unwrap(); |
61 | let mut coloured: String = ops.replace_all(line, "\x1b[35m$o\x1b[0m").into(); | 59 | let mut coloured: String = ops.replace_all(line, "\x1b[35m$o\x1b[0m").into(); |
62 | 60 | ||
63 | for c in &constants { | 61 | for c in constants { |
64 | coloured = coloured.replace(c, &format!("\x1b[33m{}\x1b[0m", c)); | 62 | // This regex consists of the following pieces: |
63 | // * the constant (`o`) to highlight (to be substituted as `{}` via `format!`), | ||
64 | // e.g. `e` or `pi`. | ||
65 | // * (optionally) an ANSI escape-code (`\x1b\[35m`) that is used to highlight | ||
66 | // a binary operator (e.g. `+`/`-`/...). With this one it is ensured that | ||
67 | // binary operators are always correctly detected after a constant | ||
68 | // (see the next bullet-point for why that's needed). | ||
69 | // * the following operator (e.g. `+`/`-`/...), a space or the end | ||
70 | // of the expression (to highlight e.g. `1+e` correctly). This is | ||
71 | // required to distinguish a constant in an expression from a function-call, | ||
72 | // e.g. `e+1` from `exp(1)`, without this matching logic, the `e` from | ||
73 | // `exp` would be improperly interpreted as constant. | ||
74 | // | ||
75 | // To make sure none of existing highlighting (i.e. highlighting | ||
76 | // of binary operators that happens before) breaks, the escape-codes & operator | ||
77 | // (called `r`) are appended after the highlighted constant. | ||
78 | let re = Regex::new(format!("(?P<o>{})(?P<r>(\x1b\\[35m)?([\\+-/\\*%\\^! ]|$))", c).as_str()).unwrap(); | ||
79 | coloured = re.replace_all(&coloured, "\x1b[33m$o\x1b[0m$r").into(); | ||
65 | } | 80 | } |
66 | for f in &functions { | 81 | for f in functions { |
67 | coloured = coloured.replace(f, &format!("\x1b[34m{}\x1b[0m", f)); | 82 | let re = Regex::new(format!("(?P<o>{})(?P<r>(\\(|$))", f).as_str()).unwrap(); |
83 | coloured = re.replace_all(&coloured, "\x1b[34m$o\x1b[0m$r").into(); | ||
68 | } | 84 | } |
69 | Owned(coloured) | 85 | Owned(coloured) |
70 | } | 86 | } |
@@ -75,6 +91,7 @@ impl Highlighter for LineHighlighter { | |||
75 | } | 91 | } |
76 | 92 | ||
77 | impl Highlighter for RLHelper { | 93 | impl Highlighter for RLHelper { |
94 | fn highlight_char(&self, _: &str, _: usize) -> bool { true } | ||
78 | fn highlight_hint<'h>(&self, hint: &'h str) -> Cow<'h, str> { | 95 | fn highlight_hint<'h>(&self, hint: &'h str) -> Cow<'h, str> { |
79 | self.highlighter.highlight_hint(hint) | 96 | self.highlighter.highlight_hint(hint) |
80 | } | 97 | } |
@@ -96,11 +113,14 @@ impl Completer for RLHelper { | |||
96 | } | 113 | } |
97 | 114 | ||
98 | impl Hinter for RLHelper { | 115 | impl Hinter for RLHelper { |
99 | fn hint(&self, line: &str, a: usize, b: &Context) -> Option<String> { | 116 | type Hint = String; |
117 | fn hint(&self, line: &str, a: usize, b: &Context) -> Option<Self::Hint> { | ||
100 | self.hinter.hint(line, a, b) | 118 | self.hinter.hint(line, a, b) |
101 | } | 119 | } |
102 | } | 120 | } |
103 | 121 | ||
122 | impl Validator for RLHelper {} | ||
123 | |||
104 | impl Helper for RLHelper {} | 124 | impl Helper for RLHelper {} |
105 | 125 | ||
106 | pub fn create_readline() -> Editor<RLHelper> { | 126 | pub fn create_readline() -> Editor<RLHelper> { |