1 /* 2 * Copyright: 2014 by Digital Mars 3 * License: $(LINK2 http://boost.org/LICENSE_1_0.txt, Boost License 1.0). 4 * Authors: Walter Bright 5 * Source: $(PHOBOSSRC std/internal/_scopebuffer.d) 6 * 7 * This module is based on Walter Bright's ScopeBuffer with slight modification. 8 * https://raw.githubusercontent.com/WalterBright/phobos/master/std/internal/scopebuffer.d 9 * http://forum.dlang.org/thread/ld2586$17f6$1@digitalmars.com 10 * http://wiki.dlang.org/Std.buffer.scopebuffer 11 * 12 * Modifications: 13 * 14 * - Use size_t instead of uint. This makes it slower than Walter's original 15 * version but we don't have deal with hitting the 32-bit limit. 16 * 17 * - "T[] opSlice()" changed to "inout(T[]) opSlice() inout". 18 * 19 */ 20 21 module fluent.databuffer; 22 23 24 //debug=DataBuffer; 25 26 private import core.exception; 27 private import core.stdc.stdlib : realloc; 28 private import std.traits; 29 30 /************************************** 31 * encapsulates using a local array as a temporary buffer. 32 * It is initialized with the local array that should be large enough for 33 * most uses. If the need exceeds the size, DataBuffer will resize it 34 * using malloc() and friends. 35 * 36 * DataBuffer is an OutputRange. 37 * 38 * Since DataBuffer potentially stores elements of type T in malloc'd memory, 39 * those elements are not scanned when the GC collects. This can cause 40 * memory corruption. Do not use DataBuffer when elements of type T point 41 * to the GC heap. 42 * 43 * Example: 44 --- 45 import core.stdc.stdio; 46 import DataBuffer.databuffer; 47 void main() 48 { 49 char[2] buf = void; 50 auto textbuf = DataBuffer!char(buf); 51 scope(exit) textbuf.free(); // necessary for cleanup 52 53 // Put characters and strings into textbuf, verify they got there 54 textbuf.put('a'); 55 textbuf.put('x'); 56 textbuf.put("abc"); 57 assert(textbuf.length == 5); 58 assert(textbuf[1..3] == "xa"); 59 assert(textbuf[3] == 'b'); 60 61 // Can shrink it 62 textbuf.length = 3; 63 assert(textbuf[0..textbuf.length] == "axa"); 64 assert(textbuf[textbuf.length - 1] == 'a'); 65 assert(textbuf[1..3] == "xa"); 66 67 textbuf.put('z'); 68 assert(textbuf[] == "axaz"); 69 70 // Can shrink it to 0 size, and reuse same memory 71 textbuf.length = 0; 72 } 73 --- 74 * It is invalid to access DataBuffer's contents when DataBuffer goes out of scope. 75 * Hence, copying the contents are necessary to keep them around: 76 --- 77 import fluent.databuffer; 78 string cat(string s1, string s2) 79 { 80 char[10] tmpbuf = void; 81 auto textbuf = DataBuffer!char(tmpbuf); 82 scope(exit) textbuf.free(); 83 textbuf.put(s1); 84 textbuf.put(s2); 85 textbuf.put("even more"); 86 return textbuf[].idup; 87 } 88 --- 89 * DataBuffer is intended for high performance usages in $(D @system) and $(D @trusted) code. 90 * If used incorrectly, memory leaks and corruption can result. Be sure to use 91 * $(D scope(exit) textbuf.free();) for proper cleanup, and do not refer to a DataBuffer 92 * instance's contents after $(D DataBuffer.free()) has been called. 93 * 94 * The realloc parameter defaults to C's realloc(). Another can be supplied to override it. 95 * 96 * DataBuffer instances may be copied, as in: 97 --- 98 textbuf = doSomething(textbuf, args); 99 --- 100 * which can be very efficent, but these must be regarded as a move rather than a copy. 101 * Additionally, the code between passing and returning the instance must not throw 102 * exceptions, otherwise when DataBuffer.free() is called, memory may get corrupted. 103 */ 104 105 @system 106 struct DataBuffer(T, alias realloc = core.stdc.stdlib.realloc) 107 if (isAssignable!T && 108 !hasElaborateDestructor!T && 109 !hasElaborateCopyConstructor!T && 110 !hasElaborateAssign!T) 111 { 112 import core.stdc..string : memcpy; 113 114 /************************** 115 * Initialize with buf to use as scratch buffer space. 116 * Params: 117 * buf = Scratch buffer space, must have length that is even 118 * Example: 119 * --- 120 * ubyte[10] tmpbuf = void; 121 * auto sbuf = DataBuffer!ubyte(tmpbuf); 122 * --- 123 * If buf was created by the same realloc passed as a parameter 124 * to DataBuffer, then the contents of DataBuffer can be extracted without needing 125 * to copy them, and DataBuffer.free() will not need to be called. 126 */ 127 this(T[] buf) 128 in 129 { 130 assert(!(buf.length & wasResized)); // assure even length of scratch buffer space 131 } 132 body 133 { 134 this.buf = buf.ptr; 135 this.bufLen = buf.length; 136 } 137 138 unittest 139 { 140 ubyte[10] tmpbuf = void; 141 auto sbuf = DataBuffer!ubyte(tmpbuf); 142 } 143 144 /************************** 145 * Releases any memory used. 146 * This will invalidate any references returned by the [] operator. 147 * A destructor is not used, because that would make it not POD 148 * (Plain Old Data) and it could not be placed in registers. 149 */ 150 void free() 151 { 152 debug(DataBuffer) buf[0 .. bufLen] = 0; 153 if (bufLen & wasResized) 154 realloc(buf, 0); 155 buf = null; 156 bufLen = 0; 157 used = 0; 158 } 159 160 /************************ 161 * Append element c to the buffer. 162 * This member function makes DataBuffer an OutputRange. 163 */ 164 void put(T c) 165 { 166 /* j will get enregistered, while used will not because resize() may change used 167 */ 168 const j = used; 169 if (j == bufLen) 170 { 171 resize(j * 2 + 16); 172 } 173 buf[j] = c; 174 used = j + 1; 175 } 176 177 /************************ 178 * Append array s to the buffer. 179 * 180 * If $(D const(T)) can be converted to $(D T), then put will accept 181 * $(D const(T)[] as input. It will accept a $(D T[]) otherwise. 182 */ 183 private alias CT = Select!(is(const(T) : T), const(T), T); 184 /// ditto 185 void put(CT[] s) 186 { 187 const newlen = used + s.length; 188 const len = bufLen; 189 if (newlen > len) 190 { 191 resize(newlen <= len * 2 ? len * 2 : newlen); 192 } 193 buf[used .. newlen] = s[]; 194 used = newlen; 195 } 196 197 /****** 198 * Retrieve a slice into the result. 199 * Returns: 200 * A slice into the temporary buffer that is only 201 * valid until the next put() or DataBuffer goes out of scope. 202 */ 203 @system T[] opSlice(size_t lower, size_t upper) 204 in 205 { 206 assert(lower <= bufLen); 207 assert(upper <= bufLen); 208 assert(lower <= upper); 209 } 210 body 211 { 212 return buf[lower .. upper]; 213 } 214 215 /// ditto 216 @system inout(T[]) opSlice() inout 217 { 218 assert(used <= bufLen); 219 return buf[0 .. used]; 220 } 221 222 /******* 223 * Returns: 224 * the element at index i. 225 */ 226 ref T opIndex(size_t i) 227 { 228 assert(i < bufLen); 229 return buf[i]; 230 } 231 232 /*** 233 * Returns: 234 * the number of elements in the DataBuffer 235 */ 236 @property size_t length() const 237 { 238 return used; 239 } 240 241 /*** 242 * Used to shrink the length of the buffer, 243 * typically to 0 so the buffer can be reused. 244 * Cannot be used to extend the length of the buffer. 245 */ 246 @property void length(size_t i) 247 in 248 { 249 assert(i <= this.used); 250 } 251 body 252 { 253 this.used = i; 254 } 255 256 alias opDollar = length; 257 258 private: 259 T* buf; 260 size_t bufLen; 261 enum wasResized = 1; // this bit is set in bufLen if we control the memory 262 size_t used; 263 264 void resize(size_t newsize) 265 { 266 //writefln("%s: oldsize %s newsize %s", id, buf.length, newsize); 267 newsize |= wasResized; 268 void *newBuf = realloc((bufLen & wasResized) ? buf : null, newsize * T.sizeof); 269 if (!newBuf) 270 core.exception.onOutOfMemoryError(); 271 if (!(bufLen & wasResized)) 272 { 273 memcpy(newBuf, buf, used * T.sizeof); 274 debug(DataBuffer) buf[0 .. bufLen] = 0; 275 } 276 buf = cast(T*)newBuf; 277 bufLen = newsize; 278 279 /* This function is called only rarely, 280 * inlining results in poorer register allocation. 281 */ 282 version (DigitalMars) 283 /* With dmd, a fake loop will prevent inlining. 284 * Using a hack until a language enhancement is implemented. 285 */ 286 while (1) { break; } 287 } 288 } 289 290 unittest 291 { 292 import core.stdc.stdio; 293 import std.range; 294 295 char[2] tmpbuf = void; 296 { 297 // Exercise all the lines of code except for assert(0)'s 298 auto textbuf = DataBuffer!char(tmpbuf); 299 scope(exit) textbuf.free(); 300 301 static assert(isOutputRange!(DataBuffer!char, char)); 302 303 textbuf.put('a'); 304 textbuf.put('x'); 305 textbuf.put("abc"); // tickle put([])'s resize 306 assert(textbuf.length == 5); 307 assert(textbuf[1..3] == "xa"); 308 assert(textbuf[3] == 'b'); 309 310 textbuf.length = textbuf.length - 1; 311 assert(textbuf[0..textbuf.length] == "axab"); 312 313 textbuf.length = 3; 314 assert(textbuf[0..textbuf.length] == "axa"); 315 assert(textbuf[textbuf.length - 1] == 'a'); 316 assert(textbuf[1..3] == "xa"); 317 318 textbuf.put(cast(dchar)'z'); 319 assert(textbuf[] == "axaz"); 320 321 textbuf.length = 0; // reset for reuse 322 assert(textbuf.length == 0); 323 324 foreach (char c; "asdf;lasdlfaklsdjfalksdjfa;lksdjflkajsfdasdfkja;sdlfj") 325 { 326 textbuf.put(c); // tickle put(c)'s resize 327 } 328 assert(textbuf[] == "asdf;lasdlfaklsdjfalksdjfa;lksdjflkajsfdasdfkja;sdlfj"); 329 } // run destructor on textbuf here 330 331 } 332 333 unittest 334 { 335 string cat(string s1, string s2) 336 { 337 char[10] tmpbuf = void; 338 auto textbuf = DataBuffer!char(tmpbuf); 339 scope(exit) textbuf.free(); 340 textbuf.put(s1); 341 textbuf.put(s2); 342 textbuf.put("even more"); 343 return textbuf[].idup; 344 } 345 346 auto s = cat("hello", "betty"); 347 assert(s == "hellobettyeven more"); 348 } 349 350 /********************************* 351 * This is a slightly simpler way to create a DataBuffer instance 352 * that uses type deduction. 353 * Params: 354 * tmpbuf = the initial buffer to use 355 * Returns: 356 * an instance of DataBuffer 357 * Example: 358 --- 359 ubyte[10] tmpbuf = void; 360 auto sb = dataBuffer(tmpbuf); 361 scope(exit) sp.free(); 362 --- 363 */ 364 365 auto dataBuffer(T)(T[] tmpbuf) 366 { 367 return DataBuffer!T(tmpbuf); 368 } 369 370 unittest 371 { 372 ubyte[10] tmpbuf = void; 373 auto sb = dataBuffer(tmpbuf); 374 scope(exit) sb.free(); 375 } 376 377 unittest 378 { 379 DataBuffer!(int*) b; 380 int*[] s; 381 b.put(s); 382 383 DataBuffer!char c; 384 string s1; 385 char[] s2; 386 c.put(s1); 387 c.put(s2); 388 }