feat: add parsing function and test
This commit is contained in:
parent
e0921b9c96
commit
50fa80df27
2 changed files with 165 additions and 0 deletions
|
|
@ -1,3 +1,5 @@
|
||||||
|
mod parse;
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
println!("Hello, world!");
|
println!("Hello, world!");
|
||||||
}
|
}
|
||||||
|
|
|
||||||
163
src/parse.rs
Normal file
163
src/parse.rs
Normal 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(),
|
||||||
|
)
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue