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