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 }