From 50fa80df278ecde149af14b904cbf1604d2fc3c0 Mon Sep 17 00:00:00 2001 From: electria Date: Thu, 18 Jun 2026 18:53:18 -0700 Subject: [PATCH] feat: add parsing function and test --- src/main.rs | 2 + src/parse.rs | 163 +++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 165 insertions(+) create mode 100644 src/parse.rs diff --git a/src/main.rs b/src/main.rs index e7a11a9..87c15c4 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,3 +1,5 @@ +mod parse; + fn main() { println!("Hello, world!"); } diff --git a/src/parse.rs b/src/parse.rs new file mode 100644 index 0000000..26c4aa5 --- /dev/null +++ b/src/parse.rs @@ -0,0 +1,163 @@ +#![allow(dead_code)] +use std::str::FromStr; + +use rug::{Float, float::ParseFloatError, ops::CompleteRound}; + +#[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), +} + +#[derive(Clone, Debug, PartialEq)] +enum Node { + Function(Function), + ParenthesisStart, + ParenthesisEnd, + Number(Float), + Operator(Operator), +} + +#[derive(Clone, Debug, PartialEq)] +enum Operator { + Plus, + Minus, +} +impl TryFrom for Operator { + type Error = (); + fn try_from(value: char) -> Result { + match value { + '+' => Ok(Self::Plus), + '-' => Ok(Self::Minus), + _ => 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| match c { + ' ' | '&' => false, + _ => true, + }; + + let filtered_string: String = s.chars().filter(is_ignored_char).collect(); + + for (i, c) in filtered_string.chars().enumerate() { + 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(512), + )); + + 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(512), + )); + } + } + + Ok(nodes) +} + +#[test] +fn test_nodes_from_str() { + assert_eq!( + nodes_from_str("sin(2) + 1").unwrap().as_slice(), + vec![ + Node::Function(Function::Sine), + Node::ParenthesisStart, + Node::Number(Float::with_val(512, 2)), + Node::ParenthesisEnd, + Node::Operator(Operator::Plus), + Node::Number(Float::with_val(512, 1)) + ] + .as_slice(), + ) +}