| ... | @@ -1,7 +1,5 @@ | ... | @@ -1,7 +1,5 @@ |
| 1 | const std = @import("../std.zig"); | 1 | const std = @import("../std.zig"); |
| 2 | const assert = std.debug.assert; | 2 | const assert = std.debug.assert; |
| 3 | const utf8Decode = std.unicode.utf8Decode; | | |
| 4 | const utf8Encode = std.unicode.utf8Encode; | | |
| 5 | | 3 | |
| 6 | pub const ParseError = error{ | 4 | pub const ParseError = error{ |
| 7 | OutOfMemory, | 5 | OutOfMemory, |
| ... | @@ -46,7 +44,7 @@ pub const Error = union(enum) { | ... | @@ -46,7 +44,7 @@ pub const Error = union(enum) { |
| 46 | duplicate_exponent: usize, | 44 | duplicate_exponent: usize, |
| 47 | /// Exponent comes directly after '_' digit separator. | 45 | /// Exponent comes directly after '_' digit separator. |
| 48 | exponent_after_underscore: usize, | 46 | exponent_after_underscore: usize, |
| 49 | /// Special character (+-.) comes directly after exponent. | 47 | /// Special character (+-.) comes directly after underscore. |
| 50 | special_after_underscore: usize, | 48 | special_after_underscore: usize, |
| 51 | /// Number ends in special character (+-.) | 49 | /// Number ends in special character (+-.) |
| 52 | trailing_special: usize, | 50 | trailing_special: usize, |
| ... | @@ -56,13 +54,15 @@ pub const Error = union(enum) { | ... | @@ -56,13 +54,15 @@ pub const Error = union(enum) { |
| 56 | invalid_character: usize, | 54 | invalid_character: usize, |
| 57 | /// [+-] not immediately after [pPeE] | 55 | /// [+-] not immediately after [pPeE] |
| 58 | invalid_exponent_sign: usize, | 56 | invalid_exponent_sign: usize, |
| 59 | /// Period comes directly after exponent. | 57 | /// Period comes after exponent. |
| 60 | period_after_exponent: usize, | 58 | period_after_exponent: usize, |
| 61 | }; | 59 | }; |
| 62 | | 60 | |
| 63 | /// Parse Zig number literal accepted by fmt.parseInt, fmt.parseFloat and big_int.setString. | 61 | /// Parse Zig number literal accepted by fmt.parseInt, fmt.parseFloat and big_int.setString. |
| 64 | /// Valid for any input. | 62 | /// Valid for any number_literal token bytes. |
| 65 | pub fn parseNumberLiteral(bytes: []const u8) Result { | 63 | pub fn parseNumberLiteral(bytes: []const u8) Result { |
| | 64 | // This is enforced by the tokenizer. |
| | 65 | assert(bytes.len > 0 and std.ascii.isDigit(bytes[0])); |
| 66 | var i: usize = 0; | 66 | var i: usize = 0; |
| 67 | var base: u8 = 10; | 67 | var base: u8 = 10; |
| 68 | if (bytes.len >= 2 and bytes[0] == '0') switch (bytes[1]) { | 68 | if (bytes.len >= 2 and bytes[0] == '0') switch (bytes[1]) { |
| ... | @@ -121,15 +121,7 @@ pub fn parseNumberLiteral(bytes: []const u8) Result { | ... | @@ -121,15 +121,7 @@ pub fn parseNumberLiteral(bytes: []const u8) Result { |
| 121 | continue; | 121 | continue; |
| 122 | }, | 122 | }, |
| 123 | '.' => { | 123 | '.' => { |
| 124 | if (exponent) { | 124 | if (exponent) return .{ .failure = .{ .period_after_exponent = i } }; |
| 125 | const digit_index = i - ".e".len; | | |
| 126 | if (digit_index < bytes.len) { | | |
| 127 | switch (bytes[digit_index]) { | | |
| 128 | '0'...'9' => return .{ .failure = .{ .period_after_exponent = i } }, | | |
| 129 | else => {}, | | |
| 130 | } | | |
| 131 | } | | |
| 132 | } | | |
| 133 | float = true; | 125 | float = true; |
| 134 | if (base != 10 and base != 16) return .{ .failure = .{ .invalid_float_base = 2 } }; | 126 | if (base != 10 and base != 16) return .{ .failure = .{ .invalid_float_base = 2 } }; |
| 135 | if (period) return .{ .failure = .duplicate_period }; | 127 | if (period) return .{ .failure = .duplicate_period }; |
| ... | @@ -177,3 +169,38 @@ pub fn parseNumberLiteral(bytes: []const u8) Result { | ... | @@ -177,3 +169,38 @@ pub fn parseNumberLiteral(bytes: []const u8) Result { |
| 177 | if (overflow) return .{ .big_int = @as(Base, @enumFromInt(base)) }; | 169 | if (overflow) return .{ .big_int = @as(Base, @enumFromInt(base)) }; |
| 178 | return .{ .int = x }; | 170 | return .{ .int = x }; |
| 179 | } | 171 | } |
| | 172 | |
| | 173 | test parseNumberLiteral { |
| | 174 | try std.testing.expectEqual(Result{ .float = .decimal }, parseNumberLiteral("3E2")); |
| | 175 | try std.testing.expectEqual(Result{ .int = 0x3E2 }, parseNumberLiteral("0x3E2")); |
| | 176 | try std.testing.expectEqual(Result{ .float = .hex }, parseNumberLiteral("0x3p2")); |
| | 177 | try std.testing.expectEqual(Result{ .failure = .{ .period_after_exponent = 3 } }, parseNumberLiteral("3E2.5")); |
| | 178 | try std.testing.expectEqual(Result{ .failure = .{ .period_after_exponent = 2 } }, parseNumberLiteral("3E.5")); |
| | 179 | try std.testing.expectEqual(Result{ .failure = .{ .period_after_exponent = 3 } }, parseNumberLiteral("3E1.")); |
| | 180 | try std.testing.expectEqual(Result{ .failure = .{ .invalid_digit = .{ .i = 3, .base = .octal } } }, parseNumberLiteral("0o3e1")); |
| | 181 | } |
| | 182 | |
| | 183 | /// Returns an error if `parseNumberLiteral` returns `.float` but `parseFloat` fails. |
| | 184 | /// AstGen relies on `parseFloat` being unable to fail after calling `parseNumberLiteral`. |
| | 185 | fn checkFloat(bytes: []const u8) !void { |
| | 186 | // Number literals must start with a digit |
| | 187 | if (bytes.len == 0 or !std.ascii.isDigit(bytes[0])) return; |
| | 188 | |
| | 189 | switch (parseNumberLiteral(bytes)) { |
| | 190 | .float => { |
| | 191 | _ = try std.fmt.parseFloat(f128, bytes); |
| | 192 | }, |
| | 193 | else => {}, |
| | 194 | } |
| | 195 | } |
| | 196 | |
| | 197 | test "parseNumberLiteral float validation" { |
| | 198 | const Context = struct { |
| | 199 | fn testOne(_: @This(), smith: *std.testing.Smith) anyerror!void { |
| | 200 | var buf: [256]u8 = undefined; |
| | 201 | const bytes = buf[0..smith.slice(&buf)]; |
| | 202 | try checkFloat(bytes); |
| | 203 | } |
| | 204 | }; |
| | 205 | return std.testing.fuzz(Context{}, Context.testOne, .{}); |
| | 206 | } |