1 /++ 2 $(H1 Scoped Buffer) 3 4 License: $(HTTP www.apache.org/licenses/LICENSE-2.0, Apache-2.0) 5 Authors: Ilya Yaroshenko 6 +/ 7 module mir.appender; 8 9 // import std.traits: isAssignable, hasElaborateDestructorhasElaborateCopyConstructor, hasElaborateAssign; 10 import mir.conv: _mir_destroy = xdestroy; 11 12 private extern(C) @system nothrow @nogc pure void* memcpy(scope void* s1, scope const void* s2, size_t n); 13 14 15 /// 16 struct ScopedBuffer(T, size_t bytes = 4096) 17 if (bytes && T.sizeof <= bytes) 18 { 19 import std.traits: Unqual, isMutable, isIterable, hasElaborateAssign, isAssignable, isArray; 20 import mir.primitives: hasLength; 21 import mir.conv: emplaceRef; 22 23 private enum size_t _bufferLength = bytes / T.sizeof + (bytes % T.sizeof != 0); 24 private T[] _buffer; 25 private size_t _currentLength; 26 27 version (mir_secure_memory) 28 private align(T.alignof) ubyte[_bufferLength * T.sizeof] _scopeBufferPayload; 29 else 30 private align(T.alignof) ubyte[_bufferLength * T.sizeof] _scopeBufferPayload = void; 31 32 private ref inout(T[_bufferLength]) _scopeBuffer() inout @trusted scope 33 { 34 return *cast(inout(T[_bufferLength])*)&_scopeBufferPayload; 35 } 36 37 private T[] prepare(size_t n) @trusted scope 38 { 39 import mir.internal.memory: realloc, malloc; 40 _currentLength += n; 41 if (_buffer.length == 0) 42 { 43 if (_currentLength <= _bufferLength) 44 { 45 return _scopeBuffer[0 .. _currentLength]; 46 } 47 else 48 { 49 auto newLen = _currentLength << 1; 50 if (auto p = malloc(T.sizeof * newLen)) 51 { 52 _buffer = (cast(T*)p)[0 .. newLen]; 53 } 54 else assert(0); 55 version (mir_secure_memory) 56 { 57 (cast(ubyte[])_buffer)[] = 0; 58 } 59 memcpy(cast(void*)_buffer.ptr, _scopeBuffer.ptr, T.sizeof * (_currentLength - n)); 60 } 61 } 62 else 63 if (_currentLength > _buffer.length) 64 { 65 auto newLen = _currentLength << 1; 66 _buffer = (cast(T*)realloc(cast(void*)_buffer.ptr, T.sizeof * newLen))[0 .. newLen]; 67 version (mir_secure_memory) 68 { 69 (cast(ubyte[])_buffer[_currentLength .. $])[] = 0; 70 } 71 } 72 return _buffer[0 .. _currentLength]; 73 } 74 75 static if (isAssignable!(T, const T)) 76 private alias R = const T; 77 else 78 private alias R = T; 79 80 /// Copy constructor is enabled only if `T` is mutable type without eleborate assign. 81 static if (isMutable!T && !hasElaborateAssign!T) 82 this(this) 83 { 84 import mir.internal.memory: malloc; 85 if (_buffer.ptr) 86 { 87 typeof(_buffer) buffer; 88 if (auto p = malloc(T.sizeof * _buffer.length)) 89 { 90 buffer = (cast(T*)p)[0 .. T.sizeof * _buffer.length]; 91 } 92 else assert(0); 93 version (mir_secure_memory) 94 { 95 (cast(ubyte[])buffer)[] = 0; 96 } 97 buffer[0 .. _currentLength] = _buffer[0 .. _currentLength]; 98 _buffer = buffer; 99 } 100 } 101 else 102 @disable this(this); 103 104 /// 105 ~this() 106 { 107 import mir.internal.memory: free; 108 data._mir_destroy; 109 version(mir_secure_memory) 110 _currentLength = 0; 111 (() @trusted { if (_buffer.ptr) free(cast(void*)_buffer.ptr); })(); 112 } 113 114 /// 115 void shrinkTo(size_t length) 116 { 117 assert(length <= _currentLength); 118 data[length .. _currentLength]._mir_destroy; 119 _currentLength = length; 120 } 121 122 /// 123 size_t length() scope const @property 124 { 125 return _currentLength; 126 } 127 128 /// 129 void popBackN(size_t n) 130 { 131 sizediff_t t = _currentLength - n; 132 if (t < 0) 133 assert(0, "ScopedBffer.popBackN: n is too large."); 134 data[t .. _currentLength]._mir_destroy; 135 _currentLength = t; 136 } 137 138 /// 139 void put(T e) @safe scope 140 { 141 auto cl = _currentLength; 142 auto d = prepare(1); 143 static if (isMutable!T) 144 { 145 import core.lifetime: moveEmplace; 146 ()@trusted{moveEmplace(e, d[cl]);}(); 147 } 148 else 149 { 150 emplaceRef!(Unqual!T)(d[cl], e); 151 } 152 } 153 154 static if (T.sizeof > 8 || hasElaborateAssign!T) 155 /// 156 void put(ref R e) scope 157 { 158 auto cl = _currentLength; 159 auto d = prepare(1); 160 emplaceRef!(Unqual!T)(d[cl], e); 161 } 162 163 static if (!hasElaborateAssign!T) 164 /// 165 void put(scope R[] e) scope 166 { 167 auto cl = _currentLength; 168 auto d = prepare(e.length); 169 if (!__ctfe) 170 (()@trusted=>memcpy(cast(void*)(d.ptr + cl), e.ptr, e.length * T.sizeof))(); 171 else 172 static if (isMutable!T) 173 (()@trusted=> d[cl .. cl + e.length] = e)(); 174 else 175 assert(0); 176 } 177 178 /// 179 void put(Iterable)(Iterable range) scope 180 if (isIterable!Iterable && !__traits(isStaticArray, Iterable) && (!isArray!Iterable || hasElaborateAssign!T)) 181 { 182 static if (hasLength!Iterable) 183 { 184 auto cl = _currentLength; 185 auto d = prepare(range.length); 186 foreach(ref e; range) 187 emplaceRef!(Unqual!T)(d[cl++], e); 188 assert(_currentLength == cl); 189 } 190 else 191 { 192 foreach(ref e; range) 193 put(e); 194 } 195 } 196 197 /// 198 void reset() scope nothrow 199 { 200 this.__dtor; 201 _currentLength = 0; 202 _buffer = null; 203 } 204 205 /// 206 void initialize() @system scope nothrow @nogc 207 { 208 _currentLength = 0; 209 _buffer = null; 210 } 211 212 /// 213 inout(T)[] data() inout @property @safe scope 214 { 215 return _buffer.length ? _buffer[0 .. _currentLength] : _scopeBuffer[0 .. _currentLength]; 216 } 217 218 /++ 219 Copies data into an array of the same length using `memcpy` C routine. 220 Shrinks the length to `0`. 221 +/ 222 void moveDataAndEmplaceTo(T[] array) @system 223 in { 224 assert(array.length == _currentLength); 225 } 226 do { 227 memcpy(cast(void*)array.ptr, data.ptr, _currentLength * T.sizeof); 228 _currentLength = 0; 229 } 230 } 231 232 /// ditto 233 auto scopedBuffer(T, size_t bytes = 4096)() @trusted 234 { 235 ScopedBuffer!(T, bytes) buffer = void; 236 buffer.initialize; 237 return buffer; 238 } 239 240 /// 241 @safe pure nothrow @nogc 242 version (mir_test) unittest 243 { 244 auto buf = scopedBuffer!char; 245 buf.put('c'); 246 buf.put("str"); 247 assert(buf.data == "cstr"); 248 249 buf.popBackN(2); 250 assert(buf.data == "cs"); 251 } 252 253 /// immutable 254 @safe pure nothrow @nogc 255 version (mir_test) unittest 256 { 257 auto buf = scopedBuffer!(immutable char); 258 buf.put('c'); 259 buf.put("str"); 260 assert(buf.data == "cstr"); 261 262 buf.popBackN(2); 263 assert(buf.data == "cs"); 264 } 265 266 @safe pure nothrow @nogc 267 version (mir_test) unittest 268 { 269 auto buf = scopedBuffer!(char, 3); 270 buf.put('c'); 271 buf.put("str"); 272 assert(buf.data == "cstr"); 273 274 buf.popBackN(2); 275 assert(buf.data == "cs"); 276 } 277 278 /// 279 struct UnsafeArrayBuffer(T) 280 { 281 import std.traits: isImplicitlyConvertible; 282 283 /// 284 T[] buffer; 285 /// 286 size_t length; 287 288 /// 289 void put(T a) 290 { 291 import core.lifetime: move; 292 assert(length < buffer.length); 293 buffer[length++] = move(a); 294 } 295 296 static if (isImplicitlyConvertible!(const T, T)) 297 private alias E = const T; 298 else 299 private alias E = T; 300 301 /// 302 void put(E[] a) 303 { 304 import core.lifetime: move; 305 assert(buffer.length >= a.length + length); 306 buffer[length .. length + a.length] = a; 307 length += a.length; 308 } 309 310 /// 311 inout(T)[] data() inout @property @safe scope 312 { 313 return buffer[0 .. length]; 314 } 315 316 /// 317 void popBackN(size_t n) 318 { 319 sizediff_t t = length - n; 320 if (t < 0) 321 assert(0, "UnsafeBuffer.popBackN: n is too large."); 322 buffer[t .. length]._mir_destroy; 323 length = t; 324 } 325 } 326 327 /// 328 @safe pure nothrow @nogc 329 version (mir_test) unittest 330 { 331 char[4] array; 332 auto buf = UnsafeArrayBuffer!char(array); 333 buf.put('c'); 334 buf.put("str"); 335 assert(buf.data == "cstr"); 336 337 buf.popBackN(2); 338 assert(buf.data == "cs"); 339 } 340 341 version(mir_bignum_test) // for DIP1000 342 @safe pure nothrow 343 unittest 344 { 345 import mir.conv: to; 346 import mir.algebraic : Algebraic; 347 static struct S 348 { 349 @safe pure nothrow @nogc: 350 @property string toString() const 351 { 352 return "_"; 353 } 354 } 355 Algebraic!(int, string, double) x; 356 x = 42; 357 auto s = x.to!string; 358 assert(s == "42", s); 359 x = "abc"; 360 assert(x.to!string == "abc"); 361 x = 42.0; 362 assert(x.to!string == "42.0"); 363 Algebraic!S y; 364 y = S(); 365 assert(y.to!string == "_"); 366 }