Compare commits

..
Author SHA1 Message Date
2db19e7a46
feat: allow changing max_digits for pretty_eval 2026-10-04 16:27:34 -07:00
69bf8f4635
refactor: impl From for Node to easily insert constants
this is mostly useful for tests and in Node::from_name
2026-10-04 16:22:53 -07:00
d8dc549954
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.
2026-10-04 16:04:25 -07:00
4 changed files with 42 additions and 16 deletions

View file

@ -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<String, Error> {
pub fn pretty_eval(mut input: String, max_digits: usize) -> Result<String, Error> {
// 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<String, Error> {
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 {

View file

@ -17,7 +17,7 @@ fn main() -> ExitCode {
}
};
match formatting::pretty_eval(input) {
match formatting::pretty_eval(input, 10) {
Ok(s) => println!("{s}"),
Err(e) => {

View file

@ -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<T> From<T> for Node
where
Float: Assign<T>,
{
fn from(value: T) -> Self {
Self::Number(Float::with_val(PREC, value))
}
}
#[derive(Debug, Default, Copy, Clone, PartialEq)]
pub enum Operator {

View file

@ -8,11 +8,12 @@ use crate::nodes::{Function, Node, Nodes, Operator, PREC};
impl Node {
fn from_name(s: &str) -> Result<Self, String> {
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)),
"h" => Self::from(6.62607015e-34),
"e" => Self::from(f64::consts::E),
_ => Self::Function(s.parse()?),
})
}
@ -70,10 +71,14 @@ impl FromStr for Nodes {
fn from_str(s: &str) -> Result<Self, Self::Err> {
/// 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])
@ -196,10 +203,23 @@ 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),
],
)
}
#[test]
fn test_e() {
assert_eq!(
Nodes::from_str("5e * 5").unwrap().as_ref(),
&[
Node::from(5),
Node::Operator(Operator::Mult),
Node::from(f64::consts::E),
Node::Operator(Operator::Mult),
Node::from(5),
],
)
}