/* * Exercise the JavaScript (ECMAScript DecimalLiteral) conversion options, * strict (chars_format::javascript) and sloppy * (chars_format::javascript_sloppy). * https://tc39.es/ecma262/#prod-DecimalLiteral * https://tc39.es/ecma262/#sec-additional-syntax-numeric-literals */ #include #include #include #include "fast_float/fast_float.h" int main_readme() { std::string const input = "01"; // not valid: leading zero double result; fast_float::parse_options options{fast_float::chars_format::javascript}; auto answer = fast_float::from_chars_advanced( input.data(), input.data() + input.size(), result, options); if (answer.ec == std::errc()) { std::cerr << "should have failed\n"; return EXIT_FAILURE; } return EXIT_SUCCESS; } int main_readme2() { std::string const input = ".5"; // valid in JavaScript, not in JSON double result; fast_float::parse_options options{fast_float::chars_format::javascript}; auto answer = fast_float::from_chars_advanced( input.data(), input.data() + input.size(), result, options); if (answer.ec != std::errc() || result != 0.5) { std::cerr << "should have parsed 0.5\n"; return EXIT_FAILURE; } return EXIT_SUCCESS; } struct ExpectedResult { double value; std::string junk_chars; }; struct AcceptedValue { std::string input; ExpectedResult expected; }; struct RejectReason { fast_float::parse_error error; intptr_t location_offset; }; struct RejectedValue { std::string input; RejectReason reason; }; int main() { std::vector const accept{ {"0", {0., ""}}, {"-0", {-0., ""}}, {"0.5", {0.5, ""}}, {"0.0", {0., ""}}, {"0e5", {0., ""}}, {"0e-2", {0., ""}}, {"0.5e-1", {0.05, ""}}, {"-0.2", {-0.2, ""}}, {"0.02", {0.02, ""}}, {"1e+0000", {1., ""}}, {"123", {123., ""}}, {"123.456", {123.456, ""}}, {"123.456e7", {1234560000., ""}}, {"1E5", {100000., ""}}, // Unlike JSON, the integer part may be empty... {".5", {0.5, ""}}, {"-.5", {-0.5, ""}}, {".5e1", {5., ""}}, {".5E-1", {0.05, ""}}, // ... and so may the fractional part. {"5.", {5., ""}}, {"-5.", {-5., ""}}, {"5.e3", {5000., ""}}, {"0.", {0., ""}}, {"0.e1", {0., ""}}, // An incomplete exponent is trailing junk, as in the other formats. {"1e", {1., "e"}}, {"1e+", {1., "e+"}}, {"5.e", {5., "e"}}, // Trailing junk. {"0x1", {0., "x1"}}, {"1n", {1., "n"}}, {"1_000", {1., "_000"}}, }; std::vector const reject{ {"00", {fast_float::parse_error::leading_zeros_in_integer_part, 0}}, {"01", {fast_float::parse_error::leading_zeros_in_integer_part, 0}}, {"007", {fast_float::parse_error::leading_zeros_in_integer_part, 0}}, {"00.5", {fast_float::parse_error::leading_zeros_in_integer_part, 0}}, {"00.0e-1", {fast_float::parse_error::leading_zeros_in_integer_part, 0}}, {"0775e-1", {fast_float::parse_error::leading_zeros_in_integer_part, 0}}, {"-01", {fast_float::parse_error::leading_zeros_in_integer_part, 1}}, {".", {fast_float::parse_error::no_digits_in_mantissa, 1}}, {".e5", {fast_float::parse_error::no_digits_in_mantissa, 1}}, {"-.", {fast_float::parse_error::no_digits_in_mantissa, 2}}, {"-", {fast_float::parse_error::missing_integer_or_dot_after_sign, 1}}, {"-e5", {fast_float::parse_error::missing_integer_or_dot_after_sign, 1}}, {"e5", {fast_float::parse_error::no_digits_in_mantissa, 0}}, // A leading plus sign needs allow_leading_plus. {"+1", {fast_float::parse_error::no_digits_in_mantissa, 0}}, // No inf/nan. {"inf", {fast_float::parse_error::no_digits_in_mantissa, 0}}, {"Infinity", {fast_float::parse_error::no_digits_in_mantissa, 0}}, {"nan", {fast_float::parse_error::no_digits_in_mantissa, 0}}, }; for (std::size_t i = 0; i != accept.size(); ++i) { auto const &s = accept[i].input; auto const &expected = accept[i].expected; double result; auto answer = fast_float::from_chars(s.data(), s.data() + s.size(), result, fast_float::chars_format::javascript); if (answer.ec != std::errc()) { std::cerr << "javascript fmt rejected valid javascript " << s << std::endl; return EXIT_FAILURE; } if (result != expected.value) { std::cerr << "javascript fmt gave wrong result " << s << " (expected " << expected.value << " got " << result << ")" << std::endl; return EXIT_FAILURE; } if (std::string(answer.ptr) != expected.junk_chars) { std::cerr << "javascript fmt has wrong trailing characters " << s << " (expected " << expected.junk_chars << " got " << answer.ptr << ")" << std::endl; return EXIT_FAILURE; } } for (std::size_t i = 0; i != reject.size(); ++i) { auto const &s = reject[i].input; double result; auto answer = fast_float::from_chars(s.data(), s.data() + s.size(), result, fast_float::chars_format::javascript); if (answer.ec == std::errc()) { std::cerr << "javascript fmt accepted invalid javascript " << s << std::endl; return EXIT_FAILURE; } } for (std::size_t i = 0; i != reject.size(); ++i) { auto const &f = reject[i].input; auto const &expected_reason = reject[i].reason; auto answer = fast_float::parse_number_string( f.data(), f.data() + f.size(), fast_float::parse_options(fast_float::chars_format::javascript)); if (!answer.invalid) { std::cerr << "javascript parse accepted invalid javascript " << f << std::endl; return EXIT_FAILURE; } if (answer.error != expected_reason.error) { std::cerr << "javascript parse failure had invalid error reason " << f << std::endl; return EXIT_FAILURE; } intptr_t error_location = answer.lastmatch - f.data(); if (error_location != expected_reason.location_offset) { std::cerr << "javascript parse failure had invalid error location " << f << " (expected " << expected_reason.location_offset << " got " << error_location << ")" << std::endl; return EXIT_FAILURE; } } // Sloppy mode accepts everything strict mode accepts... for (std::size_t i = 0; i != accept.size(); ++i) { auto const &s = accept[i].input; auto const &expected = accept[i].expected; double result; auto answer = fast_float::from_chars(s.data(), s.data() + s.size(), result, fast_float::chars_format::javascript_sloppy); if (answer.ec != std::errc() || result != expected.value || std::string(answer.ptr) != expected.junk_chars) { std::cerr << "javascript_sloppy fmt failed on valid javascript " << s << std::endl; return EXIT_FAILURE; } } // ... plus NonOctalDecimalIntegerLiteral: a leading zero followed by digits, // at least one of which is 8 or 9, is a decimal integer part. std::vector const accept_sloppy{ {"08", {8., ""}}, {"09", {9., ""}}, {"-08", {-8., ""}}, {"0888", {888., ""}}, {"0778", {778., ""}}, {"08.5", {8.5, ""}}, {"-08.5", {-8.5, ""}}, {"08.", {8., ""}}, {"0888e-1", {88.8, ""}}, {"09e+1", {90., ""}}, {"08.5e1", {85., ""}}, {"0009", {9., ""}}, {"08n", {8., "n"}}, }; for (std::size_t i = 0; i != accept_sloppy.size(); ++i) { auto const &s = accept_sloppy[i].input; auto const &expected = accept_sloppy[i].expected; double result; auto answer = fast_float::from_chars(s.data(), s.data() + s.size(), result, fast_float::chars_format::javascript_sloppy); if (answer.ec != std::errc()) { std::cerr << "javascript_sloppy fmt rejected valid sloppy javascript " << s << std::endl; return EXIT_FAILURE; } if (result != expected.value) { std::cerr << "javascript_sloppy fmt gave wrong result " << s << " (expected " << expected.value << " got " << result << ")" << std::endl; return EXIT_FAILURE; } if (std::string(answer.ptr) != expected.junk_chars) { std::cerr << "javascript_sloppy fmt has wrong trailing characters " << s << " (expected " << expected.junk_chars << " got " << answer.ptr << ")" << std::endl; return EXIT_FAILURE; } // Strict mode rejects all of these as leading zeros. auto strict = fast_float::parse_number_string( s.data(), s.data() + s.size(), fast_float::parse_options(fast_float::chars_format::javascript)); if (!strict.invalid || strict.error != fast_float::parse_error::leading_zeros_in_integer_part) { std::cerr << "javascript fmt should have rejected " << s << " for leading zeros" << std::endl; return EXIT_FAILURE; } } // A leading zero followed by octal digits only is a // LegacyOctalIntegerLiteral, which is not a decimal number. std::vector const reject_sloppy{ {"00", {fast_float::parse_error::legacy_octal_integer_part, 0}}, {"01", {fast_float::parse_error::legacy_octal_integer_part, 0}}, {"0775", {fast_float::parse_error::legacy_octal_integer_part, 0}}, {"-0775", {fast_float::parse_error::legacy_octal_integer_part, 1}}, {"00.5", {fast_float::parse_error::legacy_octal_integer_part, 0}}, {"00.0e-1", {fast_float::parse_error::legacy_octal_integer_part, 0}}, {"0775e-1", {fast_float::parse_error::legacy_octal_integer_part, 0}}, {"07.5", {fast_float::parse_error::legacy_octal_integer_part, 0}}, {"07n", {fast_float::parse_error::legacy_octal_integer_part, 0}}, // Unchanged from strict mode. {".", {fast_float::parse_error::no_digits_in_mantissa, 1}}, {"-", {fast_float::parse_error::missing_integer_or_dot_after_sign, 1}}, {"+1", {fast_float::parse_error::no_digits_in_mantissa, 0}}, {"inf", {fast_float::parse_error::no_digits_in_mantissa, 0}}, }; for (std::size_t i = 0; i != reject_sloppy.size(); ++i) { auto const &f = reject_sloppy[i].input; auto const &expected_reason = reject_sloppy[i].reason; double result; auto answer = fast_float::from_chars(f.data(), f.data() + f.size(), result, fast_float::chars_format::javascript_sloppy); if (answer.ec == std::errc()) { std::cerr << "javascript_sloppy fmt accepted invalid javascript " << f << std::endl; return EXIT_FAILURE; } auto parsed = fast_float::parse_number_string( f.data(), f.data() + f.size(), fast_float::parse_options(fast_float::chars_format::javascript_sloppy)); if (!parsed.invalid) { std::cerr << "javascript_sloppy parse accepted invalid javascript " << f << std::endl; return EXIT_FAILURE; } if (parsed.error != expected_reason.error) { std::cerr << "javascript_sloppy parse failure had invalid error reason " << f << std::endl; return EXIT_FAILURE; } intptr_t error_location = parsed.lastmatch - f.data(); if (error_location != expected_reason.location_offset) { std::cerr << "javascript_sloppy parse failure had invalid error location " << f << " (expected " << expected_reason.location_offset << " got " << error_location << ")" << std::endl; return EXIT_FAILURE; } } // With allow_leading_plus, a leading plus sign is accepted (unlike JSON, // where it is ignored). { std::vector const plus{"+1", "+.5", "+5.", "+0.5e1"}; std::vector const expected{1., 0.5, 5., 5.}; fast_float::parse_options const options{ fast_float::chars_format::javascript | fast_float::chars_format::allow_leading_plus}; for (std::size_t i = 0; i != plus.size(); ++i) { auto const &s = plus[i]; double result; auto answer = fast_float::from_chars_advanced( s.data(), s.data() + s.size(), result, options); if (answer.ec != std::errc() || result != expected[i]) { std::cerr << "javascript fmt with allow_leading_plus failed on " << s << std::endl; return EXIT_FAILURE; } } std::string const s = "+01"; double result; auto answer = fast_float::from_chars_advanced(s.data(), s.data() + s.size(), result, options); if (answer.ec == std::errc()) { std::cerr << "javascript fmt with allow_leading_plus accepted " << s << std::endl; return EXIT_FAILURE; } } if (main_readme() != EXIT_SUCCESS) { return EXIT_FAILURE; } if (main_readme2() != EXIT_SUCCESS) { return EXIT_FAILURE; } return EXIT_SUCCESS; }