From 08723e83af38e8b032057a607f63b79f8426e50b Mon Sep 17 00:00:00 2001 From: Vegard Matthey Date: Fri, 22 Mar 2024 16:59:26 +0100 Subject: [PATCH] interactive mode, fix multiplication with functions --- src/main.rs | 167 ++++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 128 insertions(+), 39 deletions(-) diff --git a/src/main.rs b/src/main.rs index 212577f..4cfe003 100644 --- a/src/main.rs +++ b/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, 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, 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 { 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) -> Vec { } _ => (), }, - 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) -> Result { 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) -> Result { 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()); } }