diff --git a/src/main.rs b/src/main.rs index 18f453e..46a2ab7 100644 --- a/src/main.rs +++ b/src/main.rs @@ -64,8 +64,7 @@ enum Token { #[derive(Clone, PartialEq, Debug)] enum FunctionType { - Sine, - Cosine, + Trigonometric(TrigonometricFunction), NaturalLog, Log10, Max, @@ -73,38 +72,90 @@ enum FunctionType { SquareRoot, } -fn main() { - if std::env::args().len() == 1 { - loop { - print!("> "); - io::stdout().flush().expect("failed to flush stdout"); +#[derive(Debug, PartialEq, Clone)] +enum TrigonometricFunction { + Sine, + InverseSine, + Cosine, + InverseCosine, + Tangent, + InverseTangent, +} - let mut input = String::new(); - io::stdin() - .read_line(&mut input) - .expect("Failed to read line"); +#[derive(Default, PartialEq)] +enum AngleUnit { + #[default] + Radian, + Degree, +} - if input == "clear\n" { - print!("\u{001b}c"); - continue; - } +#[derive(Default)] +struct Config { + angle_unit: AngleUnit, +} - let result = handle_err(compute(&input)); - print_result(result); +impl Config { + fn new() -> Self { + Self::default() + } +} + +fn input_loop(config: &Config) { + loop { + print!("> "); + io::stdout().flush().expect("failed to flush stdout"); + + let mut input = String::new(); + io::stdin() + .read_line(&mut input) + .expect("Failed to read line"); + + if input == "clear\n" { + print!("\u{001b}c"); + continue; } - } 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 = handle_err(compute(&arg)); + + let result = handle_err(compute(&input, &config)); print_result(result); } } +fn main() { + let args = std::env::args().skip(1).collect::>(); + let mut config = Config::new(); + let mut args = args.iter(); + if args.len() == 0 { + input_loop(&config); + } else { + let mut string_arg = None; + while let Some(arg) = args.next() { + match arg.as_str() { + "-a" => match args.next() { + Some(s) => match s.as_str() { + "d" | "degree" | "degrees" => config.angle_unit = AngleUnit::Degree, + "r" | "radian" | "radians" => config.angle_unit = AngleUnit::Radian, + _ => { + eprintln!("Expected type of angle unit"); + std::process::exit(1); + } + }, + None => { + eprintln!("Expected type of angle unit"); + std::process::exit(1); + } + }, + s => string_arg = Some(s), + } + } + if let Some(s) = string_arg { + let result = handle_err(compute(s, &config)); + print_result(result); + } else { + input_loop(&config); + } + } +} + fn handle_err(result: Result) -> f64 { match result { Ok(v) => v, @@ -133,18 +184,18 @@ impl Token { } fn print_result(result: f64) { - if result > 1E4 || (result < 1E-4 && result > -1E-4) && result != 0. { + if result.abs() > 1E4 || (result.abs() < 1E-4 && result != 0.) { println!("{:E}", result); } else { println!("{result}"); } } -fn compute(input: &str) -> Result { +fn compute(input: &str, config: &Config) -> Result { let tokens = tokenize(input).map_err(Error::Token)?; let tokens = implicit_operations(tokens).ok_or(Error::Implicit)?; let tokens = infix_to_postfix(tokens).map_err(Error::Conversion)?; - calculate(tokens).map_err(Error::Calculate) + calculate(tokens, config).map_err(Error::Calculate) } impl std::fmt::Display for Token { @@ -161,8 +212,18 @@ impl std::fmt::Display for Token { Token::RightParenthesis => write!(f, ")"), Token::Separator => write!(f, ","), Token::Function(function) => match function { - FunctionType::Sine => write!(f, "sin"), - FunctionType::Cosine => write!(f, "cos"), + FunctionType::Trigonometric(TrigonometricFunction::Sine) => write!(f, "sin"), + FunctionType::Trigonometric(TrigonometricFunction::InverseSine) => { + write!(f, "asin") + } + FunctionType::Trigonometric(TrigonometricFunction::Cosine) => write!(f, "cos"), + FunctionType::Trigonometric(TrigonometricFunction::InverseCosine) => { + write!(f, "acos") + } + FunctionType::Trigonometric(TrigonometricFunction::Tangent) => write!(f, "tan"), + FunctionType::Trigonometric(TrigonometricFunction::InverseTangent) => { + write!(f, "atan") + } FunctionType::NaturalLog => write!(f, "ln"), FunctionType::Max => write!(f, "max"), FunctionType::Min => write!(f, "min"), @@ -236,8 +297,24 @@ fn push_buf(tokens: &mut Vec, buf: &mut String) -> Result<(), TokenizeErr fn parse_buffer(buf: &str) -> Result { // This does not handle implicit multiplication with constants and functions. match buf { - "sin" => Ok(Token::Function(FunctionType::Sine)), - "cos" => Ok(Token::Function(FunctionType::Cosine)), + "sin" => Ok(Token::Function(FunctionType::Trigonometric( + TrigonometricFunction::Sine, + ))), + "asin" => Ok(Token::Function(FunctionType::Trigonometric( + TrigonometricFunction::InverseSine, + ))), + "cos" => Ok(Token::Function(FunctionType::Trigonometric( + TrigonometricFunction::Cosine, + ))), + "acos" => Ok(Token::Function(FunctionType::Trigonometric( + TrigonometricFunction::InverseCosine, + ))), + "tan" => Ok(Token::Function(FunctionType::Trigonometric( + TrigonometricFunction::Tangent, + ))), + "atan" => Ok(Token::Function(FunctionType::Trigonometric( + TrigonometricFunction::InverseTangent, + ))), "ln" => Ok(Token::Function(FunctionType::NaturalLog)), "log" => Ok(Token::Function(FunctionType::Log10)), "max" => Ok(Token::Function(FunctionType::Max)), @@ -433,7 +510,7 @@ fn precedence(token: &Token) -> Result { } } -fn calculate(tokens: Vec) -> Result { +fn calculate(tokens: Vec, config: &Config) -> Result { let mut stack = Vec::new(); let mut tokens = tokens.iter(); if tokens.len() == 1 { @@ -457,9 +534,28 @@ fn calculate(tokens: Vec) -> Result { if let Token::Function(f) = token { if let Token::Number(n) = a { + let n = if config.angle_unit == AngleUnit::Degree { + if let FunctionType::Trigonometric(_) = f { + std::f64::consts::PI / 180. * n + } else { + n + } + } else { + n + }; let n = match f { - FunctionType::Sine => Some(n.sin()), - FunctionType::Cosine => Some(n.cos()), + FunctionType::Trigonometric(TrigonometricFunction::Sine) => Some(n.sin()), + FunctionType::Trigonometric(TrigonometricFunction::InverseSine) => { + Some(n.asin()) + } + FunctionType::Trigonometric(TrigonometricFunction::Cosine) => Some(n.cos()), + FunctionType::Trigonometric(TrigonometricFunction::InverseCosine) => { + Some(n.acos()) + } + FunctionType::Trigonometric(TrigonometricFunction::Tangent) => Some(n.tan()), + FunctionType::Trigonometric(TrigonometricFunction::InverseTangent) => { + Some(n.atan()) + } FunctionType::NaturalLog => Some(n.ln()), FunctionType::Log10 => Some(n.log10()), FunctionType::SquareRoot => Some(n.sqrt()), @@ -534,11 +630,27 @@ mod tests { ); assert_eq!( tokenize("sin"), - Ok(vec![Token::Function(FunctionType::Sine)]) + Ok(vec![Token::Function(FunctionType::Trigonometric(TrigonometricFunction::Sine))]) + ); + assert_eq!( + tokenize("asin"), + Ok(vec![Token::Function(FunctionType::Trigonometric(TrigonometricFunction::InverseSine))]) ); assert_eq!( tokenize("cos"), - Ok(vec![Token::Function(FunctionType::Cosine)]) + Ok(vec![Token::Function(FunctionType::Trigonometric(TrigonometricFunction::Cosine))]) + ); + assert_eq!( + tokenize("acos"), + Ok(vec![Token::Function(FunctionType::Trigonometric(TrigonometricFunction::InverseCosine))]) + ); + assert_eq!( + tokenize("tan"), + Ok(vec![Token::Function(FunctionType::Trigonometric(TrigonometricFunction::Tangent))]) + ); + assert_eq!( + tokenize("atan"), + Ok(vec![Token::Function(FunctionType::Trigonometric(TrigonometricFunction::InverseTangent))]) ); assert_eq!( tokenize("sqrt"), @@ -777,39 +889,42 @@ mod tests { #[test] fn test_calculate() { + let config = Config::new(); assert_eq!( - calculate(vec![Token::Number(3.), Token::Number(2.), Token::Multiply]), + calculate(vec![Token::Number(3.), Token::Number(2.), Token::Multiply], &config), Ok(6.) ); } #[test] fn test_compute() { - assert_eq!(compute("(6 - 2)"), Ok(4.)); - assert_eq!(compute("3E2"), Ok(300.)); - assert_eq!(compute("4(3 - 1)^2"), Ok(16.)); - assert_eq!(compute("(6 - -2)"), Ok(8.)); - assert_eq!(compute("sin(3)"), Ok(3_f64.sin())); - assert_eq!(compute("sin(3 - 1)"), Ok(2_f64.sin())); - assert_eq!(compute("cos(3 - 1)"), Ok(2_f64.cos())); + let config = Config::new(); + assert_eq!(compute("(6 - 2)", &config), Ok(4.)); + assert_eq!(compute("3E2", &config), Ok(300.)); + assert_eq!(compute("4(3 - 1)^2", &config), Ok(16.)); + assert_eq!(compute("(6 - -2)", &config), Ok(8.)); + assert_eq!(compute("sin(3)", &config), Ok(3_f64.sin())); + assert_eq!(compute("sin(3 - 1)", &config), Ok(2_f64.sin())); + assert_eq!(compute("cos(3 - 1)", &config), Ok(2_f64.cos())); assert_eq!( - compute("3 + 4 × 2 ÷ ( 1 − 5 ) ^ 2 ^ 3"), + compute("3 + 4 × 2 ÷ ( 1 − 5 ) ^ 2 ^ 3", &config), Ok(3. + (4. * 2.) / (1_f64 - 5.).powf((2_f64).powf(3.))) ); assert_eq!( - compute("sin ( max ( 2, 3 ) ÷ 3 × π )"), + compute("sin ( max ( 2, 3 ) ÷ 3 × π )", &config), Ok(consts::PI.sin()) ); - assert_eq!(compute("min(2, 3)"), Ok(2.)); - assert_eq!(compute("max(2, 3)"), Ok(3.)); - assert_eq!(compute("3 +- 2"), Ok(1.)); - assert_eq!(compute("3 -+ 2"), Ok(1.)); - assert_eq!(compute("1E-3"), Ok(0.001)); - assert_eq!(compute("3sin(3)"), Ok(3. * 3_f64.sin())); - assert_eq!(compute("(1 + 2) - (1)"), Ok(2.)); - assert_eq!(compute("(1 + 2) - 1"), Ok(2.)); - assert_eq!(compute("sqrt(4)"), Ok(2.)); - assert_eq!(compute("sin(-1)"), Ok((-1f64).sin())); - assert_eq!(compute("sin(1) sin(1)"), Ok((1f64).sin().powf(2.))); + assert_eq!(compute("min(2, 3)", &config), Ok(2.)); + assert_eq!(compute("max(2, 3)", &config), Ok(3.)); + assert_eq!(compute("3 +- 2", &config), Ok(1.)); + assert_eq!(compute("3 -+ 2", &config), Ok(1.)); + assert_eq!(compute("1E-3", &config), Ok(0.001)); + assert_eq!(compute("3sin(3)", &config), Ok(3. * 3_f64.sin())); + assert_eq!(compute("(1 + 2) - (1)", &config), Ok(2.)); + assert_eq!(compute("(1 + 2) - 1", &config), Ok(2.)); + assert_eq!(compute("sqrt(4)", &config), Ok(2.)); + assert_eq!(compute("sin(-1)", &config), Ok((-1f64).sin())); + assert_eq!(compute("sin(1) sin(1)", &config), Ok((1f64).sin().powf(2.))); + assert_eq!(compute("sin(1) sin(1) + cos(1) cos(1)", &config), Ok(1.)); } }