1 /// variable length unsigned integers
2 module sbin.vluint;
3 
4 import std.range;
5 import std.traits : isUnsigned;
6 
7 ///
8 void dumpVLUInt(R)(ref R r, ulong val) if (isOutputRange!(R, ubyte))
9 {
10     immutable mask = cast(ubyte)0b0111_1111;
11     immutable flag = cast(ubyte)0b1000_0000;
12     do
13     {
14         put(r, cast(ubyte)((val & mask) + (val >= flag ? flag : 0)));
15         val >>= 7;
16     }
17     while (val > 0);
18 }
19 
20 version (unittest)
21 {
22     import std.array : appender;
23     import std.exception : enforce;
24 }
25 
26 @safe
27 unittest
28 {
29     auto buf = appender!(ubyte[]);
30     foreach (i; 0 .. 128)
31     {
32         buf.clear();
33         dumpVLUInt(buf, i);
34         assert (buf.data.length == 1);
35         assert (buf.data[0] == i);
36     }
37 
38     foreach (i; 128 .. 256)
39     {
40         buf.clear();
41         dumpVLUInt(buf, i);
42         assert (buf.data.length == 2);
43         assert (buf.data[0] == (i & 0b0111_1111) + 0b1000_0000);
44         assert (buf.data[1] == 1);
45     }
46 
47     void test(ulong val, ubyte[] need, string file=__FILE__, size_t line=__LINE__)
48     {
49         buf.clear();
50         dumpVLUInt(buf, val);
51         enforce(buf.data == need, "fail", file, line);
52     }
53 
54     test(127, [0b0111_1111]);
55     test(128, [0b1000_0000, 1]);
56     test(129, [0b1000_0001, 1]);
57     test(130, [0b1000_0010, 1]);
58 
59     test(255, [0b1111_1111, 1]);
60     test(256, [0b1000_0000, 0b10]);
61     test(257, [0b1000_0001, 0b10]);
62 
63     test(16_382, [0b1111_1110, 0b0111_1111]);
64     test(16_383, [0b1111_1111, 0b0111_1111]);
65     test(16_384, [0b1000_0000, 0b1000_0000, 1]);
66     test(16_385, [0b1000_0001, 0b1000_0000, 1]);
67 
68     enum b = ubyte.max;
69     test(ulong.max-3, [b-3,b,b, b,b,b, b,b,b, 1]);
70     test(ulong.max-2, [b-2,b,b, b,b,b, b,b,b, 1]);
71     test(ulong.max-1, [b-1,b,b, b,b,b, b,b,b, 1]);
72     test(ulong.max, [b,b,b, b,b,b, b,b,b, 1]);
73 }
74 
75 ///
76 ulong readVLUInt(R)(auto ref R r)
77     if (is(typeof(r.front()) == ubyte) && is(typeof(r.popFront())))
78 {
79     uint count;
80     return readVLUInt(r, count);
81 }
82 
83 ///
84 ulong readVLUInt(R, T)(auto ref R r, out T count) //if (isInputRange!(R, ubyte))
85     if (is(typeof(r.front()) == ubyte) && is(typeof(r.popFront())) && is(T : size_t))
86 {
87     immutable mask = 0b0111_1111uL;
88     immutable flag = 0b1000_0000uL;
89 
90     ubyte b = 0;
91 
92     ulong ret;
93 
94     while (true)
95     {
96         const v = r.front(); r.popFront();
97         ret += (v & mask) << (b++ * 7);
98         if (!(v & flag))
99         {
100             count = b;
101             return ret;
102         }
103         if (b > 10) throw new Exception("so many data for one ulong value");
104     }
105 }
106 
107 @safe
108 unittest
109 {
110     foreach (ubyte i; 0 .. 128)
111     {
112         ubyte[] buf = [i];
113         assert(readVLUInt(buf) == i);
114     }
115 
116     @safe
117     void test(ulong val, ubyte[] need, string file=__FILE__, size_t line=__LINE__)
118     {
119         int cnt;
120         auto readed = readVLUInt(need.dup, cnt);
121         //import std.stdio;
122         //stderr.writeln(cnt, " ", need.length);
123         //stderr.writeln(val, " ", readed, " ", val-readed);
124         assert (cnt == need.length);
125         enforce(readed == val, "op", file, line);
126     }
127 
128     test(127, [0b0111_1111]);
129     test(128, [0b1000_0000, 1]);
130     test(129, [0b1000_0001, 1]);
131     test(130, [0b1000_0010, 1]);
132 
133     test(255, [0b1111_1111, 1]);
134     test(256, [0b1000_0000, 0b10]);
135     test(257, [0b1000_0001, 0b10]);
136 
137     test(16_382, [0b1111_1110, 0b0111_1111]);
138     test(16_383, [0b1111_1111, 0b0111_1111]);
139     test(16_384, [0b1000_0000, 0b1000_0000, 1]);
140     test(16_385, [0b1000_0001, 0b1000_0000, 1]);
141 
142     enum b = ubyte.max;
143 
144     test(ulong.max, [b,b,b, b,b,b, b,b,b, 1]);
145     test(ulong.max-1, [b-1,b,b, b,b,b, b,b,b, 1]);
146     test(ulong.max-2, [b-2,b,b, b,b,b, b,b,b, 1]);
147 }
148 
149 @safe
150 unittest
151 {
152     static struct StaticBuffer(size_t N)
153     {
154         ubyte[N] data;
155         size_t c;
156 
157         void put(ubyte v) { data[c++] = v; }
158 
159         ubyte front() { return data[c]; }
160         void popFront() { c++; }
161         bool empty() { return data.length == c; }
162     }
163 
164     StaticBuffer!10 buf;
165 
166     import std.range : chain, iota, repeat, take;
167     import std.random : uniform, Random;
168 
169     auto rnd = Random(12);
170 
171     auto values = chain(iota(ubyte.max),
172             [ushort.max/2, ushort.max-1, ushort.max, ushort.max+1, ushort.max+2, ushort.max * 2],
173             [uint.max/2, uint.max-1, uint.max, uint.max+1, uint.max+2, uint.max * 2],
174             [ulong.max/2, ulong.max-1, ulong.max],
175             uniform(0, ulong.max, rnd).repeat.take(10_000_000)
176             );
177 
178     import std.datetime.stopwatch;
179 
180     StopWatch sw1, sw2;
181 
182     foreach (i; values)
183     {
184         buf.c = 0;
185 
186         sw1.start();
187         dumpVLUInt(buf, i);
188         sw1.stop();
189 
190         buf.c = 0;
191 
192         sw2.start();
193         const v = readVLUInt(buf);
194         sw2.stop();
195         assert(i == v);
196     }
197 
198     //import std.stdio;
199     //stderr.writeln("dump: ", sw1.peek());
200     //stderr.writeln("read: ", sw2.peek());
201 }