1 module xbuffer.varint;
2 
3 import std.traits : isIntegral, isSigned, isUnsigned, Unsigned;
4 
5 import xbuffer.buffer : Buffer;
6 
7 enum isVar(T) = is(T == Var!V, V);
8 
9 // debug
10 import std.stdio : writeln;
11 
12 unittest {
13 	
14 	static assert(isVar!varshort);
15 	static assert(!isVar!short);
16 	
17 }
18 
19 /**
20  * Utility container for reading and writing signed and unsigned
21  * varints from Google's protocol buffer.
22  */
23 struct Var(T) if(isIntegral!T && T.sizeof > 1) {
24 	
25 	alias Base = T;
26 	
27 	static if(isSigned!T) private alias U = Unsigned!T;
28 	else private enum size_t limit = T.sizeof * 8 / 7 + 1;
29 	
30 	@disable this();
31 	
32 	static void encode(Buffer buffer, T value) pure nothrow @safe @nogc {
33 		static if(isUnsigned!T) {
34 			while(value > 0x7F) {
35 				buffer.write!ubyte((value & 0x7F) | 0x80);
36 				value >>>= 7;
37 			}
38 			buffer.write!ubyte(value & 0x7F);
39 		} else {
40 			static if(T.sizeof < int.sizeof) Var!U.encode(buffer, cast(U)(value >= 0 ? value << 1 : (-cast(int)value << 1) - 1));
41 			else Var!U.encode(buffer, value >= 0 ? value << 1 : (-value << 1) - 1);
42 		}
43 	}
44 
45 	static T decode(bool consume)(Buffer buffer) pure @safe {
46 		size_t count = 0;
47 		static if(!consume) scope(success) buffer.back(count);
48 		return decodeImpl(buffer, count);
49 	}
50 
51 	static T decodeImpl(Buffer buffer, ref size_t count) pure @safe {
52 		static if(isUnsigned!T) {
53 			scope(failure) buffer.back(count);
54 			T ret;
55 			ubyte next;
56 			do {
57 				next = buffer.read!ubyte();
58 				ret |= T(next & 0x7F) << (count++ * 7);
59 			} while(next > 0x7F && count < limit);
60 			return ret;
61 		} else {
62 			U ret = Var!U.decodeImpl(buffer, count);
63 			if(ret & 1) return ((ret >> 1) + 1) * -1;
64 			else return ret >> 1;
65 		}
66 	}
67 	
68 }
69 
70 alias varshort = Var!short;
71 
72 alias varushort = Var!ushort;
73 
74 alias varint = Var!int;
75 
76 alias varuint = Var!uint;
77 
78 alias varlong = Var!long;
79 
80 alias varulong = Var!ulong;
81 
82 unittest {
83 	
84 	Buffer buffer = new Buffer(16);
85 	
86 	varint.encode(buffer, 0);
87 	assert(buffer.data!ubyte == [0]);
88 	
89 	buffer.reset();
90 	varshort.encode(buffer, -1);
91 	varint.encode(buffer, 1);
92 	varint.encode(buffer, -2);
93 	assert(buffer.data!ubyte == [1, 2, 3]);
94 	
95 	buffer.reset();
96 	varint.encode(buffer, 2147483647);
97 	varint.encode(buffer, -2147483648);
98 	assert(buffer.data!ubyte == [254, 255, 255, 255, 15, 255, 255, 255, 255, 15]);
99 
100 	assert(varint.decode!true(buffer) == 2147483647);
101 	assert(varint.decode!true(buffer) == -2147483648);
102 	
103 	buffer.data = cast(ubyte[])[1, 2, 3];
104 	assert(varint.decode!false(buffer) == -1);
105 	assert(varint.decode!true(buffer) == -1);
106 	assert(varint.decode!true(buffer) == 1);
107 	assert(varint.decode!true(buffer) == -2);
108 	
109 	varuint.encode(buffer, 1);
110 	varuint.encode(buffer, 2);
111 	varuint.encode(buffer, uint.max);
112 	assert(buffer.data!ubyte == [1, 2, 255, 255, 255, 255, 15]); 
113 	assert(varushort.decode!true(buffer) == 1);
114 	assert(varuint.decode!true(buffer) == 2);
115 	assert(varulong.decode!true(buffer) == uint.max);
116 
117 	// limit
118 
119 	buffer.data = cast(ubyte[])[255, 255, 255, 255, 255, 255];
120 	varuint.decode!true(buffer);
121 	assert(buffer.data!ubyte == [255]);
122 
123 	// exception
124 
125 	import xbuffer.buffer : BufferOverflowException;
126 
127 	buffer.data = cast(ubyte[])[255, 255, 255];
128 	try {
129 		varuint.decode!true(buffer); assert(0);
130 	} catch(BufferOverflowException) {
131 		assert(buffer.data!ubyte == [255, 255, 255]);
132 		varushort.decode!true(buffer);
133 		assert(buffer.data.length == 0);
134 	}
135 	
136 }