authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2019-02-01 13:05:34-05:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2019-02-01 13:05:34-05:00
logbbe857be96084bae6ca1e5f10e35f3631df50edc
tree00b351b140304365570efc4365ba407c82333d07
parent8d32d256198589eeaccb92892e5b3145c097514c
parent1a8570403f070933842db7739e7139779b7e04a5
signature Signed by PGP key 4AEE18F83AFDEB23

Merge pull request #1775 from tgschultz/stdlib-serialization

Added serialization, bitstreams, traits for int sign, TagPayloadType, some fixes to std

4 files changed, 1144 insertions(+), 8 deletions(-)

std/io.zig+641-5
......@@ -8,6 +8,8 @@ const debug = std.debug;
88const assert = debug.assert;
99const os = std.os;
1010const mem = std.mem;
11const meta = std.meta;
12const trait = meta.trait;
1113const Buffer = std.Buffer;
1214const fmt = std.fmt;
1315const File = std.os.File;
......@@ -463,6 +465,153 @@ pub const SliceInStream = struct {
463465 }
464466};
465467
468/// Creates a stream which allows for reading bit fields from another stream
469pub fn BitInStream(endian: builtin.Endian, comptime Error: type) type {
470 return struct {
471 const Self = @This();
472
473 in_stream: *Stream,
474 bit_buffer: u7,
475 bit_count: u3,
476 stream: Stream,
477
478 pub const Stream = InStream(Error);
479 const u8_bit_count = comptime meta.bitCount(u8);
480 const u7_bit_count = comptime meta.bitCount(u7);
481 const u4_bit_count = comptime meta.bitCount(u4);
482
483 pub fn init(in_stream: *Stream) Self {
484 return Self{
485 .in_stream = in_stream,
486 .bit_buffer = 0,
487 .bit_count = 0,
488 .stream = Stream{ .readFn = read },
489 };
490 }
491
492 /// Reads `bits` bits from the stream and returns a specified unsigned int type
493 /// containing them in the least significant end, returning an error if the
494 /// specified number of bits could not be read.
495 pub fn readBitsNoEof(self: *Self, comptime U: type, bits: usize) !U {
496 var n: usize = undefined;
497 const result = try self.readBits(U, bits, &n);
498 if (n < bits) return error.EndOfStream;
499 return result;
500 }
501
502 /// Reads `bits` bits from the stream and returns a specified unsigned int type
503 /// containing them in the least significant end. The number of bits successfully
504 /// read is placed in `out_bits`, as reaching the end of the stream is not an error.
505 pub fn readBits(self: *Self, comptime U: type, bits: usize, out_bits: *usize) Error!U {
506 debug.assert(trait.isUnsignedInt(U));
507
508 //by extending the buffer to a minimum of u8 we can cover a number of edge cases
509 // related to shifting and casting.
510 const u_bit_count = comptime meta.bitCount(U);
511 const buf_bit_count = bc: {
512 debug.assert(u_bit_count >= bits);
513 break :bc if (u_bit_count <= u8_bit_count) u8_bit_count else u_bit_count;
514 };
515 const Buf = @IntType(false, buf_bit_count);
516 const BufShift = math.Log2Int(Buf);
517
518 out_bits.* = usize(0);
519 if (U == u0 or bits == 0) return 0;
520 var out_buffer = Buf(0);
521
522 if (self.bit_count > 0) {
523 const n = if (self.bit_count >= bits) @intCast(u3, bits) else self.bit_count;
524 const shift = u7_bit_count - n;
525 switch (endian) {
526 builtin.Endian.Big => {
527 out_buffer = Buf(self.bit_buffer >> shift);
528 self.bit_buffer <<= n;
529 },
530 builtin.Endian.Little => {
531 const value = (self.bit_buffer << shift) >> shift;
532 out_buffer = Buf(value);
533 self.bit_buffer >>= n;
534 },
535 }
536 self.bit_count -= n;
537 out_bits.* = n;
538 }
539 //at this point we know bit_buffer is empty
540
541 //copy bytes until we have enough bits, then leave the rest in bit_buffer
542 while (out_bits.* < bits) {
543 const n = bits - out_bits.*;
544 const next_byte = self.in_stream.readByte() catch |err| {
545 if (err == error.EndOfStream) {
546 return @intCast(U, out_buffer);
547 }
548 //@BUG: See #1810. Not sure if the bug is that I have to do this for some
549 // streams, or that I don't for streams with emtpy errorsets.
550 return @errSetCast(Error, err);
551 };
552
553 switch (endian) {
554 builtin.Endian.Big => {
555 if (n >= u8_bit_count) {
556 out_buffer <<= @intCast(u3, u8_bit_count - 1);
557 out_buffer <<= 1;
558 out_buffer |= Buf(next_byte);
559 out_bits.* += u8_bit_count;
560 continue;
561 }
562
563 const shift = @intCast(u3, u8_bit_count - n);
564 out_buffer <<= @intCast(BufShift, n);
565 out_buffer |= Buf(next_byte >> shift);
566 out_bits.* += n;
567 self.bit_buffer = @truncate(u7, next_byte << @intCast(u3, n - 1));
568 self.bit_count = shift;
569 },
570 builtin.Endian.Little => {
571 if (n >= u8_bit_count) {
572 out_buffer |= Buf(next_byte) << @intCast(BufShift, out_bits.*);
573 out_bits.* += u8_bit_count;
574 continue;
575 }
576
577 const shift = @intCast(u3, u8_bit_count - n);
578 const value = (next_byte << shift) >> shift;
579 out_buffer |= Buf(value) << @intCast(BufShift, out_bits.*);
580 out_bits.* += n;
581 self.bit_buffer = @truncate(u7, next_byte >> @intCast(u3, n));
582 self.bit_count = shift;
583 },
584 }
585 }
586
587 return @intCast(U, out_buffer);
588 }
589
590 pub fn alignToByte(self: *Self) void {
591 self.bit_buffer = 0;
592 self.bit_count = 0;
593 }
594
595 pub fn read(self_stream: *Stream, buffer: []u8) Error!usize {
596 var self = @fieldParentPtr(Self, "stream", self_stream);
597
598 var out_bits: usize = undefined;
599 var out_bits_total = usize(0);
600 //@NOTE: I'm not sure this is a good idea, maybe alignToByte should be forced
601 if (self.bit_count > 0) {
602 for (buffer) |*b, i| {
603 b.* = try self.readBits(u8, u8_bit_count, &out_bits);
604 out_bits_total += out_bits;
605 }
606 const incomplete_byte = @boolToInt(out_bits_total % u8_bit_count > 0);
607 return (out_bits_total / u8_bit_count) + incomplete_byte;
608 }
609
610 return self.in_stream.read(buffer);
611 }
612 };
613}
614
466615/// This is a simple OutStream that writes to a slice, and returns an error
467616/// when it runs out of space.
468617pub const SliceOutStream = struct {
......@@ -656,6 +805,137 @@ pub const BufferOutStream = struct {
656805 }
657806};
658807
808/// Creates a stream which allows for writing bit fields to another stream
809pub fn BitOutStream(endian: builtin.Endian, comptime Error: type) type {
810 return struct {
811 const Self = @This();
812
813 out_stream: *Stream,
814 bit_buffer: u8,
815 bit_count: u4,
816 stream: Stream,
817
818 pub const Stream = OutStream(Error);
819 const u8_bit_count = comptime meta.bitCount(u8);
820 const u4_bit_count = comptime meta.bitCount(u4);
821
822 pub fn init(out_stream: *Stream) Self {
823 return Self{
824 .out_stream = out_stream,
825 .bit_buffer = 0,
826 .bit_count = 0,
827 .stream = Stream{ .writeFn = write },
828 };
829 }
830
831 /// Write the specified number of bits to the stream from the least significant bits of
832 /// the specified unsigned int value. Bits will only be written to the stream when there
833 /// are enough to fill a byte.
834 pub fn writeBits(self: *Self, value: var, bits: usize) Error!void {
835 if (bits == 0) return;
836
837 const U = @typeOf(value);
838 debug.assert(trait.isUnsignedInt(U));
839
840 //by extending the buffer to a minimum of u8 we can cover a number of edge cases
841 // related to shifting and casting.
842 const u_bit_count = comptime meta.bitCount(U);
843 const buf_bit_count = bc: {
844 debug.assert(u_bit_count >= bits);
845 break :bc if (u_bit_count <= u8_bit_count) u8_bit_count else u_bit_count;
846 };
847 const Buf = @IntType(false, buf_bit_count);
848 const BufShift = math.Log2Int(Buf);
849
850 const buf_value = @intCast(Buf, value);
851
852 const high_byte_shift = @intCast(BufShift, buf_bit_count - u8_bit_count);
853 var in_buffer = switch (endian) {
854 builtin.Endian.Big => buf_value << @intCast(BufShift, buf_bit_count - bits),
855 builtin.Endian.Little => buf_value,
856 };
857 var in_bits = bits;
858
859 if (self.bit_count > 0) {
860 const bits_remaining = u8_bit_count - self.bit_count;
861 const n = @intCast(u3, if (bits_remaining > bits) bits else bits_remaining);
862 switch (endian) {
863 builtin.Endian.Big => {
864 const shift = @intCast(BufShift, high_byte_shift + self.bit_count);
865 const v = @intCast(u8, in_buffer >> shift);
866 self.bit_buffer |= v;
867 in_buffer <<= n;
868 },
869 builtin.Endian.Little => {
870 const v = @truncate(u8, in_buffer) << @intCast(u3, self.bit_count);
871 self.bit_buffer |= v;
872 in_buffer >>= n;
873 },
874 }
875 self.bit_count += n;
876 in_bits -= n;
877
878 //if we didn't fill the buffer, it's because bits < bits_remaining;
879 if (self.bit_count != u8_bit_count) return;
880 try self.out_stream.writeByte(self.bit_buffer);
881 self.bit_buffer = 0;
882 self.bit_count = 0;
883 }
884 //at this point we know bit_buffer is empty
885
886 //copy bytes until we can't fill one anymore, then leave the rest in bit_buffer
887 while (in_bits >= u8_bit_count) {
888 switch (endian) {
889 builtin.Endian.Big => {
890 const v = @intCast(u8, in_buffer >> high_byte_shift);
891 try self.out_stream.writeByte(v);
892 in_buffer <<= @intCast(u3, u8_bit_count - 1);
893 in_buffer <<= 1;
894 },
895 builtin.Endian.Little => {
896 const v = @truncate(u8, in_buffer);
897 try self.out_stream.writeByte(v);
898 in_buffer >>= @intCast(u3, u8_bit_count - 1);
899 in_buffer >>= 1;
900 },
901 }
902 in_bits -= u8_bit_count;
903 }
904
905 if (in_bits > 0) {
906 self.bit_count = @intCast(u4, in_bits);
907 self.bit_buffer = switch (endian) {
908 builtin.Endian.Big => @truncate(u8, in_buffer >> high_byte_shift),
909 builtin.Endian.Little => @truncate(u8, in_buffer),
910 };
911 }
912 }
913
914 /// Flush any remaining bits to the stream.
915 pub fn flushBits(self: *Self) !void {
916 if (self.bit_count == 0) return;
917 try self.out_stream.writeByte(self.bit_buffer);
918 self.bit_buffer = 0;
919 self.bit_count = 0;
920 }
921
922 pub fn write(self_stream: *Stream, buffer: []const u8) Error!void {
923 var self = @fieldParentPtr(Self, "stream", self_stream);
924
925 //@NOTE: I'm not sure this is a good idea, maybe flushBits should be forced
926 if (self.bit_count > 0) {
927 for (buffer) |b, i|
928 try self.writeBits(b, u8_bit_count);
929 return;
930 }
931
932 return self.out_stream.write(buffer);
933 }
934 };
935}
936
937
938
659939pub const BufferedAtomicFile = struct {
660940 atomic_file: os.AtomicFile,
661941 file_stream: os.File.OutStream,
......@@ -696,11 +976,6 @@ pub const BufferedAtomicFile = struct {
696976 }
697977};
698978
699test "import io tests" {
700 comptime {
701 _ = @import("io_test.zig");
702 }
703}
704979
705980pub fn readLine(buf: *std.Buffer) ![]u8 {
706981 var stdin = try getStdIn();
......@@ -772,3 +1047,364 @@ test "io.readLineSliceFrom" {
7721047 debug.assert(mem.eql(u8, "Line 1", try readLineSliceFrom(stream, buf[0..])));
7731048 debug.assertError(readLineSliceFrom(stream, buf[0..]), error.OutOfMemory);
7741049}
1050
1051/// Creates a deserializer that deserializes types from any stream.
1052/// If `is_packed` is true, the data stream is treated as bit-packed,
1053/// otherwise data is expected to be packed to the smallest byte.
1054/// Types may implement a custom deserialization routine with a
1055/// function named `deserialize` in the form of:
1056/// pub fn deserialize(self: *Self, deserializer: var) !void
1057/// which will be called when the deserializer is used to deserialize
1058/// that type. It will pass a pointer to the type instance to deserialize
1059/// into and a pointer to the deserializer struct.
1060pub fn Deserializer(endian: builtin.Endian, is_packed: bool, comptime Error: type) type {
1061 return struct {
1062 const Self = @This();
1063
1064 in_stream: if (is_packed) BitInStream(endian, Stream.Error) else *Stream,
1065
1066 pub const Stream = InStream(Error);
1067
1068 pub fn init(in_stream: *Stream) Self {
1069 return Self{ .in_stream = switch (is_packed) {
1070 true => BitInStream(endian, Stream.Error).init(in_stream),
1071 else => in_stream,
1072 } };
1073 }
1074
1075 pub fn alignToByte(self: *Self) void {
1076 if(!is_packed) return;
1077 self.in_stream.alignToByte();
1078 }
1079
1080 //@BUG: inferred error issue. See: #1386
1081 fn deserializeInt(self: *Self, comptime T: type) (Stream.Error || error{EndOfStream})!T {
1082 debug.assert(trait.is(builtin.TypeId.Int)(T) or trait.is(builtin.TypeId.Float)(T));
1083
1084 const u8_bit_count = comptime meta.bitCount(u8);
1085 const t_bit_count = comptime meta.bitCount(T);
1086
1087 const U = @IntType(false, t_bit_count);
1088 const Log2U = math.Log2Int(U);
1089 const int_size = @sizeOf(U);
1090
1091 if (is_packed) {
1092 const result = try self.in_stream.readBitsNoEof(U, t_bit_count);
1093 return @bitCast(T, result);
1094 }
1095
1096 var buffer: [int_size]u8 = undefined;
1097 const read_size = try self.in_stream.read(buffer[0..]);
1098 if (read_size < int_size) return error.EndOfStream;
1099
1100 if (int_size == 1) return @bitCast(T, buffer[0]);
1101
1102 var result = U(0);
1103 for (buffer) |byte, i| {
1104 switch (endian) {
1105 builtin.Endian.Big => {
1106 result = (result << @intCast(u4, u8_bit_count)) | byte;
1107 },
1108 builtin.Endian.Little => {
1109 result |= U(byte) << @intCast(Log2U, u8_bit_count * i);
1110 },
1111 }
1112 }
1113
1114 return @bitCast(T, result);
1115 }
1116
1117 //@TODO: Replace this with @unionInit or whatever when it is added
1118 // see: #1315
1119 fn setTag(ptr: var, tag: var) void {
1120 const T = @typeOf(ptr);
1121 comptime debug.assert(trait.isPtrTo(builtin.TypeId.Union)(T));
1122 const U = meta.Child(T);
1123
1124 const info = @typeInfo(U).Union;
1125 if (info.tag_type) |TagType| {
1126 debug.assert(TagType == @typeOf(tag));
1127
1128 var ptr_tag = ptr: {
1129 if (@alignOf(TagType) >= @alignOf(U)) break :ptr @ptrCast(*TagType, ptr);
1130 const offset = comptime max: {
1131 var max_field_size: comptime_int = 0;
1132 for (info.fields) |field_info| {
1133 const field_size = @sizeOf(field_info.field_type);
1134 max_field_size = math.max(max_field_size, field_size);
1135 }
1136 break :max math.max(max_field_size, @alignOf(U));
1137 };
1138 break :ptr @intToPtr(*TagType, @ptrToInt(ptr) + offset);
1139 };
1140 ptr_tag.* = tag;
1141 }
1142 }
1143
1144 /// Deserializes and returns data of the specified type from the stream
1145 pub fn deserialize(self: *Self, comptime T: type) !T {
1146 var value: T = undefined;
1147 try self.deserializeInto(&value);
1148 return value;
1149 }
1150
1151 /// Deserializes data into the type pointed to by `ptr`
1152 pub fn deserializeInto(self: *Self, ptr: var) !void {
1153 const T = @typeOf(ptr);
1154 debug.assert(trait.is(builtin.TypeId.Pointer)(T));
1155
1156 if (comptime trait.isSlice(T) or comptime trait.isPtrTo(builtin.TypeId.Array)(T)) {
1157 for (ptr) |*v|
1158 try self.deserializeInto(v);
1159 return;
1160 }
1161
1162 comptime debug.assert(trait.isSingleItemPtr(T));
1163
1164 const C = comptime meta.Child(T);
1165 const child_type_id = @typeId(C);
1166
1167 //custom deserializer: fn(self: *Self, deserializer: var) !void
1168 if (comptime trait.hasFn("deserialize")(C)) return C.deserialize(ptr, self);
1169
1170 if (comptime trait.isPacked(C) and !is_packed) {
1171 var packed_deserializer = Deserializer(endian, true, Error).init(self.in_stream);
1172 return packed_deserializer.deserializeInto(ptr);
1173 }
1174
1175 switch (child_type_id) {
1176 builtin.TypeId.Void => return,
1177 builtin.TypeId.Bool => ptr.* = (try self.deserializeInt(u1)) > 0,
1178 builtin.TypeId.Float, builtin.TypeId.Int => ptr.* = try self.deserializeInt(C),
1179 builtin.TypeId.Struct => {
1180 const info = @typeInfo(C).Struct;
1181
1182 inline for (info.fields) |*field_info| {
1183 const name = field_info.name;
1184 const FieldType = field_info.field_type;
1185
1186 if (FieldType == void or FieldType == u0) continue;
1187
1188 //it doesn't make any sense to read pointers
1189 if (comptime trait.is(builtin.TypeId.Pointer)(FieldType)) {
1190 @compileError("Will not " ++ "read field " ++ name ++ " of struct " ++
1191 @typeName(C) ++ " because it " ++ "is of pointer-type " ++
1192 @typeName(FieldType) ++ ".");
1193 }
1194
1195 try self.deserializeInto(&@field(ptr, name));
1196 }
1197 },
1198 builtin.TypeId.Union => {
1199 const info = @typeInfo(C).Union;
1200 if (info.tag_type) |TagType| {
1201 //we avoid duplicate iteration over the enum tags
1202 // by getting the int directly and casting it without
1203 // safety. If it is bad, it will be caught anyway.
1204 const TagInt = @TagType(TagType);
1205 const tag = try self.deserializeInt(TagInt);
1206
1207 {
1208 @setRuntimeSafety(false);
1209 //See: #1315
1210 setTag(ptr, @intToEnum(TagType, tag));
1211 }
1212
1213 inline for (info.fields) |field_info| {
1214 if (field_info.enum_field.?.value == tag) {
1215 const name = field_info.name;
1216 const FieldType = field_info.field_type;
1217 @field(ptr, name) = FieldType(undefined);
1218 try self.deserializeInto(&@field(ptr, name));
1219 return;
1220 }
1221 }
1222 //This is reachable if the enum data is bad
1223 return error.InvalidEnumTag;
1224 }
1225 @compileError("Cannot meaningfully deserialize " ++ @typeName(C) ++
1226 " because it is an untagged union Use a custom deserialize().");
1227 },
1228 builtin.TypeId.Optional => {
1229 const OC = comptime meta.Child(C);
1230 const exists = (try self.deserializeInt(u1)) > 0;
1231 if (!exists) {
1232 ptr.* = null;
1233 return;
1234 }
1235
1236 //The way non-pointer optionals are implemented ensures a pointer to them
1237 // will point to the value. The flag is stored at the end of that data.
1238 var val_ptr = @ptrCast(*OC, ptr);
1239 try self.deserializeInto(val_ptr);
1240 //This bit ensures the null flag isn't set. Any actual copying should be
1241 // optimized out... I hope.
1242 ptr.* = val_ptr.*;
1243 },
1244 builtin.TypeId.Enum => {
1245 var value = try self.deserializeInt(@TagType(C));
1246 ptr.* = try meta.intToEnum(C, value);
1247 },
1248 else => {
1249 @compileError("Cannot deserialize " ++ @tagName(child_type_id) ++ " types (unimplemented).");
1250 },
1251 }
1252 }
1253 };
1254}
1255
1256/// Creates a serializer that serializes types to any stream.
1257/// If `is_packed` is true, the data will be bit-packed into the stream.
1258/// Note that the you must call `serializer.flush()` when you are done
1259/// writing bit-packed data in order ensure any unwritten bits are committed.
1260/// If `is_packed` is false, data is packed to the smallest byte. In the case
1261/// of packed structs, the struct will written bit-packed and with the specified
1262/// endianess, after which data will resume being written at the next byte boundary.
1263/// Types may implement a custom serialization routine with a
1264/// function named `serialize` in the form of:
1265/// pub fn serialize(self: Self, serializer: var) !void
1266/// which will be called when the serializer is used to serialize that type. It will
1267/// pass a const pointer to the type instance to be serialized and a pointer
1268/// to the serializer struct.
1269pub fn Serializer(endian: builtin.Endian, is_packed: bool, comptime Error: type) type {
1270 return struct {
1271 const Self = @This();
1272
1273 out_stream: if (is_packed) BitOutStream(endian, Stream.Error) else *Stream,
1274
1275 pub const Stream = OutStream(Error);
1276
1277 pub fn init(out_stream: *Stream) Self {
1278 return Self{ .out_stream = switch (is_packed) {
1279 true => BitOutStream(endian, Stream.Error).init(out_stream),
1280 else => out_stream,
1281 } };
1282 }
1283
1284 /// Flushes any unwritten bits to the stream
1285 pub fn flush(self: *Self) Stream.Error!void {
1286 if (is_packed) return self.out_stream.flushBits();
1287 }
1288
1289 fn serializeInt(self: *Self, value: var) !void {
1290 const T = @typeOf(value);
1291 debug.assert(trait.is(builtin.TypeId.Int)(T) or trait.is(builtin.TypeId.Float)(T));
1292
1293 const t_bit_count = comptime meta.bitCount(T);
1294 const u8_bit_count = comptime meta.bitCount(u8);
1295
1296 const U = @IntType(false, t_bit_count);
1297 const Log2U = math.Log2Int(U);
1298 const int_size = @sizeOf(U);
1299
1300 const u_value = @bitCast(U, value);
1301
1302 if (is_packed) return self.out_stream.writeBits(u_value, t_bit_count);
1303
1304 var buffer: [int_size]u8 = undefined;
1305 if (int_size == 1) buffer[0] = u_value;
1306
1307 for (buffer) |*byte, i| {
1308 const idx = switch (endian) {
1309 builtin.Endian.Big => int_size - i - 1,
1310 builtin.Endian.Little => i,
1311 };
1312 const shift = @intCast(Log2U, idx * u8_bit_count);
1313 const v = u_value >> shift;
1314 byte.* = if (t_bit_count < u8_bit_count) v else @truncate(u8, v);
1315 }
1316
1317 try self.out_stream.write(buffer);
1318 }
1319
1320 /// Serializes the passed value into the stream
1321 pub fn serialize(self: *Self, value: var) !void {
1322 const T = comptime @typeOf(value);
1323
1324 if (comptime trait.isIndexable(T)) {
1325 for (value) |v|
1326 try self.serialize(v);
1327 return;
1328 }
1329
1330 //custom serializer: fn(self: Self, serializer: var) !void
1331 if (comptime trait.hasFn("serialize")(T)) return T.serialize(value, self);
1332
1333 if (comptime trait.isPacked(T) and !is_packed) {
1334 var packed_serializer = Serializer(endian, true, Error).init(self.out_stream);
1335 try packed_serializer.serialize(value);
1336 try packed_serializer.flush();
1337 return;
1338 }
1339
1340 switch (@typeId(T)) {
1341 builtin.TypeId.Void => return,
1342 builtin.TypeId.Bool => try self.serializeInt(u1(@boolToInt(value))),
1343 builtin.TypeId.Float, builtin.TypeId.Int => try self.serializeInt(value),
1344 builtin.TypeId.Struct => {
1345 const info = @typeInfo(T);
1346
1347 inline for (info.Struct.fields) |*field_info| {
1348 const name = field_info.name;
1349 const FieldType = field_info.field_type;
1350
1351 if (FieldType == void or FieldType == u0) continue;
1352
1353 //It doesn't make sense to write pointers
1354 if (comptime trait.is(builtin.TypeId.Pointer)(FieldType)) {
1355 @compileError("Will not " ++ "serialize field " ++ name ++
1356 " of struct " ++ @typeName(T) ++ " because it " ++
1357 "is of pointer-type " ++ @typeName(FieldType) ++ ".");
1358 }
1359 try self.serialize(@field(value, name));
1360 }
1361 },
1362 builtin.TypeId.Union => {
1363 const info = @typeInfo(T).Union;
1364 if (info.tag_type) |TagType| {
1365 const active_tag = meta.activeTag(value);
1366 try self.serialize(active_tag);
1367 //This inline loop is necessary because active_tag is a runtime
1368 // value, but @field requires a comptime value. Our alternative
1369 // is to check each field for a match
1370 inline for (info.fields) |field_info| {
1371 if (field_info.enum_field.?.value == @enumToInt(active_tag)) {
1372 const name = field_info.name;
1373 const FieldType = field_info.field_type;
1374 try self.serialize(@field(value, name));
1375 return;
1376 }
1377 }
1378 unreachable;
1379 }
1380 @compileError("Cannot meaningfully serialize " ++ @typeName(T) ++
1381 " because it is an untagged union Use a custom serialize().");
1382 },
1383 builtin.TypeId.Optional => {
1384 if (value == null) {
1385 try self.serializeInt(u1(@boolToInt(false)));
1386 return;
1387 }
1388 try self.serializeInt(u1(@boolToInt(true)));
1389
1390 const OC = comptime meta.Child(T);
1391
1392 //The way non-pointer optionals are implemented ensures a pointer to them
1393 // will point to the value. The flag is stored at the end of that data.
1394 var val_ptr = @ptrCast(*const OC, &value);
1395 try self.serialize(val_ptr.*);
1396 },
1397 builtin.TypeId.Enum => {
1398 try self.serializeInt(@enumToInt(value));
1399 },
1400 else => @compileError("Cannot serialize " ++ @tagName(@typeId(T)) ++ " types (unimplemented)."),
1401 }
1402 }
1403 };
1404}
1405
1406test "import io tests" {
1407 comptime {
1408 _ = @import("io_test.zig");
1409 }
1410}
std/io_test.zig+437
......@@ -1,5 +1,7 @@
11const std = @import("index.zig");
22const io = std.io;
3const meta = std.meta;
4const trait = std.trait;
35const DefaultPrng = std.rand.DefaultPrng;
46const assert = std.debug.assert;
57const assertError = std.debug.assertError;
......@@ -132,3 +134,438 @@ test "SliceOutStream" {
132134 assertError(ss.stream.write("Hello world!"), error.OutOfSpace);
133135 assert(mem.eql(u8, ss.getWritten(), "Hello worl"));
134136}
137
138test "BitInStream" {
139 const mem_be = []u8{ 0b11001101, 0b00001011 };
140 const mem_le = []u8{ 0b00011101, 0b10010101 };
141
142 var mem_in_be = io.SliceInStream.init(mem_be[0..]);
143 const InError = io.SliceInStream.Error;
144 var bit_stream_be = io.BitInStream(builtin.Endian.Big, InError).init(&mem_in_be.stream);
145
146 var out_bits: usize = undefined;
147
148 assert(1 == try bit_stream_be.readBits(u2, 1, &out_bits));
149 assert(out_bits == 1);
150 assert(2 == try bit_stream_be.readBits(u5, 2, &out_bits));
151 assert(out_bits == 2);
152 assert(3 == try bit_stream_be.readBits(u128, 3, &out_bits));
153 assert(out_bits == 3);
154 assert(4 == try bit_stream_be.readBits(u8, 4, &out_bits));
155 assert(out_bits == 4);
156 assert(5 == try bit_stream_be.readBits(u9, 5, &out_bits));
157 assert(out_bits == 5);
158 assert(1 == try bit_stream_be.readBits(u1, 1, &out_bits));
159 assert(out_bits == 1);
160
161 mem_in_be.pos = 0;
162 bit_stream_be.bit_count = 0;
163 assert(0b110011010000101 == try bit_stream_be.readBits(u15, 15, &out_bits));
164 assert(out_bits == 15);
165
166 mem_in_be.pos = 0;
167 bit_stream_be.bit_count = 0;
168 assert(0b1100110100001011 == try bit_stream_be.readBits(u16, 16, &out_bits));
169 assert(out_bits == 16);
170
171 _ = try bit_stream_be.readBits(u0, 0, &out_bits);
172
173 assert(0 == try bit_stream_be.readBits(u1, 1, &out_bits));
174 assert(out_bits == 0);
175 assertError(bit_stream_be.readBitsNoEof(u1, 1), error.EndOfStream);
176
177 var mem_in_le = io.SliceInStream.init(mem_le[0..]);
178 var bit_stream_le = io.BitInStream(builtin.Endian.Little, InError).init(&mem_in_le.stream);
179
180 assert(1 == try bit_stream_le.readBits(u2, 1, &out_bits));
181 assert(out_bits == 1);
182 assert(2 == try bit_stream_le.readBits(u5, 2, &out_bits));
183 assert(out_bits == 2);
184 assert(3 == try bit_stream_le.readBits(u128, 3, &out_bits));
185 assert(out_bits == 3);
186 assert(4 == try bit_stream_le.readBits(u8, 4, &out_bits));
187 assert(out_bits == 4);
188 assert(5 == try bit_stream_le.readBits(u9, 5, &out_bits));
189 assert(out_bits == 5);
190 assert(1 == try bit_stream_le.readBits(u1, 1, &out_bits));
191 assert(out_bits == 1);
192
193 mem_in_le.pos = 0;
194 bit_stream_le.bit_count = 0;
195 assert(0b001010100011101 == try bit_stream_le.readBits(u15, 15, &out_bits));
196 assert(out_bits == 15);
197
198 mem_in_le.pos = 0;
199 bit_stream_le.bit_count = 0;
200 assert(0b1001010100011101 == try bit_stream_le.readBits(u16, 16, &out_bits));
201 assert(out_bits == 16);
202
203 _ = try bit_stream_le.readBits(u0, 0, &out_bits);
204
205 assert(0 == try bit_stream_le.readBits(u1, 1, &out_bits));
206 assert(out_bits == 0);
207 assertError(bit_stream_le.readBitsNoEof(u1, 1), error.EndOfStream);
208}
209
210test "BitOutStream" {
211 var mem_be = []u8{0} ** 2;
212 var mem_le = []u8{0} ** 2;
213
214 var mem_out_be = io.SliceOutStream.init(mem_be[0..]);
215 const OutError = io.SliceOutStream.Error;
216 var bit_stream_be = io.BitOutStream(builtin.Endian.Big, OutError).init(&mem_out_be.stream);
217
218 try bit_stream_be.writeBits(u2(1), 1);
219 try bit_stream_be.writeBits(u5(2), 2);
220 try bit_stream_be.writeBits(u128(3), 3);
221 try bit_stream_be.writeBits(u8(4), 4);
222 try bit_stream_be.writeBits(u9(5), 5);
223 try bit_stream_be.writeBits(u1(1), 1);
224
225 assert(mem_be[0] == 0b11001101 and mem_be[1] == 0b00001011);
226
227 mem_out_be.pos = 0;
228
229 try bit_stream_be.writeBits(u15(0b110011010000101), 15);
230 try bit_stream_be.flushBits();
231 assert(mem_be[0] == 0b11001101 and mem_be[1] == 0b00001010);
232
233 mem_out_be.pos = 0;
234 try bit_stream_be.writeBits(u32(0b110011010000101), 16);
235 assert(mem_be[0] == 0b01100110 and mem_be[1] == 0b10000101);
236
237 try bit_stream_be.writeBits(u0(0), 0);
238
239 var mem_out_le = io.SliceOutStream.init(mem_le[0..]);
240 var bit_stream_le = io.BitOutStream(builtin.Endian.Little, OutError).init(&mem_out_le.stream);
241
242 try bit_stream_le.writeBits(u2(1), 1);
243 try bit_stream_le.writeBits(u5(2), 2);
244 try bit_stream_le.writeBits(u128(3), 3);
245 try bit_stream_le.writeBits(u8(4), 4);
246 try bit_stream_le.writeBits(u9(5), 5);
247 try bit_stream_le.writeBits(u1(1), 1);
248
249 assert(mem_le[0] == 0b00011101 and mem_le[1] == 0b10010101);
250
251 mem_out_le.pos = 0;
252 try bit_stream_le.writeBits(u15(0b110011010000101), 15);
253 try bit_stream_le.flushBits();
254 assert(mem_le[0] == 0b10000101 and mem_le[1] == 0b01100110);
255
256 mem_out_le.pos = 0;
257 try bit_stream_le.writeBits(u32(0b1100110100001011), 16);
258 assert(mem_le[0] == 0b00001011 and mem_le[1] == 0b11001101);
259
260 try bit_stream_le.writeBits(u0(0), 0);
261}
262
263test "BitStreams with File Stream" {
264 const tmp_file_name = "temp_test_file.txt";
265 {
266 var file = try os.File.openWrite(tmp_file_name);
267 defer file.close();
268
269 var file_out = file.outStream();
270 var file_out_stream = &file_out.stream;
271 const OutError = os.File.WriteError;
272 var bit_stream = io.BitOutStream(builtin.endian, OutError).init(file_out_stream);
273
274 try bit_stream.writeBits(u2(1), 1);
275 try bit_stream.writeBits(u5(2), 2);
276 try bit_stream.writeBits(u128(3), 3);
277 try bit_stream.writeBits(u8(4), 4);
278 try bit_stream.writeBits(u9(5), 5);
279 try bit_stream.writeBits(u1(1), 1);
280 try bit_stream.flushBits();
281 }
282 {
283 var file = try os.File.openRead(tmp_file_name);
284 defer file.close();
285
286 var file_in = file.inStream();
287 var file_in_stream = &file_in.stream;
288 const InError = os.File.ReadError;
289 var bit_stream = io.BitInStream(builtin.endian, InError).init(file_in_stream);
290
291 var out_bits: usize = undefined;
292
293 assert(1 == try bit_stream.readBits(u2, 1, &out_bits));
294 assert(out_bits == 1);
295 assert(2 == try bit_stream.readBits(u5, 2, &out_bits));
296 assert(out_bits == 2);
297 assert(3 == try bit_stream.readBits(u128, 3, &out_bits));
298 assert(out_bits == 3);
299 assert(4 == try bit_stream.readBits(u8, 4, &out_bits));
300 assert(out_bits == 4);
301 assert(5 == try bit_stream.readBits(u9, 5, &out_bits));
302 assert(out_bits == 5);
303 assert(1 == try bit_stream.readBits(u1, 1, &out_bits));
304 assert(out_bits == 1);
305
306 assertError(bit_stream.readBitsNoEof(u1, 1), error.EndOfStream);
307 }
308 try os.deleteFile(tmp_file_name);
309}
310
311fn testIntSerializerDeserializer(comptime endian: builtin.Endian, comptime is_packed: bool) !void {
312 //@NOTE: if this test is taking too long, reduce the maximum tested bitsize
313 const max_test_bitsize = 128;
314
315 const total_bytes = comptime blk: {
316 var bytes = 0;
317 comptime var i = 0;
318 while (i <= max_test_bitsize) : (i += 1) bytes += (i / 8) + @boolToInt(i % 8 > 0);
319 break :blk bytes * 2;
320 };
321
322 var data_mem: [total_bytes]u8 = undefined;
323 var out = io.SliceOutStream.init(data_mem[0..]);
324 const OutError = io.SliceOutStream.Error;
325 var out_stream = &out.stream;
326 var serializer = io.Serializer(endian, is_packed, OutError).init(out_stream);
327
328 var in = io.SliceInStream.init(data_mem[0..]);
329 const InError = io.SliceInStream.Error;
330 var in_stream = &in.stream;
331 var deserializer = io.Deserializer(endian, is_packed, InError).init(in_stream);
332
333 comptime var i = 0;
334 inline while (i <= max_test_bitsize) : (i += 1) {
335 const U = @IntType(false, i);
336 const S = @IntType(true, i);
337 try serializer.serializeInt(U(i));
338 if (i != 0) try serializer.serializeInt(S(-1)) else try serializer.serialize(S(0));
339 }
340 try serializer.flush();
341
342 i = 0;
343 inline while (i <= max_test_bitsize) : (i += 1) {
344 const U = @IntType(false, i);
345 const S = @IntType(true, i);
346 const x = try deserializer.deserializeInt(U);
347 const y = try deserializer.deserializeInt(S);
348 assert(x == U(i));
349 if (i != 0) assert(y == S(-1)) else assert(y == 0);
350 }
351
352 const u8_bit_count = comptime meta.bitCount(u8);
353 //0 + 1 + 2 + ... n = (n * (n + 1)) / 2
354 //and we have each for unsigned and signed, so * 2
355 const total_bits = (max_test_bitsize * (max_test_bitsize + 1));
356 const extra_packed_byte = @boolToInt(total_bits % u8_bit_count > 0);
357 const total_packed_bytes = (total_bits / u8_bit_count) + extra_packed_byte;
358
359 assert(in.pos == if (is_packed) total_packed_bytes else total_bytes);
360}
361
362test "Serializer/Deserializer Int" {
363 try testIntSerializerDeserializer(builtin.Endian.Big, false);
364 try testIntSerializerDeserializer(builtin.Endian.Little, false);
365 try testIntSerializerDeserializer(builtin.Endian.Big, true);
366 try testIntSerializerDeserializer(builtin.Endian.Little, true);
367}
368
369fn testIntSerializerDeserializerInfNaN(comptime endian: builtin.Endian,
370 comptime is_packed: bool) !void
371{
372 const mem_size = (16*2 + 32*2 + 64*2 + 128*2) / comptime meta.bitCount(u8);
373 var data_mem: [mem_size]u8 = undefined;
374
375 var out = io.SliceOutStream.init(data_mem[0..]);
376 const OutError = io.SliceOutStream.Error;
377 var out_stream = &out.stream;
378 var serializer = io.Serializer(endian, is_packed, OutError).init(out_stream);
379
380 var in = io.SliceInStream.init(data_mem[0..]);
381 const InError = io.SliceInStream.Error;
382 var in_stream = &in.stream;
383 var deserializer = io.Deserializer(endian, is_packed, InError).init(in_stream);
384
385 //@TODO: isInf/isNan not currently implemented for f128.
386 try serializer.serialize(std.math.nan(f16));
387 try serializer.serialize(std.math.inf(f16));
388 try serializer.serialize(std.math.nan(f32));
389 try serializer.serialize(std.math.inf(f32));
390 try serializer.serialize(std.math.nan(f64));
391 try serializer.serialize(std.math.inf(f64));
392 //try serializer.serialize(std.math.nan(f128));
393 //try serializer.serialize(std.math.inf(f128));
394 const nan_check_f16 = try deserializer.deserialize(f16);
395 const inf_check_f16 = try deserializer.deserialize(f16);
396 const nan_check_f32 = try deserializer.deserialize(f32);
397 const inf_check_f32 = try deserializer.deserialize(f32);
398 const nan_check_f64 = try deserializer.deserialize(f64);
399 const inf_check_f64 = try deserializer.deserialize(f64);
400 //const nan_check_f128 = try deserializer.deserialize(f128);
401 //const inf_check_f128 = try deserializer.deserialize(f128);
402 assert(std.math.isNan(nan_check_f16));
403 assert(std.math.isInf(inf_check_f16));
404 assert(std.math.isNan(nan_check_f32));
405 assert(std.math.isInf(inf_check_f32));
406 assert(std.math.isNan(nan_check_f64));
407 assert(std.math.isInf(inf_check_f64));
408 //assert(std.math.isNan(nan_check_f128));
409 //assert(std.math.isInf(inf_check_f128));
410}
411
412test "Serializer/Deserializer Int: Inf/NaN" {
413 try testIntSerializerDeserializerInfNaN(builtin.Endian.Big, false);
414 try testIntSerializerDeserializerInfNaN(builtin.Endian.Little, false);
415 try testIntSerializerDeserializerInfNaN(builtin.Endian.Big, true);
416 try testIntSerializerDeserializerInfNaN(builtin.Endian.Little, true);
417}
418
419fn testAlternateSerializer(self: var, serializer: var) !void {
420 try serializer.serialize(self.f_f16);
421}
422
423fn testSerializerDeserializer(comptime endian: builtin.Endian, comptime is_packed: bool) !void {
424 const ColorType = enum(u4) {
425 RGB8 = 1,
426 RA16 = 2,
427 R32 = 3,
428 };
429
430 const TagAlign = union(enum(u32)) {
431 A: u8,
432 B: u8,
433 C: u8,
434 };
435
436 const Color = union(ColorType) {
437 RGB8: struct {
438 r: u8,
439 g: u8,
440 b: u8,
441 a: u8,
442 },
443 RA16: struct {
444 r: u16,
445 a: u16,
446 },
447 R32: u32,
448 };
449
450 const PackedStruct = packed struct {
451 f_i3: i3,
452 f_u2: u2,
453 };
454
455
456
457 //to test custom serialization
458 const Custom = struct {
459 f_f16: f16,
460 f_unused_u32: u32,
461
462 pub fn deserialize(self: *@This(), deserializer: var) !void {
463 try deserializer.deserializeInto(&self.f_f16);
464 self.f_unused_u32 = 47;
465 }
466
467 pub const serialize = testAlternateSerializer;
468 };
469
470 const MyStruct = struct {
471 f_i3: i3,
472 f_u8: u8,
473 f_tag_align: TagAlign,
474 f_u24: u24,
475 f_i19: i19,
476 f_void: void,
477 f_f32: f32,
478 f_f128: f128,
479 f_packed_0: PackedStruct,
480 f_i7arr: [10]i7,
481 f_of64n: ?f64,
482 f_of64v: ?f64,
483 f_color_type: ColorType,
484 f_packed_1: PackedStruct,
485 f_custom: Custom,
486 f_color: Color,
487 };
488
489 const my_inst = MyStruct{
490 .f_i3 = -1,
491 .f_u8 = 8,
492 .f_tag_align = TagAlign{ .B = 148 },
493 .f_u24 = 24,
494 .f_i19 = 19,
495 .f_void = {},
496 .f_f32 = 32.32,
497 .f_f128 = 128.128,
498 .f_packed_0 = PackedStruct{ .f_i3 = -1, .f_u2 = 2 },
499 .f_i7arr = [10]i7{ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 },
500 .f_of64n = null,
501 .f_of64v = 64.64,
502 .f_color_type = ColorType.R32,
503 .f_packed_1 = PackedStruct{ .f_i3 = 1, .f_u2 = 1 },
504 .f_custom = Custom{ .f_f16 = 38.63, .f_unused_u32 = 47 },
505 .f_color = Color{ .R32 = 123822 },
506 };
507
508 var data_mem: [@sizeOf(MyStruct)]u8 = undefined;
509 var out = io.SliceOutStream.init(data_mem[0..]);
510 const OutError = io.SliceOutStream.Error;
511 var out_stream = &out.stream;
512 var serializer = io.Serializer(endian, is_packed, OutError).init(out_stream);
513
514 var in = io.SliceInStream.init(data_mem[0..]);
515 const InError = io.SliceInStream.Error;
516 var in_stream = &in.stream;
517 var deserializer = io.Deserializer(endian, is_packed, InError).init(in_stream);
518
519 try serializer.serialize(my_inst);
520
521 const my_copy = try deserializer.deserialize(MyStruct);
522 assert(meta.eql(my_copy, my_inst));
523}
524
525test "Serializer/Deserializer generic" {
526 try testSerializerDeserializer(builtin.Endian.Big, false);
527 try testSerializerDeserializer(builtin.Endian.Little, false);
528 try testSerializerDeserializer(builtin.Endian.Big, true);
529 try testSerializerDeserializer(builtin.Endian.Little, true);
530}
531
532fn testBadData(comptime endian: builtin.Endian, comptime is_packed: bool) !void {
533 const E = enum(u14) {
534 One = 1,
535 Two = 2,
536 };
537
538 const A = struct {
539 e: E,
540 };
541
542 const C = union(E) {
543 One: u14,
544 Two: f16,
545 };
546
547 var data_mem: [4]u8 = undefined;
548 var out = io.SliceOutStream.init(data_mem[0..]);
549 const OutError = io.SliceOutStream.Error;
550 var out_stream = &out.stream;
551 var serializer = io.Serializer(endian, is_packed, OutError).init(out_stream);
552
553 var in = io.SliceInStream.init(data_mem[0..]);
554 const InError = io.SliceInStream.Error;
555 var in_stream = &in.stream;
556 var deserializer = io.Deserializer(endian, is_packed, InError).init(in_stream);
557
558 try serializer.serialize(u14(3));
559 assertError(deserializer.deserialize(A), error.InvalidEnumTag);
560 out.pos = 0;
561 try serializer.serialize(u14(3));
562 try serializer.serialize(u14(88));
563 assertError(deserializer.deserialize(C), error.InvalidEnumTag);
564}
565
566test "Deserializer bad data" {
567 try testBadData(builtin.Endian.Big, false);
568 try testBadData(builtin.Endian.Little, false);
569 try testBadData(builtin.Endian.Big, true);
570 try testBadData(builtin.Endian.Little, true);
571}
\ No newline at end of file
std/meta/index.zig+35-3
......@@ -95,7 +95,7 @@ test "std.meta.stringToEnum" {
9595 debug.assert(null == stringToEnum(E1, "C"));
9696}
9797
98pub fn bitCount(comptime T: type) u32 {
98pub fn bitCount(comptime T: type) comptime_int {
9999 return switch (@typeInfo(T)) {
100100 TypeId.Int => |info| info.bits,
101101 TypeId.Float => |info| info.bits,
......@@ -108,7 +108,7 @@ test "std.meta.bitCount" {
108108 debug.assert(bitCount(f32) == 32);
109109}
110110
111pub fn alignment(comptime T: type) u29 {
111pub fn alignment(comptime T: type) comptime_int {
112112 //@alignOf works on non-pointer types
113113 const P = if (comptime trait.is(TypeId.Pointer)(T)) T else *T;
114114 return @typeInfo(P).Pointer.alignment;
......@@ -386,6 +386,33 @@ test "std.meta.activeTag" {
386386 debug.assert(activeTag(u) == UE.Float);
387387}
388388
389///Given a tagged union type, and an enum, return the type of the union
390/// field corresponding to the enum tag.
391pub fn TagPayloadType(comptime U: type, tag: var) type {
392 const Tag = @typeOf(tag);
393 debug.assert(trait.is(builtin.TypeId.Union)(U));
394 debug.assert(trait.is(builtin.TypeId.Enum)(Tag));
395
396 const info = @typeInfo(U).Union;
397
398 inline for (info.fields) |field_info| {
399 if (field_info.enum_field.?.value == @enumToInt(tag)) return field_info.field_type;
400 }
401 unreachable;
402}
403
404test "std.meta.TagPayloadType" {
405 const Event = union(enum) {
406 Moved: struct {
407 from: i32,
408 to: i32,
409 },
410 };
411 const MovedEvent = TagPayloadType(Event, Event.Moved);
412 var e: Event = undefined;
413 debug.assert(MovedEvent == @typeOf(e.Moved));
414}
415
389416///Compares two of any type for equality. Containers are compared on a field-by-field basis,
390417/// where possible. Pointers are not followed.
391418pub fn eql(a: var, b: @typeOf(a)) bool {
......@@ -439,6 +466,11 @@ pub fn eql(a: var, b: @typeOf(a)) bool {
439466 builtin.TypeInfo.Pointer.Size.Slice => return a.ptr == b.ptr and a.len == b.len,
440467 }
441468 },
469 builtin.TypeId.Optional => {
470 if(a == null and b == null) return true;
471 if(a == null or b == null) return false;
472 return eql(a.?, b.?);
473 },
442474 else => return a == b,
443475 }
444476}
......@@ -452,7 +484,7 @@ test "std.meta.eql" {
452484
453485 const U = union(enum) {
454486 s: S,
455 f: f32,
487 f: ?f32,
456488 };
457489
458490 const s_1 = S{
std/meta/trait.zig+31
......@@ -231,6 +231,37 @@ test "std.meta.trait.isPacked" {
231231 debug.assert(!isPacked(u8));
232232}
233233
234///
235pub fn isUnsignedInt(comptime T: type) bool {
236 return switch (@typeId(T)) {
237 builtin.TypeId.Int => !@typeInfo(T).Int.is_signed,
238 else => false,
239 };
240}
241
242test "isUnsignedInt" {
243 debug.assert(isUnsignedInt(u32) == true);
244 debug.assert(isUnsignedInt(comptime_int) == false);
245 debug.assert(isUnsignedInt(i64) == false);
246 debug.assert(isUnsignedInt(f64) == false);
247}
248
249///
250pub fn isSignedInt(comptime T: type) bool {
251 return switch (@typeId(T)) {
252 builtin.TypeId.ComptimeInt => true,
253 builtin.TypeId.Int => @typeInfo(T).Int.is_signed,
254 else => false,
255 };
256}
257
258test "isSignedInt" {
259 debug.assert(isSignedInt(u32) == false);
260 debug.assert(isSignedInt(comptime_int) == true);
261 debug.assert(isSignedInt(i64) == true);
262 debug.assert(isSignedInt(f64) == false);
263}
264
234265///
235266pub fn isSingleItemPtr(comptime T: type) bool {
236267 if (comptime is(builtin.TypeId.Pointer)(T)) {