diff --git a/src/runtime/value.zig b/src/runtime/value.zig index 87a9d085..dde68496 100644 --- a/src/runtime/value.zig +++ b/src/runtime/value.zig @@ -1348,7 +1348,9 @@ pub const Value = union(enum) { if (i < s.len and (s[i] == 'e' or s[i] == 'E')) { i += 1; if (i < s.len and (s[i] == '-' or s[i] == '+')) i += 1; + const exponent_start = i; while (i < s.len and s[i] >= '0' and s[i] <= '9') i += 1; + if (i == exponent_start) return false; } while (i < s.len and (s[i] == ' ' or s[i] == '\t' or s[i] == '\n' or s[i] == '\r')) i += 1; return has_digit and i == s.len; @@ -1956,6 +1958,22 @@ pub const Value = union(enum) { } }; +test "numeric string exponent requires digits" { + const invalid = [_][]const u8{ + "0e", "1e", "1e+", "1e-", "1E", "1E-", " 1e ", "-1e+\t", "1e+ 2", + }; + for (invalid) |s| { + try std.testing.expect(!Value.isNumericString(s)); + } + + const valid = [_][]const u8{ + "1", "1.5", "1e2", "1E+2", "1e-2", " +1E+2\t", "-1e-2\r\n", + }; + for (valid) |s| { + try std.testing.expect(Value.isNumericString(s)); + } +} + test "truthiness" { try std.testing.expect(!Value.isTruthy(.null)); try std.testing.expect(!Value.isTruthy(.{ .bool = false })); diff --git a/tests/numeric_string_exponent.php b/tests/numeric_string_exponent.php new file mode 100644 index 00000000..7d6217dd --- /dev/null +++ b/tests/numeric_string_exponent.php @@ -0,0 +1,32 @@ + "0"); +var_dump("1e" <=> "1"); +var_dump("1E-" <=> "1"); + +// valid scientific notation remains numeric +var_dump("1e2" == 100); +var_dump("1E+2" == 100); +var_dump("1e-2" == 0.01); +var_dump("1e2" <=> "100"); + +// existing native is_numeric behavior is a guard, not a reason to change its code +var_dump(is_numeric("0e")); +var_dump(is_numeric("1e")); +var_dump(is_numeric("1e+")); +var_dump(is_numeric("1e-")); +var_dump(is_numeric("1E")); +var_dump(is_numeric("1E-")); +var_dump(is_numeric("1e2")); +var_dump(is_numeric("1E+2")); +var_dump(is_numeric("1e-2"));