| ... | ... | @@ -1,6 +1,7 @@ |
| 1 | 1 | const std = @import("std"); |
| 2 | 2 | const common = @import("common.zig"); |
| 3 | 3 | const builtin = @import("builtin"); |
| 4 | const assert = std.debug.assert; |
| 4 | 5 | const div_t = std.c.div_t; |
| 5 | 6 | const ldiv_t = std.c.ldiv_t; |
| 6 | 7 | const lldiv_t = std.c.lldiv_t; |
| ... | ... | @@ -16,6 +17,24 @@ comptime { |
| 16 | 17 | @export(&ldiv, .{ .name = "ldiv", .linkage = common.linkage, .visibility = common.visibility }); |
| 17 | 18 | @export(&lldiv, .{ .name = "lldiv", .linkage = common.linkage, .visibility = common.visibility }); |
| 18 | 19 | |
| 20 | @export(&atoi, .{ .name = "atoi", .linkage = common.linkage, .visibility = common.visibility }); |
| 21 | @export(&atol, .{ .name = "atol", .linkage = common.linkage, .visibility = common.visibility }); |
| 22 | @export(&atoll, .{ .name = "atoll", .linkage = common.linkage, .visibility = common.visibility }); |
| 23 | |
| 24 | @export(&strtol, .{ .name = "strtol", .linkage = common.linkage, .visibility = common.visibility }); |
| 25 | @export(&strtoll, .{ .name = "strtoll", .linkage = common.linkage, .visibility = common.visibility }); |
| 26 | @export(&strtoul, .{ .name = "strtoul", .linkage = common.linkage, .visibility = common.visibility }); |
| 27 | @export(&strtoull, .{ .name = "strtoull", .linkage = common.linkage, .visibility = common.visibility }); |
| 28 | @export(&strtoimax, .{ .name = "strtoimax", .linkage = common.linkage, .visibility = common.visibility }); |
| 29 | @export(&strtoumax, .{ .name = "strtoumax", .linkage = common.linkage, .visibility = common.visibility }); |
| 30 | |
| 31 | @export(&strtol, .{ .name = "__strtol_internal", .linkage = common.linkage, .visibility = common.visibility }); |
| 32 | @export(&strtoll, .{ .name = "__strtoll_internal", .linkage = common.linkage, .visibility = common.visibility }); |
| 33 | @export(&strtoul, .{ .name = "__strtoul_internal", .linkage = common.linkage, .visibility = common.visibility }); |
| 34 | @export(&strtoull, .{ .name = "__strtoull_internal", .linkage = common.linkage, .visibility = common.visibility }); |
| 35 | @export(&strtoimax, .{ .name = "__strtoimax_internal", .linkage = common.linkage, .visibility = common.visibility }); |
| 36 | @export(&strtoumax, .{ .name = "__strtoumax_internal", .linkage = common.linkage, .visibility = common.visibility }); |
| 37 | |
| 19 | 38 | @export(&qsort_r, .{ .name = "qsort_r", .linkage = common.linkage, .visibility = common.visibility }); |
| 20 | 39 | @export(&qsort, .{ .name = "qsort", .linkage = common.linkage, .visibility = common.visibility }); |
| 21 | 40 | |
| ... | ... | @@ -56,6 +75,160 @@ fn lldiv(a: c_longlong, b: c_longlong) callconv(.c) lldiv_t { |
| 56 | 75 | }; |
| 57 | 76 | } |
| 58 | 77 | |
| 78 | fn atoi(str: [*:0]const c_char) callconv(.c) c_int { |
| 79 | return asciiToInteger(c_int, @ptrCast(str)); |
| 80 | } |
| 81 | |
| 82 | fn atol(str: [*:0]const c_char) callconv(.c) c_long { |
| 83 | return asciiToInteger(c_long, @ptrCast(str)); |
| 84 | } |
| 85 | |
| 86 | fn atoll(str: [*:0]const c_char) callconv(.c) c_longlong { |
| 87 | return asciiToInteger(c_longlong, @ptrCast(str)); |
| 88 | } |
| 89 | |
| 90 | fn asciiToInteger(comptime T: type, buf: [*:0]const u8) T { |
| 91 | comptime assert(std.math.isPowerOfTwo(@bitSizeOf(T))); |
| 92 | |
| 93 | var current = buf; |
| 94 | while (std.ascii.isWhitespace(current[0])) : (current += 1) {} |
| 95 | |
| 96 | // The behaviour *is* undefined if the result cannot be represented |
| 97 | // but as they are usually called with untrusted input we can just handle overflow gracefully. |
| 98 | if (current[0] == '-') return parseDigitsWithSignGenericCharacter(T, u8, current + 1, null, 10, .neg) catch std.math.minInt(T); |
| 99 | if (current[0] == '+') current += 1; |
| 100 | return parseDigitsWithSignGenericCharacter(T, u8, current, null, 10, .pos) catch std.math.maxInt(T); |
| 101 | } |
| 102 | |
| 103 | fn strtol(noalias str: [*:0]const c_char, noalias str_end: ?*[*:0]const c_char, base: c_int) callconv(.c) c_long { |
| 104 | return stringToInteger(c_long, @ptrCast(str), if (str_end) |end| @ptrCast(end) else null, base); |
| 105 | } |
| 106 | |
| 107 | fn strtoll(noalias str: [*:0]const c_char, noalias str_end: ?*[*:0]const c_char, base: c_int) callconv(.c) c_longlong { |
| 108 | return stringToInteger(c_longlong, @ptrCast(str), if (str_end) |end| @ptrCast(end) else null, base); |
| 109 | } |
| 110 | |
| 111 | fn strtoul(noalias str: [*:0]const c_char, noalias str_end: ?*[*:0]const c_char, base: c_int) callconv(.c) c_ulong { |
| 112 | return stringToInteger(c_ulong, @ptrCast(str), if (str_end) |end| @ptrCast(end) else null, base); |
| 113 | } |
| 114 | |
| 115 | fn strtoull(noalias str: [*:0]const c_char, noalias str_end: ?*[*:0]const c_char, base: c_int) callconv(.c) c_ulonglong { |
| 116 | return stringToInteger(c_ulonglong, @ptrCast(str), if (str_end) |end| @ptrCast(end) else null, base); |
| 117 | } |
| 118 | |
| 119 | // XXX: These belong in inttypes.zig but we'd have to make stringToInteger pub or move it somewhere else. |
| 120 | fn strtoimax(noalias str: [*:0]const c_char, noalias str_end: ?*[*:0]const c_char, base: c_int) callconv(.c) std.c.intmax_t { |
| 121 | return stringToInteger(std.c.intmax_t, @ptrCast(str), if (str_end) |end| @ptrCast(end) else null, base); |
| 122 | } |
| 123 | |
| 124 | fn strtoumax(noalias str: [*:0]const c_char, noalias str_end: ?*[*:0]const c_char, base: c_int) callconv(.c) std.c.uintmax_t { |
| 125 | return stringToInteger(std.c.uintmax_t, @ptrCast(str), if (str_end) |end| @ptrCast(end) else null, base); |
| 126 | } |
| 127 | |
| 128 | fn stringToInteger(comptime T: type, noalias buf: [*:0]const u8, noalias maybe_end: ?*[*:0]const u8, base: c_int) T { |
| 129 | comptime assert(std.math.isPowerOfTwo(@bitSizeOf(T))); |
| 130 | |
| 131 | if (base == 1 or base > 36) { |
| 132 | if (maybe_end) |end| { |
| 133 | end.* = buf; |
| 134 | } |
| 135 | |
| 136 | std.c._errno().* = @intFromEnum(std.c.E.INVAL); |
| 137 | return 0; |
| 138 | } |
| 139 | |
| 140 | var current = buf; |
| 141 | while (std.ascii.isWhitespace(current[0])) : (current += 1) {} |
| 142 | |
| 143 | const negative: bool = switch (current[0]) { |
| 144 | '-' => blk: { |
| 145 | current += 1; |
| 146 | break :blk true; |
| 147 | }, |
| 148 | '+' => blk: { |
| 149 | current += 1; |
| 150 | break :blk false; |
| 151 | }, |
| 152 | else => false, |
| 153 | }; |
| 154 | |
| 155 | // The prefix is allowed iff base == 0 or base == base of the prefix |
| 156 | const real_base: u6 = if (current[0] == '0') blk: { |
| 157 | current += 1; |
| 158 | |
| 159 | if ((base == 0 or base == 16) and std.ascii.toLower(current[0]) == 'x' and std.ascii.isHex(current[1])) { |
| 160 | current += 1; |
| 161 | break :blk 16; |
| 162 | } |
| 163 | |
| 164 | if ((base == 0 or base == 8) and std.ascii.isDigit(current[0])) { |
| 165 | break :blk 8; |
| 166 | } |
| 167 | |
| 168 | break :blk switch (base) { |
| 169 | 0 => 10, |
| 170 | else => @intCast(base), |
| 171 | }; |
| 172 | } else switch (base) { |
| 173 | 0 => 10, |
| 174 | else => @intCast(base), |
| 175 | }; |
| 176 | |
| 177 | if (@typeInfo(T).int.signedness == .unsigned) { |
| 178 | const result = parseDigitsWithSignGenericCharacter(T, u8, current, maybe_end, real_base, .pos) catch { |
| 179 | std.c._errno().* = @intFromEnum(std.c.E.RANGE); |
| 180 | return std.math.maxInt(T); |
| 181 | }; |
| 182 | |
| 183 | return if (negative) -%result else result; |
| 184 | } |
| 185 | |
| 186 | if (negative) return parseDigitsWithSignGenericCharacter(T, u8, current, maybe_end, real_base, .neg) catch blk: { |
| 187 | std.c._errno().* = @intFromEnum(std.c.E.RANGE); |
| 188 | break :blk std.math.minInt(T); |
| 189 | }; |
| 190 | |
| 191 | return parseDigitsWithSignGenericCharacter(T, u8, current, maybe_end, real_base, .pos) catch blk: { |
| 192 | std.c._errno().* = @intFromEnum(std.c.E.RANGE); |
| 193 | break :blk std.math.maxInt(T); |
| 194 | }; |
| 195 | } |
| 196 | |
| 197 | fn parseDigitsWithSignGenericCharacter( |
| 198 | comptime T: type, |
| 199 | comptime Char: type, |
| 200 | noalias buf: [*:0]const Char, |
| 201 | noalias maybe_end: ?*[*:0]const Char, |
| 202 | base: u6, |
| 203 | comptime sign: enum { pos, neg }, |
| 204 | ) error{Overflow}!T { |
| 205 | assert(base >= 2 and base <= 36); |
| 206 | |
| 207 | var current = buf; |
| 208 | defer if (maybe_end) |end| { |
| 209 | end.* = current; |
| 210 | }; |
| 211 | |
| 212 | const add = switch (sign) { |
| 213 | .pos => std.math.add, |
| 214 | .neg => std.math.sub, |
| 215 | }; |
| 216 | |
| 217 | var value: T = 0; |
| 218 | while (true) { |
| 219 | const c: u8 = std.math.cast(u8, current[0]) orelse break; |
| 220 | if (!std.ascii.isAlphanumeric(c)) break; |
| 221 | |
| 222 | const digit: u6 = @intCast(std.fmt.charToDigit(c, base) catch break); |
| 223 | defer current += 1; |
| 224 | |
| 225 | value = try std.math.mul(T, value, base); |
| 226 | value = try add(T, value, digit); |
| 227 | } |
| 228 | |
| 229 | return value; |
| 230 | } |
| 231 | |
| 59 | 232 | // NOTE: Despite its name, `qsort` doesn't have to use quicksort or make any complexity or stability guarantee. |
| 60 | 233 | fn qsort_r(base: *anyopaque, n: usize, size: usize, compare: *const fn (a: *const anyopaque, b: *const anyopaque, arg: ?*anyopaque) callconv(.c) c_int, arg: ?*anyopaque) callconv(.c) void { |
| 61 | 234 | const Context = struct { |
| ... | ... | @@ -147,6 +320,53 @@ test lldiv { |
| 147 | 320 | try std.testing.expectEqual(expected, lldiv(5, 3)); |
| 148 | 321 | } |
| 149 | 322 | |
| 323 | test atoi { |
| 324 | try std.testing.expectEqual(0, atoi(@ptrCast("stop42true"))); |
| 325 | try std.testing.expectEqual(42, atoi(@ptrCast("42true"))); |
| 326 | try std.testing.expectEqual(-1, atoi(@ptrCast("-01"))); |
| 327 | try std.testing.expectEqual(1, atoi(@ptrCast("+001"))); |
| 328 | try std.testing.expectEqual(100, atoi(@ptrCast(" 100"))); |
| 329 | try std.testing.expectEqual(500, atoi(@ptrCast("000000000000500"))); |
| 330 | try std.testing.expectEqual(1111, atoi(@ptrCast("0000000000001111_0000"))); |
| 331 | try std.testing.expectEqual(0, atoi(@ptrCast("0xAA"))); |
| 332 | try std.testing.expectEqual(700, atoi(@ptrCast("700B"))); |
| 333 | try std.testing.expectEqual(32453, atoi(@ptrCast("+32453more"))); |
| 334 | try std.testing.expectEqual(std.math.maxInt(c_int), atoi(@ptrCast(std.fmt.comptimePrint("{d}", .{std.math.maxInt(c_int)})))); |
| 335 | try std.testing.expectEqual(std.math.minInt(c_int), atoi(@ptrCast(std.fmt.comptimePrint("{d}", .{std.math.minInt(c_int)})))); |
| 336 | } |
| 337 | |
| 338 | test atol { |
| 339 | try std.testing.expectEqual(0, atol(@ptrCast("stop42true"))); |
| 340 | try std.testing.expectEqual(42, atol(@ptrCast("42true"))); |
| 341 | try std.testing.expectEqual(-1, atol(@ptrCast("-01"))); |
| 342 | try std.testing.expectEqual(1, atol(@ptrCast("+001"))); |
| 343 | try std.testing.expectEqual(100, atol(@ptrCast(" 100"))); |
| 344 | try std.testing.expectEqual(500, atol(@ptrCast("000000000000500"))); |
| 345 | try std.testing.expectEqual(1111, atol(@ptrCast("0000000000001111_0000"))); |
| 346 | try std.testing.expectEqual(0, atol(@ptrCast("0xAA"))); |
| 347 | try std.testing.expectEqual(700, atol(@ptrCast("700B"))); |
| 348 | try std.testing.expectEqual(32453, atol(@ptrCast("+32453more"))); |
| 349 | try std.testing.expectEqual(std.math.maxInt(c_long), atol(@ptrCast(std.fmt.comptimePrint("{d}", .{std.math.maxInt(c_long)})))); |
| 350 | try std.testing.expectEqual(std.math.minInt(c_long), atol(@ptrCast(std.fmt.comptimePrint("{d}", .{std.math.minInt(c_long)})))); |
| 351 | } |
| 352 | |
| 353 | test atoll { |
| 354 | try std.testing.expectEqual(0, atoll(@ptrCast("stop42true"))); |
| 355 | try std.testing.expectEqual(42, atoll(@ptrCast("42true"))); |
| 356 | try std.testing.expectEqual(-1, atoll(@ptrCast("-01"))); |
| 357 | try std.testing.expectEqual(1, atoll(@ptrCast("+001"))); |
| 358 | try std.testing.expectEqual(100, atoll(@ptrCast(" 100"))); |
| 359 | try std.testing.expectEqual(500, atoll(@ptrCast("000000000000500"))); |
| 360 | try std.testing.expectEqual(1111, atoll(@ptrCast("0000000000001111_0000"))); |
| 361 | try std.testing.expectEqual(0, atoll(@ptrCast("0xAA"))); |
| 362 | try std.testing.expectEqual(700, atoll(@ptrCast("700B"))); |
| 363 | try std.testing.expectEqual(32453, atoll(@ptrCast(" +32453more"))); |
| 364 | try std.testing.expectEqual(std.math.maxInt(c_longlong), atoll(@ptrCast(std.fmt.comptimePrint("{d}", .{std.math.maxInt(c_longlong)})))); |
| 365 | try std.testing.expectEqual(std.math.minInt(c_longlong), atoll(@ptrCast(std.fmt.comptimePrint("{d}", .{std.math.minInt(c_longlong)})))); |
| 366 | } |
| 367 | |
| 368 | // FIXME: We cannot test strtol, strtoll, strtoul, etc.. here as it must modify errno and libc is not linked in tests |
| 369 | |
| 150 | 370 | test bsearch { |
| 151 | 371 | const Comparison = struct { |
| 152 | 372 | pub fn compare(a: *const anyopaque, b: *const anyopaque) callconv(.c) c_int { |