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:
parent
d2368adc8f
commit
35da9a0d40
5 changed files with 77 additions and 34 deletions
|
|
@ -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<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('&');
|
||||
|
|
@ -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 {
|
||||
|
|
|
|||
14
src/main.rs
14
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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<Float, Error> {
|
||||
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<Node>, mut end: usize) {
|
||||
fn collapse_toplevel(nodes: &mut Vec<Node>, 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<Node>, 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<Node>, 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<Node>, 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<Node>, 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<Node>, 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<Node>, 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<Node>, 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,
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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<rug::Float, from_str::Error> {
|
||||
pub fn eval(s: &str) -> Result<rug::Float, Error> {
|
||||
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,
|
||||
|
|
|
|||
|
|
@ -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]
|
||||
|
|
|
|||
Loading…
Reference in a new issue