aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/lex.rs25
-rw-r--r--src/main.rs31
-rw-r--r--src/readline.rs42
3 files changed, 81 insertions, 17 deletions
diff --git a/src/lex.rs b/src/lex.rs
index 76f61a0..1220a1f 100644
--- a/src/lex.rs
+++ b/src/lex.rs
@@ -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;
7use rustyline::highlight::Highlighter; 7use rustyline::highlight::Highlighter;
8use rustyline::hint::{Hinter, HistoryHinter}; 8use rustyline::hint::{Hinter, HistoryHinter};
9use rustyline::{Context, Editor, Helper}; 9use rustyline::{Context, Editor, Helper};
10use rustyline::validate::Validator;
10 11
11use directories::ProjectDirs; 12use directories::ProjectDirs;
12 13
@@ -14,6 +15,7 @@ use regex::Regex;
14 15
15use crate::error::CalcError; 16use crate::error::CalcError;
16use crate::eval_math_expression; 17use crate::eval_math_expression;
18use crate::lex::{CONSTANTS, FUNCTIONS};
17 19
18pub struct RLHelper { 20pub 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
77impl Highlighter for RLHelper { 93impl 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
98impl Hinter for RLHelper { 115impl 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
122impl Validator for RLHelper {}
123
104impl Helper for RLHelper {} 124impl Helper for RLHelper {}
105 125
106pub fn create_readline() -> Editor<RLHelper> { 126pub fn create_readline() -> Editor<RLHelper> {