diff --git a/Cargo.toml b/Cargo.toml index 5793b00..a6c52cf 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,7 +10,3 @@ thiserror = "2.0.18" version = "1.30" default-features = false features = ["std", "float"] - -[profile.release] -lto = true -codegen-units = 1 diff --git a/flake.nix b/flake.nix index f7862ee..62c5763 100644 --- a/flake.nix +++ b/flake.nix @@ -12,14 +12,14 @@ let pkgs = import inputs.nixpkgs { inherit system; }; - src = ./.; - cargoToml = fromTOML (builtins.readFile "${src}/Cargo.toml"); + cargoToml = fromTOML (builtins.readFile ./Cargo.toml); name = cargoToml.package.name; crate = pkgs.rustPlatform.buildRustPackage { - inherit name src; + inherit name; + src = ./.; - cargoLock.lockFile = "${src}/Cargo.lock"; + cargoLock.lockFile = ./Cargo.lock; nativeBuildInputs = with pkgs; [ m4 diff --git a/src/formatting.rs b/src/formatting.rs deleted file mode 100644 index e20e378..0000000 --- a/src/formatting.rs +++ /dev/null @@ -1,100 +0,0 @@ -use std::str::FromStr; - -use rug::Float; - -use crate::nodes::{Error, Nodes}; - -pub fn pretty_eval(mut input: String) -> 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('&'); - let should_add_back_backslash = input.contains('\\'); - let leading_spaces = input.chars().take_while(|c| *c == ' ').count(); - - let should_add_back_equals = input - .find('=') - .inspect(|i| { - input.remove(*i); - }) - .is_some(); - - let result = Nodes::from_str(&input)?.evaluate()?; - let significant_digits = get_significant_digits(&result); - - Ok(format!( - "{:leading_spaces$}{}{result:.significant_digits$}{}", - ' ', - if should_add_back_equals && should_add_back_alignment { - "& = " - } else if should_add_back_equals { - "= " - } else if should_add_back_alignment { - "& " - } else { - "" - }, - if should_add_back_backslash { " \\" } else { "" } - )) -} -#[test] -fn test_character_readdition() { - assert_eq!(pretty_eval(" = 11".into()).unwrap(), " = 11"); - assert_eq!(pretty_eval(" &= 11".into()).unwrap(), " & = 11"); - assert_eq!(pretty_eval(" & 11".into()).unwrap(), " & 11"); - assert_eq!(pretty_eval(" & 11 \\".into()).unwrap(), " & 11 \\"); -} - -pub fn get_significant_digits(n: &Float) -> usize { - let raw_string: String = format!("{n}").chars().filter(|c| *c != '.').collect(); - - let mut consecutive_zeros = 0; - let mut consecutive_nines = 0; - for (i, c) in raw_string.char_indices() { - if c == '0' { - consecutive_zeros += 1; - } else { - consecutive_zeros = 0; - } - - if c == '9' { - consecutive_nines += 1; - } else { - consecutive_nines = 0; - } - - if consecutive_zeros >= 10 { - return i + 1 - consecutive_zeros; - } else if consecutive_nines >= 10 { - return i + 1 - consecutive_nines; - } - - if !raw_string[i..raw_string.len()].contains(|c| matches!(c, '1'..='9')) { - return i; - } - } - - // all digits are significant - raw_string.len() -} - -#[cfg(test)] -fn eval(s: &str) -> Float { - crate::nodes::eval(s).unwrap() -} - -#[test] -fn test_get_significant_digits() { - let many_zeros = eval("14.00643 + 1.00784 * 4 + 35.446 + 15.9994 * 4"); - assert_eq!(many_zeros.to_f64(), 117.48139); - let many_nines = eval("12.0096 + 15.9994 * 2"); - assert_eq!(many_nines.to_f64(), 44.0084); - let zero_nine_zero = eval("2+12321(5+123)"); - assert_eq!(zero_nine_zero.to_f64(), 1577090.0); - let test_4 = eval("12.0096 + 1.00784 * 3 + 12.0096 + 1.00784 * 2 + 15.9994 + 1.00784"); - assert_eq!(test_4.to_f64(), 46.06564); - - assert_eq!(get_significant_digits(&many_zeros), 8); - assert_eq!(get_significant_digits(&many_nines), 6); - assert_eq!(get_significant_digits(&zero_nine_zero), 6); - assert_eq!(get_significant_digits(&test_4), 7); -} diff --git a/src/lib.rs b/src/lib.rs deleted file mode 100644 index 71e5c48..0000000 --- a/src/lib.rs +++ /dev/null @@ -1,4 +0,0 @@ -pub mod formatting; -pub mod nodes; - -pub use nodes::eval; diff --git a/src/main.rs b/src/main.rs index 32f04a3..3e9206b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,13 +1,14 @@ use std::{ env, io::{self, Read}, - process::ExitCode, + str::FromStr, }; -mod formatting; +use rug::Float; + mod nodes; -fn main() -> ExitCode { +fn main() { let input = match env::args().skip(1).rev().reduce(|x, a| a + &x) { Some(s) => s, None => { @@ -17,14 +18,100 @@ fn main() -> ExitCode { } }; - match formatting::pretty_eval(input) { - Ok(s) => println!("{s}"), + println!("{}", parse_and_calc_humanreadable(input)); +} - Err(e) => { - eprintln!("{e}"); - return ExitCode::FAILURE; +pub fn parse_and_calc_humanreadable(mut input: String) -> String { + let should_add_back_equals = if let Some((i, _)) = input + .chars() + .enumerate() + .filter(|(_, c)| !matches!(c, ' ' | '&' | '\n')) + .find(|(_, c)| *c == '=') + { + input.remove(i); + true + } else { + false + }; + + // no need to remove the character here, + // as we ignore the ampersand in all other logic already + let should_add_back_alignment = input.contains('&'); + + let result = parse_and_calc(&input); + + format!( + "{0}{result:.1$}", + if should_add_back_equals && should_add_back_alignment { + "& = " + } else if should_add_back_equals { + "= " + } else if should_add_back_alignment { + "& " + } else { + "" + }, + get_significant_digits(&result), + ) +} +#[test] +fn test_equals_readdition() { + assert_eq!(parse_and_calc_humanreadable(" = 11".into()), "= 11"); + assert_eq!(parse_and_calc_humanreadable(" &= 11".into()), "& = 11"); + assert_eq!(parse_and_calc_humanreadable(" & 11".into()), "& 11"); +} + +pub fn parse_and_calc(s: &str) -> Float { + nodes::Nodes::from_str(s).unwrap().evaluate() +} + +pub fn get_significant_digits(n: &Float) -> usize { + let raw_string: String = format!("{n}").chars().filter(|c| *c != '.').collect(); + + let mut consecutive_zeros = 0; + let mut consecutive_nines = 0; + for (i, c) in raw_string.char_indices() { + if c == '0' { + consecutive_zeros += 1; + } else { + consecutive_zeros = 0; + } + + if c == '9' { + consecutive_nines += 1; + } else { + consecutive_nines = 0; + } + + if consecutive_zeros >= 10 { + return i + 1 - consecutive_zeros; + } else if consecutive_nines >= 10 { + return i + 1 - consecutive_nines; + } + + if !raw_string[i..raw_string.len()].contains(|c| matches!(c, '1'..='9')) { + return i; } } - ExitCode::SUCCESS + // all digits are significant + raw_string.len() +} + +#[test] +fn test_get_significant_digits() { + let many_zeros = parse_and_calc("14.00643 + 1.00784 * 4 + 35.446 + 15.9994 * 4"); + assert_eq!(many_zeros.to_f64(), 117.48139); + let many_nines = parse_and_calc("12.0096 + 15.9994 * 2"); + assert_eq!(many_nines.to_f64(), 44.0084); + let zero_nine_zero = parse_and_calc("2+12321(5+123)"); + assert_eq!(zero_nine_zero.to_f64(), 1577090.0); + let test_4 = + parse_and_calc("12.0096 + 1.00784 * 3 + 12.0096 + 1.00784 * 2 + 15.9994 + 1.00784"); + assert_eq!(test_4.to_f64(), 46.06564); + + assert_eq!(get_significant_digits(&many_zeros), 8); + assert_eq!(get_significant_digits(&many_nines), 6); + assert_eq!(get_significant_digits(&zero_nine_zero), 6); + assert_eq!(get_significant_digits(&test_4), 7); } diff --git a/src/nodes/eval.rs b/src/nodes/evaluate.rs similarity index 68% rename from src/nodes/eval.rs rename to src/nodes/evaluate.rs index 27d6907..c9984bd 100644 --- a/src/nodes/eval.rs +++ b/src/nodes/evaluate.rs @@ -5,35 +5,25 @@ use rug::{ use crate::nodes::{Function, Node, Nodes, Operator}; -#[derive(Clone, Debug, thiserror::Error)] -pub enum Error { - #[error("invalid operand: {0}")] - InvalidOperand(Node), - #[error("missing argument, got: {0}")] - MissingArgument(Node), - #[error("collapse failed, left with: {0}")] - CollapseFailed(Nodes), -} - impl Nodes { /// Consume the `Nodes`, collapsing them down to a single number - pub fn evaluate(mut self) -> Result { + pub fn evaluate(mut self) -> Float { let nodes = &mut self.0; while let Some(end) = nodes.iter().position(|n| *n == Node::ParenthesisEnd) { - collapse_toplevel(nodes, end)?; + collapse_toplevel(nodes, end); } let len = nodes.len(); - collapse_toplevel(nodes, len)?; + collapse_toplevel(nodes, len); match nodes.pop().unwrap() { - Node::Number(n) => Ok(n), - _ => Err(Error::CollapseFailed(self)), + Node::Number(n) => n, + _ => panic!("failed to collapse nodes: {nodes:?}"), } } } -fn collapse_toplevel(nodes: &mut Vec, mut end: usize) -> Result<(), Error> { +fn collapse_toplevel(nodes: &mut Vec, mut end: usize) { let start = nodes[0..end] .iter() .rposition(|n| *n == Node::ParenthesisStart) @@ -61,7 +51,7 @@ fn collapse_toplevel(nodes: &mut Vec, mut end: usize) -> Result<(), Error> Function::Secant => n.sec_mut(), Function::CoSecant => n.csc_mut(), }, - n => return Err(Error::MissingArgument(n.clone())), + n => panic!("function is missing an argument, got: {n:?}"), }; nodes.remove(start + index); @@ -74,12 +64,12 @@ fn collapse_toplevel(nodes: &mut Vec, mut end: usize) -> Result<(), Error> { let lhs = match nodes.remove(start + operator - 1) { Node::Number(n) => n, - n => return Err(Error::InvalidOperand(n)), + n => panic!("operator has an invalid operand: {n:?}"), }; end -= 1; match &mut nodes[start..end][operator] { Node::Number(n) => n.pow_from(lhs), - n => return Err(Error::InvalidOperand(n.clone())), + n => panic!("operator has an invalid operand: {n:?}"), }; nodes.remove(start + operator - 1); @@ -92,12 +82,12 @@ fn collapse_toplevel(nodes: &mut Vec, mut end: usize) -> Result<(), Error> { let lhs = match nodes.remove(start + operator - 1) { Node::Number(n) => n, - n => return Err(Error::InvalidOperand(n)), + n => panic!("operator has an invalid operand: {n:?}"), }; end -= 1; match &mut nodes[start..end][operator] { Node::Number(n) => n.div_from(lhs), - n => return Err(Error::InvalidOperand(n.clone())), + n => panic!("operator has an invalid operand: {n:?}"), }; nodes.remove(start + operator - 1); @@ -110,30 +100,12 @@ fn collapse_toplevel(nodes: &mut Vec, mut end: usize) -> Result<(), Error> { let lhs = match nodes.remove(start + operator - 1) { Node::Number(n) => n, - n => return Err(Error::InvalidOperand(n)), + n => panic!("operator has an invalid operand: {n:?}"), }; end -= 1; match &mut nodes[start..end][operator] { Node::Number(n) => n.mul_from(lhs), - n => return Err(Error::InvalidOperand(n.clone())), - }; - - nodes.remove(start + operator - 1); - end -= 1; - } - - while let Some(operator) = nodes[start..end] - .iter() - .position(|n| *n == Node::Operator(Operator::Minus)) - { - let lhs = match nodes.remove(start + operator - 1) { - Node::Number(n) => n, - n => return Err(Error::InvalidOperand(n)), - }; - end -= 1; - match &mut nodes[start..end][operator] { - Node::Number(n) => n.sub_from(lhs), - n => return Err(Error::InvalidOperand(n.clone())), + n => panic!("operator has an invalid operand: {n:?}"), }; nodes.remove(start + operator - 1); @@ -146,12 +118,30 @@ fn collapse_toplevel(nodes: &mut Vec, mut end: usize) -> Result<(), Error> { let lhs = match nodes.remove(start + operator - 1) { Node::Number(n) => n, - n => return Err(Error::InvalidOperand(n)), + n => panic!("operator has an invalid operand: {n:?}"), }; end -= 1; match &mut nodes[start..end][operator] { Node::Number(n) => n.add_from(lhs), - n => return Err(Error::InvalidOperand(n.clone())), + n => panic!("operator has an invalid operand: {n:?}"), + }; + + nodes.remove(start + operator - 1); + end -= 1; + } + + while let Some(operator) = nodes[start..end] + .iter() + .position(|n| *n == Node::Operator(Operator::Minus)) + { + let lhs = match nodes.remove(start + operator - 1) { + Node::Number(n) => n, + n => panic!("operator has an invalid operand: {n:?}"), + }; + end -= 1; + match &mut nodes[start..end][operator] { + Node::Number(n) => n.sub_from(lhs), + n => panic!("operator has an invalid operand: {n:?}"), }; nodes.remove(start + operator - 1); @@ -162,27 +152,4 @@ fn collapse_toplevel(nodes: &mut Vec, mut end: usize) -> Result<(), Error> nodes.remove(end); nodes.remove(start); } - - Ok(()) -} - -#[cfg(test)] -use super::PREC; - -#[test] -fn test() { - assert_eq!( - Nodes(vec![ - Node::Function(Function::Sine), - Node::ParenthesisStart, - Node::Number(Float::with_val(PREC, 2)), - Node::ParenthesisEnd, - Node::Operator(Operator::Plus), - Node::Number(Float::with_val(PREC, 1)) - ]) - .evaluate() - .unwrap() - .to_f64(), - 1.9092974268256817, - ); } diff --git a/src/nodes/parse.rs b/src/nodes/from_str.rs similarity index 93% rename from src/nodes/parse.rs rename to src/nodes/from_str.rs index 07f6d4c..14e4c9a 100644 --- a/src/nodes/parse.rs +++ b/src/nodes/from_str.rs @@ -81,12 +81,12 @@ impl FromStr for Nodes { /// /// We ignore whitespace since it has no semantic significance in typst /// (aside from variables, which are not implemented) - /// and `&`, `\` because they are only used for visual alignment. + /// and `&` because it is only used for visual alignment. /// /// Newlines are also ignored because they're likely accidental; /// like in the case of `echo 1+1 | tylc`. fn is_not_ignored_char(c: &char) -> bool { - !matches!(c, ' ' | '&' | '\n' | '\\') + !matches!(c, ' ' | '&' | '\n') } let mut nodes = Vec::new(); @@ -188,18 +188,3 @@ impl FromStr for Nodes { Ok(Self(nodes)) } } - -#[test] -fn test() { - assert_eq!( - Nodes::from_str("sin(2) + 1").unwrap().as_ref(), - &[ - Node::Function(Function::Sine), - Node::ParenthesisStart, - Node::Number(Float::with_val(PREC, 2)), - Node::ParenthesisEnd, - Node::Operator(Operator::Plus), - Node::Number(Float::with_val(PREC, 1)) - ], - ) -} diff --git a/src/nodes/mod.rs b/src/nodes/mod.rs index f435db4..f46ad3b 100644 --- a/src/nodes/mod.rs +++ b/src/nodes/mod.rs @@ -2,29 +2,13 @@ //! as well as the logic to parse it from a string (`Node::from_str`), //! and the logic to evaluate it into a single number (`Node::evaluate`). -use std::fmt::{Debug, Display}; - use rug::Float; -mod eval; -pub mod parse; +mod evaluate; +mod from_str; #[cfg(test)] mod tests; -#[derive(Clone, Debug, thiserror::Error)] -pub enum Error { - #[error("{0}")] - Parsing(#[from] parse::Error), - #[error("{0}")] - Evaluation(#[from] eval::Error), -} - -/// helper function to parse to Nodes and immediately evaluate -pub fn eval(s: &str) -> Result { - use std::str::FromStr; - Ok(Nodes::from_str(s)?.evaluate()?) -} - pub const PREC: u32 = 512; #[derive(Debug, Default, Clone, PartialEq)] @@ -34,28 +18,18 @@ impl AsRef<[Node]> for Nodes { self.0.as_ref() } } -impl Display for Nodes { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - Debug::fmt(&self.0, f) - } -} #[derive(Debug, Clone, PartialEq)] -pub enum Node { +enum Node { Function(Function), ParenthesisStart, ParenthesisEnd, Number(Float), Operator(Operator), } -impl Display for Node { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - Debug::fmt(&self, f) - } -} #[derive(Debug, Default, Copy, Clone, PartialEq)] -pub enum Operator { +enum Operator { Plus, Minus, #[default] @@ -65,7 +39,7 @@ pub enum Operator { } #[derive(Debug, Copy, Clone, PartialEq)] -pub enum Function { +enum Function { Sqrt, Sine, diff --git a/src/nodes/tests.rs b/src/nodes/tests.rs index 1d40f50..2acaac0 100644 --- a/src/nodes/tests.rs +++ b/src/nodes/tests.rs @@ -1,40 +1,78 @@ use core::f64; use std::str::FromStr; -use crate::nodes::Nodes; +use rug::Float; -fn eval(s: &str) -> f64 { - Nodes::from_str(s).unwrap().evaluate().unwrap().to_f64() -} - -#[test] -fn plus_minus_order() { - assert_eq!(eval("1 - 4 - 5"), -8.); - assert_eq!(eval("1 - 4 + 5"), 2.); - assert_eq!(eval("1 + 4 - 5"), 0.); -} +use crate::nodes::{Function, Node, Nodes, Operator, PREC}; #[test] fn e_edge_case() { - assert_eq!(eval("1e-1"), 0.1) + assert_eq!(Nodes::from_str("1e-1").unwrap().evaluate().to_f64(), 0.1) } #[test] fn implicit_mult() { - assert_eq!(eval("10 sin(10)"), -5.440211108893698); + assert_eq!( + Nodes::from_str("10 sin(10)").unwrap().evaluate().to_f64(), + -5.440211108893698 + ); - assert_eq!(eval("(10)(10)"), 100.,) + assert_eq!( + Nodes::from_str("(10)(10)").unwrap().evaluate().to_f64(), + 100., + ) } #[test] fn constants() { - assert_eq!(eval("c"), 299792458.); - assert_eq!(eval("A"), 6.02214076e23); - assert_eq!(eval("pi"), f64::consts::PI); + assert_eq!( + Nodes::from_str("c").unwrap().evaluate().to_f64(), + 299792458. + ); + assert_eq!( + Nodes::from_str("A").unwrap().evaluate().to_f64(), + 6.02214076e23 + ); + assert_eq!( + Nodes::from_str("pi").unwrap().evaluate().to_f64(), + f64::consts::PI + ); } #[test] fn unary_operators() { - assert_eq!(eval("-10"), -10.); - assert_eq!(eval("+10"), 10.); + assert_eq!(Nodes::from_str("-10").unwrap().evaluate().to_f64(), -10.); + assert_eq!(Nodes::from_str("+10").unwrap().evaluate().to_f64(), 10.); +} + +#[test] +fn test_nodes_to_num() { + assert_eq!( + Nodes(vec![ + Node::Function(Function::Sine), + Node::ParenthesisStart, + Node::Number(Float::with_val(PREC, 2)), + Node::ParenthesisEnd, + Node::Operator(Operator::Plus), + Node::Number(Float::with_val(PREC, 1)) + ]) + .evaluate() + .to_f64(), + 1.9092974268256817, + ); +} + +#[test] +fn test_nodes_from_str() { + assert_eq!( + Nodes::from_str("sin(2) + 1").unwrap().as_ref(), + &[ + Node::Function(Function::Sine), + Node::ParenthesisStart, + Node::Number(Float::with_val(PREC, 2)), + Node::ParenthesisEnd, + Node::Operator(Operator::Plus), + Node::Number(Float::with_val(PREC, 1)) + ], + ) }