diff --git a/lib/std/io/stream.c3 b/lib/std/io/stream.c3 index 05e0c8028..c61eb6b92 100644 --- a/lib/std/io/stream.c3 +++ b/lib/std/io/stream.c3 @@ -383,3 +383,61 @@ macro usz! copy_through_buffer(Stream *self, Stream* dst, char[] buffer) @local if (written != len) return IoError.INCOMPLETE_WRITE?; } } + +const char[*] MAX_VARS @private = { [2] = 3, [4] = 5, [8] = 10 }; + +/** + * @require $typeof(x_ptr).kindof == POINTER && $typeof(x_ptr).inner.kindof.is_int() + **/ +macro usz! Stream.read_varint(&self, x_ptr) +{ + var $Type = $typefrom($typeof(x_ptr).inner); + const MAX = MAX_VARS[$Type.sizeof]; + $Type x; + uint shift; + usz n; + for (usz i = 0; i < MAX; i++) + { + char! c = self.read_byte(); + if (catch err = c) + { + case IoError.EOF: + return IoError.UNEXPECTED_EOF?; + default: + return err?; + } + n++; + if (c & 0x80 == 0) + { + if (i + 1 == MAX && c > 1) break; + x |= c << shift; + $if $Type.kindof == SIGNED_INT: + x = x & 1 == 0 ? x >> 1 : ~(x >> 1); + $endif + *x_ptr = x; + return n; + } + x |= (c & 0x7F) << shift; + shift += 7; + } + return MathError.OVERFLOW?; +} + +/** + * @require $typeof(x).kindof.is_int() + **/ +macro usz! Stream.write_varint(&self, x) +{ + var $Type = $typeof(x); + const MAX = MAX_VARS[$Type.sizeof]; + char[MAX] buffer; + usz i; + while (x >= 0x80) + { + buffer[i] = (char)(x | 0x80); + x >>= 7; + i++; + } + buffer[i] = (char)x; + return self.write_all(buffer[:i + 1]); +} \ No newline at end of file diff --git a/test/unit/stdlib/io/varint.c3 b/test/unit/stdlib/io/varint.c3 new file mode 100644 index 000000000..4cb61f035 --- /dev/null +++ b/test/unit/stdlib/io/varint.c3 @@ -0,0 +1,54 @@ +module std::io::varint @test; +import std::io; + +fn void! write_read() +{ + ByteBuffer buf; + buf.tinit(16)!; + usz n; + uint x; + uint y; + + n = buf.write_varint(123)!; + assert(n == 1, "got %d; want 1", n); + buf.read_varint(&y)!; + assert(y == 123, "got %d; want 123", y); + + n = buf.write_varint(123456789)!; + assert(n == 4, "got %d; want 4", n); + buf.read_varint(&y)!; + assert(y == 123456789, "got %d; want 123456789", y); +} + +struct VarIntTest +{ + uint in; + char[] bytes; +} + +fn void! samples() +{ + VarIntTest[] tcases = { + { 0, { 0x00 } }, + { 100, { 0x64 } }, + { 127, { 0x7F } }, + { 128, { 0x80, 0x01 } }, + { 16271, { 0x8F, 0x7F } }, + { 16383, { 0xFF, 0x7F } }, + { 16384, { 0x80, 0x80, 0x01 } }, + { 1048576, { 0x80, 0x80, 0x40 } }, + { 2097151, { 0xFF, 0xFF, 0x7F } }, + { 2097152, { 0x80, 0x80, 0x80, 0x01 } }, + { 2147483648, { 0x80, 0x80, 0x80, 0x80, 0x08 } }, + { 4294967295, { 0xFF, 0xFF, 0xFF, 0xFF, 0x0F } }, + }; + foreach (tc : tcases) + { + ByteWriter bw; + bw.tinit(); + usz n = bw.write_varint(tc.in)!; + assert(n == tc.bytes.len, "got %d; want %d", n, tc.bytes.len); + char[] bytes = bw.bytes[:bw.index]; + assert(bytes == tc.bytes, "got %d; want %d", bytes, tc.bytes); + } +} \ No newline at end of file