refactor: OOP a little
This commit is contained in:
parent
877ae39d23
commit
2746345cc6
6 changed files with 374 additions and 329 deletions
12
src/main.rs
12
src/main.rs
|
|
@ -1,9 +1,16 @@
|
||||||
use std::{
|
use std::{
|
||||||
env,
|
env,
|
||||||
io::{self, Read},
|
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() {
|
fn main() {
|
||||||
let input = match env::args().nth(1) {
|
let input = match env::args().nth(1) {
|
||||||
|
|
@ -14,5 +21,6 @@ fn main() {
|
||||||
buf
|
buf
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
println!("{:.3}", parse::parse(&input));
|
|
||||||
|
println!("{:.3}", parse_and_calc(&input));
|
||||||
}
|
}
|
||||||
|
|
|
||||||
148
src/nodes/evaluate.rs
Normal file
148
src/nodes/evaluate.rs
Normal file
|
|
@ -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<Node>, 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
110
src/nodes/from_str.rs
Normal file
110
src/nodes/from_str.rs
Normal file
|
|
@ -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<Self, Self::Err> {
|
||||||
|
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))
|
||||||
|
}
|
||||||
|
}
|
||||||
69
src/nodes/mod.rs
Normal file
69
src/nodes/mod.rs
Normal file
|
|
@ -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<Node>);
|
||||||
|
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<char> for Operator {
|
||||||
|
type Error = ();
|
||||||
|
fn try_from(value: char) -> Result<Self, Self::Error> {
|
||||||
|
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<Self, Self::Err> {
|
||||||
|
match s {
|
||||||
|
"sin" => Ok(Self::Sine),
|
||||||
|
"cos" => Ok(Self::Cosine),
|
||||||
|
_ => Err(()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
37
src/nodes/tests.rs
Normal file
37
src/nodes/tests.rs
Normal file
|
|
@ -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))
|
||||||
|
],
|
||||||
|
)
|
||||||
|
}
|
||||||
327
src/parse.rs
327
src/parse.rs
|
|
@ -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<char> for Operator {
|
|
||||||
type Error = ();
|
|
||||||
fn try_from(value: char) -> Result<Self, Self::Error> {
|
|
||||||
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<Self, Self::Err> {
|
|
||||||
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<Vec<Node>, 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<Node>, 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<Node>) -> 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))
|
|
||||||
],
|
|
||||||
)
|
|
||||||
}
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue