| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544 |
- mod common;
- use common::get_test_context;
- use insta::assert_snapshot;
- use numbat::markup::{Formatter, PlainTextFormatter};
- use numbat::resolver::CodeSource;
- use numbat::{pretty_print::PrettyPrint, Context, InterpreterResult};
- #[track_caller]
- fn expect_output_with_context(ctx: &mut Context, code: &str, expected_output: impl AsRef<str>) {
- let expected_output = expected_output.as_ref();
- println!("Expecting output '{expected_output}' for code '{code}'");
- if let InterpreterResult::Value(val) = ctx.interpret(code, CodeSource::Internal).unwrap().1 {
- let fmt = PlainTextFormatter {};
- let actual_output = fmt.format(&val.pretty_print(), false);
- assert_eq!(actual_output.trim(), expected_output);
- } else {
- panic!();
- }
- }
- #[track_caller]
- fn expect_output(code: &str, expected_output: impl AsRef<str>) {
- let mut ctx = get_test_context();
- expect_output_with_context(&mut ctx, code, expected_output)
- }
- #[track_caller]
- fn expect_failure(code: &str, msg_part: &str) {
- let mut ctx = get_test_context();
- if let Err(e) = ctx.interpret(code, CodeSource::Internal) {
- let error_message = e.to_string();
- println!("{}", error_message);
- assert!(error_message.contains(msg_part));
- } else {
- panic!();
- }
- }
- #[track_caller]
- fn get_error_message(code: &str) -> String {
- let mut ctx = get_test_context();
- if let Err(e) = ctx.interpret(code, CodeSource::Internal) {
- e.to_string()
- } else {
- panic!();
- }
- }
- #[test]
- fn simple_value() {
- expect_output("0", "0");
- expect_output("0_0", "0");
- expect_output("0_0.0_0", "0");
- expect_output(".0", "0");
- expect_failure("_.0", "Unexpected character in identifier: '.'");
- expect_failure("._0", "Expected digit");
- expect_output(".0_0", "0");
- expect_failure(".0_", "Unexpected character in number literal: '_'");
- expect_output("0b0", "0");
- expect_output("0b01", "1");
- expect_output("0b0_0", "0");
- expect_failure("0b012", "Expected base-2 digit");
- expect_failure("0b", "Expected base-2 digit");
- expect_failure("0b_", "Expected base-2 digit");
- expect_failure("0b_0", "Expected base-2 digit");
- expect_failure("0b0_", "Expected base-2 digit");
- expect_failure("0b0.0", "Expected base-2 digit");
- expect_output("0o0", "0");
- expect_output("0o01234567", "342_391");
- expect_output("0o0_0", "0");
- expect_failure("0o012345678", "Expected base-8 digit");
- expect_failure("0o", "Expected base-8 digit");
- expect_failure("0o_", "Expected base-8 digit");
- expect_failure("0o_0", "Expected base-8 digit");
- expect_failure("0o0_", "Expected base-8 digit");
- expect_failure("0o0.0", "Expected base-8 digit");
- expect_output("0x0", "0");
- expect_output("0x0123456789abcdef", "8.19855e+16");
- expect_output("0x0_0", "0");
- expect_failure("0x0123456789abcdefg", "Expected base-16 digit");
- expect_failure("0x", "Expected base-16 digit");
- expect_failure("0x_", "Expected base-16 digit");
- expect_failure("0x_0", "Expected base-16 digit");
- expect_failure("0x0_", "Expected base-16 digit");
- expect_failure("0x0.0", "Expected base-16 digit");
- }
- #[test]
- fn test_factorial() {
- expect_output("0!", "1");
- expect_output("4!", "24");
- expect_output("4.0!", "24");
- expect_output("4 !", "24");
- expect_output(" 4 !", "24");
- expect_output("(4)!", "24");
- expect_output("3!^3", "216");
- // Not supported, at least for now.
- // expect_output("3!³", "216");
- expect_output("(3!)^3", "216");
- expect_output("3^3!", "729");
- expect_output("-5!", "-120");
- expect_output("-(5!)", "-120");
- expect_output("-(5)!", "-120");
- expect_failure(
- "(-1)!",
- "Expected factorial argument to be a non-negative integer",
- );
- expect_failure(
- "1.5!",
- "Expected factorial argument to be a finite integer number",
- );
- expect_failure(
- "(-1.5)!",
- "Expected factorial argument to be a non-negative integer",
- );
- expect_failure(
- "(2m)!",
- "Argument of factorial needs to be dimensionless (got Length).",
- );
- }
- #[test]
- fn test_exponentiation() {
- expect_output("3²*2", "18");
- expect_output("3² 2", "18");
- expect_output("3²·2", "18");
- expect_output("3³*2", "54");
- expect_output("3³(2)", "54");
- expect_output("(1+2)²", "9");
- expect_output("2²pi", "12.5664");
- expect_output("2² pi", "12.5664");
- expect_output("2²·pi", "12.5664");
- expect_output("5m² to cm·m", "500 cm·m");
- expect_output("2⁵", "32");
- expect_output("-4¹", "-4");
- expect_output("2⁻¹", "0.5");
- expect_output("2⁻²", "0.25");
- expect_output("10⁻⁵", "0.00001");
- }
- #[test]
- fn test_conversions() {
- expect_output("2in to cm", "5.08 cm");
- expect_output("5m^2 -> m*cm", "500 m·cm");
- expect_output("5m^2 -> cm*m", "500 cm·m");
- expect_output("1 kB / 10 ms -> MB/s", "0.1 MB/s");
- expect_output("55! / (6! (55 - 6)!) -> million", "28.9897 million");
- }
- #[test]
- fn test_implicit_conversion() {
- let mut ctx = get_test_context();
- let _ = ctx.interpret("let x = 5 m", CodeSource::Internal).unwrap();
- expect_output_with_context(&mut ctx, "x", "5 m");
- expect_output_with_context(&mut ctx, "2x", "10 m");
- expect_output_with_context(&mut ctx, "2 x", "10 m");
- expect_output_with_context(&mut ctx, "x x", "25 m²");
- expect_output_with_context(&mut ctx, "x²", "25 m²");
- expect_failure("x2", "Unknown identifier 'x2'");
- }
- #[test]
- fn test_reset_after_runtime_error() {
- let mut ctx = get_test_context();
- let _ = ctx.interpret("let x = 1", CodeSource::Internal).unwrap();
- let res = ctx.interpret("1/0", CodeSource::Internal);
- assert!(res.is_err());
- expect_output_with_context(&mut ctx, "x", "1");
- }
- #[test]
- fn test_function_inverses() {
- expect_output("sin(asin(0.1234))", "0.1234");
- expect_output("cos(acos(0.1234))", "0.1234");
- expect_output("tan(atan(0.1234))", "0.1234");
- expect_output("sinh(asinh(0.1234))", "0.1234");
- expect_output("cosh(acosh(1.1234))", "1.1234");
- expect_output("tanh(atanh(0.1234))", "0.1234");
- expect_output("log(exp(0.1234))", "0.1234");
- expect_output("log10(10^0.1234)", "0.1234");
- expect_output("log2(2^0.1234)", "0.1234");
- expect_output("sqr(sqrt(0.1234))", "0.1234");
- expect_output("asin(sin(0.1234))", "0.1234");
- expect_output("acos(cos(0.1234))", "0.1234");
- expect_output("atan(tan(0.1234))", "0.1234");
- expect_output("asinh(sinh(0.1234))", "0.1234");
- expect_output("acosh(cosh(1.1234))", "1.1234");
- expect_output("atanh(tanh(0.1234))", "0.1234");
- expect_output("exp(log(0.1234))", "0.1234");
- expect_output("10^(log10(0.1234))", "0.1234");
- expect_output("2^(log2(0.1234))", "0.1234");
- expect_output("sqrt(sqr(0.1234))", "0.1234");
- }
- #[test]
- fn test_math() {
- expect_output("sin(90°)", "1");
- expect_output("sin(30°)", "0.5");
- expect_output("sin(pi/2)", "1");
- expect_output("atan2(10, 0) / (pi / 2)", "1");
- expect_output("atan2(100 cm, 1 m) / (pi / 4)", "1");
- expect_failure(
- "atan2(100 cm, 1 m²)",
- "parameter type: Length\n argument type: Length²",
- );
- expect_output("mod(5, 3)", "2");
- expect_output("mod(-1, 4)", "3");
- expect_output("mod(8 cm, 5 cm)", "3 cm");
- expect_output("mod(235 cm, 1 m)", "35 cm");
- expect_output("mod(2 m, 7 cm)", "0.04 m");
- expect_failure(
- "mod(8 m, 5 s)",
- "parameter type: Length\n argument type: Time",
- )
- }
- #[test]
- fn test_incompatible_dimension_errors() {
- assert_snapshot!(
- get_error_message("kg m / s^2 + kg m^2"),
- @r###"
- left hand side: Length × Mass × Time⁻² [= Force]
- right hand side: Length² × Mass [= MomentOfInertia]
- "###
- );
- assert_snapshot!(
- get_error_message("1 + m"),
- @r###"
- left hand side: Scalar [= Angle, Scalar, SolidAngle]
- right hand side: Length
- Suggested fix: divide the expression on the right hand side by a `Length` factor
- "###
- );
- assert_snapshot!(
- get_error_message("m / s + K A"),
- @r###"
- left hand side: Length / Time [= Velocity]
- right hand side: Current × Temperature
- "###
- );
- assert_snapshot!(
- get_error_message("m + 1 / m"),
- @r###"
- left hand side: Length
- right hand side: Length⁻¹ [= Wavenumber]
- Suggested fix: invert the expression on the right hand side
- "###
- );
- assert_snapshot!(
- get_error_message("kW -> J"),
- @r###"
- left hand side: Length² × Mass × Time⁻³ [= Power]
- right hand side: Length² × Mass × Time⁻² [= Energy, Torque]
- Suggested fix: divide the expression on the right hand side by a `Time` factor
- "###
- );
- assert_snapshot!(
- get_error_message("sin(1 meter)"),
- @r###"
- parameter type: Scalar [= Angle, Scalar, SolidAngle]
- argument type: Length
- Suggested fix: divide the function argument by a `Length` factor
- "###
- );
- assert_snapshot!(
- get_error_message("let x: Acceleration = 4 m / s"),
- @r###"
- specified dimension: Length × Time⁻² [= Acceleration]
- actual dimension: Length × Time⁻¹ [= Velocity]
- Suggested fix: divide the right hand side expression by a `Time` factor
- "###
- );
- assert_snapshot!(
- get_error_message("unit x: Acceleration = 4 m / s"),
- @r###"
- specified dimension: Length × Time⁻² [= Acceleration]
- actual dimension: Length × Time⁻¹ [= Velocity]
- Suggested fix: divide the right hand side expression by a `Time` factor
- "###
- );
- assert_snapshot!(
- get_error_message("fn acceleration(length: Length, time: Time) -> Acceleration = length / time"),
- @r###"
- specified return type: Length × Time⁻² [= Acceleration]
- actual return type: Length × Time⁻¹ [= Velocity]
- Suggested fix: divide the expression in the function body by a `Time` factor
- "###
- );
- }
- #[test]
- fn test_temperature_conversions() {
- expect_output("from_celsius(11.5)", "284.65 K");
- expect_output("from_fahrenheit(89.3)", "304.983 K");
- expect_output("to_celsius(0 K)", "-273.15");
- expect_output("to_fahrenheit(30 K)", "-405.67");
- expect_output("to_celsius(from_celsius(100))", "100");
- expect_output("to_fahrenheit(from_fahrenheit(100))", "100.0");
- expect_output("from_celsius(to_celsius(123 K))", "123 K");
- expect_output("from_fahrenheit(to_fahrenheit(123 K))", "123 K");
- expect_output("-40 // from_fahrenheit // to_celsius", "-40");
- }
- #[test]
- fn test_other_functions() {
- expect_output("sqrt(4)", "2");
- expect_output("log10(100000)", "5");
- expect_output("log(e^15)", "15");
- expect_output("ln(e^15)", "15");
- expect_output("ceil(3.1)", "4");
- expect_output("floor(3.9)", "3");
- expect_output("round(3.9)", "4");
- expect_output("round(3.1)", "3");
- }
- #[test]
- fn test_last_result_identifier() {
- let mut ctx = get_test_context();
- let _ = ctx.interpret("2 + 3", CodeSource::Internal).unwrap();
- expect_output_with_context(&mut ctx, "ans", "5");
- let _ = ctx.interpret("1 + 2", CodeSource::Internal).unwrap();
- expect_output_with_context(&mut ctx, "_", "3");
- }
- #[test]
- fn test_misc_examples() {
- expect_output("1920/16*9", "1080");
- expect_output("2^32", "4_294_967_296");
- expect_output("sqrt(1.4^2 + 1.5^2) * cos(pi/3)^2", "0.512957");
- expect_output("2min + 30s", "2.5 min");
- expect_output("2min + 30s -> sec", "150 s");
- expect_output("4/3 * pi * (6000km)³", "9.04779e+11 km³");
- expect_output("40kg * 9.8m/s^2 * 150cm", "588 kg·m²/s²");
- expect_output("sin(30°)", "0.5");
- expect_output("60mph -> m/s", "26.8224 m/s");
- expect_output("240km/day -> km/h", "10 km/h");
- expect_output("1mrad -> °", "0.0572958°");
- expect_output("52weeks -> days", "364 day");
- expect_output("5in + 2ft -> cm", "73.66 cm");
- expect_output("atan(30cm / 2m) -> deg", "8.53077°");
- expect_output("6Mbit/s * 1.5h -> GB", "4.05 GB");
- expect_output("6Mbit/s * 1.5h -> GiB", "3.77186 GiB");
- expect_output("3m/4m", "0.75");
- expect_output("4/2*2", "4");
- expect_output("1/2 Hz -> s", "0.5 s");
- }
- #[test]
- fn test_bohr_radius_regression() {
- // Make sure that the unit is 'm', and not 'F·J²/(C²·kg·m·Hz²)', like we had before
- expect_output("bohr_radius", "5.29177e-11 m");
- }
- #[test]
- fn test_full_simplify() {
- expect_output("5 cm/m", "0.05");
- expect_output("hour/second", "3600");
- expect_output("5 to cm/m", "500 cm/m");
- expect_output(
- "fn f(x: Scalar) -> Scalar = x to cm/m
- f(5)",
- "500 cm/m",
- );
- expect_output("1 Wh/W", "1 Wh/W"); // This output is not great (and should be improved). But we keep this as a regression test for a bug in previous versions.
- expect_output("1 × (m/s)^2/(m/s)", "1 m/s");
- }
- #[test]
- fn test_prefixes() {
- expect_output("hertz second", "1");
- expect_output("kilohertz millisecond", "1");
- expect_output("megahertz microsecond", "1");
- expect_output("gigahertz nanosecond", "1");
- expect_output("terahertz picosecond", "1");
- expect_output("petahertz femtosecond", "1");
- expect_output("exahertz attosecond", "1");
- expect_output("zettahertz zeptosecond", "1");
- expect_output("yottahertz yoctosecond", "1");
- expect_output("ronnahertz rontosecond", "1");
- expect_output("quettahertz quectosecond", "1");
- }
- #[test]
- fn test_parse_errors() {
- expect_failure(
- "3kg+",
- "Expected one of: number, identifier, parenthesized expression",
- );
- expect_failure("let print=2", "Expected identifier after 'let' keyword");
- expect_failure(
- "fn print(x: Scalar) = 1",
- "Expected identifier after 'fn' keyword",
- );
- }
- #[test]
- fn test_name_clash_errors() {
- expect_failure("let kg=2", "Identifier is already in use: 'kg'");
- expect_failure("fn kg(x: Scalar) = 1", "Identifier is already in use: 'kg'");
- expect_failure("fn _()=0", "Reserved identifier");
- }
- #[test]
- fn test_type_check_errors() {
- expect_failure("foo", "Unknown identifier 'foo'");
- expect_failure("let sin=2", "This name is already used by a function");
- expect_failure("fn pi() = 1", "This name is already used by a constant");
- expect_failure(
- "fn sin(x)=0",
- "This name is already used by a foreign function",
- );
- }
- #[test]
- fn test_runtime_errors() {
- expect_failure("1/0", "Division by zero");
- }
- #[test]
- fn test_comparisons() {
- expect_output("2 < 3", "true");
- expect_output("2 m < 3 m", "true");
- expect_output("20 cm < 3 m", "true");
- expect_output("2 m < 100 cm", "false");
- expect_output("2 > 3", "false");
- expect_output("2 m > 3 m", "false");
- expect_output("20 cm > 3 m", "false");
- expect_output("2 m > 100 cm", "true");
- expect_output("2 <= 2", "true");
- expect_output("2.1 <= 2", "false");
- expect_output("2 >= 2", "true");
- expect_output("2 >= 2.1", "false");
- expect_output("200 cm == 2 m", "true");
- expect_output("201 cm == 2 m", "false");
- expect_output("200 cm != 2 m", "false");
- expect_output("201 cm != 2 m", "true");
- }
- #[test]
- fn test_conditionals() {
- expect_output("if 1 < 2 then 3 else 4", "3");
- expect_output("if 4 < 3 then 2 else 1", "1");
- expect_output(
- "if 4 > 3 then \"four is larger!\" else \"four is not larger!\"",
- "four is larger!",
- );
- }
- #[test]
- fn test_string_interpolation() {
- expect_output("\"pi = {pi}!\"", "pi = 3.14159!");
- expect_output("\"1 + 2 = {1 + 2}\"", "1 + 2 = 3");
- }
- #[test]
- fn test_overwrite_regular_function() {
- expect_output(
- "
- fn f(x)=0
- fn f(x)=1
- f(2)",
- "1",
- );
- }
- #[test]
- fn test_overwrite_inner_function() {
- expect_output(
- "
- fn inner() = 0
- fn outer() = inner()
- fn inner(x) = 1
- outer()
- ",
- "0",
- );
- }
- #[test]
- fn test_override_constants() {
- expect_output("let x = 1\nlet x = 2\nx", "2");
- expect_output("let pi = 4\npi", "4");
- }
- #[test]
- fn test_overwrite_captured_constant() {
- expect_output(
- "
- let x = 1
- fn f() = sin(x)
- let x = 1 m
- f()
- ",
- "0.841471",
- );
- }
|