fix tests and result printing

This commit is contained in:
2024-11-13 11:48:55 +01:00
parent 35aedf4f6a
commit da3e179ed5
+174 -59
View File
@@ -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::<Vec<String>>();
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, Error>) -> 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<f64, Error> {
fn compute(input: &str, config: &Config) -> Result<f64, Error> {
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<Token>, buf: &mut String) -> Result<(), TokenizeErr
fn parse_buffer(buf: &str) -> Result<Token, TokenizeError> {
// 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<u8, InfixToPostfixError> {
}
}
fn calculate(tokens: Vec<Token>) -> Result<f64, CalculateError> {
fn calculate(tokens: Vec<Token>, config: &Config) -> Result<f64, CalculateError> {
let mut stack = Vec::new();
let mut tokens = tokens.iter();
if tokens.len() == 1 {
@@ -457,9 +534,28 @@ fn calculate(tokens: Vec<Token>) -> Result<f64, CalculateError> {
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.));
}
}