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
This commit is contained in:
electria 2026-09-30 19:39:34 -07:00
commit 35da9a0d40
Signed by: electria
SSH key fingerprint: SHA256:8LlB3ucPbBHqozqkhsNbaV5oG3SlzzqUj8FZDL6IPQs
5 changed files with 77 additions and 34 deletions

View file

@ -2,9 +2,9 @@ use std::str::FromStr;
use rug::Float; 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<String, Error> {
// no need to remove the characters here, // no need to remove the characters here,
// as we ignore the them in all other logic already // as we ignore the them in all other logic already
let should_add_back_alignment = input.contains('&'); let should_add_back_alignment = input.contains('&');
@ -18,10 +18,10 @@ pub fn pretty_eval(mut input: String) -> String {
}) })
.is_some(); .is_some();
let result = Nodes::from_str(&input).unwrap().evaluate(); let result = Nodes::from_str(&input)?.evaluate()?;
let significant_digits = get_significant_digits(&result); let significant_digits = get_significant_digits(&result);
format!( Ok(format!(
"{:leading_spaces$}{}{result:.significant_digits$}{}", "{:leading_spaces$}{}{result:.significant_digits$}{}",
' ', ' ',
if should_add_back_equals && should_add_back_alignment { 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 { "" } if should_add_back_backslash { " \\" } else { "" }
) ))
} }
#[test] #[test]
fn test_character_readdition() { fn test_character_readdition() {
assert_eq!(pretty_eval(" = 11".into()), " = 11"); assert_eq!(pretty_eval(" = 11".into()).unwrap(), " = 11");
assert_eq!(pretty_eval(" &= 11".into()), " & = 11"); assert_eq!(pretty_eval(" &= 11".into()).unwrap(), " & = 11");
assert_eq!(pretty_eval(" & 11".into()), " & 11"); assert_eq!(pretty_eval(" & 11".into()).unwrap(), " & 11");
assert_eq!(pretty_eval(" & 11 \\".into()), " & 11 \\"); assert_eq!(pretty_eval(" & 11 \\".into()).unwrap(), " & 11 \\");
} }
pub fn get_significant_digits(n: &Float) -> usize { pub fn get_significant_digits(n: &Float) -> usize {

View file

@ -1,12 +1,13 @@
use std::{ use std::{
env, env,
io::{self, Read}, io::{self, Read},
process::ExitCode,
}; };
mod formatting; mod formatting;
mod nodes; mod nodes;
fn main() { fn main() -> ExitCode {
let input = match env::args().skip(1).rev().reduce(|x, a| a + &x) { let input = match env::args().skip(1).rev().reduce(|x, a| a + &x) {
Some(s) => s, Some(s) => s,
None => { 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
} }

View file

@ -5,25 +5,35 @@ use rug::{
use crate::nodes::{Function, Node, Nodes, Operator}; 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 { impl Nodes {
/// Consume the `Nodes`, collapsing them down to a single number /// Consume the `Nodes`, collapsing them down to a single number
pub fn evaluate(mut self) -> Float { pub fn evaluate(mut self) -> Result<Float, Error> {
let nodes = &mut self.0; let nodes = &mut self.0;
while let Some(end) = nodes.iter().position(|n| *n == Node::ParenthesisEnd) { while let Some(end) = nodes.iter().position(|n| *n == Node::ParenthesisEnd) {
collapse_toplevel(nodes, end); collapse_toplevel(nodes, end)?;
} }
let len = nodes.len(); let len = nodes.len();
collapse_toplevel(nodes, len); collapse_toplevel(nodes, len)?;
match nodes.pop().unwrap() { match nodes.pop().unwrap() {
Node::Number(n) => n, Node::Number(n) => Ok(n),
_ => panic!("failed to collapse nodes: {nodes:?}"), _ => Err(Error::CollapseFailed(self)),
} }
} }
} }
fn collapse_toplevel(nodes: &mut Vec<Node>, mut end: usize) { fn collapse_toplevel(nodes: &mut Vec<Node>, mut end: usize) -> Result<(), Error> {
let start = nodes[0..end] let start = nodes[0..end]
.iter() .iter()
.rposition(|n| *n == Node::ParenthesisStart) .rposition(|n| *n == Node::ParenthesisStart)
@ -51,7 +61,7 @@ fn collapse_toplevel(nodes: &mut Vec<Node>, mut end: usize) {
Function::Secant => n.sec_mut(), Function::Secant => n.sec_mut(),
Function::CoSecant => n.csc_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); nodes.remove(start + index);
@ -64,12 +74,12 @@ fn collapse_toplevel(nodes: &mut Vec<Node>, mut end: usize) {
{ {
let lhs = match nodes.remove(start + operator - 1) { let lhs = match nodes.remove(start + operator - 1) {
Node::Number(n) => n, Node::Number(n) => n,
n => panic!("operator has an invalid operand: {n:?}"), n => return Err(Error::InvalidOperand(n)),
}; };
end -= 1; end -= 1;
match &mut nodes[start..end][operator] { match &mut nodes[start..end][operator] {
Node::Number(n) => n.pow_from(lhs), 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); nodes.remove(start + operator - 1);
@ -82,12 +92,12 @@ fn collapse_toplevel(nodes: &mut Vec<Node>, mut end: usize) {
{ {
let lhs = match nodes.remove(start + operator - 1) { let lhs = match nodes.remove(start + operator - 1) {
Node::Number(n) => n, Node::Number(n) => n,
n => panic!("operator has an invalid operand: {n:?}"), n => return Err(Error::InvalidOperand(n)),
}; };
end -= 1; end -= 1;
match &mut nodes[start..end][operator] { match &mut nodes[start..end][operator] {
Node::Number(n) => n.div_from(lhs), 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); nodes.remove(start + operator - 1);
@ -100,12 +110,12 @@ fn collapse_toplevel(nodes: &mut Vec<Node>, mut end: usize) {
{ {
let lhs = match nodes.remove(start + operator - 1) { let lhs = match nodes.remove(start + operator - 1) {
Node::Number(n) => n, Node::Number(n) => n,
n => panic!("operator has an invalid operand: {n:?}"), n => return Err(Error::InvalidOperand(n)),
}; };
end -= 1; end -= 1;
match &mut nodes[start..end][operator] { match &mut nodes[start..end][operator] {
Node::Number(n) => n.mul_from(lhs), 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); nodes.remove(start + operator - 1);
@ -118,12 +128,12 @@ fn collapse_toplevel(nodes: &mut Vec<Node>, mut end: usize) {
{ {
let lhs = match nodes.remove(start + operator - 1) { let lhs = match nodes.remove(start + operator - 1) {
Node::Number(n) => n, Node::Number(n) => n,
n => panic!("operator has an invalid operand: {n:?}"), n => return Err(Error::InvalidOperand(n)),
}; };
end -= 1; end -= 1;
match &mut nodes[start..end][operator] { match &mut nodes[start..end][operator] {
Node::Number(n) => n.sub_from(lhs), 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); nodes.remove(start + operator - 1);
@ -136,12 +146,12 @@ fn collapse_toplevel(nodes: &mut Vec<Node>, mut end: usize) {
{ {
let lhs = match nodes.remove(start + operator - 1) { let lhs = match nodes.remove(start + operator - 1) {
Node::Number(n) => n, Node::Number(n) => n,
n => panic!("operator has an invalid operand: {n:?}"), n => return Err(Error::InvalidOperand(n)),
}; };
end -= 1; end -= 1;
match &mut nodes[start..end][operator] { match &mut nodes[start..end][operator] {
Node::Number(n) => n.add_from(lhs), 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); nodes.remove(start + operator - 1);
@ -152,6 +162,8 @@ fn collapse_toplevel(nodes: &mut Vec<Node>, mut end: usize) {
nodes.remove(end); nodes.remove(end);
nodes.remove(start); nodes.remove(start);
} }
Ok(())
} }
#[cfg(test)] #[cfg(test)]
@ -169,6 +181,7 @@ fn test() {
Node::Number(Float::with_val(PREC, 1)) Node::Number(Float::with_val(PREC, 1))
]) ])
.evaluate() .evaluate()
.unwrap()
.to_f64(), .to_f64(),
1.9092974268256817, 1.9092974268256817,
); );

View file

@ -2,6 +2,8 @@
//! as well as the logic to parse it from a string (`Node::from_str`), //! 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`). //! and the logic to evaluate it into a single number (`Node::evaluate`).
use std::fmt::{Debug, Display};
use rug::Float; use rug::Float;
mod evaluate; mod evaluate;
@ -9,10 +11,18 @@ pub mod from_str;
#[cfg(test)] #[cfg(test)]
mod tests; 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 /// helper function to parse to Nodes and immediately evaluate
pub fn eval(s: &str) -> Result<rug::Float, from_str::Error> { pub fn eval(s: &str) -> Result<rug::Float, Error> {
use std::str::FromStr; use std::str::FromStr;
Nodes::from_str(s).map(|nodes| nodes.evaluate()) Ok(Nodes::from_str(s)?.evaluate()?)
} }
pub const PREC: u32 = 512; pub const PREC: u32 = 512;
@ -24,18 +34,28 @@ impl AsRef<[Node]> for Nodes {
self.0.as_ref() 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)] #[derive(Debug, Clone, PartialEq)]
enum Node { pub enum Node {
Function(Function), Function(Function),
ParenthesisStart, ParenthesisStart,
ParenthesisEnd, ParenthesisEnd,
Number(Float), Number(Float),
Operator(Operator), 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)] #[derive(Debug, Default, Copy, Clone, PartialEq)]
enum Operator { pub enum Operator {
Plus, Plus,
Minus, Minus,
#[default] #[default]
@ -45,7 +65,7 @@ enum Operator {
} }
#[derive(Debug, Copy, Clone, PartialEq)] #[derive(Debug, Copy, Clone, PartialEq)]
enum Function { pub enum Function {
Sqrt, Sqrt,
Sine, Sine,

View file

@ -4,7 +4,7 @@ use std::str::FromStr;
use crate::nodes::Nodes; use crate::nodes::Nodes;
fn eval(s: &str) -> f64 { fn eval(s: &str) -> f64 {
Nodes::from_str(s).unwrap().evaluate().to_f64() Nodes::from_str(s).unwrap().evaluate().unwrap().to_f64()
} }
#[test] #[test]