refactor: OOP a little

This commit is contained in:
electria 2026-07-13 21:53:37 -07:00
commit 2746345cc6
Signed by: electria
SSH key fingerprint: SHA256:8LlB3ucPbBHqozqkhsNbaV5oG3SlzzqUj8FZDL6IPQs
6 changed files with 374 additions and 329 deletions

View file

@ -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
View 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
View 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
View 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
View 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))
],
)
}

View file

@ -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))
],
)
}