diff --git a/src/main.rs b/src/main.rs index a29c779..107dbf5 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,9 +1,16 @@ use std::{ env, io::{self, Read}, + str::FromStr, }; -mod parse; +use rug::Float; + +mod nodes; + +pub fn parse_and_calc(s: &str) -> Float { + nodes::Nodes::from_str(s).unwrap().evaluate() +} fn main() { let input = match env::args().nth(1) { @@ -14,5 +21,6 @@ fn main() { buf } }; - println!("{:.3}", parse::parse(&input)); + + println!("{:.3}", parse_and_calc(&input)); } diff --git a/src/nodes/evaluate.rs b/src/nodes/evaluate.rs new file mode 100644 index 0000000..ae78ed1 --- /dev/null +++ b/src/nodes/evaluate.rs @@ -0,0 +1,148 @@ +use rug::{ + Float, + ops::{AddFrom, DivFrom, MulFrom, PowFrom, SubFrom}, +}; + +use crate::nodes::{Function, Node, Nodes, Operator}; + +impl Nodes { + /// Consume the `Nodes`, collapsing them down to a single number + 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); + } + let len = nodes.len(); + collapse_toplevel(nodes, len); + + match nodes.pop().unwrap() { + Node::Number(n) => n, + _ => panic!("failed to collapse nodes: {nodes:?}"), + } + } +} + +fn collapse_toplevel(nodes: &mut Vec, mut end: usize) { + let start = nodes[0..end] + .iter() + .rposition(|n| *n == Node::ParenthesisStart) + .unwrap_or(0); + + while let Some((index, variant)) = nodes[start..end] + .iter() + .filter_map(|n| match n { + Node::Function(f) => Some(f), + _ => None, + }) + .cloned() + .enumerate() + .next() + { + match &mut nodes[start..end][index + 1] { + Node::Number(n) => match variant { + Function::Sine => n.sin_mut(), + Function::Cosine => n.cos_mut(), + }, + _ => panic!("function is missing an argument"), + }; + + nodes.remove(start + index); + end -= 1; + } + + while let Some(operator) = nodes[start..end] + .iter() + .position(|n| *n == Node::Operator(Operator::Exp)) + { + 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.pow_from(lhs), + 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::Div)) + { + 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.div_from(lhs), + 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::Mult)) + { + 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.mul_from(lhs), + 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::Plus)) + { + 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.add_from(lhs), + 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); + end -= 1; + } + + if nodes.len() > 1 { + nodes.remove(end); + nodes.remove(start); + } +} diff --git a/src/nodes/from_str.rs b/src/nodes/from_str.rs new file mode 100644 index 0000000..95f25a7 --- /dev/null +++ b/src/nodes/from_str.rs @@ -0,0 +1,110 @@ +use std::str::FromStr; + +use rug::{Float, float::ParseFloatError, ops::CompleteRound}; + +use crate::nodes::{Node, Nodes, PREC}; + +#[derive(Clone, Debug, thiserror::Error)] +pub enum Error { + #[error("invalid function: {0}")] + InvalidFunction(String), + #[error("invalid operator: {0}")] + InvalidOperator(char), + #[error("failed to parse number '{0}': {1}")] + NumberParsing(String, ParseFloatError), +} + +#[derive(Clone, Debug, Default)] +enum ParsingState { + #[default] + Start, + ReadingFunctionName(usize), + ReadingNumber(usize), +} + +impl FromStr for Nodes { + type Err = Error; + fn from_str(s: &str) -> Result { + let mut nodes = Vec::new(); + let mut state = ParsingState::default(); + + let is_ignored_char = |c: &char| !matches!(c, ' ' | '&' | '\n'); + + let filtered_string: String = s.chars().filter(is_ignored_char).collect(); + + for (i, c) in filtered_string.char_indices() { + let mut start = false; + match state { + ParsingState::Start => start = true, + ParsingState::ReadingFunctionName(start_index) => { + if !c.is_alphabetic() { + nodes.push(Node::Function( + filtered_string[start_index..i].parse().map_err(|()| { + Error::InvalidFunction(filtered_string[start_index..i].to_owned()) + })?, + )); + + state = ParsingState::Start; + start = true; + } + } + ParsingState::ReadingNumber(start_index) => { + if !c.is_numeric() { + nodes.push(Node::Number( + Float::parse_radix(&filtered_string[start_index..i], 10) + .map_err(|e| { + Error::NumberParsing( + filtered_string[start_index..i].to_owned(), + e, + ) + })? + .complete(PREC), + )); + + state = ParsingState::Start; + start = true; + } + } + } + + if start { + if c.is_alphabetic() { + state = ParsingState::ReadingFunctionName(i); + } else if c.is_numeric() { + state = ParsingState::ReadingNumber(i); + } else if c == '(' { + nodes.push(Node::ParenthesisStart); + } else if c == ')' { + nodes.push(Node::ParenthesisEnd); + } else { + nodes.push(Node::Operator( + c.try_into().map_err(|()| Error::InvalidOperator(c))?, + )); + } + } + } + + let i = filtered_string.len(); + match state { + ParsingState::Start => {} + ParsingState::ReadingFunctionName(start_index) => { + nodes.push(Node::Function( + filtered_string[start_index..i].parse().map_err(|()| { + Error::InvalidFunction(filtered_string[start_index..i].to_owned()) + })?, + )); + } + ParsingState::ReadingNumber(start_index) => { + nodes.push(Node::Number( + Float::parse_radix(&filtered_string[start_index..i], 10) + .map_err(|e| { + Error::NumberParsing(filtered_string[start_index..i].to_owned(), e) + })? + .complete(PREC), + )); + } + } + + Ok(Nodes(nodes)) + } +} diff --git a/src/nodes/mod.rs b/src/nodes/mod.rs new file mode 100644 index 0000000..874c98b --- /dev/null +++ b/src/nodes/mod.rs @@ -0,0 +1,69 @@ +//! This module contains the intermediate representation (IR) used by this crate, +//! 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::str::FromStr; + +use rug::Float; + +mod evaluate; +mod from_str; +#[cfg(test)] +mod tests; + +pub const PREC: u32 = 512; + +#[derive(Default, Debug, Clone, PartialEq)] +pub struct Nodes(Vec); +impl AsRef<[Node]> for Nodes { + fn as_ref(&self) -> &[Node] { + self.0.as_ref() + } +} + +#[derive(Clone, Debug, PartialEq)] +enum Node { + Function(Function), + ParenthesisStart, + ParenthesisEnd, + Number(Float), + Operator(Operator), +} + +#[derive(Clone, Debug, PartialEq)] +enum Operator { + Plus, + Minus, + Mult, + Div, + Exp, +} +impl TryFrom for Operator { + type Error = (); + fn try_from(value: char) -> Result { + match value { + '+' => Ok(Self::Plus), + '-' => Ok(Self::Minus), + '*' => Ok(Self::Mult), + '/' => Ok(Self::Div), + '^' => Ok(Self::Exp), + _ => Err(()), + } + } +} + +#[derive(Clone, Debug, PartialEq)] +enum Function { + Sine, + Cosine, +} +impl FromStr for Function { + type Err = (); + fn from_str(s: &str) -> Result { + match s { + "sin" => Ok(Self::Sine), + "cos" => Ok(Self::Cosine), + _ => Err(()), + } + } +} diff --git a/src/nodes/tests.rs b/src/nodes/tests.rs new file mode 100644 index 0000000..022f343 --- /dev/null +++ b/src/nodes/tests.rs @@ -0,0 +1,37 @@ +use std::str::FromStr; + +use rug::Float; + +use crate::nodes::{Function, Node, Nodes, Operator, PREC}; + +#[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_round(rug::float::Round::Nearest), + 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)) + ], + ) +} diff --git a/src/parse.rs b/src/parse.rs deleted file mode 100644 index 7141938..0000000 --- a/src/parse.rs +++ /dev/null @@ -1,327 +0,0 @@ -use std::str::FromStr; - -use rug::{ - Float, - float::ParseFloatError, - ops::{AddFrom, CompleteRound, DivFrom, MulFrom, PowFrom, SubFrom}, -}; - -#[derive(Clone, Debug, thiserror::Error)] -enum Error { - #[error("invalid function: {0}")] - InvalidFunction(String), - #[error("invalid operator: {0}")] - InvalidOperator(char), - #[error("failed to parse number '{0}': {1}")] - NumberParsing(String, ParseFloatError), -} - -const PREC: u32 = 512; - -#[derive(Clone, Debug, PartialEq)] -enum Node { - Function(Function), - ParenthesisStart, - ParenthesisEnd, - Number(Float), - Operator(Operator), -} - -#[derive(Clone, Debug, PartialEq)] -enum Operator { - Plus, - Minus, - Mult, - Div, - Exp, -} -impl TryFrom for Operator { - type Error = (); - fn try_from(value: char) -> Result { - match value { - '+' => Ok(Self::Plus), - '-' => Ok(Self::Minus), - '*' => Ok(Self::Mult), - '/' => Ok(Self::Div), - '^' => Ok(Self::Exp), - _ => Err(()), - } - } -} - -#[derive(Clone, Debug, PartialEq)] -enum Function { - Sine, - Cosine, -} -impl FromStr for Function { - type Err = (); - fn from_str(s: &str) -> Result { - match s { - "sin" => Ok(Self::Sine), - "cos" => Ok(Self::Cosine), - _ => Err(()), - } - } -} - -#[derive(Clone, Debug, Default)] -enum ParsingState { - #[default] - Start, - ReadingFunctionName(usize), - ReadingNumber(usize), -} - -fn nodes_from_str(s: &str) -> Result, Error> { - let mut nodes = Vec::new(); - let mut state = ParsingState::default(); - - let is_ignored_char = |c: &char| !matches!(c, ' ' | '&' | '\n'); - - let filtered_string: String = s.chars().filter(is_ignored_char).collect(); - - for (i, c) in filtered_string.char_indices() { - let mut start = false; - match state { - ParsingState::Start => start = true, - ParsingState::ReadingFunctionName(start_index) => { - if !c.is_alphabetic() { - nodes.push(Node::Function( - filtered_string[start_index..i].parse().map_err(|()| { - Error::InvalidFunction(filtered_string[start_index..i].to_owned()) - })?, - )); - - state = ParsingState::Start; - start = true; - } - } - ParsingState::ReadingNumber(start_index) => { - if !c.is_numeric() { - nodes.push(Node::Number( - Float::parse_radix(&filtered_string[start_index..i], 10) - .map_err(|e| { - Error::NumberParsing(filtered_string[start_index..i].to_owned(), e) - })? - .complete(PREC), - )); - - state = ParsingState::Start; - start = true; - } - } - } - - if start { - if c.is_alphabetic() { - state = ParsingState::ReadingFunctionName(i); - } else if c.is_numeric() { - state = ParsingState::ReadingNumber(i); - } else if c == '(' { - nodes.push(Node::ParenthesisStart); - } else if c == ')' { - nodes.push(Node::ParenthesisEnd); - } else { - nodes.push(Node::Operator( - c.try_into().map_err(|()| Error::InvalidOperator(c))?, - )); - } - } - } - - let i = filtered_string.len(); - match state { - ParsingState::Start => {} - ParsingState::ReadingFunctionName(start_index) => { - nodes.push(Node::Function( - filtered_string[start_index..i].parse().map_err(|()| { - Error::InvalidFunction(filtered_string[start_index..i].to_owned()) - })?, - )); - } - ParsingState::ReadingNumber(start_index) => { - nodes.push(Node::Number( - Float::parse_radix(&filtered_string[start_index..i], 10) - .map_err(|e| { - Error::NumberParsing(filtered_string[start_index..i].to_owned(), e) - })? - .complete(PREC), - )); - } - } - - Ok(nodes) -} - -fn collapse_toplevel(nodes: &mut Vec, mut end: usize) { - let start = nodes[0..end] - .iter() - .rposition(|n| *n == Node::ParenthesisStart) - .unwrap_or(0); - - while let Some((index, variant)) = nodes[start..end] - .iter() - .filter_map(|n| match n { - Node::Function(f) => Some(f), - _ => None, - }) - .cloned() - .enumerate() - .next() - { - match &mut nodes[start..end][index + 1] { - Node::Number(n) => match variant { - Function::Sine => n.sin_mut(), - Function::Cosine => n.cos_mut(), - }, - _ => panic!("function is missing an argument"), - }; - - nodes.remove(start + index); - end -= 1; - } - - while let Some(operator) = nodes[start..end] - .iter() - .position(|n| *n == Node::Operator(Operator::Exp)) - { - 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.pow_from(lhs), - 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::Div)) - { - 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.div_from(lhs), - 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::Mult)) - { - 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.mul_from(lhs), - 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::Plus)) - { - 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.add_from(lhs), - 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); - end -= 1; - } - - if nodes.len() > 1 { - nodes.remove(end); - nodes.remove(start); - } -} - -fn nodes_to_num(mut nodes: Vec) -> Float { - while let Some(end) = nodes.iter().position(|n| *n == Node::ParenthesisEnd) { - collapse_toplevel(&mut nodes, end); - } - let len = nodes.len(); - collapse_toplevel(&mut nodes, len); - - match nodes.pop().unwrap() { - Node::Number(n) => n, - _ => panic!("failed to collapse nodes: {nodes:?}"), - } -} - -pub fn parse(s: &str) -> Float { - nodes_to_num(nodes_from_str(s).unwrap()) -} - -#[test] -fn test_nodes_to_num() { - assert_eq!( - nodes_to_num(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)) - ]) - .to_f64_round(rug::float::Round::Nearest), - 1.9092974268256817, - ); -} - -#[test] -fn test_nodes_from_str() { - assert_eq!( - nodes_from_str("sin(2) + 1").unwrap().as_slice(), - &[ - 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)) - ], - ) -}