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 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 {
|
||||||
|
|
|
||||||
14
src/main.rs
14
src/main.rs
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -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]
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue