diff --git a/Cargo.toml b/Cargo.toml index 9308d33..fa65733 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,9 +11,25 @@ license = "CC0-1.0" repository = "https://github.com/terror/val" resolver = "2" +[profile.release] +lto = true +codegen-units = 1 + +[lints] +workspace = true + [workspace] members = [".", "crates/*"] +[workspace.lints.clippy] +all = { level = "deny", priority = -1 } +arbitrary-source-item-ordering = "deny" +pedantic = { level = "deny", priority = -1 } +wildcard_imports = "allow" + +[workspace.lints.rust] +unreachable_pub = "deny" + [[bench]] name = "main" harness = false diff --git a/benches/main.rs b/benches/main.rs index d1860b5..aeb54a7 100644 --- a/benches/main.rs +++ b/benches/main.rs @@ -15,7 +15,7 @@ fn bench_increment_value(criterion: &mut Criterion) { group.bench_function(format!("n = {number}"), |bencher| { bencher.iter(|| { black_box(Evaluator::from(Environment::default()).eval(&ast)).unwrap(); - }) + }); }); } @@ -27,7 +27,7 @@ fn bench_prime_count(criterion: &mut Criterion) { for &number in &[5_000_u32, 10_000_u32] { let program = format!( - r#" + r" fn prime(n) {{ if (n < 2) {{ return false @@ -63,7 +63,7 @@ fn bench_prime_count(criterion: &mut Criterion) { }} count({number}) - "# + " ); let ast = val::parse(&program).unwrap(); @@ -91,7 +91,7 @@ fn bench_recursive_factorial(criterion: &mut Criterion) { group.bench_function(format!("n = {number}"), |bencher| { bencher.iter(|| { black_box(Evaluator::from(Environment::default()).eval(&ast)).unwrap(); - }) + }); }); } diff --git a/clippy.toml b/clippy.toml new file mode 100644 index 0000000..bfc2898 --- /dev/null +++ b/clippy.toml @@ -0,0 +1,3 @@ +cognitive-complexity-threshold = 1337 +too-many-lines-threshold = 250 +source-item-ordering = ['enum', 'struct', 'trait'] diff --git a/src/arguments.rs b/src/arguments.rs index 22ee9cc..e29c48d 100644 --- a/src/arguments.rs +++ b/src/arguments.rs @@ -61,25 +61,8 @@ pub struct Arguments { } impl Arguments { - pub fn run(self) -> Result { - match (&self.filename, &self.expression) { - (Some(filename), _) => self.eval(filename.clone()), - (_, Some(expression)) => self.eval_expression(expression.clone()), - _ => { - #[cfg(not(target_family = "wasm"))] - { - self.read() - } - #[cfg(target_family = "wasm")] - { - Err(anyhow::anyhow!("Interactive mode not supported in WASM")) - } - } - } - } - - fn eval(&self, filename: PathBuf) -> Result { - let content = fs::read_to_string(&filename)?; + fn eval(&self, filename: &PathBuf) -> Result { + let content = fs::read_to_string(filename)?; let filename = filename.to_string_lossy().to_string(); @@ -124,7 +107,7 @@ impl Arguments { return Ok(()); } - println!("{}", value); + println!("{value}"); Ok(()) } @@ -228,6 +211,23 @@ impl Arguments { } } } + + pub fn run(self) -> Result { + match (&self.filename, &self.expression) { + (Some(filename), _) => self.eval(filename), + (_, Some(expression)) => self.eval_expression(expression.clone()), + _ => { + #[cfg(not(target_family = "wasm"))] + { + self.read() + } + #[cfg(target_family = "wasm")] + { + Err(anyhow::anyhow!("Interactive mode not supported in WASM")) + } + } + } + } } #[cfg(test)] @@ -301,8 +301,7 @@ mod tests { assert!( error.contains("cannot be used with"), - "Error should mention conflicts: {}", - error + "Error should mention conflicts: {error}" ); } @@ -321,8 +320,7 @@ mod tests { assert!( error.contains("cannot be used with"), - "Error should mention conflicts: {}", - error + "Error should mention conflicts: {error}" ); } } diff --git a/src/ast.rs b/src/ast.rs index d6a258e..cc37f6a 100644 --- a/src/ast.rs +++ b/src/ast.rs @@ -24,6 +24,7 @@ impl Display for Program<'_> { } impl Program<'_> { + #[must_use] pub fn kind(&self) -> String { String::from(match self { Program::Statements(_) => "statements", @@ -160,6 +161,7 @@ impl Display for Statement<'_> { } impl Statement<'_> { + #[must_use] pub fn kind(&self) -> String { String::from(match self { Statement::Assignment(_, _) => "assignment", @@ -251,7 +253,7 @@ impl Display for Expression<'_> { Expression::BinaryOp(op, lhs, rhs) => { write!(f, "binary_op({}, {}, {})", op, lhs.0, rhs.0) } - Expression::Boolean(boolean) => write!(f, "boolean({})", boolean), + Expression::Boolean(boolean) => write!(f, "boolean({boolean})"), Expression::FunctionCall(name, arguments) => { write!( f, @@ -265,7 +267,7 @@ impl Display for Expression<'_> { ) } Expression::Identifier(identifier) => { - write!(f, "identifier({})", identifier) + write!(f, "identifier({identifier})") } Expression::List(list) => { write!( @@ -283,7 +285,7 @@ impl Display for Expression<'_> { } Expression::Null => write!(f, "null"), Expression::Number(number) => write!(f, "number({})", number.display()), - Expression::String(string) => write!(f, "string(\"{}\")", string), + Expression::String(string) => write!(f, "string(\"{string}\")"), Expression::UnaryOp(op, expr) => { write!(f, "unary_op({}, {})", op, expr.0) } @@ -292,6 +294,7 @@ impl Display for Expression<'_> { } impl Expression<'_> { + #[must_use] pub fn kind(&self) -> String { String::from(match self { Expression::BinaryOp(_, _, _) => "binary_op", diff --git a/src/builtin.rs b/src/builtin.rs index 305a310..57a6f25 100644 --- a/src/builtin.rs +++ b/src/builtin.rs @@ -1,7 +1,7 @@ use super::*; pub type BuiltinFunction = - for<'src> fn(BuiltinFunctionPayload<'src>) -> Result, Error>; + for<'src> fn(&BuiltinFunctionPayload<'src>) -> Result, Error>; pub struct BuiltinFunctionPayload<'src> { pub arguments: Vec>, @@ -13,7 +13,7 @@ pub struct BuiltinFunctionPayload<'src> { pub enum Builtin { Constant { name: &'static str, - value: fn(Config) -> Value<'static>, + value: fn(&Config) -> Value<'static>, }, Function { function: BuiltinFunction, @@ -22,6 +22,7 @@ pub enum Builtin { } impl Builtin { + #[must_use] pub fn kind(&self) -> &'static str { match self { Self::Constant { .. } => "constant", @@ -29,6 +30,7 @@ impl Builtin { } } + #[must_use] pub fn name(&self) -> &'static str { match self { Self::Constant { name, .. } | Self::Function { name, .. } => name, diff --git a/src/builtins.rs b/src/builtins.rs index 5d706f8..943aaa5 100644 --- a/src/builtins.rs +++ b/src/builtins.rs @@ -179,7 +179,7 @@ pub(crate) const BUILTINS: &[Builtin] = &[ }, ]; -fn abs<'a>(payload: BuiltinFunctionPayload<'a>) -> Result, Error> { +fn abs<'a>(payload: &BuiltinFunctionPayload<'a>) -> Result, Error> { if payload.arguments.len() != 1 { return Err(Error::new( payload.span, @@ -195,7 +195,7 @@ fn abs<'a>(payload: BuiltinFunctionPayload<'a>) -> Result, Error> { )) } -fn acos<'a>(payload: BuiltinFunctionPayload<'a>) -> Result, Error> { +fn acos<'a>(payload: &BuiltinFunctionPayload<'a>) -> Result, Error> { if payload.arguments.len() != 1 { return Err(Error::new( payload.span, @@ -226,7 +226,7 @@ fn acos<'a>(payload: BuiltinFunctionPayload<'a>) -> Result, Error> { Ok(Value::Number(result)) } -fn acot<'a>(payload: BuiltinFunctionPayload<'a>) -> Result, Error> { +fn acot<'a>(payload: &BuiltinFunctionPayload<'a>) -> Result, Error> { if payload.arguments.len() != 1 { return Err(Error::new( payload.span, @@ -256,7 +256,7 @@ fn acot<'a>(payload: BuiltinFunctionPayload<'a>) -> Result, Error> { ))) } -fn acsc<'a>(payload: BuiltinFunctionPayload<'a>) -> Result, Error> { +fn acsc<'a>(payload: &BuiltinFunctionPayload<'a>) -> Result, Error> { if payload.arguments.len() != 1 { return Err(Error::new( payload.span, @@ -293,7 +293,9 @@ fn acsc<'a>(payload: BuiltinFunctionPayload<'a>) -> Result, Error> { Ok(Value::Number(result)) } -fn append<'a>(payload: BuiltinFunctionPayload<'a>) -> Result, Error> { +fn append<'a>( + payload: &BuiltinFunctionPayload<'a>, +) -> Result, Error> { if payload.arguments.len() != 2 { return Err(Error::new( payload.span, @@ -313,7 +315,7 @@ fn append<'a>(payload: BuiltinFunctionPayload<'a>) -> Result, Error> { Ok(Value::List(list)) } -fn arc<'a>(payload: BuiltinFunctionPayload<'a>) -> Result, Error> { +fn arc<'a>(payload: &BuiltinFunctionPayload<'a>) -> Result, Error> { if payload.arguments.len() != 1 { return Err(Error::new( payload.span, @@ -337,7 +339,7 @@ fn arc<'a>(payload: BuiltinFunctionPayload<'a>) -> Result, Error> { Ok(Value::Number(result)) } -fn asec<'a>(payload: BuiltinFunctionPayload<'a>) -> Result, Error> { +fn asec<'a>(payload: &BuiltinFunctionPayload<'a>) -> Result, Error> { if payload.arguments.len() != 1 { return Err(Error::new( payload.span, @@ -374,7 +376,7 @@ fn asec<'a>(payload: BuiltinFunctionPayload<'a>) -> Result, Error> { Ok(Value::Number(result)) } -fn asin<'a>(payload: BuiltinFunctionPayload<'a>) -> Result, Error> { +fn asin<'a>(payload: &BuiltinFunctionPayload<'a>) -> Result, Error> { if payload.arguments.len() != 1 { return Err(Error::new( payload.span, @@ -405,7 +407,9 @@ fn asin<'a>(payload: BuiltinFunctionPayload<'a>) -> Result, Error> { Ok(Value::Number(result)) } -fn r#bool<'a>(payload: BuiltinFunctionPayload<'a>) -> Result, Error> { +fn r#bool<'a>( + payload: &BuiltinFunctionPayload<'a>, +) -> Result, Error> { if payload.arguments.len() != 1 { return Err(Error::new( payload.span, @@ -424,14 +428,14 @@ fn r#bool<'a>(payload: BuiltinFunctionPayload<'a>) -> Result, Error> { Value::String(s) => Ok(Value::Boolean(!s.is_empty())), Value::List(items) => Ok(Value::Boolean(!items.is_empty())), Value::Null => Ok(Value::Boolean(false)), - _ => Err(Error::new( + Value::Function(_) => Err(Error::new( payload.span, format!("Cannot convert {} to bool", value.type_name()), )), } } -fn ceil<'a>(payload: BuiltinFunctionPayload<'a>) -> Result, Error> { +fn ceil<'a>(payload: &BuiltinFunctionPayload<'a>) -> Result, Error> { if payload.arguments.len() != 1 { return Err(Error::new( payload.span, @@ -447,23 +451,23 @@ fn ceil<'a>(payload: BuiltinFunctionPayload<'a>) -> Result, Error> { )) } -fn constant_e(config: Config) -> Value<'static> { +fn constant_e(config: &Config) -> Value<'static> { Value::Number(with_consts(|consts| { consts.e(config.precision, config.rounding_mode) })) } -fn constant_phi(config: Config) -> Value<'static> { +fn constant_phi(config: &Config) -> Value<'static> { Value::Number(Float::from_f64(1.618_033_988_749_895_f64, config.precision)) } -fn constant_pi(config: Config) -> Value<'static> { +fn constant_pi(config: &Config) -> Value<'static> { Value::Number(with_consts(|consts| { consts.pi(config.precision, config.rounding_mode) })) } -fn constant_tau(config: Config) -> Value<'static> { +fn constant_tau(config: &Config) -> Value<'static> { let pi = with_consts(|consts| consts.pi(config.precision, config.rounding_mode)); @@ -474,7 +478,7 @@ fn constant_tau(config: Config) -> Value<'static> { )) } -fn cos<'a>(payload: BuiltinFunctionPayload<'a>) -> Result, Error> { +fn cos<'a>(payload: &BuiltinFunctionPayload<'a>) -> Result, Error> { if payload.arguments.len() != 1 { return Err(Error::new( payload.span, @@ -498,7 +502,7 @@ fn cos<'a>(payload: BuiltinFunctionPayload<'a>) -> Result, Error> { Ok(Value::Number(result)) } -fn cosh<'a>(payload: BuiltinFunctionPayload<'a>) -> Result, Error> { +fn cosh<'a>(payload: &BuiltinFunctionPayload<'a>) -> Result, Error> { if payload.arguments.len() != 1 { return Err(Error::new( payload.span, @@ -522,7 +526,7 @@ fn cosh<'a>(payload: BuiltinFunctionPayload<'a>) -> Result, Error> { Ok(Value::Number(result)) } -fn cot<'a>(payload: BuiltinFunctionPayload<'a>) -> Result, Error> { +fn cot<'a>(payload: &BuiltinFunctionPayload<'a>) -> Result, Error> { if payload.arguments.len() != 1 { return Err(Error::new( payload.span, @@ -557,7 +561,7 @@ fn cot<'a>(payload: BuiltinFunctionPayload<'a>) -> Result, Error> { ))) } -fn csc<'a>(payload: BuiltinFunctionPayload<'a>) -> Result, Error> { +fn csc<'a>(payload: &BuiltinFunctionPayload<'a>) -> Result, Error> { if payload.arguments.len() != 1 { return Err(Error::new( payload.span, @@ -592,7 +596,7 @@ fn csc<'a>(payload: BuiltinFunctionPayload<'a>) -> Result, Error> { ))) } -fn e<'a>(payload: BuiltinFunctionPayload<'a>) -> Result, Error> { +fn e<'a>(payload: &BuiltinFunctionPayload<'a>) -> Result, Error> { if payload.arguments.len() != 1 { return Err(Error::new( payload.span, @@ -616,7 +620,7 @@ fn e<'a>(payload: BuiltinFunctionPayload<'a>) -> Result, Error> { Ok(Value::Number(result)) } -fn exit<'a>(payload: BuiltinFunctionPayload<'a>) -> Result, Error> { +fn exit<'a>(payload: &BuiltinFunctionPayload<'a>) -> Result, Error> { if payload.arguments.is_empty() { process::exit(0); } @@ -635,19 +639,45 @@ fn exit<'a>(payload: BuiltinFunctionPayload<'a>) -> Result, Error> { .number(payload.span)? .to_f64(payload.config.rounding_mode) { - Some(n) if n.is_finite() && n >= 0.0 => n as usize, - _ => { - return Err(Error::new( - payload.span, - "Argument to `exit` must be a non-negative finite number", - )); - } + Some(n) => finite_non_negative_usize(n), + None => None, + }; + + let Some(code) = code else { + return Err(Error::new( + payload.span, + "Argument to `exit` must be a non-negative finite number", + )); }; - process::exit(code as i32); + let Ok(code) = i32::try_from(code) else { + return Err(Error::new( + payload.span, + "Argument to `exit` must fit in a 32-bit signed integer", + )); + }; + + process::exit(code); } -fn float<'a>(payload: BuiltinFunctionPayload<'a>) -> Result, Error> { +fn finite_i64(number: f64) -> Option { + if number.is_finite() && number.fract() == 0.0 { + format!("{number:.0}").parse().ok() + } else { + None + } +} + +fn finite_non_negative_usize(number: f64) -> Option { + if number.is_finite() && number >= 0.0 { + let number = number.trunc(); + format!("{number:.0}").parse().ok() + } else { + None + } +} + +fn float<'a>(payload: &BuiltinFunctionPayload<'a>) -> Result, Error> { if payload.arguments.len() != 1 { return Err(Error::new( payload.span, @@ -666,7 +696,7 @@ fn float<'a>(payload: BuiltinFunctionPayload<'a>) -> Result, Error> { Ok(n) => Ok(Value::Number(Float::from(n))), Err(_) => Err(Error::new( payload.span, - format!("Cannot convert '{}' to float", s), + format!("Cannot convert '{s}' to float"), )), }, Value::Boolean(b) => { @@ -679,7 +709,7 @@ fn float<'a>(payload: BuiltinFunctionPayload<'a>) -> Result, Error> { } } -fn floor<'a>(payload: BuiltinFunctionPayload<'a>) -> Result, Error> { +fn floor<'a>(payload: &BuiltinFunctionPayload<'a>) -> Result, Error> { if payload.arguments.len() != 1 { return Err(Error::new( payload.span, @@ -695,7 +725,7 @@ fn floor<'a>(payload: BuiltinFunctionPayload<'a>) -> Result, Error> { )) } -fn gcd<'a>(payload: BuiltinFunctionPayload<'a>) -> Result, Error> { +fn gcd<'a>(payload: &BuiltinFunctionPayload<'a>) -> Result, Error> { if payload.arguments.len() != 2 { return Err(Error::new( payload.span, @@ -721,7 +751,7 @@ fn gcd<'a>(payload: BuiltinFunctionPayload<'a>) -> Result, Error> { Ok(Value::Number(x)) } -fn input<'a>(payload: BuiltinFunctionPayload<'a>) -> Result, Error> { +fn input<'a>(payload: &BuiltinFunctionPayload<'a>) -> Result, Error> { use std::io::{self, BufRead, Write}; if payload.arguments.len() > 1 { @@ -757,12 +787,12 @@ fn input<'a>(payload: BuiltinFunctionPayload<'a>) -> Result, Error> { } Err(e) => Err(Error::new( payload.span, - format!("Failed to read input: {}", e), + format!("Failed to read input: {e}"), )), } } -fn int<'a>(payload: BuiltinFunctionPayload<'a>) -> Result, Error> { +fn int<'a>(payload: &BuiltinFunctionPayload<'a>) -> Result, Error> { if payload.arguments.len() != 1 { return Err(Error::new( payload.span, @@ -781,7 +811,7 @@ fn int<'a>(payload: BuiltinFunctionPayload<'a>) -> Result, Error> { Ok(n) => Ok(Value::Number(Float::from(n).floor())), Err(_) => Err(Error::new( payload.span, - format!("Cannot convert '{}' to int", s), + format!("Cannot convert '{s}' to int"), )), }, Value::Boolean(b) => { @@ -794,7 +824,7 @@ fn int<'a>(payload: BuiltinFunctionPayload<'a>) -> Result, Error> { } } -fn join<'a>(payload: BuiltinFunctionPayload<'a>) -> Result, Error> { +fn join<'a>(payload: &BuiltinFunctionPayload<'a>) -> Result, Error> { if payload.arguments.len() != 2 { return Err(Error::new( payload.span, @@ -821,7 +851,7 @@ fn join<'a>(payload: BuiltinFunctionPayload<'a>) -> Result, Error> { Ok(Value::String(Box::leak(joined_string.into_boxed_str()))) } -fn lcm<'a>(payload: BuiltinFunctionPayload<'a>) -> Result, Error> { +fn lcm<'a>(payload: &BuiltinFunctionPayload<'a>) -> Result, Error> { if payload.arguments.len() != 2 { return Err(Error::new( payload.span, @@ -857,7 +887,7 @@ fn lcm<'a>(payload: BuiltinFunctionPayload<'a>) -> Result, Error> { Ok(Value::Number(lcm)) } -fn len<'a>(payload: BuiltinFunctionPayload<'a>) -> Result, Error> { +fn len<'a>(payload: &BuiltinFunctionPayload<'a>) -> Result, Error> { if payload.arguments.len() != 1 { return Err(Error::new( payload.span, @@ -871,8 +901,12 @@ fn len<'a>(payload: BuiltinFunctionPayload<'a>) -> Result, Error> { let value = &payload.arguments[0]; match value { - Value::String(s) => Ok(Value::Number(Float::from(s.len() as f64))), - Value::List(items) => Ok(Value::Number(Float::from(items.len() as f64))), + Value::String(s) => len_to_float(s.len()) + .map(Value::Number) + .ok_or_else(|| Error::new(payload.span, "String length is too large")), + Value::List(items) => len_to_float(items.len()) + .map(Value::Number) + .ok_or_else(|| Error::new(payload.span, "List length is too large")), _ => Err(Error::new( payload.span, format!("Cannot get length of {}", value.type_name()), @@ -880,7 +914,11 @@ fn len<'a>(payload: BuiltinFunctionPayload<'a>) -> Result, Error> { } } -fn list<'a>(payload: BuiltinFunctionPayload<'a>) -> Result, Error> { +fn len_to_float(len: usize) -> Option { + i64::try_from(len).ok().map(Float::from) +} + +fn list<'a>(payload: &BuiltinFunctionPayload<'a>) -> Result, Error> { if payload.arguments.len() != 1 { return Err(Error::new( payload.span, @@ -904,7 +942,7 @@ fn list<'a>(payload: BuiltinFunctionPayload<'a>) -> Result, Error> { } } -fn ln<'a>(payload: BuiltinFunctionPayload<'a>) -> Result, Error> { +fn ln<'a>(payload: &BuiltinFunctionPayload<'a>) -> Result, Error> { if payload.arguments.len() != 1 { return Err(Error::new( payload.span, @@ -935,7 +973,7 @@ fn ln<'a>(payload: BuiltinFunctionPayload<'a>) -> Result, Error> { Ok(Value::Number(result)) } -fn log10<'a>(payload: BuiltinFunctionPayload<'a>) -> Result, Error> { +fn log10<'a>(payload: &BuiltinFunctionPayload<'a>) -> Result, Error> { if payload.arguments.len() != 1 { return Err(Error::new( payload.span, @@ -966,7 +1004,7 @@ fn log10<'a>(payload: BuiltinFunctionPayload<'a>) -> Result, Error> { Ok(Value::Number(result)) } -fn log2<'a>(payload: BuiltinFunctionPayload<'a>) -> Result, Error> { +fn log2<'a>(payload: &BuiltinFunctionPayload<'a>) -> Result, Error> { if payload.arguments.len() != 1 { return Err(Error::new( payload.span, @@ -997,33 +1035,39 @@ fn log2<'a>(payload: BuiltinFunctionPayload<'a>) -> Result, Error> { Ok(Value::Number(result)) } -fn print<'a>(payload: BuiltinFunctionPayload<'a>) -> Result, Error> { +fn print<'a>(payload: &BuiltinFunctionPayload<'a>) -> Result, Error> { + use std::io::Write; + let mut output_strings = Vec::with_capacity(payload.arguments.len()); for argument in &payload.arguments { - output_strings.push(format!("{}", argument)); + output_strings.push(format!("{argument}")); } - print!("{}", output_strings.join(" ")); + write!(std::io::stdout(), "{}", output_strings.join(" ")) + .map_err(|error| Error::new(payload.span, error.to_string()))?; Ok(Value::Null) } fn println<'a>( - payload: BuiltinFunctionPayload<'a>, + payload: &BuiltinFunctionPayload<'a>, ) -> Result, Error> { + use std::io::Write; + let mut output_strings = Vec::with_capacity(payload.arguments.len()); for argument in &payload.arguments { - output_strings.push(format!("{}", argument)); + output_strings.push(format!("{argument}")); } - println!("{}", output_strings.join(" ")); + writeln!(std::io::stdout(), "{}", output_strings.join(" ")) + .map_err(|error| Error::new(payload.span, error.to_string()))?; Ok(Value::Null) } -fn quit<'a>(payload: BuiltinFunctionPayload<'a>) -> Result, Error> { +fn quit<'a>(payload: &BuiltinFunctionPayload<'a>) -> Result, Error> { if payload.arguments.is_empty() { process::exit(0); } @@ -1042,19 +1086,28 @@ fn quit<'a>(payload: BuiltinFunctionPayload<'a>) -> Result, Error> { .number(payload.span)? .to_f64(payload.config.rounding_mode) { - Some(n) if n.is_finite() && n >= 0.0 => n as usize, - _ => { - return Err(Error::new( - payload.span, - "Argument to `quit` must be a non-negative finite number", - )); - } + Some(n) => finite_non_negative_usize(n), + None => None, + }; + + let Some(code) = code else { + return Err(Error::new( + payload.span, + "Argument to `quit` must be a non-negative finite number", + )); + }; + + let Ok(code) = i32::try_from(code) else { + return Err(Error::new( + payload.span, + "Argument to `quit` must fit in a 32-bit signed integer", + )); }; - process::exit(code as i32); + process::exit(code); } -fn range<'a>(payload: BuiltinFunctionPayload<'a>) -> Result, Error> { +fn range<'a>(payload: &BuiltinFunctionPayload<'a>) -> Result, Error> { if payload.arguments.len() != 2 && payload.arguments.len() != 3 { return Err(Error::new( payload.span, @@ -1072,14 +1125,9 @@ fn range<'a>(payload: BuiltinFunctionPayload<'a>) -> Result, Error> { .number(payload.span)? .to_f64(payload.config.rounding_mode); - match number { - Some(number) - if number.is_finite() - && number.fract() == 0.0 - && number >= i64::MIN as f64 - && number <= i64::MAX as f64 => - { - numbers.push(number as i64); + match number.and_then(finite_i64) { + Some(number) => { + numbers.push(number); } _ => { return Err(Error::new( @@ -1124,7 +1172,7 @@ fn range<'a>(payload: BuiltinFunctionPayload<'a>) -> Result, Error> { Ok(Value::List(result)) } -fn sec<'a>(payload: BuiltinFunctionPayload<'a>) -> Result, Error> { +fn sec<'a>(payload: &BuiltinFunctionPayload<'a>) -> Result, Error> { if payload.arguments.len() != 1 { return Err(Error::new( payload.span, @@ -1156,7 +1204,7 @@ fn sec<'a>(payload: BuiltinFunctionPayload<'a>) -> Result, Error> { ))) } -fn sin<'a>(payload: BuiltinFunctionPayload<'a>) -> Result, Error> { +fn sin<'a>(payload: &BuiltinFunctionPayload<'a>) -> Result, Error> { if payload.arguments.len() != 1 { return Err(Error::new( payload.span, @@ -1180,7 +1228,7 @@ fn sin<'a>(payload: BuiltinFunctionPayload<'a>) -> Result, Error> { Ok(Value::Number(result)) } -fn sinh<'a>(payload: BuiltinFunctionPayload<'a>) -> Result, Error> { +fn sinh<'a>(payload: &BuiltinFunctionPayload<'a>) -> Result, Error> { if payload.arguments.len() != 1 { return Err(Error::new( payload.span, @@ -1204,7 +1252,7 @@ fn sinh<'a>(payload: BuiltinFunctionPayload<'a>) -> Result, Error> { Ok(Value::Number(result)) } -fn split<'a>(payload: BuiltinFunctionPayload<'a>) -> Result, Error> { +fn split<'a>(payload: &BuiltinFunctionPayload<'a>) -> Result, Error> { if payload.arguments.len() != 2 { return Err(Error::new( payload.span, @@ -1228,7 +1276,7 @@ fn split<'a>(payload: BuiltinFunctionPayload<'a>) -> Result, Error> { )) } -fn sqrt<'a>(payload: BuiltinFunctionPayload<'a>) -> Result, Error> { +fn sqrt<'a>(payload: &BuiltinFunctionPayload<'a>) -> Result, Error> { if payload.arguments.len() != 1 { return Err(Error::new( payload.span, @@ -1254,7 +1302,7 @@ fn sqrt<'a>(payload: BuiltinFunctionPayload<'a>) -> Result, Error> { ))) } -fn sum<'a>(payload: BuiltinFunctionPayload<'a>) -> Result, Error> { +fn sum<'a>(payload: &BuiltinFunctionPayload<'a>) -> Result, Error> { if payload.arguments.is_empty() { return Err(Error::new( payload.span, @@ -1291,7 +1339,7 @@ fn sum<'a>(payload: BuiltinFunctionPayload<'a>) -> Result, Error> { Ok(Value::Number(sum)) } -fn tan<'a>(payload: BuiltinFunctionPayload<'a>) -> Result, Error> { +fn tan<'a>(payload: &BuiltinFunctionPayload<'a>) -> Result, Error> { if payload.arguments.len() != 1 { return Err(Error::new( payload.span, @@ -1315,7 +1363,7 @@ fn tan<'a>(payload: BuiltinFunctionPayload<'a>) -> Result, Error> { Ok(Value::Number(result)) } -fn tanh<'a>(payload: BuiltinFunctionPayload<'a>) -> Result, Error> { +fn tanh<'a>(payload: &BuiltinFunctionPayload<'a>) -> Result, Error> { if payload.arguments.len() != 1 { return Err(Error::new( payload.span, @@ -1376,7 +1424,7 @@ mod tests { "constant", BUILTINS.iter().filter_map(|builtin| match builtin { Builtin::Constant { name, .. } => Some(*name), - _ => None, + Builtin::Function { .. } => None, }), ); @@ -1384,7 +1432,7 @@ mod tests { "function", BUILTINS.iter().filter_map(|builtin| match builtin { Builtin::Function { name, .. } => Some(*name), - _ => None, + Builtin::Constant { .. } => None, }), ); } diff --git a/src/config.rs b/src/config.rs index e573072..35e5c2a 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,4 +1,4 @@ -#[derive(Clone, Debug)] +#[derive(Clone, Copy, Debug)] pub struct Config { pub precision: usize, pub rounding_mode: astro_float::RoundingMode, diff --git a/src/environment.rs b/src/environment.rs index a6efbf2..d75dc09 100644 --- a/src/environment.rs +++ b/src/environment.rs @@ -14,9 +14,10 @@ struct Symbol<'src> { } impl<'src> Environment<'src> { + #[must_use] pub fn new(config: Config) -> Self { let mut environment = Self { - config: config.clone(), + config, parent: None, symbols: HashMap::new(), }; @@ -24,7 +25,7 @@ impl<'src> Environment<'src> { for builtin in BUILTINS { match builtin { Builtin::Constant { value, .. } => { - environment.add_symbol(builtin.name(), value(config.clone())); + environment.add_symbol(builtin.name(), value(&config)); } Builtin::Function { function, .. } => { environment.add_function( @@ -49,20 +50,20 @@ impl<'src> Environment<'src> { self.symbols.entry(name).or_default().function = Some(function); } - pub fn call_function( + pub(crate) fn call_function( &self, name: &str, arguments: Vec>, span: Span, ) -> Result, Error> { if let Some(function) = self.resolve_function(name) { - function.call(arguments, self.config.clone(), span) + function.call(arguments, self.config, span) } else if self.resolve_symbol(name).is_some() { - Err(Error::new(span, format!("`{}` is not a function", name))) + Err(Error::new(span, format!("`{name}` is not a function"))) } else { Err(Error::new( span, - format!("Function `{}` is not defined", name), + format!("Function `{name}` is not defined"), )) } } @@ -85,7 +86,7 @@ impl<'src> Environment<'src> { } } - pub fn resolve_symbol(&self, symbol: &str) -> Option> { + pub(crate) fn resolve_symbol(&self, symbol: &str) -> Option> { if let Some(symbol) = self.symbols.get(symbol) { if let Some(value) = &symbol.value { Some(value.clone()) @@ -99,9 +100,9 @@ impl<'src> Environment<'src> { } } - pub fn with_parent(parent: Environment<'src>) -> Self { + pub(crate) fn with_parent(parent: Environment<'src>) -> Self { Self { - config: parent.config.clone(), + config: parent.config, parent: Some(Box::new(parent)), symbols: HashMap::new(), } diff --git a/src/error.rs b/src/error.rs index b598c40..203c8e0 100644 --- a/src/error.rs +++ b/src/error.rs @@ -2,19 +2,22 @@ use super::*; #[derive(Debug, PartialEq)] pub struct Error { - pub span: Span, pub message: String, + pub span: Span, } impl Error { pub fn new(span: Span, message: impl Into) -> Self { Self { - span, message: message.into(), + span, } } - pub fn report<'a>(&self, id: &'a str) -> Report<'a, (&'a str, Range)> { + pub(crate) fn report<'a>( + &self, + id: &'a str, + ) -> Report<'a, (&'a str, Range)> { let span_range = self.span.into_range(); let mut report = Report::build( diff --git a/src/eval_result.rs b/src/eval_result.rs index feda174..eabf7df 100644 --- a/src/eval_result.rs +++ b/src/eval_result.rs @@ -8,22 +8,22 @@ pub enum EvalResult<'a> { } impl<'a> EvalResult<'a> { - pub fn unwrap(&self) -> Value<'a> { + pub(crate) fn unwrap(&self) -> Value<'a> { match self { EvalResult::Value(v) | EvalResult::Return(v) => v.clone(), EvalResult::Break | EvalResult::Continue => Value::Null, } } - pub fn is_return(&self) -> bool { + pub(crate) fn is_return(&self) -> bool { matches!(self, EvalResult::Return(_)) } - pub fn is_break(&self) -> bool { + pub(crate) fn is_break(&self) -> bool { matches!(self, EvalResult::Break) } - pub fn is_continue(&self) -> bool { + pub(crate) fn is_continue(&self) -> bool { matches!(self, EvalResult::Continue) } } diff --git a/src/evaluator.rs b/src/evaluator.rs index 5b8a68d..63bd23b 100644 --- a/src/evaluator.rs +++ b/src/evaluator.rs @@ -16,7 +16,19 @@ impl<'a> From> for Evaluator<'a> { } } +fn finite_non_negative_usize(number: f64) -> Option { + if number.is_finite() && number >= 0.0 { + let number = number.trunc(); + format!("{number:.0}").parse().ok() + } else { + None + } +} + impl<'a> Evaluator<'a> { + /// # Errors + /// + /// Returns an evaluation error when a statement or expression is invalid. pub fn eval( &mut self, ast: &Spanned>, @@ -45,7 +57,7 @@ impl<'a> Evaluator<'a> { } } - pub fn eval_statement( + pub(crate) fn eval_statement( &mut self, statement: &Spanned>, ) -> Result, Error> { @@ -85,23 +97,21 @@ impl<'a> Evaluator<'a> { None => { return Err(Error::new( list_span, - format!("Undefined variable `{}`", list_name), + format!("Undefined variable `{list_name}`"), )); } }; - let index = match self + let Some(index) = self .eval_expression(index_box)? .number(index_box.1)? .to_f64(self.environment.config.rounding_mode) - { - Some(n) if n.is_finite() && n >= 0.0 => n as usize, - _ => { - return Err(Error::new( - index_box.1, - "List index must be a non-negative finite number", - )); - } + .and_then(finite_non_negative_usize) + else { + return Err(Error::new( + index_box.1, + "List index must be a non-negative finite number", + )); }; if index >= list.len() { @@ -335,14 +345,14 @@ impl<'a> Evaluator<'a> { self.environment.config.precision, self.environment.config.rounding_mode, ))), - (Value::String(a), Value::String(b)) => Ok(Value::String(Box::leak( - format!("{}{}", a, b).into_boxed_str(), - ))), + (Value::String(a), Value::String(b)) => { + Ok(Value::String(Box::leak(format!("{a}{b}").into_boxed_str()))) + } (Value::String(a), _) => Ok(Value::String(Box::leak( - format!("{}{}", a, rhs_val).into_boxed_str(), + format!("{a}{rhs_val}").into_boxed_str(), ))), (_, Value::String(b)) => Ok(Value::String(Box::leak( - format!("{}{}", lhs_val, b).into_boxed_str(), + format!("{lhs_val}{b}").into_boxed_str(), ))), (Value::List(a), Value::List(b)) => { let mut result = a.clone(); @@ -513,7 +523,7 @@ impl<'a> Evaluator<'a> { match self.environment.resolve_symbol(name) { Some(value) => Ok(value), None => { - Err(Error::new(*span, format!("Undefined variable `{}`", name))) + Err(Error::new(*span, format!("Undefined variable `{name}`"))) } } } @@ -534,23 +544,21 @@ impl<'a> Evaluator<'a> { _ => { return Err(Error::new( list.1, - format!("'{}' is not a list", list_value), + format!("'{list_value}' is not a list"), )); } }; - let index = match self + let Some(index) = self .eval_expression(index)? .number(index.1)? .to_f64(self.environment.config.rounding_mode) - { - Some(n) if n.is_finite() && n >= 0.0 => n as usize, - _ => { - return Err(Error::new( - index.1, - "List index must be a non-negative finite number", - )); - } + .and_then(finite_non_negative_usize) + else { + return Err(Error::new( + index.1, + "List index must be a non-negative finite number", + )); }; if index >= list.len() { diff --git a/src/float_ext.rs b/src/float_ext.rs index 8038361..166601e 100644 --- a/src/float_ext.rs +++ b/src/float_ext.rs @@ -54,11 +54,21 @@ impl FloatExt for Float { digits.push_str(int_part); digits.push_str(frac_part); - let length = int_part.len() as i32 + exponent; - let digits_len = digits.len() as i32; + let Ok(length) = i32::try_from(int_part.len()) else { + return formatted; + }; + + let length = length + exponent; + + let Ok(digits_len) = i32::try_from(digits.len()) else { + return formatted; + }; let mut result = if length <= 0 { - let zeros = (-length) as usize; + let Ok(zeros) = usize::try_from(-length) else { + return formatted; + }; + let mut out = String::with_capacity(sign.len() + 2 + zeros + digits.len()); out.push_str(sign); @@ -68,14 +78,20 @@ impl FloatExt for Float { out.push_str(&digits); out } else if length >= digits_len { - let zeros = (length - digits_len) as usize; + let Ok(zeros) = usize::try_from(length - digits_len) else { + return formatted; + }; + let mut out = String::with_capacity(sign.len() + digits.len() + zeros); out.push_str(sign); out.push_str(&digits); out.extend(std::iter::repeat_n('0', zeros)); out } else { - let split_at = length as usize; + let Ok(split_at) = usize::try_from(length) else { + return formatted; + }; + let (left, right) = digits.split_at(split_at); let mut out = String::with_capacity(sign.len() + left.len() + 1 + right.len()); @@ -100,6 +116,12 @@ impl FloatExt for Float { } fn to_f64(&self, rounding_mode: astro_float::RoundingMode) -> Option { + const F64_EXPONENT_BIAS: isize = 0x3ff; + const F64_EXPONENT_MAX: isize = 0x7ff; + const F64_SIGNIFICAND_BITS: usize = 52; + const INTERNAL_SHIFT: usize = 12; + const SIGN_MASK: u64 = 1u64 << 63; + if self.is_nan() { return None; } @@ -124,12 +146,6 @@ impl FloatExt for Float { let mantissa_digits = big_float.mantissa_digits()?; let mantissa = *mantissa_digits.first().unwrap_or(&0); - const F64_EXPONENT_BIAS: isize = 0x3ff; - const F64_EXPONENT_MAX: isize = 0x7ff; - const F64_SIGNIFICAND_BITS: usize = 52; - const INTERNAL_SHIFT: usize = 12; - const SIGN_MASK: u64 = 1u64 << 63; - if mantissa == 0 { return Some(if sign == Sign::Neg { f64::from_bits(SIGN_MASK) @@ -150,7 +166,7 @@ impl FloatExt for Float { let sign_bit = if sign == Sign::Neg { SIGN_MASK } else { 0 }; if exponent <= 0 { - let shift = (-exponent) as usize; + let shift = (-exponent).cast_unsigned(); if shift >= F64_SIGNIFICAND_BITS { return Some(f64::from_bits(sign_bit)); @@ -162,7 +178,7 @@ impl FloatExt for Float { } let adjusted_mantissa = mantissa << 1; - let adjusted_exponent = (exponent - 1) as u64; + let adjusted_exponent = u64::try_from(exponent - 1).ok()?; let exponent_bits = adjusted_exponent << F64_SIGNIFICAND_BITS; let fraction_bits = adjusted_mantissa >> INTERNAL_SHIFT; @@ -204,8 +220,8 @@ mod tests { fn integers() { assert_eq!(Float::from(1).display(), "1"); assert_eq!(Float::from(-1).display(), "-1"); - assert_eq!(Float::from(123456789).display(), "123456789"); - assert_eq!(Float::from(-123456789).display(), "-123456789"); + assert_eq!(Float::from(123_456_789).display(), "123456789"); + assert_eq!(Float::from(-123_456_789).display(), "-123456789"); } #[test] @@ -281,6 +297,6 @@ mod tests { assert!(value.is_sign_negative()); - assert_eq!(value, -0.0); + assert_eq!(value.to_bits(), (-0.0_f64).to_bits()); } } diff --git a/src/function.rs b/src/function.rs index 177b563..76edead 100644 --- a/src/function.rs +++ b/src/function.rs @@ -15,14 +15,14 @@ pub enum Function<'src> { } impl<'src> Function<'src> { - pub fn call( + pub(crate) fn call( &self, arguments: Vec>, config: Config, span: Span, ) -> Result, Error> { match self { - Self::Builtin { function, .. } => function(BuiltinFunctionPayload { + Self::Builtin { function, .. } => function(&BuiltinFunctionPayload { arguments, config, span, @@ -61,7 +61,7 @@ impl<'src> Function<'src> { return Ok(Value::Null); } - for statement in body.iter() { + for statement in body { let result = evaluator.eval_statement(statement)?; if result.is_return() { @@ -74,7 +74,7 @@ impl<'src> Function<'src> { } } - pub fn name(&self) -> &'src str { + pub(crate) fn name(&self) -> &'src str { match self { Self::Builtin { name, .. } | Self::UserDefined { name, .. } => name, } diff --git a/src/highlighter.rs b/src/highlighter.rs index 981508d..29ad5d1 100644 --- a/src/highlighter.rs +++ b/src/highlighter.rs @@ -10,16 +10,16 @@ const COLOR_OPERATOR: &str = "\x1b[36m"; // Cyan const COLOR_RESET: &str = "\x1b[0m"; const COLOR_STRING: &str = "\x1b[32m"; // Green -pub struct TreeHighlighter<'src> { +pub(crate) struct TreeHighlighter<'src> { content: &'src str, } impl<'src> TreeHighlighter<'src> { - pub fn new(content: &'src str) -> Self { + pub(crate) fn new(content: &'src str) -> Self { Self { content } } - pub fn highlight(&self) -> Cow<'src, str> { + pub(crate) fn highlight(&self) -> Cow<'src, str> { match parse(self.content) { Ok(ast) => self.colorize_ast(&ast), Err(_) => { @@ -429,7 +429,7 @@ impl<'src> TreeHighlighter<'src> { } } Expression::String(value) => { - let quoted_value = format!("'{}'", value); + let quoted_value = format!("'{value}'"); if let Some(str_pos) = self.content[start..end].find("ed_value) { spans.push(( @@ -438,7 +438,7 @@ impl<'src> TreeHighlighter<'src> { COLOR_STRING, )); } else { - let double_quoted = format!("\"{}\"", value); + let double_quoted = format!("\"{value}\""); if let Some(str_pos) = self.content[start..end].find(&double_quoted) { spans.push(( @@ -499,13 +499,13 @@ impl<'src> TreeHighlighter<'src> { } } -pub struct Highlighter { +pub(crate) struct Highlighter { completer: FilenameCompleter, hinter: HistoryHinter, } impl Highlighter { - pub fn new() -> Self { + pub(crate) fn new() -> Self { Self { completer: FilenameCompleter::new(), hinter: HistoryHinter::new(), @@ -541,7 +541,7 @@ impl RustylineHighlighter for Highlighter { } fn highlight_hint<'h>(&self, hint: &'h str) -> Cow<'h, str> { - Owned(format!("\x1b[90m{}\x1b[0m", hint)) + Owned(format!("\x1b[90m{hint}\x1b[0m")) } fn highlight<'l>(&self, line: &'l str, _pos: usize) -> Cow<'l, str> { diff --git a/src/parser.rs b/src/parser.rs index ad2ab88..ed492e6 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -1,5 +1,8 @@ use super::*; +/// # Errors +/// +/// Returns parser errors when input cannot be parsed into a complete program. pub fn parse(input: &str) -> Result>, Vec> { let result = program_parser().parse(input); @@ -428,7 +431,7 @@ mod tests { Test::new() .program("25") .ast("statements(expression(number(25)))") - .run() + .run(); } #[test] diff --git a/src/rounding_mode.rs b/src/rounding_mode.rs index 8d083d2..5e28bee 100644 --- a/src/rounding_mode.rs +++ b/src/rounding_mode.rs @@ -2,27 +2,27 @@ use super::*; #[derive(Eq, PartialEq, Debug, Copy, Clone)] pub enum RoundingMode { - None = 1, - Up = 2, Down = 4, - ToZero = 8, FromZero = 16, + None = 1, ToEven = 32, ToOdd = 64, + ToZero = 8, + Up = 2, } impl std::fmt::Display for RoundingMode { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { let s = match self { - RoundingMode::None => "none", - RoundingMode::Up => "up", RoundingMode::Down => "down", - RoundingMode::ToZero => "to-zero", RoundingMode::FromZero => "from-zero", + RoundingMode::None => "none", RoundingMode::ToEven => "to-even", RoundingMode::ToOdd => "to-odd", + RoundingMode::ToZero => "to-zero", + RoundingMode::Up => "up", }; - write!(f, "{}", s) + write!(f, "{s}") } } @@ -31,19 +31,19 @@ impl std::str::FromStr for RoundingMode { fn from_str(s: &str) -> Result { match s.to_lowercase().as_str() { - "none" => Ok(RoundingMode::None), - "up" => Ok(RoundingMode::Up), "down" => Ok(RoundingMode::Down), - "tozero" | "to_zero" | "to-zero" | "toward_zero" | "toward-zero" => { - Ok(RoundingMode::ToZero) - } "fromzero" | "from_zero" | "from-zero" | "away_from_zero" | "away-from-zero" => Ok(RoundingMode::FromZero), + "none" => Ok(RoundingMode::None), "toeven" | "to_even" | "to-even" | "nearest_even" | "bankers" => { Ok(RoundingMode::ToEven) } "toodd" | "to_odd" | "to-odd" | "nearest_odd" => Ok(RoundingMode::ToOdd), - _ => Err(format!("Unknown rounding mode: {}", s)), + "tozero" | "to_zero" | "to-zero" | "toward_zero" | "toward-zero" => { + Ok(RoundingMode::ToZero) + } + "up" => Ok(RoundingMode::Up), + _ => Err(format!("Unknown rounding mode: {s}")), } } } @@ -51,13 +51,13 @@ impl std::str::FromStr for RoundingMode { impl From for astro_float::RoundingMode { fn from(mode: RoundingMode) -> Self { match mode { - RoundingMode::None => astro_float::RoundingMode::None, - RoundingMode::Up => astro_float::RoundingMode::Up, RoundingMode::Down => astro_float::RoundingMode::Down, - RoundingMode::ToZero => astro_float::RoundingMode::ToZero, RoundingMode::FromZero => astro_float::RoundingMode::FromZero, + RoundingMode::None => astro_float::RoundingMode::None, RoundingMode::ToEven => astro_float::RoundingMode::ToEven, RoundingMode::ToOdd => astro_float::RoundingMode::ToOdd, + RoundingMode::ToZero => astro_float::RoundingMode::ToZero, + RoundingMode::Up => astro_float::RoundingMode::Up, } } } diff --git a/src/value.rs b/src/value.rs index edf72f3..a9a3738 100644 --- a/src/value.rs +++ b/src/value.rs @@ -51,51 +51,51 @@ impl PartialEq for Value<'_> { } impl<'a> Value<'a> { - pub fn boolean(&self, span: Span) -> Result { + pub(crate) fn boolean(&self, span: Span) -> Result { if let Value::Boolean(x) = self { Ok(*x) } else { Err(Error { span, - message: format!("'{}' is not a boolean", self), + message: format!("'{self}' is not a boolean"), }) } } - pub fn list(&self, span: Span) -> Result>, Error> { + pub(crate) fn list(&self, span: Span) -> Result>, Error> { if let Value::List(x) = self { Ok(x.clone()) } else { Err(Error { span, - message: format!("'{}' is not a list", self), + message: format!("'{self}' is not a list"), }) } } - pub fn number(&self, span: Span) -> Result { + pub(crate) fn number(&self, span: Span) -> Result { if let Value::Number(x) = self { Ok(x.clone()) } else { Err(Error { span, - message: format!("'{}' is not a number", self), + message: format!("'{self}' is not a number"), }) } } - pub fn string(&self, span: Span) -> Result<&str, Error> { + pub(crate) fn string(&self, span: Span) -> Result<&str, Error> { if let Value::String(x) = self { Ok(*x) } else { Err(Error { span, - message: format!("'{}' is not a string", self), + message: format!("'{self}' is not a string"), }) } } - pub fn type_name(&self) -> &'static str { + pub(crate) fn type_name(&self) -> &'static str { match self { Value::Boolean(_) => "boolean", Value::Function(_) => "function", diff --git a/tests/integration.rs b/tests/integration.rs index 36f8ce4..9fd4d4b 100644 --- a/tests/integration.rs +++ b/tests/integration.rs @@ -98,23 +98,21 @@ impl<'a> Test<'a> { match &self.expected_stderr { Match::Empty => { - if !stderr.is_empty() { - panic!("Expected empty stderr, but received: {}", stderr); - } + assert!( + stderr.is_empty(), + "Expected empty stderr, but received: {stderr}" + ); } Match::Contains(pattern) => { assert!( stderr.contains(pattern), - "Expected stderr to contain: '{}', but got: '{}'", - pattern, - stderr + "Expected stderr to contain: '{pattern}', but got: '{stderr}'", ); } Match::Exact(expected) => { assert_eq!( stderr, *expected, - "Expected exact stderr: '{}', but got: '{}'", - expected, stderr + "Expected exact stderr: '{expected}', but got: '{stderr}'", ); } } @@ -123,23 +121,21 @@ impl<'a> Test<'a> { match &self.expected_stdout { Match::Empty => { - if !stdout.is_empty() { - panic!("Expected empty stdout, but received: {}", stdout); - } + assert!( + stdout.is_empty(), + "Expected empty stdout, but received: {stdout}" + ); } Match::Contains(pattern) => { assert!( stdout.contains(pattern), - "Expected stdout to contain: '{}', but got: '{}'", - pattern, - stdout + "Expected stdout to contain: '{pattern}', but got: '{stdout}'", ); } Match::Exact(expected) => { assert_eq!( stdout, *expected, - "Expected exact stdout: '{}', but got: '{}'", - expected, stdout + "Expected exact stdout: '{expected}', but got: '{stdout}'", ); } } @@ -1193,7 +1189,7 @@ fn function_calling_builtin() -> Result { } #[test] -#[ignore] +#[ignore = "captures current unsupported function scope behavior"] fn function_modifying_outer_scope() -> Result { Test::new()? .program(indoc! { @@ -1255,7 +1251,7 @@ fn function_with_multiple_statements() -> Result { } #[test] -#[ignore] +#[ignore = "captures current unsupported zero-argument function behavior"] fn function_with_no_arguments() -> Result { Test::new()? .argument("-p")