diff --git a/lib/std/zig/AstGen.zig b/lib/std/zig/AstGen.zig index 19057918cea3abe07d8adc61a73062c88d853b14..16e0616ede4ac141162b054a48a36694f12e222f 100644 --- a/lib/std/zig/AstGen.zig +++ b/lib/std/zig/AstGen.zig @@ -8493,7 +8493,7 @@ fn numberLiteral(gz: *GenZir, ri: ResultInfo, node: Ast.Node.Index, source_node: }, .float => { const unsigned_float_number = std.fmt.parseFloat(f128, bytes) catch |err| switch (err) { - error.InvalidCharacter => unreachable, // validated by tokenizer + error.InvalidCharacter => unreachable, // validated by `parseNumberLiteral` }; const float_number = switch (sign) { .negative => -unsigned_float_number, diff --git a/lib/std/zig/number_literal.zig b/lib/std/zig/number_literal.zig index a4dc33eb91c3f70536431174b9badc5222fe4258..754fefafb586a3674e9e278b094a875f29174979 100644 --- a/lib/std/zig/number_literal.zig +++ b/lib/std/zig/number_literal.zig @@ -1,7 +1,5 @@ const std = @import("../std.zig"); const assert = std.debug.assert; -const utf8Decode = std.unicode.utf8Decode; -const utf8Encode = std.unicode.utf8Encode; pub const ParseError = error{ OutOfMemory, @@ -46,7 +44,7 @@ pub const Error = union(enum) { duplicate_exponent: usize, /// Exponent comes directly after '_' digit separator. exponent_after_underscore: usize, - /// Special character (+-.) comes directly after exponent. + /// Special character (+-.) comes directly after underscore. special_after_underscore: usize, /// Number ends in special character (+-.) trailing_special: usize, @@ -56,13 +54,15 @@ pub const Error = union(enum) { invalid_character: usize, /// [+-] not immediately after [pPeE] invalid_exponent_sign: usize, - /// Period comes directly after exponent. + /// Period comes after exponent. period_after_exponent: usize, }; /// Parse Zig number literal accepted by fmt.parseInt, fmt.parseFloat and big_int.setString. -/// Valid for any input. +/// Valid for any number_literal token bytes. pub fn parseNumberLiteral(bytes: []const u8) Result { + // This is enforced by the tokenizer. + assert(bytes.len > 0 and std.ascii.isDigit(bytes[0])); var i: usize = 0; var base: u8 = 10; if (bytes.len >= 2 and bytes[0] == '0') switch (bytes[1]) { @@ -121,15 +121,7 @@ pub fn parseNumberLiteral(bytes: []const u8) Result { continue; }, '.' => { - if (exponent) { - const digit_index = i - ".e".len; - if (digit_index < bytes.len) { - switch (bytes[digit_index]) { - '0'...'9' => return .{ .failure = .{ .period_after_exponent = i } }, - else => {}, - } - } - } + if (exponent) return .{ .failure = .{ .period_after_exponent = i } }; float = true; if (base != 10 and base != 16) return .{ .failure = .{ .invalid_float_base = 2 } }; if (period) return .{ .failure = .duplicate_period }; @@ -177,3 +169,38 @@ pub fn parseNumberLiteral(bytes: []const u8) Result { if (overflow) return .{ .big_int = @as(Base, @enumFromInt(base)) }; return .{ .int = x }; } + +test parseNumberLiteral { + try std.testing.expectEqual(Result{ .float = .decimal }, parseNumberLiteral("3E2")); + try std.testing.expectEqual(Result{ .int = 0x3E2 }, parseNumberLiteral("0x3E2")); + try std.testing.expectEqual(Result{ .float = .hex }, parseNumberLiteral("0x3p2")); + try std.testing.expectEqual(Result{ .failure = .{ .period_after_exponent = 3 } }, parseNumberLiteral("3E2.5")); + try std.testing.expectEqual(Result{ .failure = .{ .period_after_exponent = 2 } }, parseNumberLiteral("3E.5")); + try std.testing.expectEqual(Result{ .failure = .{ .period_after_exponent = 3 } }, parseNumberLiteral("3E1.")); + try std.testing.expectEqual(Result{ .failure = .{ .invalid_digit = .{ .i = 3, .base = .octal } } }, parseNumberLiteral("0o3e1")); +} + +/// Returns an error if `parseNumberLiteral` returns `.float` but `parseFloat` fails. +/// AstGen relies on `parseFloat` being unable to fail after calling `parseNumberLiteral`. +fn checkFloat(bytes: []const u8) !void { + // Number literals must start with a digit + if (bytes.len == 0 or !std.ascii.isDigit(bytes[0])) return; + + switch (parseNumberLiteral(bytes)) { + .float => { + _ = try std.fmt.parseFloat(f128, bytes); + }, + else => {}, + } +} + +test "parseNumberLiteral float validation" { + const Context = struct { + fn testOne(_: @This(), smith: *std.testing.Smith) anyerror!void { + var buf: [256]u8 = undefined; + const bytes = buf[0..smith.slice(&buf)]; + try checkFloat(bytes); + } + }; + return std.testing.fuzz(Context{}, Context.testOne, .{}); +} diff --git a/test/cases/compile_errors/invalid_number_literals.zig b/test/cases/compile_errors/invalid_number_literals.zig index bd41ba27c9200b83faf619e7c40930287cab7a7e..5732c2bd44d8ee01cbc3a753a7aafbeb8d82e407 100644 --- a/test/cases/compile_errors/invalid_number_literals.zig +++ b/test/cases/compile_errors/invalid_number_literals.zig @@ -10,6 +10,9 @@ comptime { comptime { _ = 12E.0; } +comptime { + _ = 12E1.0; +} comptime { _ = 0xp0; } @@ -23,5 +26,6 @@ comptime { // :5:11: error: unexpected period after exponent // :8:12: error: unexpected period after exponent // :11:12: error: unexpected period after exponent -// :14:9: error: expected a digit after base prefix +// :14:13: error: unexpected period after exponent // :17:9: error: expected a digit after base prefix +// :20:9: error: expected a digit after base prefix