feature. See also
. The project being documented here (as the example) is the Zig library itself.
File
Code
const std = @import("../std.zig");
const assert = std.debug.assert;
pub const ParseError = error{
OutOfMemory,
InvalidLiteral,
};
pub const Base = enum(u8) { decimal = 10, hex = 16, binary = 2, octal = 8 };
pub const FloatBase = enum(u8) { decimal = 10, hex = 16 };
pub const Result = union(enum) {
int: u64,
big_int: Base,
float: FloatBase,
failure: Error,
};
pub const Error = union(enum) {
leading_zero,
digit_after_base,
upper_case_base: usize,
invalid_float_base: usize,
repeated_underscore: usize,
invalid_underscore_after_special: usize,
invalid_digit: struct { i: usize, base: Base },
invalid_digit_exponent: usize,
duplicate_period,
duplicate_exponent: usize,
exponent_after_underscore: usize,
special_after_underscore: usize,
trailing_special: usize,
trailing_underscore: usize,
invalid_character: usize,
invalid_exponent_sign: usize,
period_after_exponent: usize,
};
pub fn parseNumberLiteral(bytes: []const u8) Result {
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]) {
'b' => {
base = 2;
i = 2;
},
'o' => {
base = 8;
i = 2;
},
'x' => {
base = 16;
i = 2;
},
'B', 'O', 'X' => return .{ .failure = .{ .upper_case_base = 1 } },
'.', 'e', 'E' => {},
else => return .{ .failure = .leading_zero },
};
if (bytes.len == 2 and base != 10) return .{ .failure = .digit_after_base };
var x: u64 = 0;
var overflow = false;
var underscore = false;
var period = false;
var special: u8 = 0;
var exponent = false;
var float = false;
while (i < bytes.len) : (i += 1) {
const c = bytes[i];
switch (c) {
'_' => {
if (i == 2 and base != 10) return .{ .failure = .{ .invalid_underscore_after_special = i } };
if (special != 0) return .{ .failure = .{ .invalid_underscore_after_special = i } };
if (underscore) return .{ .failure = .{ .repeated_underscore = i } };
underscore = true;
continue;
},
'e', 'E' => if (base == 10) {
float = true;
if (exponent) return .{ .failure = .{ .duplicate_exponent = i } };
if (underscore) return .{ .failure = .{ .exponent_after_underscore = i } };
special = c;
exponent = true;
continue;
},
'p', 'P' => if (base == 16) {
if (i == 2) {
return .{ .failure = .{ .digit_after_base = {} } };
}
float = true;
if (exponent) return .{ .failure = .{ .duplicate_exponent = i } };
if (underscore) return .{ .failure = .{ .exponent_after_underscore = i } };
special = c;
exponent = true;
continue;
},
'.' => {
if (exponent) return .{ .failure = .{ .period_after_exponent = i } };
float = true;
if (base != 10 and base != 16) return .{ .failure = .{ .invalid_float_base = 1 } };
if (period) return .{ .failure = .duplicate_period };
period = true;
if (underscore) return .{ .failure = .{ .special_after_underscore = i } };
special = c;
continue;
},
'+', '-' => {
switch (special) {
'p', 'P' => {},
'e', 'E' => if (base != 10) return .{ .failure = .{ .invalid_exponent_sign = i } },
else => return .{ .failure = .{ .invalid_exponent_sign = i } },
}
special = c;
continue;
},
else => {},
}
const digit = switch (c) {
'0'...'9' => c - '0',
'A'...'Z' => c - 'A' + 10,
'a'...'z' => c - 'a' + 10,
else => return .{ .failure = .{ .invalid_character = i } },
};
if (digit >= base) return .{ .failure = .{ .invalid_digit = .{ .i = i, .base = @as(Base, @fromBackingInt(@intCast(base))) } } };
if (exponent and digit >= 10) return .{ .failure = .{ .invalid_digit_exponent = i } };
underscore = false;
special = 0;
if (float) continue;
if (x != 0) {
const res = @mulWithOverflow(x, base);
if (res[1] != 0) overflow = true;
x = res[0];
}
const res = @addWithOverflow(x, digit);
if (res[1] != 0) overflow = true;
x = res[0];
}
if (underscore) return .{ .failure = .{ .trailing_underscore = bytes.len - 1 } };
if (special != 0) return .{ .failure = .{ .trailing_special = bytes.len - 1 } };
if (float) return .{ .float = @as(FloatBase, @fromBackingInt(@intCast(base))) };
if (overflow) return .{ .big_int = @as(Base, @fromBackingInt(@intCast(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_float_base = 1 } }, parseNumberLiteral("0o3.1"));
try std.testing.expectEqual(Result{ .failure = .{ .invalid_digit = .{ .i = 3, .base = .octal } } }, parseNumberLiteral("0o3e1"));
}
fn checkFloat(bytes: []const u8) !void {
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, .{});
}