From 35da9a0d40b85e3a71eb1ceefe38c5fa4a87677c Mon Sep 17 00:00:00 2001 From: electria Date: Wed, 30 Sep 2026 19:39:34 -0700 Subject: [PATCH] feat: return Result instead of panicking this doesn't always work still; there are a lot of panicking index operations there. a feature was stabilized very recently that would help fix that: https://github.com/rust-lang/rust/issues/146954 --- src/formatting.rs | 18 ++++++++--------- src/main.rs | 14 +++++++++++-- src/nodes/evaluate.rs | 47 +++++++++++++++++++++++++++---------------- src/nodes/mod.rs | 30 ++++++++++++++++++++++----- src/nodes/tests.rs | 2 +- 5 files changed, 77 insertions(+), 34 deletions(-) diff --git a/src/formatting.rs b/src/formatting.rs index b439616..e20e378 100644 --- a/src/formatting.rs +++ b/src/formatting.rs @@ -2,9 +2,9 @@ use std::str::FromStr; use rug::Float; -use crate::nodes::Nodes; +use crate::nodes::{Error, Nodes}; -pub fn pretty_eval(mut input: String) -> String { +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('&'); @@ -18,10 +18,10 @@ pub fn pretty_eval(mut input: String) -> String { }) .is_some(); - let result = Nodes::from_str(&input).unwrap().evaluate(); + let result = Nodes::from_str(&input)?.evaluate()?; let significant_digits = get_significant_digits(&result); - format!( + Ok(format!( "{:leading_spaces$}{}{result:.significant_digits$}{}", ' ', if should_add_back_equals && should_add_back_alignment { @@ -34,14 +34,14 @@ pub fn pretty_eval(mut input: String) -> String { "" }, if should_add_back_backslash { " \\" } else { "" } - ) + )) } #[test] fn test_character_readdition() { - assert_eq!(pretty_eval(" = 11".into()), " = 11"); - assert_eq!(pretty_eval(" &= 11".into()), " & = 11"); - assert_eq!(pretty_eval(" & 11".into()), " & 11"); - assert_eq!(pretty_eval(" & 11 \\".into()), " & 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"); + assert_eq!(pretty_eval(" & 11 \\".into()).unwrap(), " & 11 \\"); } pub fn get_significant_digits(n: &Float) -> usize { diff --git a/src/main.rs b/src/main.rs index e22e595..32f04a3 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,12 +1,13 @@ use std::{ env, io::{self, Read}, + process::ExitCode, }; mod formatting; mod nodes; -fn main() { +fn main() -> ExitCode { let input = match env::args().skip(1).rev().reduce(|x, a| a + &x) { Some(s) => s, None => { @@ -16,5 +17,14 @@ fn main() { } }; - println!("{}", formatting::pretty_eval(input)); + match formatting::pretty_eval(input) { + Ok(s) => println!("{s}"), + + Err(e) => { + eprintln!("{e}"); + return ExitCode::FAILURE; + } + } + + ExitCode::SUCCESS } diff --git a/src/nodes/evaluate.rs b/src/nodes/evaluate.rs index ceb768a..27d6907 100644 --- a/src/nodes/evaluate.rs +++ b/src/nodes/evaluate.rs @@ -5,25 +5,35 @@ 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) -> Float { + pub fn evaluate(mut self) -> Result { 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) => n, - _ => panic!("failed to collapse nodes: {nodes:?}"), + Node::Number(n) => Ok(n), + _ => Err(Error::CollapseFailed(self)), } } } -fn collapse_toplevel(nodes: &mut Vec, mut end: usize) { +fn collapse_toplevel(nodes: &mut Vec, mut end: usize) -> Result<(), Error> { let start = nodes[0..end] .iter() .rposition(|n| *n == Node::ParenthesisStart) @@ -51,7 +61,7 @@ fn collapse_toplevel(nodes: &mut Vec, mut end: usize) { Function::Secant => n.sec_mut(), Function::CoSecant => n.csc_mut(), }, - n => panic!("function is missing an argument, got: {n:?}"), + n => return Err(Error::MissingArgument(n.clone())), }; nodes.remove(start + index); @@ -64,12 +74,12 @@ fn collapse_toplevel(nodes: &mut Vec, mut end: usize) { { let lhs = match nodes.remove(start + operator - 1) { Node::Number(n) => n, - n => panic!("operator has an invalid operand: {n:?}"), + n => return Err(Error::InvalidOperand(n)), }; end -= 1; match &mut nodes[start..end][operator] { Node::Number(n) => n.pow_from(lhs), - n => panic!("operator has an invalid operand: {n:?}"), + n => return Err(Error::InvalidOperand(n.clone())), }; nodes.remove(start + operator - 1); @@ -82,12 +92,12 @@ fn collapse_toplevel(nodes: &mut Vec, mut end: usize) { { let lhs = match nodes.remove(start + operator - 1) { Node::Number(n) => n, - n => panic!("operator has an invalid operand: {n:?}"), + n => return Err(Error::InvalidOperand(n)), }; end -= 1; match &mut nodes[start..end][operator] { Node::Number(n) => n.div_from(lhs), - n => panic!("operator has an invalid operand: {n:?}"), + n => return Err(Error::InvalidOperand(n.clone())), }; nodes.remove(start + operator - 1); @@ -100,12 +110,12 @@ fn collapse_toplevel(nodes: &mut Vec, mut end: usize) { { let lhs = match nodes.remove(start + operator - 1) { Node::Number(n) => n, - n => panic!("operator has an invalid operand: {n:?}"), + n => return Err(Error::InvalidOperand(n)), }; end -= 1; match &mut nodes[start..end][operator] { Node::Number(n) => n.mul_from(lhs), - n => panic!("operator has an invalid operand: {n:?}"), + n => return Err(Error::InvalidOperand(n.clone())), }; nodes.remove(start + operator - 1); @@ -118,12 +128,12 @@ fn collapse_toplevel(nodes: &mut Vec, mut end: usize) { { let lhs = match nodes.remove(start + operator - 1) { Node::Number(n) => n, - n => panic!("operator has an invalid operand: {n:?}"), + n => return Err(Error::InvalidOperand(n)), }; end -= 1; match &mut nodes[start..end][operator] { Node::Number(n) => n.sub_from(lhs), - n => panic!("operator has an invalid operand: {n:?}"), + n => return Err(Error::InvalidOperand(n.clone())), }; nodes.remove(start + operator - 1); @@ -136,12 +146,12 @@ fn collapse_toplevel(nodes: &mut Vec, mut end: usize) { { let lhs = match nodes.remove(start + operator - 1) { Node::Number(n) => n, - n => panic!("operator has an invalid operand: {n:?}"), + n => return Err(Error::InvalidOperand(n)), }; end -= 1; match &mut nodes[start..end][operator] { Node::Number(n) => n.add_from(lhs), - n => panic!("operator has an invalid operand: {n:?}"), + n => return Err(Error::InvalidOperand(n.clone())), }; nodes.remove(start + operator - 1); @@ -152,6 +162,8 @@ fn collapse_toplevel(nodes: &mut Vec, mut end: usize) { nodes.remove(end); nodes.remove(start); } + + Ok(()) } #[cfg(test)] @@ -169,6 +181,7 @@ fn test() { Node::Number(Float::with_val(PREC, 1)) ]) .evaluate() + .unwrap() .to_f64(), 1.9092974268256817, ); diff --git a/src/nodes/mod.rs b/src/nodes/mod.rs index 1f8f81b..32daaff 100644 --- a/src/nodes/mod.rs +++ b/src/nodes/mod.rs @@ -2,6 +2,8 @@ //! 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 evaluate; @@ -9,10 +11,18 @@ pub mod from_str; #[cfg(test)] mod tests; +#[derive(Clone, Debug, thiserror::Error)] +pub enum Error { + #[error("{0}")] + Parsing(#[from] from_str::Error), + #[error("{0}")] + Evaluation(#[from] evaluate::Error), +} + /// helper function to parse to Nodes and immediately evaluate -pub fn eval(s: &str) -> Result { +pub fn eval(s: &str) -> Result { use std::str::FromStr; - Nodes::from_str(s).map(|nodes| nodes.evaluate()) + Ok(Nodes::from_str(s)?.evaluate()?) } pub const PREC: u32 = 512; @@ -24,18 +34,28 @@ 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)] -enum Node { +pub 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)] -enum Operator { +pub enum Operator { Plus, Minus, #[default] @@ -45,7 +65,7 @@ enum Operator { } #[derive(Debug, Copy, Clone, PartialEq)] -enum Function { +pub enum Function { Sqrt, Sine, diff --git a/src/nodes/tests.rs b/src/nodes/tests.rs index 439acf4..1d40f50 100644 --- a/src/nodes/tests.rs +++ b/src/nodes/tests.rs @@ -4,7 +4,7 @@ use std::str::FromStr; use crate::nodes::Nodes; fn eval(s: &str) -> f64 { - Nodes::from_str(s).unwrap().evaluate().to_f64() + Nodes::from_str(s).unwrap().evaluate().unwrap().to_f64() } #[test]