| /* |
| * 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 <cstdlib> |
| #include <iostream> |
| #include <vector> |
| #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<AcceptedValue> 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<RejectedValue> 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<false>( |
| f.data(), f.data() + f.size(), |
| fast_float::parse_options(fast_float::chars_format::javascript)); |
| if (answer.valid) { |
| 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<AcceptedValue> 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<false>( |
| s.data(), s.data() + s.size(), |
| fast_float::parse_options(fast_float::chars_format::javascript)); |
| if (strict.valid || |
| 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<RejectedValue> 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<false>( |
| f.data(), f.data() + f.size(), |
| fast_float::parse_options(fast_float::chars_format::javascript_sloppy)); |
| if (parsed.valid) { |
| 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<std::string> const plus{"+1", "+.5", "+5.", "+0.5e1"}; |
| std::vector<double> 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; |
| } |