feat: add parsing function and test

This commit is contained in:
electria 2026-06-18 18:53:18 -07:00
commit 50fa80df27
Signed by: electria
SSH key fingerprint: SHA256:8LlB3ucPbBHqozqkhsNbaV5oG3SlzzqUj8FZDL6IPQs
2 changed files with 165 additions and 0 deletions

View file

@ -1,3 +1,5 @@
mod parse;
fn main() { fn main() {
println!("Hello, world!"); println!("Hello, world!");
} }

163
src/parse.rs Normal file
View file

@ -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<char> for Operator {
type Error = ();
fn try_from(value: char) -> Result<Self, Self::Error> {
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<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| 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(),
)
}