1 module cerealed.cerealiser;
2 
3 
4 import cerealed.cereal: grain;
5 import cerealed.range: DynamicArrayRange, ScopeBufferRange, isCerealiserRange;
6 import cerealed.traits: isCereal, isCerealiser;
7 import concepts: models;
8 import std.array: Appender;
9 
10 
11 alias AppenderCerealiser = CerealiserImpl!(Appender!(ubyte[]));
12 alias DynamicArrayCerealiser = CerealiserImpl!DynamicArrayRange;
13 alias ScopeBufferCerealiser = CerealiserImpl!ScopeBufferRange;
14 
15 alias Cerealiser = AppenderCerealiser; //the default, easy option
16 
17 /**
18  * Uses a ScopeBufferCerealiaser to write the bytes. The reason
19  * it takes a function as a template parameter is to be able
20  * to do something with the bytes. The bytes shouldn't be used
21  * directly because once the function exits that is no longer
22  * valid memory (it's been popped off the stack or freed).
23  */
24 auto cerealise(alias F, ushort N = 32, T)(auto ref T val) @system  {
25     static assert(N % 2 == 0, "cerealise must be passed an even number of bytes");
26     ubyte[N] buf = void;
27     auto sbufRange = ScopeBufferRange(buf);
28     auto enc = ScopeBufferCerealiser(sbufRange);
29     enc ~= val;
30     static if(is(ReturnType!F == void)) {
31          F(enc.bytes);
32     } else {
33          return F(enc.bytes);
34     }
35 }
36 
37 /**
38  * Slower version of $(D cerealise) that returns a ubyte slice.
39  * It's preferable to use the version with the lambda template alias
40  */
41 ubyte[] cerealise(T)(auto ref T val) {
42     auto enc = Cerealiser();
43     enc ~= val;
44     return enc.bytes.dup;
45 }
46 
47 alias cerealize = cerealise;
48 
49 @models!(Cerealiser, isCereal)
50 @models!(Cerealiser, isCerealiser)
51 struct CerealiserImpl(R) if(isCerealiserRange!R) {
52 
53     import cerealed.cereal: CerealType;
54     import std.traits: isArray, isAssociativeArray, isDynamicArray, isAggregateType, Unqual;
55 
56     //interface
57     enum type = CerealType.WriteBytes;
58 
59     void grainUByte(ref ubyte val) @trusted {
60         _output.put(val);
61     }
62 
63     void grainBits(ref uint value, int bits) @safe {
64         writeBits(value, bits);
65     }
66 
67     void grainClass(T)(T val) @trusted if(is(T == class)) {
68         import cerealed.cereal: grainClassImpl;
69 
70         if(val.classinfo.name in _childCerealisers) {
71             _childCerealisers[val.classinfo.name](this, val);
72         } else {
73             grainClassImpl(this, val);
74         }
75     }
76 
77     void grainRaw(ubyte[] val) @trusted {
78         _output.put(val);
79     }
80 
81     //specific:
82     this(R r) {
83         _output = r;
84     }
85 
86     const(ubyte[]) bytes() const nothrow @property @safe {
87         return _output.data;
88     }
89 
90     ref CerealiserImpl opOpAssign(string op : "~", T)(T val) @safe {
91         write(val);
92         return this;
93     }
94 
95     void write(T)(T val) @safe if(!isArray!T && !isAssociativeArray!T) {
96         Unqual!T lval = val;
97         grain(this, lval);
98     }
99 
100     void write(T)(const ref T val) @safe if(!isDynamicArray!T &&
101                                             !isAssociativeArray!T &&
102                                             !isAggregateType!T) {
103         T lval = val;
104         grain(this, lval);
105     }
106 
107     void write(T)(const(T)[] val) @trusted {
108         auto lval = cast(T[])val.dup;
109         grain(this, lval);
110     }
111 
112     void write(K, V)(const(V[K]) val) @trusted {
113         auto lval = cast(V[K])val.dup;
114         grain(this, lval);
115     }
116 
117     void writeBits(in int value, in int bits) @safe {
118         import std.conv: text;
119         import std.exception: enforce;
120 
121         enforce(value < (1 << bits), text("value ", value, " too big for ", bits, " bits"));
122         enum bitsInByte = 8;
123         if(_bitIndex + bits >= bitsInByte) { //carries over to next byte
124             const remainingBits = _bitIndex + bits - bitsInByte;
125             const thisByteValue = (value >> remainingBits);
126             _currentByte |= thisByteValue;
127             grainUByte(_currentByte);
128             _currentByte = 0;
129             _bitIndex = 0;
130             if(remainingBits > 0) {
131                 ubyte remainingValue = value & (0xff >> (bitsInByte - remainingBits));
132                 writeBits(remainingValue, remainingBits);
133             }
134             return;
135         }
136         _currentByte |= (value << (bitsInByte - bits - _bitIndex));
137         _bitIndex += bits;
138     }
139 
140     void reset() @safe {
141         _output.clear();
142     }
143 
144     static void registerChildClass(T)() @safe {
145         import cerealed.cereal: grainClassImpl;
146         _childCerealisers[T.classinfo.name] = (ref Cerealiser cereal, Object val) {
147             T child = cast(T)val;
148             cereal.grainClassImpl(child);
149         };
150     }
151 
152 private:
153 
154     R _output;
155     ubyte _currentByte;
156     int _bitIndex;
157     alias ChildCerealiser = void function(ref CerealiserImpl cereal, Object val);
158     static ChildCerealiser[string] _childCerealisers;
159 
160     // static assert(isCereal!CerealiserImpl);
161     // static assert(isCerealiser!CerealiserImpl);
162 }