| ... | ... | @@ -1,7 +1,5 @@ |
| 1 | 1 | const std = @import("../std.zig"); |
| 2 | 2 | const assert = std.debug.assert; |
| 3 | | const utf8Decode = std.unicode.utf8Decode; |
| 4 | | const utf8Encode = std.unicode.utf8Encode; |
| 5 | 3 | |
| 6 | 4 | pub const ParseError = error{ |
| 7 | 5 | OutOfMemory, |
| ... | ... | @@ -46,7 +44,7 @@ pub const Error = union(enum) { |
| 46 | 44 | duplicate_exponent: usize, |
| 47 | 45 | /// Exponent comes directly after '_' digit separator. |
| 48 | 46 | exponent_after_underscore: usize, |
| 49 | | /// Special character (+-.) comes directly after exponent. |
| 47 | /// Special character (+-.) comes directly after underscore. |
| 50 | 48 | special_after_underscore: usize, |
| 51 | 49 | /// Number ends in special character (+-.) |
| 52 | 50 | trailing_special: usize, |
| ... | ... | @@ -56,13 +54,15 @@ pub const Error = union(enum) { |
| 56 | 54 | invalid_character: usize, |
| 57 | 55 | /// [+-] not immediately after [pPeE] |
| 58 | 56 | invalid_exponent_sign: usize, |
| 59 | | /// Period comes directly after exponent. |
| 57 | /// Period comes after exponent. |
| 60 | 58 | period_after_exponent: usize, |
| 61 | 59 | }; |
| 62 | 60 | |
| 63 | 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 | 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 | 66 | var i: usize = 0; |
| 67 | 67 | var base: u8 = 10; |
| 68 | 68 | if (bytes.len >= 2 and bytes[0] == '0') switch (bytes[1]) { |
| ... | ... | @@ -121,17 +121,9 @@ pub fn parseNumberLiteral(bytes: []const u8) Result { |
| 121 | 121 | continue; |
| 122 | 122 | }, |
| 123 | 123 | '.' => { |
| 124 | | if (exponent) { |
| 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 | | } |
| 124 | if (exponent) return .{ .failure = .{ .period_after_exponent = i } }; |
| 133 | 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 = 1 } }; |
| 135 | 127 | if (period) return .{ .failure = .duplicate_period }; |
| 136 | 128 | period = true; |
| 137 | 129 | if (underscore) return .{ .failure = .{ .special_after_underscore = i } }; |
| ... | ... | @@ -177,3 +169,39 @@ pub fn parseNumberLiteral(bytes: []const u8) Result { |
| 177 | 169 | if (overflow) return .{ .big_int = @as(Base, @enumFromInt(base)) }; |
| 178 | 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_float_base = 1 } }, parseNumberLiteral("0o3.1")); |
| 181 | try std.testing.expectEqual(Result{ .failure = .{ .invalid_digit = .{ .i = 3, .base = .octal } } }, parseNumberLiteral("0o3e1")); |
| 182 | } |
| 183 | |
| 184 | /// Returns an error if `parseNumberLiteral` returns `.float` but `parseFloat` fails. |
| 185 | /// AstGen relies on `parseFloat` being unable to fail after calling `parseNumberLiteral`. |
| 186 | fn checkFloat(bytes: []const u8) !void { |
| 187 | // Number literals must start with a digit |
| 188 | if (bytes.len == 0 or !std.ascii.isDigit(bytes[0])) return; |
| 189 | |
| 190 | switch (parseNumberLiteral(bytes)) { |
| 191 | .float => { |
| 192 | _ = try std.fmt.parseFloat(f128, bytes); |
| 193 | }, |
| 194 | else => {}, |
| 195 | } |
| 196 | } |
| 197 | |
| 198 | test "parseNumberLiteral float validation" { |
| 199 | const Context = struct { |
| 200 | fn testOne(_: @This(), smith: *std.testing.Smith) anyerror!void { |
| 201 | var buf: [256]u8 = undefined; |
| 202 | const bytes = buf[0..smith.slice(&buf)]; |
| 203 | try checkFloat(bytes); |
| 204 | } |
| 205 | }; |
| 206 | return std.testing.fuzz(Context{}, Context.testOne, .{}); |
| 207 | } |