interactive mode, fix multiplication with functions
This commit is contained in:
167
src/main.rs
167
src/main.rs
@@ -1,6 +1,8 @@
|
||||
use std::{io, io::Write};
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
enum TokenizeError {
|
||||
NumberParseError,
|
||||
NumberParseError(String),
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
@@ -34,24 +36,53 @@ enum Token {
|
||||
enum FunctionType {
|
||||
Sine,
|
||||
Cosine,
|
||||
NaturalLog,
|
||||
Max,
|
||||
Min,
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let arg = match std::env::args().nth(1) {
|
||||
Some(v) => v,
|
||||
None => {
|
||||
eprintln!("missing argument, Usage: expr, Example: 1 + 1 * 3 (6 - 4)^2");
|
||||
std::process::exit(1);
|
||||
}
|
||||
};
|
||||
if std::env::args().len() == 1 {
|
||||
loop {
|
||||
print!("> ");
|
||||
io::stdout().flush().unwrap();
|
||||
|
||||
println!("result: {}", compute(&arg));
|
||||
let mut input = String::new();
|
||||
io::stdin()
|
||||
.read_line(&mut input)
|
||||
.expect("Failed to read line");
|
||||
|
||||
let result = compute(&input);
|
||||
print_result(result);
|
||||
}
|
||||
} else {
|
||||
let arg = match std::env::args().nth(1) {
|
||||
Some(v) => v,
|
||||
None => {
|
||||
eprintln!("missing argument, Usage: expr, Example: 1 + 1 * 3 (6 - 4)^2");
|
||||
std::process::exit(1);
|
||||
}
|
||||
};
|
||||
let result = compute(&arg);
|
||||
print_result(result);
|
||||
}
|
||||
}
|
||||
|
||||
fn print_result(result: f64) {
|
||||
if result > 1000. || (result < 0.001 && result > -0.001) && result != 0. {
|
||||
println!("{:e}", result);
|
||||
} else {
|
||||
println!("{result}");
|
||||
}
|
||||
}
|
||||
|
||||
fn compute(input: &str) -> f64 {
|
||||
let input = &input.replace(' ', "");
|
||||
let input = &input
|
||||
.trim()
|
||||
.replace(' ', "")
|
||||
.replace("pi", "π")
|
||||
.replace("tau", "τ");
|
||||
|
||||
let tokens = match tokenize(input) {
|
||||
Ok(v) => v,
|
||||
Err(e) => {
|
||||
@@ -62,7 +93,6 @@ fn compute(input: &str) -> f64 {
|
||||
|
||||
let tokens = implicit_operations(tokens);
|
||||
let tokens = infix_to_postfix(tokens);
|
||||
println!("tokens: {:?}", tokens);
|
||||
|
||||
match calculate(tokens) {
|
||||
Ok(v) => v,
|
||||
@@ -89,6 +119,7 @@ impl std::fmt::Display for Token {
|
||||
Token::Function(function) => match function {
|
||||
FunctionType::Sine => write!(f, "sin"),
|
||||
FunctionType::Cosine => write!(f, "cos"),
|
||||
FunctionType::NaturalLog => write!(f, "ln"),
|
||||
FunctionType::Max => write!(f, "max"),
|
||||
FunctionType::Min => write!(f, "min"),
|
||||
},
|
||||
@@ -97,8 +128,10 @@ impl std::fmt::Display for Token {
|
||||
}
|
||||
|
||||
fn tokenize(input: &str) -> Result<Vec<Token>, TokenizeError> {
|
||||
let mut buf = String::new();
|
||||
let mut num_buf = String::new();
|
||||
let mut fun_buf = String::new();
|
||||
let mut tokens = Vec::new();
|
||||
let mut prev = None;
|
||||
for c in input.chars() {
|
||||
let token = match c {
|
||||
'(' => Token::LeftParenthesis,
|
||||
@@ -106,27 +139,60 @@ fn tokenize(input: &str) -> Result<Vec<Token>, TokenizeError> {
|
||||
'*' | '×' => Token::Multiply,
|
||||
'/' | '÷' => Token::Divide,
|
||||
'+' => Token::Add,
|
||||
'-' | '−' => Token::Subtract,
|
||||
'-' | '−' => {
|
||||
// exception for negative exponent for scientific notation
|
||||
if let Some(p) = prev {
|
||||
if p == 'E' {
|
||||
num_buf.push(c);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
Token::Subtract
|
||||
}
|
||||
'^' => Token::Power,
|
||||
'%' => Token::Modulus,
|
||||
',' => Token::Separator,
|
||||
'π' => Token::Number(std::f64::consts::PI),
|
||||
'e' => Token::Number(std::f64::consts::E),
|
||||
'τ' => Token::Number(std::f64::consts::TAU),
|
||||
'0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9' | 'E' | '.' => {
|
||||
if !fun_buf.is_empty() {
|
||||
tokens.push(parse_buffer(&fun_buf)?);
|
||||
fun_buf.clear();
|
||||
}
|
||||
num_buf.push(c);
|
||||
prev = Some(c);
|
||||
continue;
|
||||
}
|
||||
_ => {
|
||||
buf.push(c);
|
||||
if !num_buf.is_empty() {
|
||||
tokens.push(parse_buffer(&num_buf)?);
|
||||
num_buf.clear();
|
||||
}
|
||||
fun_buf.push(c);
|
||||
prev = Some(c);
|
||||
continue;
|
||||
}
|
||||
};
|
||||
if !buf.is_empty() {
|
||||
tokens.push(parse_buffer(&buf)?);
|
||||
buf.clear();
|
||||
if !num_buf.is_empty() {
|
||||
tokens.push(parse_buffer(&num_buf)?);
|
||||
num_buf.clear();
|
||||
}
|
||||
if !fun_buf.is_empty() {
|
||||
tokens.push(parse_buffer(&fun_buf)?);
|
||||
fun_buf.clear();
|
||||
}
|
||||
tokens.push(token);
|
||||
prev = Some(c);
|
||||
}
|
||||
if !buf.is_empty() {
|
||||
tokens.push(parse_buffer(&buf)?);
|
||||
buf.clear();
|
||||
|
||||
if !num_buf.is_empty() {
|
||||
tokens.push(parse_buffer(&num_buf)?);
|
||||
num_buf.clear();
|
||||
}
|
||||
if !fun_buf.is_empty() {
|
||||
tokens.push(parse_buffer(&fun_buf)?);
|
||||
fun_buf.clear();
|
||||
}
|
||||
Ok(tokens)
|
||||
}
|
||||
@@ -135,13 +201,14 @@ fn parse_buffer(buf: &str) -> Result<Token, TokenizeError> {
|
||||
match buf {
|
||||
"sin" => Ok(Token::Function(FunctionType::Sine)),
|
||||
"cos" => Ok(Token::Function(FunctionType::Cosine)),
|
||||
"ln" => Ok(Token::Function(FunctionType::NaturalLog)),
|
||||
"max" => Ok(Token::Function(FunctionType::Max)),
|
||||
"min" => Ok(Token::Function(FunctionType::Min)),
|
||||
_ => {
|
||||
if let Ok(number) = buf.parse() {
|
||||
Ok(Token::Number(number))
|
||||
} else {
|
||||
Err(TokenizeError::NumberParseError)
|
||||
Err(TokenizeError::NumberParseError(buf.to_string()))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -167,7 +234,13 @@ fn implicit_operations(tokens: Vec<Token>) -> Vec<Token> {
|
||||
}
|
||||
_ => (),
|
||||
},
|
||||
Token::RightParenthesis | Token::Number(_) => match token {
|
||||
Token::Number(_) => match token {
|
||||
Token::LeftParenthesis | Token::Number(_) | Token::Function(_) => {
|
||||
new_tokens.push(Token::Multiply)
|
||||
}
|
||||
_ => (),
|
||||
},
|
||||
Token::RightParenthesis => match token {
|
||||
Token::LeftParenthesis | Token::Number(_) => new_tokens.push(Token::Multiply),
|
||||
_ => (),
|
||||
},
|
||||
@@ -303,20 +376,21 @@ fn calculate(tokens: Vec<Token>) -> Result<f64, CalculateError> {
|
||||
|
||||
let a = stack.pop().ok_or(CalculateError::PostfixExpectedNumbers)?;
|
||||
|
||||
if stack.is_empty() {
|
||||
if let Token::Function(f) = token {
|
||||
if let Token::Number(n) = a {
|
||||
let n = match f {
|
||||
FunctionType::Sine => n.sin(),
|
||||
FunctionType::Cosine => n.cos(),
|
||||
if let Token::Function(f) = token {
|
||||
if let Token::Number(n) = a {
|
||||
let n = match f {
|
||||
FunctionType::Sine => Some(n.sin()),
|
||||
FunctionType::Cosine => Some(n.cos()),
|
||||
FunctionType::NaturalLog => Some(n.ln()),
|
||||
|
||||
_ => unreachable!(),
|
||||
};
|
||||
_ => None,
|
||||
};
|
||||
if let Some(n) = n {
|
||||
stack.push(Token::Number(n));
|
||||
continue;
|
||||
} else {
|
||||
return Err(CalculateError::PostfixExpectedNumbers);
|
||||
}
|
||||
} else {
|
||||
return Err(CalculateError::PostfixExpectedNumbers);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -340,12 +414,13 @@ fn calculate(tokens: Vec<Token>) -> Result<f64, CalculateError> {
|
||||
stack.push(Token::Number(n));
|
||||
}
|
||||
|
||||
assert!(stack.len() == 1);
|
||||
|
||||
if stack.is_empty() {
|
||||
return Err(CalculateError::EmptyStack);
|
||||
}
|
||||
if let Token::Number(n) = stack[0] {
|
||||
Ok(n)
|
||||
} else {
|
||||
Err(CalculateError::EmptyStack)
|
||||
Err(CalculateError::PostfixExpectedNumbers)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -381,11 +456,23 @@ mod tests {
|
||||
Ok(vec![Token::Number(3.14159265358979323)])
|
||||
);
|
||||
assert_eq!(tokenize("0."), Ok(vec![Token::Number(0.)]));
|
||||
assert_eq!(tokenize("."), Err(TokenizeError::NumberParseError));
|
||||
assert_eq!(tokenize(".."), Err(TokenizeError::NumberParseError));
|
||||
assert_eq!(tokenize(".1."), Err(TokenizeError::NumberParseError));
|
||||
assert_eq!(
|
||||
tokenize("."),
|
||||
Err(TokenizeError::NumberParseError(".".to_string()))
|
||||
);
|
||||
assert_eq!(
|
||||
tokenize(".."),
|
||||
Err(TokenizeError::NumberParseError("..".to_string()))
|
||||
);
|
||||
assert_eq!(
|
||||
tokenize(".1."),
|
||||
Err(TokenizeError::NumberParseError(".1.".to_string()))
|
||||
);
|
||||
assert_eq!(tokenize("1E3"), Ok(vec![Token::Number(1000.)]));
|
||||
assert_eq!(tokenize("E3"), Err(TokenizeError::NumberParseError));
|
||||
assert_eq!(
|
||||
tokenize("E3"),
|
||||
Err(TokenizeError::NumberParseError("E3".to_string()))
|
||||
);
|
||||
assert_eq!(
|
||||
tokenize("1+1"),
|
||||
Ok(vec![Token::Number(1.), Token::Add, Token::Number(1.)])
|
||||
@@ -614,5 +701,7 @@ mod tests {
|
||||
assert_eq!(compute("max(2, 3)"), 3.);
|
||||
assert_eq!(compute("3 +- 2"), 1.);
|
||||
assert_eq!(compute("3 -+ 2"), 1.);
|
||||
assert_eq!(compute("1E-3"), 0.001);
|
||||
assert_eq!(compute("3sin(3)"), 3. * 3_f64.sin());
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user