1 module cerealed.decerealiser;
2 
3 import cerealed.cereal: grain;
4 import cerealed.traits: isCereal, isDecerealiser;
5 import concepts: models;
6 
7 auto decerealise(T)(in ubyte[] bytes) @trusted {
8     return Decerealiser(bytes).value!T;
9 }
10 
11 @models!(Decerealiser, isCereal)
12 @models!(Decerealiser, isDecerealiser)
13 struct Decerealiser {
14 
15     import cerealed.cereal: CerealType;
16     import std.traits: isNumeric, isDynamicArray, isAssociativeArray;
17 
18     //interface:
19     enum type = CerealType.ReadBytes;
20 
21     void grainUByte(ref ubyte val) @safe {
22         val = _bytes[0];
23         _bytes = _bytes[1..$];
24     }
25 
26     void grainBits(ref uint value, int bits) @safe {
27         value = readBits(bits);
28     }
29 
30     void grainClass(T)(T val) @trusted if(is(T == class)) {
31         import cerealed.cereal: grainClassImpl;
32         grainClassImpl(this, val);
33     }
34 
35     auto grainRaw(size_t length) @safe {
36         auto res = _bytes[0..length];
37         _bytes = _bytes[length..$];
38         return res;
39     }
40 
41     //specific:
42     this(T)(in T[] bytes) @safe if(isNumeric!T) {
43         setBytes(bytes);
44     }
45 
46     const(ubyte[]) bytes() const nothrow @property @safe {
47         return _bytes;
48     }
49 
50     ulong bytesLeft() const @safe { return bytes.length; }
51 
52     @property T value(T)() if(!isDynamicArray!T && !isAssociativeArray!T &&
53                               !is(T == class) && __traits(compiles, T())) {
54         T val;
55         grain(this, val);
56         return val;
57     }
58 
59     @property T value(T)() if(!isDynamicArray!T && !isAssociativeArray!T &&
60                               !is(T == class) && !__traits(compiles, T())) {
61         T val = void;
62         grain(this, val);
63         return val;
64     }
65 
66     @property @trusted T value(T, A...)(A args) if(is(T == class)) {
67         auto val = new T(args);
68         grain(this, val);
69         return val;
70     }
71 
72     @property @safe T value(T)() if(isDynamicArray!T || isAssociativeArray!T) {
73         return value!(T, ushort)();
74     }
75 
76     @property @safe T value(T, U)() if(isDynamicArray!T || isAssociativeArray!T) {
77         T val;
78         grain!U(this, val);
79         return val;
80     }
81 
82 
83     void reset() @safe {
84         /**resets the decerealiser to read from the beginning again*/
85         reset(_originalBytes);
86     }
87 
88     void reset(T)(in T[] bytes) @safe if(isNumeric!T) {
89         /**resets the decerealiser to use the new slice*/
90         _bitIndex = 0;
91         _currentByte = 0;
92         setBytes(bytes);
93     }
94 
95     void read(T)(ref T val) @trusted {
96         grain(this, val);
97     }
98 
99     uint readBits(int bits) @safe {
100         if(_bitIndex == 0) {
101             _currentByte = this.value!ubyte;
102         }
103 
104         return readBitsHelper(bits);
105     }
106 
107     const(ubyte)[] originalBytes() @safe pure nothrow const {
108         return _originalBytes;
109     }
110 
111 private:
112 
113     const (ubyte)[] _originalBytes;
114     const (ubyte)[] _bytes;
115     ubyte _currentByte;
116     int _bitIndex;
117 
118     uint readBitsHelper(int bits) @safe {
119         enum bitsInByte = 8;
120         if(_bitIndex + bits > bitsInByte) { //have to carry on to the next byte
121             immutable bits1stTime = bitsInByte - _bitIndex; //what's left of this byte
122             immutable bits2ndTime = (_bitIndex + bits) - bitsInByte; //bits to read from next byte
123             immutable value1 = readBitsHelper(bits1stTime);
124             _bitIndex = 0;
125             _currentByte = this.value!ubyte;
126             immutable value2 = readBitsHelper(bits2ndTime);
127             return (value1 << bits2ndTime) | value2;
128         }
129 
130         _bitIndex += bits;
131 
132         auto shift =  _currentByte >> (bitsInByte - _bitIndex);
133         return shift & (0xff >> (bitsInByte - bits));
134     }
135 
136     void setBytes(T)(in T[] bytes) @trusted if(isNumeric!T) {
137         static if(is(T == ubyte)) {
138             _bytes = bytes;
139         } else {
140             foreach(b; bytes) _bytes ~= cast(ubyte)b;
141         }
142 
143         _originalBytes = _bytes;
144     }
145 
146     // static assert(isCereal!Decerealiser);
147     // static assert(isDecerealiser!Decerealiser);
148 }