fix tests and result printing
This commit is contained in:
+174
-59
@@ -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.));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user