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 }