From 27c5539c0aa8e7df2947d9addbab90b2df8c3a3d Mon Sep 17 00:00:00 2001 From: Maximilian Bosch Date: Sun, 26 Dec 2021 13:53:22 +0100 Subject: Add `exp(x)` function which evaluates to `e^x` Quite useful when doing analysis at University, also preferable over `e^x` because it's way nicer to write `exp(arbitrary expr)` rather than `e^(arbitrary expr)`. --- src/lex.rs | 1 + 1 file changed, 1 insertion(+) (limited to 'src') diff --git a/src/lex.rs b/src/lex.rs index 76f61a0..4bdd7b6 100644 --- a/src/lex.rs +++ b/src/lex.rs @@ -101,6 +101,7 @@ lazy_static! { m.insert("acsc", Function::token_from_fn("acsc".into(), |x| (1./x).asin())); m.insert("asec", Function::token_from_fn("asec".into(), |x| (1./x).acos())); m.insert("acot", Function::token_from_fn("acot".into(), |x| (1./x).atan())); + m.insert("exp", Function::token_from_fn("exp".into(), |x| x.exp())); // single arg function s can be added here m }; -- cgit v1.2.3 From 66790c32e9cc80eed512d67fe3630bbe124c6c61 Mon Sep 17 00:00:00 2001 From: Maximilian Bosch Date: Sun, 26 Dec 2021 14:02:19 +0100 Subject: Add `exp2` function and ensure it's correctly lexed --- src/lex.rs | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) (limited to 'src') diff --git a/src/lex.rs b/src/lex.rs index 4bdd7b6..1c686a1 100644 --- a/src/lex.rs +++ b/src/lex.rs @@ -102,6 +102,7 @@ lazy_static! { m.insert("asec", Function::token_from_fn("asec".into(), |x| (1./x).acos())); m.insert("acot", Function::token_from_fn("acot".into(), |x| (1./x).atan())); m.insert("exp", Function::token_from_fn("exp".into(), |x| x.exp())); + m.insert("exp2", Function::token_from_fn("exp2".into(), |x| x.exp2())); // single arg function s can be added here m }; @@ -135,19 +136,23 @@ pub fn lexer(input: &str, prev_ans: Option) -> Result, CalcError '0'..='9' | '.' => { if !char_vec.is_empty() { if FUNCTIONS.get(&char_vec[..]).is_some() { - return Err(CalcError::Syntax(format!( - "Function '{}' expected parentheses", - char_vec - ))); + char_vec.push(letter); + if !FUNCTIONS.get(&char_vec[..]).is_some() { + return Err(CalcError::Syntax(format!( + "Function '{}' expected parentheses", + &char_vec[..char_vec.chars().count()-1] + ))); + } } else { return Err(CalcError::Syntax(format!( "Unexpected character '{}'", char_vec ))); } + } else { + num_vec.push(letter); + last_char_is_op = false; } - num_vec.push(letter); - last_char_is_op = false; } '_' => { if prev_ans.is_none() { -- cgit v1.2.3 From cb3e69df8b17ffc0cab721e2dcc285731a1c5bc9 Mon Sep 17 00:00:00 2001 From: Maximilian Bosch Date: Sun, 26 Dec 2021 14:09:27 +0100 Subject: Add functions to highlighting list --- src/readline.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/readline.rs b/src/readline.rs index d689f95..2939ee1 100644 --- a/src/readline.rs +++ b/src/readline.rs @@ -55,7 +55,7 @@ impl Highlighter for LineHighlighter { let functions = [ "sin", "cos", "tan", "csc", "sec", "cot", "sinh", "cosh", "tanh", "ln", "log", "sqrt", "ceil", "floor", "rad", "deg", "abs", "asin", "acos", "atan", "acsc", - "asec", "acot", + "asec", "acot", "exp2", "exp" ]; let ops = Regex::new(r"(?P[\+-/\*%\^!])").unwrap(); let mut coloured: String = ops.replace_all(line, "\x1b[35m$o\x1b[0m").into(); -- cgit v1.2.3 From 4fd3ec330a89e4177099c21277d3c12e97ca0c21 Mon Sep 17 00:00:00 2001 From: Maximilian Bosch Date: Sun, 26 Dec 2021 15:22:57 +0100 Subject: Update rustyline to v9 Also refresh line always. This isn't a big deal for the small expressions we have here and also fixes a few annoying issues I had with the highlighter. --- src/readline.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/readline.rs b/src/readline.rs index 2939ee1..f59cd22 100644 --- a/src/readline.rs +++ b/src/readline.rs @@ -7,6 +7,7 @@ use rustyline::error::ReadlineError; use rustyline::highlight::Highlighter; use rustyline::hint::{Hinter, HistoryHinter}; use rustyline::{Context, Editor, Helper}; +use rustyline::validate::Validator; use directories::ProjectDirs; @@ -75,6 +76,7 @@ impl Highlighter for LineHighlighter { } impl Highlighter for RLHelper { + fn highlight_char(&self, _: &str, _: usize) -> bool { true } fn highlight_hint<'h>(&self, hint: &'h str) -> Cow<'h, str> { self.highlighter.highlight_hint(hint) } @@ -96,11 +98,14 @@ impl Completer for RLHelper { } impl Hinter for RLHelper { - fn hint(&self, line: &str, a: usize, b: &Context) -> Option { + type Hint = String; + fn hint(&self, line: &str, a: usize, b: &Context) -> Option { self.hinter.hint(line, a, b) } } +impl Validator for RLHelper {} + impl Helper for RLHelper {} pub fn create_readline() -> Editor { -- cgit v1.2.3 From acf947b608db99ff2e46a8020b6e70466c233088 Mon Sep 17 00:00:00 2001 From: Maximilian Bosch Date: Sun, 26 Dec 2021 15:27:41 +0100 Subject: Fix highlighting of `e` vs `exp` --- src/readline.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/readline.rs b/src/readline.rs index f59cd22..9912af6 100644 --- a/src/readline.rs +++ b/src/readline.rs @@ -62,10 +62,12 @@ impl Highlighter for LineHighlighter { let mut coloured: String = ops.replace_all(line, "\x1b[35m$o\x1b[0m").into(); for c in &constants { - coloured = coloured.replace(c, &format!("\x1b[33m{}\x1b[0m", c)); + let re = Regex::new(format!("(?P{})(?P(\x1b\\[35m)?([\\+-/\\*%\\^! ]|$))", c).as_str()).unwrap(); + coloured = re.replace_all(&coloured, "\x1b[33m$o\x1b[0m$r").into(); } for f in &functions { - coloured = coloured.replace(f, &format!("\x1b[34m{}\x1b[0m", f)); + let re = Regex::new(format!("(?P{})(?P(\\(|$))", f).as_str()).unwrap(); + coloured = re.replace_all(&coloured, "\x1b[34m$o\x1b[0m$r").into(); } Owned(coloured) } -- cgit v1.2.3 From 7d1db014ebc6bf0a8fd3e5a25abdc81aa2270a1d Mon Sep 17 00:00:00 2001 From: Maximilian Bosch Date: Sun, 26 Dec 2021 15:31:13 +0100 Subject: Deduplicate references to functions & constants --- src/readline.rs | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) (limited to 'src') diff --git a/src/readline.rs b/src/readline.rs index 9912af6..6734674 100644 --- a/src/readline.rs +++ b/src/readline.rs @@ -15,6 +15,7 @@ use regex::Regex; use crate::error::CalcError; use crate::eval_math_expression; +use crate::lex::{CONSTANTS, FUNCTIONS}; pub struct RLHelper { completer: FilenameCompleter, @@ -52,20 +53,16 @@ impl Highlighter for LineHighlighter { let op = eval_math_expression(line, prev_ans); match op { Ok(_) => { - let constants = ["e", "pi"]; - let functions = [ - "sin", "cos", "tan", "csc", "sec", "cot", "sinh", "cosh", "tanh", "ln", "log", - "sqrt", "ceil", "floor", "rad", "deg", "abs", "asin", "acos", "atan", "acsc", - "asec", "acot", "exp2", "exp" - ]; + let constants = CONSTANTS.keys(); + let functions = FUNCTIONS.keys(); let ops = Regex::new(r"(?P[\+-/\*%\^!])").unwrap(); let mut coloured: String = ops.replace_all(line, "\x1b[35m$o\x1b[0m").into(); - for c in &constants { + for c in constants { let re = Regex::new(format!("(?P{})(?P(\x1b\\[35m)?([\\+-/\\*%\\^! ]|$))", c).as_str()).unwrap(); coloured = re.replace_all(&coloured, "\x1b[33m$o\x1b[0m$r").into(); } - for f in &functions { + for f in functions { let re = Regex::new(format!("(?P{})(?P(\\(|$))", f).as_str()).unwrap(); coloured = re.replace_all(&coloured, "\x1b[34m$o\x1b[0m$r").into(); } -- cgit v1.2.3 From 18a1ce8f0343f7865f8a01741fb96f98945645d0 Mon Sep 17 00:00:00 2001 From: Maximilian Bosch Date: Sun, 26 Dec 2021 15:41:47 +0100 Subject: Allow expressions such as `e2` as multiplication of `e * 2` --- src/lex.rs | 6 ++++++ src/main.rs | 5 +++++ 2 files changed, 11 insertions(+) (limited to 'src') diff --git a/src/lex.rs b/src/lex.rs index 1c686a1..da542d5 100644 --- a/src/lex.rs +++ b/src/lex.rs @@ -143,6 +143,12 @@ pub fn lexer(input: &str, prev_ans: Option) -> Result, CalcError &char_vec[..char_vec.chars().count()-1] ))); } + } else if CONSTANTS.get(&char_vec[..]).is_some() { + result.push(CONSTANTS.get(&char_vec[..]).unwrap().clone()); + result.push(OPERATORS.get(&'*').unwrap().clone()); + char_vec.clear(); + num_vec.push(letter); + last_char_is_op = false; } else { return Err(CalcError::Syntax(format!( "Unexpected character '{}'", diff --git a/src/main.rs b/src/main.rs index 75c33bb..f5e5c59 100644 --- a/src/main.rs +++ b/src/main.rs @@ -259,4 +259,9 @@ mod tests { let evaled = eval_math_expression("9 + _ ", Some(0f64)).unwrap(); assert_eq!(9., evaled); } + #[test] + fn eval_const_multiplication() { + let evaled = eval_math_expression("e2", None).unwrap(); + assert_eq!(5.4365636569, evaled); + } } -- cgit v1.2.3 From a18e994ae5fc95761d4d3569a99d98dd4a99660b Mon Sep 17 00:00:00 2001 From: Maximilian Bosch Date: Sun, 26 Dec 2021 16:01:25 +0100 Subject: Add `round()` function --- src/lex.rs | 1 + 1 file changed, 1 insertion(+) (limited to 'src') diff --git a/src/lex.rs b/src/lex.rs index da542d5..1220a1f 100644 --- a/src/lex.rs +++ b/src/lex.rs @@ -103,6 +103,7 @@ lazy_static! { m.insert("acot", Function::token_from_fn("acot".into(), |x| (1./x).atan())); m.insert("exp", Function::token_from_fn("exp".into(), |x| x.exp())); m.insert("exp2", Function::token_from_fn("exp2".into(), |x| x.exp2())); + m.insert("round", Function::token_from_fn("round".into(), |x| x.round())); // single arg function s can be added here m }; -- cgit v1.2.3 From 794a795f525ee3587ce317e65b24935aeb9ba5cf Mon Sep 17 00:00:00 2001 From: Maximilian Bosch Date: Thu, 30 Dec 2021 18:16:46 +0100 Subject: Implement a few more testcases --- src/main.rs | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) (limited to 'src') diff --git a/src/main.rs b/src/main.rs index f5e5c59..e6a865e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -264,4 +264,30 @@ mod tests { let evaled = eval_math_expression("e2", None).unwrap(); assert_eq!(5.4365636569, evaled); } + #[test] + fn eval_round() { + let evaled = eval_math_expression("round(0.5)+round(2.4)", None).unwrap(); + assert_eq!(3., evaled); + } + #[test] + fn eval_exp2() { + assert_eq!( + 256., + eval_math_expression("exp2(8)", None).unwrap() + ); + } + #[test] + fn eval_exp() { + assert_eq!( + 20.0855369232 as f64, + eval_math_expression("exp(3)", None).unwrap() + ); + } + #[test] + fn eval_e_times_n() { + assert_eq!( + 0. as f64, + eval_math_expression("e0", None).unwrap() + ); + } } -- cgit v1.2.3 From 3f0f8d9f0f910b4c9477297049d4a508eacc3734 Mon Sep 17 00:00:00 2001 From: Maximilian Bosch Date: Tue, 4 Jan 2022 17:46:33 +0100 Subject: readline: explain constant highlighting regex --- src/readline.rs | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) (limited to 'src') diff --git a/src/readline.rs b/src/readline.rs index 6734674..8906a4f 100644 --- a/src/readline.rs +++ b/src/readline.rs @@ -59,6 +59,22 @@ impl Highlighter for LineHighlighter { let mut coloured: String = ops.replace_all(line, "\x1b[35m$o\x1b[0m").into(); for c in constants { + // This regex consists of the following pieces: + // * the constant (`o`) to highlight (to be substituted as `{}` via `format!`), + // e.g. `e` or `pi`. + // * (optionally) an ANSI escape-code (`\x1b\[35m`) that is used to highlight + // a binary operator (e.g. `+`/`-`/...). With this one it is ensured that + // binary operators are always correctly detected after a constant + // (see the next bullet-point for why that's needed). + // * the following operator (e.g. `+`/`-`/...), a space or the end + // of the expression (to highlight e.g. `1+e` correctly). This is + // required to distinguish a constant in an expression from a function-call, + // e.g. `e+1` from `exp(1)`, without this matching logic, the `e` from + // `exp` would be improperly interpreted as constant. + // + // To make sure none of existing highlighting (i.e. highlighting + // of binary operators that happens before) breaks, the escape-codes & operator + // (called `r`) are appended after the highlighted constant. let re = Regex::new(format!("(?P{})(?P(\x1b\\[35m)?([\\+-/\\*%\\^! ]|$))", c).as_str()).unwrap(); coloured = re.replace_all(&coloured, "\x1b[33m$o\x1b[0m$r").into(); } -- cgit v1.2.3