From d8dc5499542eed1476d525a7249126701934f257 Mon Sep 17 00:00:00 2001 From: electria Date: Sun, 4 Oct 2026 16:04:25 -0700 Subject: [PATCH 1/3] feat: add "e" as a constant this is not great, because due to lacking whitespace-awareness `e sin(1)` evaluates as `esin(1)` which does not exist. --- src/nodes/parse.rs | 28 ++++++++++++++++++++++++---- 1 file changed, 24 insertions(+), 4 deletions(-) diff --git a/src/nodes/parse.rs b/src/nodes/parse.rs index 07f6d4c..68e6fcc 100644 --- a/src/nodes/parse.rs +++ b/src/nodes/parse.rs @@ -13,6 +13,7 @@ impl Node { "pi" => Self::Number(Float::with_val(PREC, f64::consts::PI)), // Planck's constant, in J*s (joule-seconds) "h" => Self::Number(Float::with_val(PREC, 6.62607015e-34)), + "e" => Self::Number(Float::with_val(PREC, f64::consts::E)), _ => Self::Function(s.parse()?), }) } @@ -70,10 +71,14 @@ impl FromStr for Nodes { fn from_str(s: &str) -> Result { /// A superset of `char::is_numeric` /// that includes other characters parseable into numbers, - /// such as `.` (for decimal) and `e` for scientific notation. + /// now only `.` (for decimals) + /// + /// since including `e` makes it not parsable as a constant, + /// this function effectively only checks if the character can *start* a number + /// (since a number cannot start with `e`) fn is_number_char(c: char) -> bool { match c { - '.' | 'e' => true, + '.' => true, c => c.is_numeric(), } } @@ -114,8 +119,10 @@ impl FromStr for Nodes { } ParsingState::ReadingNumber(start_index) => { if !is_number_char(c) - // don't stop reading number on a special case, eg "1e-1" - && !(filtered_string.chars().nth(i - 1) == Some('e') && c == '-') + && !(c == 'e' && filtered_string.chars().nth(i + 1).is_some_and(char::is_numeric)) + // don't stop reading number on special case eg "1e-1" + && !(c == 'e' && filtered_string.chars().nth(i + 1).is_some_and(|c| c == '-')) + && !(c == '-' && filtered_string.chars().nth(i - 1).is_some_and(|c| c == 'e')) { nodes.push(Node::Number( Float::parse(&filtered_string[start_index..i]) @@ -203,3 +210,16 @@ fn test() { ], ) } +#[test] +fn test_e() { + assert_eq!( + Nodes::from_str("5e * 5").unwrap().as_ref(), + &[ + Node::Number(Float::with_val(PREC, 5)), + Node::Operator(Operator::Mult), + Node::Number(Float::with_val(PREC, f64::consts::E)), + Node::Operator(Operator::Mult), + Node::Number(Float::with_val(PREC, 5)) + ], + ) +} From 69bf8f4635501cf47aa0724fd148e7f2b370da98 Mon Sep 17 00:00:00 2001 From: electria Date: Sun, 4 Oct 2026 16:22:53 -0700 Subject: [PATCH 2/3] refactor: impl From for Node to easily insert constants this is mostly useful for tests and in Node::from_name --- src/nodes/mod.rs | 10 +++++++++- src/nodes/parse.rs | 20 ++++++++++---------- 2 files changed, 19 insertions(+), 11 deletions(-) diff --git a/src/nodes/mod.rs b/src/nodes/mod.rs index da7c34e..d8e05d9 100644 --- a/src/nodes/mod.rs +++ b/src/nodes/mod.rs @@ -4,7 +4,7 @@ use std::fmt::{Debug, Display}; -use rug::Float; +use rug::{Assign, Float}; mod eval; pub mod parse; @@ -53,6 +53,14 @@ impl Display for Node { Debug::fmt(&self, f) } } +impl From for Node +where + Float: Assign, +{ + fn from(value: T) -> Self { + Self::Number(Float::with_val(PREC, value)) + } +} #[derive(Debug, Default, Copy, Clone, PartialEq)] pub enum Operator { diff --git a/src/nodes/parse.rs b/src/nodes/parse.rs index 68e6fcc..22a0518 100644 --- a/src/nodes/parse.rs +++ b/src/nodes/parse.rs @@ -8,12 +8,12 @@ use crate::nodes::{Function, Node, Nodes, Operator, PREC}; impl Node { fn from_name(s: &str) -> Result { Ok(match s { - "c" => Self::Number(Float::with_val(PREC, 299_792_458)), - "A" => Self::Number(Float::with_val(PREC, 6.02214076e23)), - "pi" => Self::Number(Float::with_val(PREC, f64::consts::PI)), + "c" => Self::from(299_792_458), + "A" => Self::from(6.02214076e23), + "pi" => Self::from(f64::consts::PI), // Planck's constant, in J*s (joule-seconds) - "h" => Self::Number(Float::with_val(PREC, 6.62607015e-34)), - "e" => Self::Number(Float::with_val(PREC, f64::consts::E)), + "h" => Self::from(6.62607015e-34), + "e" => Self::from(f64::consts::E), _ => Self::Function(s.parse()?), }) } @@ -203,10 +203,10 @@ fn test() { &[ Node::Function(Function::Sine), Node::ParenthesisStart, - Node::Number(Float::with_val(PREC, 2)), + Node::from(2), Node::ParenthesisEnd, Node::Operator(Operator::Plus), - Node::Number(Float::with_val(PREC, 1)) + Node::from(1), ], ) } @@ -215,11 +215,11 @@ fn test_e() { assert_eq!( Nodes::from_str("5e * 5").unwrap().as_ref(), &[ - Node::Number(Float::with_val(PREC, 5)), + Node::from(5), Node::Operator(Operator::Mult), - Node::Number(Float::with_val(PREC, f64::consts::E)), + Node::from(f64::consts::E), Node::Operator(Operator::Mult), - Node::Number(Float::with_val(PREC, 5)) + Node::from(5), ], ) } From 2db19e7a463f27a743945ed91028cf8a706bdd9e Mon Sep 17 00:00:00 2001 From: electria Date: Sun, 4 Oct 2026 16:27:34 -0700 Subject: [PATCH 3/3] feat: allow changing max_digits for pretty_eval --- src/formatting.rs | 6 ++---- src/main.rs | 2 +- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/src/formatting.rs b/src/formatting.rs index b157564..d229d54 100644 --- a/src/formatting.rs +++ b/src/formatting.rs @@ -2,9 +2,7 @@ use rug::Float; use crate::nodes::{self, Error}; -pub const MAX_DIGITS: usize = 10; - -pub fn pretty_eval(mut input: String) -> Result { +pub fn pretty_eval(mut input: String, max_digits: usize) -> Result { // no need to remove the characters here, // as we ignore the them in all other logic already let should_add_back_alignment = input.contains('&'); @@ -21,7 +19,7 @@ pub fn pretty_eval(mut input: String) -> Result { let result = nodes::eval(&input)?; let display_digits = get_significant_digits(&result) - .min(MAX_DIGITS) + .min(max_digits) .max(if result >= 1000 || result < 1 { 1 } else if result >= 100 { diff --git a/src/main.rs b/src/main.rs index 32f04a3..aace0d9 100644 --- a/src/main.rs +++ b/src/main.rs @@ -17,7 +17,7 @@ fn main() -> ExitCode { } }; - match formatting::pretty_eval(input) { + match formatting::pretty_eval(input, 10) { Ok(s) => println!("{s}"), Err(e) => {